@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.
Files changed (32) hide show
  1. package/.github/workflows/test-pr.yml +30 -0
  2. package/README.md +207 -1
  3. package/__mocks__/nuxt/app.js +20 -0
  4. package/dist/adapters/__tests__/angular.spec.d.ts +1 -0
  5. package/dist/adapters/__tests__/angular.spec.js +237 -0
  6. package/dist/adapters/__tests__/next.spec.d.ts +1 -0
  7. package/dist/adapters/__tests__/next.spec.js +179 -0
  8. package/dist/adapters/__tests__/nuxt.spec.d.ts +1 -0
  9. package/dist/adapters/__tests__/nuxt.spec.js +145 -0
  10. package/dist/adapters/__tests__/svelte.spec.d.ts +1 -0
  11. package/dist/adapters/__tests__/svelte.spec.js +149 -0
  12. package/dist/core/__tests__/auth.spec.d.ts +1 -0
  13. package/dist/core/__tests__/auth.spec.js +83 -0
  14. package/dist/core/__tests__/client.spec.d.ts +1 -0
  15. package/dist/core/__tests__/client.spec.js +102 -0
  16. package/dist/core/__tests__/socket.spec.d.ts +1 -0
  17. package/dist/core/__tests__/socket.spec.js +122 -0
  18. package/dist/core/__tests__/sync.spec.d.ts +1 -0
  19. package/dist/core/__tests__/sync.spec.js +375 -0
  20. package/dist/core/sync.js +30 -11
  21. package/jest.config.js +24 -0
  22. package/jest.setup.js +38 -0
  23. package/package.json +4 -1
  24. package/src/adapters/__tests__/angular.spec.ts +338 -0
  25. package/src/adapters/__tests__/next.spec.ts +240 -0
  26. package/src/adapters/__tests__/nuxt.spec.ts +185 -0
  27. package/src/adapters/__tests__/svelte.spec.ts +194 -0
  28. package/src/core/__tests__/auth.spec.ts +142 -0
  29. package/src/core/__tests__/client.spec.ts +128 -0
  30. package/src/core/__tests__/socket.spec.ts +153 -0
  31. package/src/core/__tests__/sync.spec.ts +508 -0
  32. package/src/core/sync.ts +53 -26
