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