44 * @vitest -environment node
55 */
66
7- import { loggerMock } from '@sim/testing'
7+ import { databaseMock , loggerMock } from '@sim/testing'
88import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
99
10- vi . mock ( '@sim/db' , ( ) => ( {
11- db : {
12- select : vi . fn ( ) . mockReturnThis ( ) ,
13- from : vi . fn ( ) . mockReturnThis ( ) ,
14- where : vi . fn ( ) . mockReturnThis ( ) ,
15- limit : vi . fn ( ) . mockReturnValue ( [ ] ) ,
16- update : vi . fn ( ) . mockReturnThis ( ) ,
17- set : vi . fn ( ) . mockReturnThis ( ) ,
18- orderBy : vi . fn ( ) . mockReturnThis ( ) ,
19- } ,
20- } ) )
10+ vi . mock ( '@sim/db' , ( ) => databaseMock )
2111
2212vi . mock ( '@/lib/oauth/oauth' , ( ) => ( {
2313 refreshOAuthToken : vi . fn ( ) ,
@@ -34,13 +24,36 @@ import {
3424 refreshTokenIfNeeded ,
3525} from '@/app/api/auth/oauth/utils'
3626
37- const mockDbTyped = db as any
27+ const mockDb = db as any
3828const mockRefreshOAuthToken = refreshOAuthToken as any
3929
30+ /**
31+ * Creates a chainable mock for db.select() calls.
32+ * Returns a nested chain: select() -> from() -> where() -> limit() / orderBy()
33+ */
34+ function mockSelectChain ( limitResult : unknown [ ] ) {
35+ const mockLimit = vi . fn ( ) . mockReturnValue ( limitResult )
36+ const mockOrderBy = vi . fn ( ) . mockReturnValue ( limitResult )
37+ const mockWhere = vi . fn ( ) . mockReturnValue ( { limit : mockLimit , orderBy : mockOrderBy } )
38+ const mockFrom = vi . fn ( ) . mockReturnValue ( { where : mockWhere } )
39+ mockDb . select . mockReturnValueOnce ( { from : mockFrom } )
40+ return { mockFrom, mockWhere, mockLimit }
41+ }
42+
43+ /**
44+ * Creates a chainable mock for db.update() calls.
45+ * Returns a nested chain: update() -> set() -> where()
46+ */
47+ function mockUpdateChain ( ) {
48+ const mockWhere = vi . fn ( ) . mockResolvedValue ( { } )
49+ const mockSet = vi . fn ( ) . mockReturnValue ( { where : mockWhere } )
50+ mockDb . update . mockReturnValueOnce ( { set : mockSet } )
51+ return { mockSet, mockWhere }
52+ }
53+
4054describe ( 'OAuth Utils' , ( ) => {
4155 beforeEach ( ( ) => {
4256 vi . clearAllMocks ( )
43- mockDbTyped . limit . mockReturnValue ( [ ] )
4457 } )
4558
4659 afterEach ( ( ) => {
@@ -50,20 +63,20 @@ describe('OAuth Utils', () => {
5063 describe ( 'getCredential' , ( ) => {
5164 it ( 'should return credential when found' , async ( ) => {
5265 const mockCredential = { id : 'credential-id' , userId : 'test-user-id' }
53- mockDbTyped . limit . mockReturnValueOnce ( [ mockCredential ] )
66+ const { mockFrom , mockWhere , mockLimit } = mockSelectChain ( [ mockCredential ] )
5467
5568 const credential = await getCredential ( 'request-id' , 'credential-id' , 'test-user-id' )
5669
57- expect ( mockDbTyped . select ) . toHaveBeenCalled ( )
58- expect ( mockDbTyped . from ) . toHaveBeenCalled ( )
59- expect ( mockDbTyped . where ) . toHaveBeenCalled ( )
60- expect ( mockDbTyped . limit ) . toHaveBeenCalledWith ( 1 )
70+ expect ( mockDb . select ) . toHaveBeenCalled ( )
71+ expect ( mockFrom ) . toHaveBeenCalled ( )
72+ expect ( mockWhere ) . toHaveBeenCalled ( )
73+ expect ( mockLimit ) . toHaveBeenCalledWith ( 1 )
6174
6275 expect ( credential ) . toEqual ( mockCredential )
6376 } )
6477
6578 it ( 'should return undefined when credential is not found' , async ( ) => {
66- mockDbTyped . limit . mockReturnValueOnce ( [ ] )
79+ mockSelectChain ( [ ] )
6780
6881 const credential = await getCredential ( 'request-id' , 'nonexistent-id' , 'test-user-id' )
6982
@@ -102,11 +115,12 @@ describe('OAuth Utils', () => {
102115 refreshToken : 'new-refresh-token' ,
103116 } )
104117
118+ mockUpdateChain ( )
119+
105120 const result = await refreshTokenIfNeeded ( 'request-id' , mockCredential , 'credential-id' )
106121
107122 expect ( mockRefreshOAuthToken ) . toHaveBeenCalledWith ( 'google' , 'refresh-token' )
108- expect ( mockDbTyped . update ) . toHaveBeenCalled ( )
109- expect ( mockDbTyped . set ) . toHaveBeenCalled ( )
123+ expect ( mockDb . update ) . toHaveBeenCalled ( )
110124 expect ( result ) . toEqual ( { accessToken : 'new-token' , refreshed : true } )
111125 } )
112126
@@ -152,7 +166,7 @@ describe('OAuth Utils', () => {
152166 providerId : 'google' ,
153167 userId : 'test-user-id' ,
154168 }
155- mockDbTyped . limit . mockReturnValueOnce ( [ mockCredential ] )
169+ mockSelectChain ( [ mockCredential ] )
156170
157171 const token = await refreshAccessTokenIfNeeded ( 'credential-id' , 'test-user-id' , 'request-id' )
158172
@@ -169,7 +183,8 @@ describe('OAuth Utils', () => {
169183 providerId : 'google' ,
170184 userId : 'test-user-id' ,
171185 }
172- mockDbTyped . limit . mockReturnValueOnce ( [ mockCredential ] )
186+ mockSelectChain ( [ mockCredential ] )
187+ mockUpdateChain ( )
173188
174189 mockRefreshOAuthToken . mockResolvedValueOnce ( {
175190 accessToken : 'new-token' ,
@@ -180,13 +195,12 @@ describe('OAuth Utils', () => {
180195 const token = await refreshAccessTokenIfNeeded ( 'credential-id' , 'test-user-id' , 'request-id' )
181196
182197 expect ( mockRefreshOAuthToken ) . toHaveBeenCalledWith ( 'google' , 'refresh-token' )
183- expect ( mockDbTyped . update ) . toHaveBeenCalled ( )
184- expect ( mockDbTyped . set ) . toHaveBeenCalled ( )
198+ expect ( mockDb . update ) . toHaveBeenCalled ( )
185199 expect ( token ) . toBe ( 'new-token' )
186200 } )
187201
188202 it ( 'should return null if credential not found' , async ( ) => {
189- mockDbTyped . limit . mockReturnValueOnce ( [ ] )
203+ mockSelectChain ( [ ] )
190204
191205 const token = await refreshAccessTokenIfNeeded ( 'nonexistent-id' , 'test-user-id' , 'request-id' )
192206
@@ -202,7 +216,7 @@ describe('OAuth Utils', () => {
202216 providerId : 'google' ,
203217 userId : 'test-user-id' ,
204218 }
205- mockDbTyped . limit . mockReturnValueOnce ( [ mockCredential ] )
219+ mockSelectChain ( [ mockCredential ] )
206220
207221 mockRefreshOAuthToken . mockResolvedValueOnce ( null )
208222
0 commit comments