@rsweeten/dropbox-sync 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test-pr.yml +30 -0
- package/README.md +207 -1
- package/__mocks__/nuxt/app.js +20 -0
- package/dist/adapters/__tests__/angular.spec.d.ts +1 -0
- package/dist/adapters/__tests__/angular.spec.js +237 -0
- package/dist/adapters/__tests__/next.spec.d.ts +1 -0
- package/dist/adapters/__tests__/next.spec.js +179 -0
- package/dist/adapters/__tests__/nuxt.spec.d.ts +1 -0
- package/dist/adapters/__tests__/nuxt.spec.js +145 -0
- package/dist/adapters/__tests__/svelte.spec.d.ts +1 -0
- package/dist/adapters/__tests__/svelte.spec.js +149 -0
- package/dist/core/__tests__/auth.spec.d.ts +1 -0
- package/dist/core/__tests__/auth.spec.js +83 -0
- package/dist/core/__tests__/client.spec.d.ts +1 -0
- package/dist/core/__tests__/client.spec.js +102 -0
- package/dist/core/__tests__/socket.spec.d.ts +1 -0
- package/dist/core/__tests__/socket.spec.js +122 -0
- package/dist/core/__tests__/sync.spec.d.ts +1 -0
- package/dist/core/__tests__/sync.spec.js +375 -0
- package/dist/core/sync.js +30 -11
- package/jest.config.js +24 -0
- package/jest.setup.js +38 -0
- package/package.json +4 -1
- package/src/adapters/__tests__/angular.spec.ts +338 -0
- package/src/adapters/__tests__/next.spec.ts +240 -0
- package/src/adapters/__tests__/nuxt.spec.ts +185 -0
- package/src/adapters/__tests__/svelte.spec.ts +194 -0
- package/src/core/__tests__/auth.spec.ts +142 -0
- package/src/core/__tests__/client.spec.ts +128 -0
- package/src/core/__tests__/socket.spec.ts +153 -0
- package/src/core/__tests__/sync.spec.ts +508 -0
- package/src/core/sync.ts +53 -26
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// Mock Angular modules before importing the code under test
|
|
2
|
+
jest.mock('@angular/core', () => ({
|
|
3
|
+
Injectable: () => jest.fn(),
|
|
4
|
+
}))
|
|
5
|
+
|
|
6
|
+
jest.mock('@angular/common/http', () => ({
|
|
7
|
+
HttpClient: jest.fn(),
|
|
8
|
+
HttpHeaders: jest.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
// Now import the code that uses Angular modules
|
|
12
|
+
import { DropboxSyncService, getCredentialsFromEnvironment } from '../angular'
|
|
13
|
+
import { createDropboxSyncClient } from '../../core/client'
|
|
14
|
+
import { of } from 'rxjs'
|
|
15
|
+
import { Observable } from 'rxjs'
|
|
16
|
+
|
|
17
|
+
// Mock the core client
|
|
18
|
+
jest.mock('../../core/client')
|
|
19
|
+
|
|
20
|
+
describe('Angular adapter', () => {
|
|
21
|
+
let service: DropboxSyncService
|
|
22
|
+
const mockHttpClient = {
|
|
23
|
+
get: jest.fn(),
|
|
24
|
+
post: jest.fn(),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks()
|
|
29
|
+
service = new DropboxSyncService(mockHttpClient as any)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('DropboxSyncService', () => {
|
|
33
|
+
it('should be created', () => {
|
|
34
|
+
expect(service).toBeTruthy()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should initialize with credentials', () => {
|
|
38
|
+
const mockCredentials = {
|
|
39
|
+
clientId: 'test-client-id',
|
|
40
|
+
clientSecret: 'test-client-secret',
|
|
41
|
+
accessToken: 'test-access-token',
|
|
42
|
+
refreshToken: 'test-refresh-token',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mockClient = { mock: 'client' }
|
|
46
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
47
|
+
|
|
48
|
+
const result = service.initialize(mockCredentials)
|
|
49
|
+
|
|
50
|
+
expect(createDropboxSyncClient).toHaveBeenCalledWith(
|
|
51
|
+
mockCredentials
|
|
52
|
+
)
|
|
53
|
+
expect(result).toEqual(mockClient)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should get client after initialization', () => {
|
|
57
|
+
const mockCredentials = { clientId: 'test-id' }
|
|
58
|
+
const mockClient = { mock: 'client' }
|
|
59
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
60
|
+
|
|
61
|
+
service.initialize(mockCredentials)
|
|
62
|
+
const client = service.getClient()
|
|
63
|
+
|
|
64
|
+
expect(client).toEqual(mockClient)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should throw error if getClient called before initialization', () => {
|
|
68
|
+
expect(() => service.getClient()).toThrow(
|
|
69
|
+
'DropboxSyncService not initialized. Call initialize() first.'
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should check connection status via HTTP', () => {
|
|
74
|
+
mockHttpClient.get.mockReturnValue(of({ connected: true }))
|
|
75
|
+
|
|
76
|
+
service.checkConnection().subscribe((result) => {
|
|
77
|
+
expect(result).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
81
|
+
'/api/dropbox/status'
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should handle connection error', () => {
|
|
86
|
+
mockHttpClient.get.mockReturnValue(of(null)) // Simulate error
|
|
87
|
+
|
|
88
|
+
service.checkConnection().subscribe((result) => {
|
|
89
|
+
expect(result).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should disconnect via HTTP', () => {
|
|
94
|
+
mockHttpClient.post.mockReturnValue(of({ success: true }))
|
|
95
|
+
|
|
96
|
+
service.disconnectDropbox().subscribe((result) => {
|
|
97
|
+
expect(result).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
101
|
+
'/api/dropbox/logout',
|
|
102
|
+
{}
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should start sync process', () => {
|
|
107
|
+
const mockOptions = { localDir: '/test-dir' }
|
|
108
|
+
const mockResult = { uploaded: [], downloaded: [] }
|
|
109
|
+
|
|
110
|
+
const mockClient = {
|
|
111
|
+
sync: {
|
|
112
|
+
syncFiles: jest.fn().mockResolvedValue(mockResult),
|
|
113
|
+
},
|
|
114
|
+
socket: {
|
|
115
|
+
connect: jest.fn(),
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
120
|
+
service.initialize({ clientId: 'test-id' })
|
|
121
|
+
|
|
122
|
+
// Mock setupSocketListeners to return an observable
|
|
123
|
+
service.setupSocketListeners = jest.fn().mockReturnValue(of({}))
|
|
124
|
+
|
|
125
|
+
service.startSync(mockOptions).subscribe((result) => {
|
|
126
|
+
expect(result).toEqual(mockResult)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
expect(mockClient.socket.connect).toHaveBeenCalled()
|
|
130
|
+
expect(mockClient.sync.syncFiles).toHaveBeenCalledWith(mockOptions)
|
|
131
|
+
expect(service.setupSocketListeners).toHaveBeenCalled()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should cancel sync', () => {
|
|
135
|
+
const mockClient = {
|
|
136
|
+
sync: {
|
|
137
|
+
cancelSync: jest.fn(),
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
142
|
+
service.initialize({ clientId: 'test-id' })
|
|
143
|
+
|
|
144
|
+
service.cancelSync()
|
|
145
|
+
|
|
146
|
+
expect(mockClient.sync.cancelSync).toHaveBeenCalled()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Mock the implementation of setupSocketListeners for testing
|
|
150
|
+
it('should setup socket listeners', () => {
|
|
151
|
+
const mockSocket = {
|
|
152
|
+
on: jest.fn(),
|
|
153
|
+
off: jest.fn(),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const mockClient = {
|
|
157
|
+
socket: mockSocket,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
161
|
+
service.initialize({ clientId: 'test-id' })
|
|
162
|
+
|
|
163
|
+
const observable = service.setupSocketListeners()
|
|
164
|
+
|
|
165
|
+
// Should return an Observable
|
|
166
|
+
expect(observable).toBeInstanceOf(Observable)
|
|
167
|
+
|
|
168
|
+
// Get the subscriber function that was used to create the Observable
|
|
169
|
+
const observer = {
|
|
170
|
+
next: jest.fn(),
|
|
171
|
+
error: jest.fn(),
|
|
172
|
+
complete: jest.fn(),
|
|
173
|
+
}
|
|
174
|
+
const subscription = observable.subscribe(observer)
|
|
175
|
+
|
|
176
|
+
// Since we have direct access to the socket mock, we can verify its behavior
|
|
177
|
+
expect(mockSocket.on).toHaveBeenCalledWith(
|
|
178
|
+
'sync:progress',
|
|
179
|
+
expect.any(Function)
|
|
180
|
+
)
|
|
181
|
+
expect(mockSocket.on).toHaveBeenCalledWith(
|
|
182
|
+
'sync:queue',
|
|
183
|
+
expect.any(Function)
|
|
184
|
+
)
|
|
185
|
+
expect(mockSocket.on).toHaveBeenCalledWith(
|
|
186
|
+
'sync:complete',
|
|
187
|
+
expect.any(Function)
|
|
188
|
+
)
|
|
189
|
+
expect(mockSocket.on).toHaveBeenCalledWith(
|
|
190
|
+
'sync:error',
|
|
191
|
+
expect.any(Function)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// Clean up the subscription
|
|
195
|
+
subscription.unsubscribe()
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should handle OAuth callback', () => {
|
|
199
|
+
const mockCode = 'test-auth-code'
|
|
200
|
+
const mockTokens = {
|
|
201
|
+
accessToken: 'new-access-token',
|
|
202
|
+
refreshToken: 'new-refresh-token',
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const mockClient = {
|
|
206
|
+
auth: {
|
|
207
|
+
exchangeCodeForToken: jest
|
|
208
|
+
.fn()
|
|
209
|
+
.mockResolvedValue(mockTokens),
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
214
|
+
service.initialize({ clientId: 'test-id' })
|
|
215
|
+
|
|
216
|
+
// Create a proper localStorage mock
|
|
217
|
+
const mockLocalStorage = {
|
|
218
|
+
setItem: jest.fn(),
|
|
219
|
+
getItem: jest.fn(),
|
|
220
|
+
removeItem: jest.fn(),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Object.defineProperty(global, 'localStorage', {
|
|
224
|
+
value: mockLocalStorage,
|
|
225
|
+
writable: true,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// Mock window.location.origin
|
|
229
|
+
Object.defineProperty(window, 'location', {
|
|
230
|
+
value: {
|
|
231
|
+
origin: 'http://localhost',
|
|
232
|
+
},
|
|
233
|
+
writable: true,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Subscribe to the observable to trigger the code
|
|
237
|
+
let receivedResult: any
|
|
238
|
+
service.handleOAuthCallback(mockCode).subscribe((result) => {
|
|
239
|
+
receivedResult = result
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// Verify the exchangeCodeForToken was called with the expected args
|
|
243
|
+
expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalledWith(
|
|
244
|
+
mockCode,
|
|
245
|
+
'http://localhost/api/dropbox/auth/callback'
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
// Create and resolve the promise to trigger the localStorage set calls
|
|
249
|
+
const resolvePromise = Promise.resolve(mockTokens)
|
|
250
|
+
|
|
251
|
+
// Return a promise that will resolve after our mocked promise
|
|
252
|
+
return resolvePromise.then(() => {
|
|
253
|
+
// Now we can check localStorage was called
|
|
254
|
+
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
|
255
|
+
'dropbox_access_token',
|
|
256
|
+
mockTokens.accessToken
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
|
260
|
+
'dropbox_refresh_token',
|
|
261
|
+
mockTokens.refreshToken
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
|
|
265
|
+
'dropbox_connected',
|
|
266
|
+
'true'
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
// Verify we received the tokens
|
|
270
|
+
expect(receivedResult).toEqual(mockTokens)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe('getCredentialsFromEnvironment', () => {
|
|
276
|
+
it('should extract credentials from environment', () => {
|
|
277
|
+
const mockEnvironment = {
|
|
278
|
+
dropboxAppKey: 'env-app-key',
|
|
279
|
+
dropboxAppSecret: 'env-app-secret',
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Mock localStorage
|
|
283
|
+
const mockLocalStorage = {
|
|
284
|
+
getItem: jest.fn().mockImplementation((key) => {
|
|
285
|
+
if (key === 'dropbox_access_token')
|
|
286
|
+
return 'storage-access-token'
|
|
287
|
+
if (key === 'dropbox_refresh_token')
|
|
288
|
+
return 'storage-refresh-token'
|
|
289
|
+
return null
|
|
290
|
+
}),
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Object.defineProperty(global, 'localStorage', {
|
|
294
|
+
value: mockLocalStorage,
|
|
295
|
+
writable: true,
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
const credentials = getCredentialsFromEnvironment(mockEnvironment)
|
|
299
|
+
|
|
300
|
+
expect(credentials).toEqual({
|
|
301
|
+
clientId: 'env-app-key',
|
|
302
|
+
clientSecret: 'env-app-secret',
|
|
303
|
+
accessToken: 'storage-access-token',
|
|
304
|
+
refreshToken: 'storage-refresh-token',
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(
|
|
308
|
+
'dropbox_access_token'
|
|
309
|
+
)
|
|
310
|
+
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(
|
|
311
|
+
'dropbox_refresh_token'
|
|
312
|
+
)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('should handle missing environment values', () => {
|
|
316
|
+
const mockEnvironment = {}
|
|
317
|
+
|
|
318
|
+
// Mock localStorage with no tokens
|
|
319
|
+
const mockLocalStorage = {
|
|
320
|
+
getItem: jest.fn().mockReturnValue(null),
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
Object.defineProperty(global, 'localStorage', {
|
|
324
|
+
value: mockLocalStorage,
|
|
325
|
+
writable: true,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const credentials = getCredentialsFromEnvironment(mockEnvironment)
|
|
329
|
+
|
|
330
|
+
expect(credentials).toEqual({
|
|
331
|
+
clientId: '',
|
|
332
|
+
clientSecret: undefined,
|
|
333
|
+
accessToken: undefined,
|
|
334
|
+
refreshToken: undefined,
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useNextDropboxSync,
|
|
3
|
+
getCredentialsFromCookies,
|
|
4
|
+
handleOAuthCallback,
|
|
5
|
+
createNextDropboxApiHandlers,
|
|
6
|
+
} from '../next'
|
|
7
|
+
import { createDropboxSyncClient } from '../../core/client'
|
|
8
|
+
import { cookies } from 'next/headers'
|
|
9
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
10
|
+
|
|
11
|
+
// Mock dependencies
|
|
12
|
+
jest.mock('../../core/client')
|
|
13
|
+
jest.mock('next/headers')
|
|
14
|
+
jest.mock('next/server')
|
|
15
|
+
|
|
16
|
+
describe('Next.js adapter', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks()
|
|
19
|
+
|
|
20
|
+
// Mock process.env
|
|
21
|
+
process.env.DROPBOX_APP_KEY = 'test-app-key'
|
|
22
|
+
process.env.DROPBOX_APP_SECRET = 'test-app-secret'
|
|
23
|
+
process.env.NEXT_PUBLIC_APP_URL = 'https://example.com'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('useNextDropboxSync', () => {
|
|
27
|
+
it('should create a Dropbox sync client with provided credentials', () => {
|
|
28
|
+
const mockCredentials = {
|
|
29
|
+
clientId: 'custom-client-id',
|
|
30
|
+
clientSecret: 'custom-client-secret',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue({
|
|
34
|
+
mock: 'client',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const result = useNextDropboxSync(mockCredentials)
|
|
38
|
+
|
|
39
|
+
expect(createDropboxSyncClient).toHaveBeenCalledWith(
|
|
40
|
+
mockCredentials
|
|
41
|
+
)
|
|
42
|
+
expect(result).toEqual({ mock: 'client' })
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('getCredentialsFromCookies', () => {
|
|
47
|
+
it('should get credentials from cookies', async () => {
|
|
48
|
+
const mockCookieStore = {
|
|
49
|
+
get: jest.fn((name) => {
|
|
50
|
+
if (name === 'dropbox_access_token')
|
|
51
|
+
return { value: 'test-access-token' }
|
|
52
|
+
if (name === 'dropbox_refresh_token')
|
|
53
|
+
return { value: 'test-refresh-token' }
|
|
54
|
+
return null
|
|
55
|
+
}),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
;(cookies as jest.Mock).mockResolvedValue(mockCookieStore)
|
|
59
|
+
|
|
60
|
+
const credentials = await getCredentialsFromCookies()
|
|
61
|
+
|
|
62
|
+
expect(cookies).toHaveBeenCalled()
|
|
63
|
+
expect(credentials).toEqual({
|
|
64
|
+
clientId: 'test-app-key',
|
|
65
|
+
clientSecret: 'test-app-secret',
|
|
66
|
+
accessToken: 'test-access-token',
|
|
67
|
+
refreshToken: 'test-refresh-token',
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should handle missing cookie values', async () => {
|
|
72
|
+
const mockCookieStore = {
|
|
73
|
+
get: jest.fn().mockReturnValue(null),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
;(cookies as jest.Mock).mockResolvedValue(mockCookieStore)
|
|
77
|
+
|
|
78
|
+
const credentials = await getCredentialsFromCookies()
|
|
79
|
+
|
|
80
|
+
expect(credentials).toEqual({
|
|
81
|
+
clientId: 'test-app-key',
|
|
82
|
+
clientSecret: 'test-app-secret',
|
|
83
|
+
accessToken: undefined,
|
|
84
|
+
refreshToken: undefined,
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('handleOAuthCallback', () => {
|
|
90
|
+
it('should handle successful OAuth callback', async () => {
|
|
91
|
+
// Mock URL with auth code
|
|
92
|
+
const mockRequest = {
|
|
93
|
+
url: 'https://example.com/callback?code=test-auth-code',
|
|
94
|
+
} as unknown as NextRequest
|
|
95
|
+
|
|
96
|
+
// Mock URL constructor behavior
|
|
97
|
+
;(global as any).URL = jest.fn().mockImplementation(() => {
|
|
98
|
+
return {
|
|
99
|
+
searchParams: {
|
|
100
|
+
get: jest.fn().mockReturnValue('test-auth-code'),
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Mock Dropbox client
|
|
106
|
+
const mockClient = {
|
|
107
|
+
auth: {
|
|
108
|
+
exchangeCodeForToken: jest.fn().mockResolvedValue({
|
|
109
|
+
accessToken: 'new-access-token',
|
|
110
|
+
refreshToken: 'new-refresh-token',
|
|
111
|
+
expiresAt: Date.now() + 14400 * 1000,
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
117
|
+
|
|
118
|
+
// Mock NextResponse
|
|
119
|
+
const mockResponseCookies = {
|
|
120
|
+
set: jest.fn(),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const mockResponse = {
|
|
124
|
+
cookies: mockResponseCookies,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
;(NextResponse.redirect as jest.Mock).mockReturnValue(mockResponse)
|
|
128
|
+
|
|
129
|
+
await handleOAuthCallback(mockRequest)
|
|
130
|
+
|
|
131
|
+
expect(createDropboxSyncClient).toHaveBeenCalled()
|
|
132
|
+
expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalledWith(
|
|
133
|
+
'test-auth-code',
|
|
134
|
+
expect.any(String)
|
|
135
|
+
)
|
|
136
|
+
expect(NextResponse.redirect).toHaveBeenCalled()
|
|
137
|
+
expect(mockResponseCookies.set).toHaveBeenCalledTimes(3) // Access, refresh, and connected cookies
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should redirect to error page if code is missing', async () => {
|
|
141
|
+
const mockRequest = {
|
|
142
|
+
url: 'https://example.com/callback',
|
|
143
|
+
} as unknown as NextRequest
|
|
144
|
+
|
|
145
|
+
;(global as any).URL = jest.fn().mockImplementation(() => {
|
|
146
|
+
return {
|
|
147
|
+
searchParams: {
|
|
148
|
+
get: jest.fn().mockReturnValue(null),
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Create a proper URL string for the mock response
|
|
154
|
+
const errorUrl = 'https://example.com/auth/error'
|
|
155
|
+
|
|
156
|
+
// Mock the redirect implementation to return this URL
|
|
157
|
+
;(NextResponse.redirect as jest.Mock).mockReturnValue({
|
|
158
|
+
url: errorUrl,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const result = await handleOAuthCallback(mockRequest)
|
|
162
|
+
|
|
163
|
+
// Now we'll check that the redirect was called, and the result contains our URL
|
|
164
|
+
expect(NextResponse.redirect).toHaveBeenCalled()
|
|
165
|
+
expect(result.url).toBe(errorUrl)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('createNextDropboxApiHandlers', () => {
|
|
170
|
+
it('should create API handlers with necessary methods', () => {
|
|
171
|
+
const handlers = createNextDropboxApiHandlers()
|
|
172
|
+
|
|
173
|
+
expect(handlers).toHaveProperty('status')
|
|
174
|
+
expect(handlers).toHaveProperty('oauthStart')
|
|
175
|
+
expect(handlers).toHaveProperty('logout')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('should check status from cookies', async () => {
|
|
179
|
+
const mockCookieStore = {
|
|
180
|
+
get: jest.fn().mockReturnValue({ value: 'test-token' }),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
;(cookies as jest.Mock).mockResolvedValue(mockCookieStore)
|
|
184
|
+
;(NextResponse.json as jest.Mock).mockReturnValue({
|
|
185
|
+
json: 'response',
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const handlers = createNextDropboxApiHandlers()
|
|
189
|
+
const response = await handlers.status()
|
|
190
|
+
|
|
191
|
+
expect(cookies).toHaveBeenCalled()
|
|
192
|
+
expect(mockCookieStore.get).toHaveBeenCalledWith(
|
|
193
|
+
'dropbox_access_token'
|
|
194
|
+
)
|
|
195
|
+
expect(NextResponse.json).toHaveBeenCalledWith({ connected: true })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should start OAuth flow', async () => {
|
|
199
|
+
const mockClient = {
|
|
200
|
+
auth: {
|
|
201
|
+
getAuthUrl: jest
|
|
202
|
+
.fn()
|
|
203
|
+
.mockResolvedValue('https://dropbox.com/oauth'),
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
|
|
208
|
+
;(NextResponse.redirect as jest.Mock).mockReturnValue({
|
|
209
|
+
redirect: 'response',
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const handlers = createNextDropboxApiHandlers()
|
|
213
|
+
const response = await handlers.oauthStart()
|
|
214
|
+
|
|
215
|
+
expect(createDropboxSyncClient).toHaveBeenCalled()
|
|
216
|
+
expect(mockClient.auth.getAuthUrl).toHaveBeenCalled()
|
|
217
|
+
expect(NextResponse.redirect).toHaveBeenCalledWith(
|
|
218
|
+
'https://dropbox.com/oauth'
|
|
219
|
+
)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should handle logout', async () => {
|
|
223
|
+
const mockResponseCookies = {
|
|
224
|
+
delete: jest.fn(),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const mockResponse = {
|
|
228
|
+
cookies: mockResponseCookies,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
;(NextResponse.json as jest.Mock).mockReturnValue(mockResponse)
|
|
232
|
+
|
|
233
|
+
const handlers = createNextDropboxApiHandlers()
|
|
234
|
+
const response = await handlers.logout()
|
|
235
|
+
|
|
236
|
+
expect(NextResponse.json).toHaveBeenCalledWith({ success: true })
|
|
237
|
+
expect(mockResponseCookies.delete).toHaveBeenCalledTimes(3) // Should delete three cookies
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
})
|