@@ -0,0 +1,185 @@
1
+ import {
2
+ useNuxtDropboxSync,
3
+ getCredentialsFromCookies,
4
+ createNuxtApiHandlers,
5
+ } from '../nuxt'
6
+ import { createDropboxSyncClient } from '../../core/client'
7
+ import type { H3Event } from 'h3'
8
+
9
+ // Mock dependencies
10
+ jest.mock('../../core/client')
11
+
12
+ // Manual mocks for Nuxt modules
13
+ jest.mock('nuxt/app', () => ({
14
+ useRuntimeConfig: jest.fn().mockReturnValue({
15
+ public: {
16
+ dropboxAppKey: 'test-app-key',
17
+ appUrl: 'https://example.com',
18
+ },
19
+ dropboxAppSecret: 'test-app-secret',
20
+ dropboxRedirectUri: 'https://example.com/api/dropbox/auth/callback',
21
+ }),
22
+ useCookie: jest.fn().mockImplementation((name) => {
23
+ if (name === 'dropbox_access_token') {
24
+ return { value: 'test-access-token' }
25
+ }
26
+ if (name === 'dropbox_refresh_token') {
27
+ return { value: 'test-refresh-token' }
28
+ }
29
+ return { value: null }
30
+ }),
31
+ }))
32
+
33
+ // Mock H3 utilities
34
+ const mockGetCookie = jest.fn()
35
+ const mockSetCookie = jest.fn()
36
+ const mockDeleteCookie = jest.fn()
37
+ const mockGetQuery = jest.fn()
38
+ const mockSendRedirect = jest.fn()
39
+
40
+ jest.mock('h3', () => ({
41
+ getCookie: mockGetCookie,
42
+ setCookie: mockSetCookie,
43
+ deleteCookie: mockDeleteCookie,
44
+ getQuery: mockGetQuery,
45
+ sendRedirect: mockSendRedirect,
46
+ }))
47
+
48
+ describe('Nuxt.js adapter', () => {
49
+ beforeEach(() => {
50
+ jest.clearAllMocks()
51
+
52
+ // Define process.client as a property for client/server detection
53
+ if (!('client' in process)) {
54
+ Object.defineProperty(process, 'client', {
55
+ value: false,
56
+ writable: true,
57
+ configurable: true,
58
+ })
59
+ } else {
60
+ ;(process as any).client = false
61
+ }
62
+ })
63
+
64
+ describe('useNuxtDropboxSync', () => {
65
+ it('should create a Dropbox sync client with config values', () => {
66
+ ;(createDropboxSyncClient as jest.Mock).mockReturnValue({
67
+ mock: 'client',
68
+ })
69
+
70
+ const result = useNuxtDropboxSync()
71
+
72
+ expect(createDropboxSyncClient).toHaveBeenCalledWith({
73
+ clientId: 'test-app-key',
74
+ clientSecret: 'test-app-secret',
75
+ })
76
+ expect(result).toEqual({ mock: 'client' })
77
+ })
78
+
79
+ it('should add cookie values on client-side', () => {
80
+ // Set as client-side
81
+ ;(process as any).client = true
82
+
83
+ ;(createDropboxSyncClient as jest.Mock).mockReturnValue({
84
+ mock: 'client',
85
+ })
86
+
87
+ const result = useNuxtDropboxSync()
88
+
89
+ expect(createDropboxSyncClient).toHaveBeenCalledWith(
90
+ expect.objectContaining({
91
+ clientId: 'test-app-key',
92
+ clientSecret: 'test-app-secret',
93
+ accessToken: 'test-access-token',
94
+ refreshToken: 'test-refresh-token',
95
+ })
96
+ )
97
+ })
98
+ })
99
+
100
+ describe('getCredentialsFromCookies', () => {
101
+ it('should get credentials from H3 event', () => {
102
+ const mockEvent = {} as H3Event
103
+
104
+ // Set up mock implementation
105
+ mockGetCookie.mockImplementation((event: any, name: string) => {
106
+ if (name === 'dropbox_access_token') return 'h3-access-token'
107
+ if (name === 'dropbox_refresh_token') return 'h3-refresh-token'
108
+ return null
109
+ })
110
+
111
+ const credentials = getCredentialsFromCookies(mockEvent)
112
+
113
+ expect(mockGetCookie).toHaveBeenCalledWith(
114
+ mockEvent,
115
+ 'dropbox_access_token'
116
+ )
117
+ expect(mockGetCookie).toHaveBeenCalledWith(
118
+ mockEvent,
119
+ 'dropbox_refresh_token'
120
+ )
121
+ expect(credentials).toEqual({
122
+ clientId: 'test-app-key',
123
+ clientSecret: 'test-app-secret',
124
+ accessToken: 'h3-access-token',
125
+ refreshToken: 'h3-refresh-token',
126
+ })
127
+ })
128
+ })
129
+
130
+ describe('createNuxtApiHandlers', () => {
131
+ const mockEvent = {} as H3Event
132
+
133
+ beforeEach(() => {
134
+ // Reset H3 mocks for each test
135
+ mockGetCookie.mockReset()
136
+ mockSetCookie.mockReset()
137
+ mockDeleteCookie.mockReset()
138
+ mockGetQuery.mockReset()
139
+ mockSendRedirect.mockReset()
140
+ })
141
+
142
+ it('should create Nuxt API handlers with necessary methods', () => {
143
+ const handlers = createNuxtApiHandlers()
144
+
145
+ expect(handlers).toHaveProperty('status')
146
+ expect(handlers).toHaveProperty('oauthStart')
147
+ expect(handlers).toHaveProperty('oauthCallback')
148
+ expect(handlers).toHaveProperty('logout')
149
+ })
150
+
151
+ it('should check connection status', async () => {
152
+ mockGetCookie.mockReturnValue('h3-access-token')
153
+
154
+ const handlers = createNuxtApiHandlers()
155
+ const result = await handlers.status(mockEvent)
156
+
157
+ expect(mockGetCookie).toHaveBeenCalledWith(
158
+ mockEvent,
159
+ 'dropbox_access_token'
160
+ )
161
+ expect(result).toEqual({ connected: true })
162
+ })
163
+
164
+ it('should handle OAuth start', async () => {
165
+ ;(createDropboxSyncClient as jest.Mock).mockReturnValue({
166
+ auth: {
167
+ getAuthUrl: jest
168
+ .fn()
169
+ .mockResolvedValue('https://dropbox.com/oauth'),
170
+ },
171
+ })
172
+
173
+ mockSendRedirect.mockImplementation(() => ({ redirected: true }))
174
+
175
+ const handlers = createNuxtApiHandlers()
176
+ await handlers.oauthStart(mockEvent)
177
+
178
+ expect(createDropboxSyncClient).toHaveBeenCalled()
179
+ expect(mockSendRedirect).toHaveBeenCalledWith(
180
+ mockEvent,
181
+ 'https://dropbox.com/oauth'
182
+ )
183
+ })
184
+ })
185
+ })
@@ -0,0 +1,194 @@
1
+ import {
2
+ useSvelteDropboxSync,
3
+ getCredentialsFromCookies,
4
+ createSvelteKitHandlers,
5
+ } from '../svelte'
6
+ import { createDropboxSyncClient } from '../../core/client'
7
+ import type { Cookies } from '@sveltejs/kit'
8
+
9
+ // Mock dependencies
10
+ jest.mock('../../core/client')
11
+
12
+ describe('SvelteKit adapter', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks()
15
+
16
+ // Mock process.env
17
+ process.env.DROPBOX_APP_KEY = 'test-app-key'
18
+ process.env.DROPBOX_APP_SECRET = 'test-app-secret'
19
+ process.env.PUBLIC_APP_URL = 'https://example.com'
20
+ })
21
+
22
+ describe('useSvelteDropboxSync', () => {
23
+ it('should create a Dropbox sync client with provided credentials', () => {
24
+ const mockCredentials = {
25
+ clientId: 'custom-client-id',
26
+ clientSecret: 'custom-client-secret',
27
+ }
28
+
29
+ ;(createDropboxSyncClient as jest.Mock).mockReturnValue({
30
+ mock: 'client',
31
+ })
32
+
33
+ const result = useSvelteDropboxSync(mockCredentials)
34
+
35
+ expect(createDropboxSyncClient).toHaveBeenCalledWith(
36
+ mockCredentials
37
+ )
38
+ expect(result).toEqual({ mock: 'client' })
39
+ })
40
+ })
41
+
42
+ describe('getCredentialsFromCookies', () => {
43
+ it('should get credentials from cookies', () => {
44
+ const mockCookies = {
45
+ get: jest.fn((name) => {
46
+ if (name === 'dropbox_access_token')
47
+ return 'test-access-token'
48
+ if (name === 'dropbox_refresh_token')
49
+ return 'test-refresh-token'
50
+ return null
51
+ }),
52
+ } as unknown as Cookies
53
+
54
+ const credentials = getCredentialsFromCookies(mockCookies)
55
+
56
+ expect(mockCookies.get).toHaveBeenCalledWith('dropbox_access_token')
57
+ expect(mockCookies.get).toHaveBeenCalledWith(
58
+ 'dropbox_refresh_token'
59
+ )
60
+ expect(credentials).toEqual({
61
+ clientId: 'test-app-key',
62
+ clientSecret: 'test-app-secret',
63
+ accessToken: 'test-access-token',
64
+ refreshToken: 'test-refresh-token',
65
+ })
66
+ })
67
+ })
68
+
69
+ describe('createSvelteKitHandlers', () => {
70
+ it('should create SvelteKit handlers with necessary methods', () => {
71
+ const handlers = createSvelteKitHandlers()
72
+
73
+ expect(handlers).toHaveProperty('status')
74
+ expect(handlers).toHaveProperty('oauthStart')
75
+ expect(handlers).toHaveProperty('oauthCallback')
76
+ expect(handlers).toHaveProperty('logout')
77
+ })
78
+
79
+ it('should check connection status', async () => {
80
+ const mockCookies = {
81
+ get: jest.fn().mockReturnValue('test-token'),
82
+ }
83
+
84
+ const handlers = createSvelteKitHandlers()
85
+ const response = await handlers.status({
86
+ cookies: mockCookies as unknown as Cookies,
87
+ })
88
+
89
+ expect(mockCookies.get).toHaveBeenCalledWith('dropbox_access_token')
90
+ expect(response.status).toBe(200)
91
+
92
+ // Parse the response body
93
+ const responseText = await response.text()
94
+ const responseData = JSON.parse(responseText)
95
+ expect(responseData).toEqual({ connected: true })
96
+ })
97
+
98
+ it('should start OAuth flow', async () => {
99
+ const mockClient = {
100
+ auth: {
101
+ getAuthUrl: jest
102
+ .fn()
103
+ .mockResolvedValue('https://dropbox.com/oauth'),
104
+ },
105
+ }
106
+
107
+ ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
108
+
109
+ const handlers = createSvelteKitHandlers()
110
+ const response = await handlers.oauthStart()
111
+
112
+ expect(createDropboxSyncClient).toHaveBeenCalled()
113
+ expect(mockClient.auth.getAuthUrl).toHaveBeenCalled()
114
+ expect(response.status).toBe(302)
115
+ expect(response.headers.get('Location')).toBe(
116
+ 'https://dropbox.com/oauth'
117
+ )
118
+ })
119
+
120
+ it('should handle OAuth callback success', async () => {
121
+ const mockUrl = {
122
+ searchParams: {
123
+ get: jest.fn().mockReturnValue('test-auth-code'),
124
+ },
125
+ } as unknown as URL
126
+
127
+ const mockCookies = {
128
+ set: jest.fn(),
129
+ } as unknown as Cookies
130
+
131
+ const mockClient = {
132
+ auth: {
133
+ exchangeCodeForToken: jest.fn().mockResolvedValue({
134
+ accessToken: 'new-access-token',
135
+ refreshToken: 'new-refresh-token',
136
+ expiresAt: Date.now() + 14400 * 1000,
137
+ }),
138
+ },
139
+ }
140
+
141
+ ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
142
+
143
+ const handlers = createSvelteKitHandlers()
144
+ const response = await handlers.oauthCallback({
145
+ url: mockUrl,
146
+ cookies: mockCookies,
147
+ })
148
+
149
+ expect(mockUrl.searchParams.get).toHaveBeenCalledWith('code')
150
+ expect(createDropboxSyncClient).toHaveBeenCalled()
151
+ expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalled()
152
+ expect(mockCookies.set).toHaveBeenCalledTimes(3) // Access, refresh, and connected cookies
153
+ expect(response.status).toBe(302)
154
+ expect(response.headers.get('Location')).toBe('/')
155
+ })
156
+
157
+ it('should handle OAuth callback with missing code', async () => {
158
+ const mockUrl = {
159
+ searchParams: {
160
+ get: jest.fn().mockReturnValue(null),
161
+ },
162
+ } as unknown as URL
163
+
164
+ const mockCookies = {} as unknown as Cookies
165
+
166
+ const handlers = createSvelteKitHandlers()
167
+ const response = await handlers.oauthCallback({
168
+ url: mockUrl,
169
+ cookies: mockCookies,
170
+ })
171
+
172
+ expect(mockUrl.searchParams.get).toHaveBeenCalledWith('code')
173
+ expect(response.status).toBe(302)
174
+ expect(response.headers.get('Location')).toBe('/auth/error')
175
+ })
176
+
177
+ it('should handle logout', async () => {
178
+ const mockCookies = {
179
+ delete: jest.fn(),
180
+ } as unknown as Cookies
181
+
182
+ const handlers = createSvelteKitHandlers()
183
+ const response = await handlers.logout({ cookies: mockCookies })
184
+
185
+ expect(mockCookies.delete).toHaveBeenCalledTimes(3) // Should delete three cookies
186
+ expect(response.status).toBe(200)
187
+
188
+ // Parse the response body
189
+ const responseText = await response.text()
190
+ const responseData = JSON.parse(responseText)
191
+ expect(responseData).toEqual({ success: true })
192
+ })
193
+ })
194
+ })
@@ -0,0 +1,142 @@
1
+ import { createAuthMethods } from '../auth'
2
+
3
+ describe('createAuthMethods', () => {
4
+ const mockClient = {
5
+ auth: {
6
+ getAuthenticationUrl: jest
7
+ .fn()
8
+ .mockReturnValue('https://dropbox.com/auth'),
9
+ getAccessTokenFromCode: jest.fn().mockResolvedValue({
10
+ result: {
11
+ access_token: 'new-access-token',
12
+ refresh_token: 'new-refresh-token',
13
+ expires_in: 14400,
14
+ },
15
+ }),
16
+ },
17
+ }
18
+
19
+ const mockGetClient = jest.fn().mockReturnValue(mockClient)
20
+ const mockSetAccessToken = jest.fn()
21
+ const mockCredentials = {
22
+ clientId: 'test-client-id',
23
+ clientSecret: 'test-client-secret',
24
+ accessToken: 'test-access-token',
25
+ refreshToken: 'test-refresh-token',
26
+ }
27
+
28
+ beforeEach(() => {
29
+ jest.clearAllMocks()
30
+ // Mock fetch for refreshAccessToken method
31
+ global.fetch = jest.fn().mockResolvedValue({
32
+ ok: true,
33
+ json: jest.fn().mockResolvedValue({
34
+ access_token: 'refreshed-token',
35
+ refresh_token: 'new-refresh-token',
36
+ expires_in: 14400,
37
+ }),
38
+ })
39
+ })
40
+
41
+ it('should create auth methods object', () => {
42
+ const auth = createAuthMethods(
43
+ mockGetClient,
44
+ mockCredentials,
45
+ mockSetAccessToken
46
+ )
47
+
48
+ expect(auth).toHaveProperty('getAuthUrl')
49
+ expect(auth).toHaveProperty('exchangeCodeForToken')
50
+ expect(auth).toHaveProperty('refreshAccessToken')
51
+ })
52
+
53
+ it('should generate auth URL', async () => {
54
+ const auth = createAuthMethods(
55
+ mockGetClient,
56
+ mockCredentials,
57
+ mockSetAccessToken
58
+ )
59
+
60
+ const url = await auth.getAuthUrl(
61
+ 'http://localhost/callback',
62
+ 'test-state'
63
+ )
64
+
65
+ expect(mockGetClient).toHaveBeenCalled()
66
+ expect(mockClient.auth.getAuthenticationUrl).toHaveBeenCalledWith(
67
+ 'http://localhost/callback',
68
+ 'test-state',
69
+ 'code',
70
+ 'offline',
71
+ undefined,
72
+ undefined,
73
+ true
74
+ )
75
+ expect(url).toEqual('https://dropbox.com/auth')
76
+ })
77
+
78
+ it('should exchange code for token', async () => {
79
+ const auth = createAuthMethods(
80
+ mockGetClient,
81
+ mockCredentials,
82
+ mockSetAccessToken
83
+ )
84
+
85
+ const tokens = await auth.exchangeCodeForToken(
86
+ 'auth-code',
87
+ 'http://localhost/callback'
88
+ )
89
+
90
+ expect(mockGetClient).toHaveBeenCalled()
91
+ expect(mockClient.auth.getAccessTokenFromCode).toHaveBeenCalledWith(
92
+ 'http://localhost/callback',
93
+ 'auth-code'
94
+ )
95
+ expect(mockSetAccessToken).toHaveBeenCalledWith('new-access-token')
96
+ expect(tokens).toEqual({
97
+ accessToken: 'new-access-token',
98
+ refreshToken: 'new-refresh-token',
99
+ expiresAt: expect.any(Number),
100
+ })
101
+ })
102
+
103
+ it('should refresh access token', async () => {
104
+ const auth = createAuthMethods(
105
+ mockGetClient,
106
+ mockCredentials,
107
+ mockSetAccessToken
108
+ )
109
+
110
+ const tokens = await auth.refreshAccessToken()
111
+
112
+ expect(fetch).toHaveBeenCalledWith(
113
+ 'https://api.dropboxapi.com/oauth2/token',
114
+ {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/x-www-form-urlencoded',
118
+ },
119
+ body: expect.stringContaining('grant_type=refresh_token'),
120
+ }
121
+ )
122
+
123
+ expect(mockSetAccessToken).toHaveBeenCalledWith('refreshed-token')
124
+ expect(tokens).toEqual({
125
+ accessToken: 'refreshed-token',
126
+ refreshToken: 'new-refresh-token',
127
+ expiresAt: expect.any(Number),
128
+ })
129
+ })
130
+
131
+ it('should throw error if refresh token is missing', async () => {
132
+ const auth = createAuthMethods(
133
+ mockGetClient,
134
+ { clientId: 'test-id', clientSecret: 'test-secret' },
135
+ mockSetAccessToken
136
+ )
137
+
138
+ await expect(auth.refreshAccessToken()).rejects.toThrow(
139
+ 'Refresh token, client ID, and client secret are required to refresh access token'
140
+ )
141
+ })
142
+ })
@@ -0,0 +1,128 @@
1
+ import { createDropboxSyncClient } from '../client'
2
+ import { createAuthMethods } from '../auth'
3
+ import { createSyncMethods } from '../sync'
4
+ import { createSocketMethods } from '../socket'
5
+
6
+ // First setup all the mocks before importing the actual code
7
+ let dropboxInstanceCreated = false
8
+
9
+ // Mock the auth module to return a mock auth object that requires client initialization
10
+ jest.mock('../auth', () => ({
11
+ createAuthMethods: jest.fn().mockImplementation((getClient) => ({
12
+ mockAuth: true,
13
+ getAuthUrl: jest.fn().mockImplementation((redirectUri) => {
14
+ // This will force the getClient() function to be called
15
+ getClient()
16
+ return Promise.resolve('https://dropbox.com/oauth')
17
+ }),
18
+ })),
19
+ }))
20
+
21
+ jest.mock('../sync', () => ({
22
+ createSyncMethods: jest.fn().mockReturnValue({ mockSync: true }),
23
+ }))
24
+
25
+ jest.mock('../socket', () => ({
26
+ createSocketMethods: jest.fn().mockReturnValue({ mockSocket: true }),
27
+ }))
28
+
29
+ // Mock Dropbox constructor and track constructor options
30
+ interface DropboxOptions {
31
+ accessToken?: string
32
+ clientId?: string
33
+ }
34
+
35
+ const mockDropboxOptions: DropboxOptions[] = []
36
+ const mockDropboxClient = {
37
+ auth: {
38
+ getAuthenticationUrl: jest.fn(),
39
+ getAccessTokenFromCode: jest.fn(),
40
+ },
41
+ filesListFolder: jest.fn(),
42
+ filesUpload: jest.fn(),
43
+ filesDownload: jest.fn(),
44
+ }
45
+
46
+ // Mock the Dropbox class
47
+ jest.mock('dropbox', () => {
48
+ return {
49
+ Dropbox: jest.fn().mockImplementation((options: DropboxOptions) => {
50
+ mockDropboxOptions.push(options)
51
+ dropboxInstanceCreated = true
52
+ return mockDropboxClient
53
+ }),
54
+ }
55
+ })
56
+
57
+ describe('createDropboxSyncClient', () => {
58
+ beforeEach(() => {
59
+ jest.clearAllMocks()
60
+ mockDropboxOptions.length = 0
61
+ dropboxInstanceCreated = false
62
+ })
63
+
64
+ it('should create a client with accessToken', async () => {
65
+ const credentials = { clientId: 'test-id', accessToken: 'test-token' }
66
+ const client = createDropboxSyncClient(credentials)
67
+
68
+ expect(client).toHaveProperty('auth')
69
+ expect(client).toHaveProperty('sync')
70
+ expect(client).toHaveProperty('socket')
71
+
72
+ // Force client instantiation by calling getAuthUrl which calls getClient internally
73
+ await client.auth.getAuthUrl('https://example.com/callback')
74
+
75
+ // Check that Dropbox was constructed with the correct options
76
+ expect(dropboxInstanceCreated).toBe(true)
77
+ expect(mockDropboxOptions.length).toBe(1)
78
+ expect(mockDropboxOptions[0]).toEqual({ accessToken: 'test-token' })
79
+ expect(createAuthMethods).toHaveBeenCalled()
80
+ expect(createSyncMethods).toHaveBeenCalled()
81
+ expect(createSocketMethods).toHaveBeenCalled()
82
+ })
83
+
84
+ it('should create a client with clientId when accessToken is not provided', async () => {
85
+ const credentials = { clientId: 'test-id' }
86
+ const client = createDropboxSyncClient(credentials)
87
+
88
+ expect(client).toHaveProperty('auth')
89
+ expect(client).toHaveProperty('sync')
90
+ expect(client).toHaveProperty('socket')
91
+
92
+ // Force client instantiation by calling getAuthUrl which calls getClient internally
93
+ await client.auth.getAuthUrl('https://example.com/callback')
94
+
95
+ // Check that Dropbox was constructed with the correct options
96
+ expect(dropboxInstanceCreated).toBe(true)
97
+ expect(mockDropboxOptions.length).toBe(1)
98
+ expect(mockDropboxOptions[0]).toEqual({ clientId: 'test-id' })
99
+ expect(createAuthMethods).toHaveBeenCalled()
100
+ expect(createSyncMethods).toHaveBeenCalled()
101
+ expect(createSocketMethods).toHaveBeenCalled()
102
+ })
103
+
104
+ it('should throw error if neither clientId nor accessToken is provided', async () => {
105
+ // Mock the auth implementation for this test specifically
106
+ ;(createAuthMethods as jest.Mock).mockImplementationOnce(
107
+ (getClient) => ({
108
+ getAuthUrl: jest.fn().mockImplementation(() => {
109
+ // This will throw when called with empty credentials
110
+ try {
111
+ getClient()
112
+ } catch (error) {
113
+ return Promise.reject(error)
114
+ }
115
+ return Promise.resolve('https://dropbox.com/oauth')
116
+ }),
117
+ })
118
+ )
119
+
120
+ const credentials = {} as any
121
+ const client = createDropboxSyncClient(credentials)
122
+
123
+ // This should throw when getClient() is called inside getAuthUrl
124
+ await expect(
125
+ client.auth.getAuthUrl('https://example.com/callback')
126
+ ).rejects.toThrow('Either clientId or accessToken must be provided')
127
+ })
128
+ })