@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,30 @@
|
|
|
1
|
+
name: Test Pull Request
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [20.x]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v3
|
|
19
|
+
|
|
20
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
21
|
+
uses: actions/setup-node@v3
|
|
22
|
+
with:
|
|
23
|
+
node-version: ${{ matrix.node-version }}
|
|
24
|
+
cache: 'npm'
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: npm test
|
package/README.md
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
# Dropbox Sync Module
|
|
2
2
|
|
|
3
|
+
[](https://github.com/sweetenr/dropbox-sync-service/actions/workflows/test-pr.yml)
|
|
4
|
+
|
|
3
5
|
A reusable TypeScript module for syncing files between your application and Dropbox. Features framework-specific adapters for Next.js, SvelteKit, Nuxt, and Angular.
|
|
4
6
|
|
|
5
7
|
> N.B. This NPM package is neither tested nor supported
|
|
6
8
|
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Core Usage](#core-usage)
|
|
15
|
+
- [Development Setup](#development-setup)
|
|
16
|
+
- [Architecture](#architecture)
|
|
17
|
+
- [Data Flow](#data-flow)
|
|
18
|
+
- [Testing](#testing)
|
|
19
|
+
- [Test Structure](#test-structure)
|
|
20
|
+
- [Running Tests](#running-tests)
|
|
21
|
+
- [Testing Approach](#testing-approach)
|
|
22
|
+
- [Mock Strategy](#mock-strategy)
|
|
23
|
+
- [Test Coverage](#test-coverage)
|
|
24
|
+
- [Framework-Specific Usage](#framework-specific-usage)
|
|
25
|
+
- [Next.js](#nextjs)
|
|
26
|
+
- [Nuxt.js](#nuxtjs)
|
|
27
|
+
- [SvelteKit](#sveltekit)
|
|
28
|
+
- [Angular](#angular)
|
|
29
|
+
- [API Reference](#api-reference)
|
|
30
|
+
- [Core Client](#core-client)
|
|
31
|
+
- [Auth Methods](#auth-methods)
|
|
32
|
+
- [Sync Methods](#sync-methods)
|
|
33
|
+
- [Socket Methods](#socket-methods)
|
|
34
|
+
- [Socket Events](#socket-events)
|
|
35
|
+
- [Configuration Options](#configuration-options)
|
|
36
|
+
- [License](#license)
|
|
37
|
+
|
|
7
38
|
## Features
|
|
8
39
|
|
|
9
40
|
- **File Synchronization**: Upload local files to Dropbox and download Dropbox files to your local filesystem
|
|
@@ -16,7 +47,18 @@ A reusable TypeScript module for syncing files between your application and Drop
|
|
|
16
47
|
## Installation
|
|
17
48
|
|
|
18
49
|
```bash
|
|
19
|
-
|
|
50
|
+
# Install the package in your project
|
|
51
|
+
npm install dropbox-sync
|
|
52
|
+
# or
|
|
53
|
+
yarn add dropbox-sync
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You may also need to install peer dependencies:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install socket.io socket.io-client
|
|
60
|
+
# or
|
|
61
|
+
yarn add socket.io socket.io-client
|
|
20
62
|
```
|
|
21
63
|
|
|
22
64
|
## Quick Start
|
|
@@ -54,6 +96,170 @@ console.log(`Uploaded ${syncResult.uploaded.length} files`)
|
|
|
54
96
|
console.log(`Downloaded ${syncResult.downloaded.length} files`)
|
|
55
97
|
```
|
|
56
98
|
|
|
99
|
+
## Development Setup
|
|
100
|
+
|
|
101
|
+
To set up this project for local development:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Clone the repository
|
|
105
|
+
git clone https://github.com/yourusername/dropbox-sync-service.git
|
|
106
|
+
cd dropbox-sync-service
|
|
107
|
+
|
|
108
|
+
# Install dependencies
|
|
109
|
+
npm install
|
|
110
|
+
|
|
111
|
+
# Run tests
|
|
112
|
+
npm test
|
|
113
|
+
|
|
114
|
+
# Build the package
|
|
115
|
+
npm run build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
119
|
+
|
|
120
|
+
The Dropbox Sync Module architecture is designed with modularity, extensibility, and framework-agnosticism in mind.
|
|
121
|
+
|
|
122
|
+
```mermaid
|
|
123
|
+
graph TD
|
|
124
|
+
A[Application] --> B[Framework Adapter]
|
|
125
|
+
B --> C[Core Client]
|
|
126
|
+
C --> D[Auth Module]
|
|
127
|
+
C --> E[Sync Module]
|
|
128
|
+
C --> F[Socket Module]
|
|
129
|
+
D --> G[Dropbox API]
|
|
130
|
+
E --> G
|
|
131
|
+
F --> H[Socket.IO]
|
|
132
|
+
H --> E
|
|
133
|
+
H --> A
|
|
134
|
+
|
|
135
|
+
%% File Flow
|
|
136
|
+
I[Local Files] --> E
|
|
137
|
+
E --> I
|
|
138
|
+
G --> E
|
|
139
|
+
E --> G
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Data Flow
|
|
143
|
+
|
|
144
|
+
1. **Authentication Flow:**
|
|
145
|
+
|
|
146
|
+
```mermaid
|
|
147
|
+
sequenceDiagram
|
|
148
|
+
participant User
|
|
149
|
+
participant App
|
|
150
|
+
participant DropboxSync
|
|
151
|
+
participant Dropbox
|
|
152
|
+
|
|
153
|
+
User->>App: Initiate auth
|
|
154
|
+
App->>DropboxSync: getAuthUrl()
|
|
155
|
+
DropboxSync->>Dropbox: Request auth URL
|
|
156
|
+
Dropbox-->>DropboxSync: Auth URL
|
|
157
|
+
DropboxSync-->>App: Auth URL
|
|
158
|
+
App-->>User: Redirect to auth URL
|
|
159
|
+
User->>Dropbox: Authorize app
|
|
160
|
+
Dropbox-->>App: Auth code (via redirect)
|
|
161
|
+
App->>DropboxSync: exchangeCodeForToken(code)
|
|
162
|
+
DropboxSync->>Dropbox: Exchange code for token
|
|
163
|
+
Dropbox-->>DropboxSync: Access & refresh tokens
|
|
164
|
+
DropboxSync-->>App: Tokens
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
2. **Sync Flow:**
|
|
168
|
+
|
|
169
|
+
```mermaid
|
|
170
|
+
sequenceDiagram
|
|
171
|
+
participant App
|
|
172
|
+
participant DropboxSync
|
|
173
|
+
participant Socket
|
|
174
|
+
participant Dropbox
|
|
175
|
+
participant LocalFS
|
|
176
|
+
|
|
177
|
+
App->>DropboxSync: syncFiles(options)
|
|
178
|
+
DropboxSync->>LocalFS: Scan local files
|
|
179
|
+
LocalFS-->>DropboxSync: Local file list
|
|
180
|
+
DropboxSync->>Dropbox: List files
|
|
181
|
+
Dropbox-->>DropboxSync: Dropbox file list
|
|
182
|
+
DropboxSync->>DropboxSync: Create sync queue
|
|
183
|
+
|
|
184
|
+
loop For each file to upload
|
|
185
|
+
DropboxSync->>Dropbox: Upload file
|
|
186
|
+
DropboxSync->>Socket: Emit progress
|
|
187
|
+
Socket-->>App: Progress update
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
loop For each file to download
|
|
191
|
+
DropboxSync->>Dropbox: Download file
|
|
192
|
+
DropboxSync->>LocalFS: Write file
|
|
193
|
+
DropboxSync->>Socket: Emit progress
|
|
194
|
+
Socket-->>App: Progress update
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
DropboxSync-->>App: Sync results
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Testing
|
|
201
|
+
|
|
202
|
+
The Dropbox Sync Module uses Jest for unit testing. Tests are organized to match the structure of the source code.
|
|
203
|
+
|
|
204
|
+
### Test Structure
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
__tests__/ # Test utilities and mocks
|
|
208
|
+
src/
|
|
209
|
+
core/
|
|
210
|
+
__tests__/ # Core module tests
|
|
211
|
+
auth.spec.ts # Authentication tests
|
|
212
|
+
client.spec.ts # Client tests
|
|
213
|
+
socket.spec.ts # Socket tests
|
|
214
|
+
sync.spec.ts # Sync tests
|
|
215
|
+
adapters/
|
|
216
|
+
__tests__/ # Framework adapter tests
|
|
217
|
+
angular.spec.ts # Angular adapter tests
|
|
218
|
+
next.spec.ts # Next.js adapter tests
|
|
219
|
+
nuxt.spec.ts # Nuxt adapter tests
|
|
220
|
+
svelte.spec.ts # SvelteKit adapter tests
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Running Tests
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Run all tests
|
|
227
|
+
npm test
|
|
228
|
+
|
|
229
|
+
# Run tests with coverage
|
|
230
|
+
npm test -- --coverage
|
|
231
|
+
|
|
232
|
+
# Run specific test file
|
|
233
|
+
npm test -- src/core/__tests__/auth.spec.ts
|
|
234
|
+
|
|
235
|
+
# Run tests in watch mode during development
|
|
236
|
+
npm test -- --watch
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Testing Approach
|
|
240
|
+
|
|
241
|
+
1. **Unit Tests**: Each module is tested in isolation with mocked dependencies
|
|
242
|
+
2. **Integration Tests**: Tests for interactions between modules
|
|
243
|
+
3. **Adapter Tests**: Tests for framework-specific adapters
|
|
244
|
+
|
|
245
|
+
### Mock Strategy
|
|
246
|
+
|
|
247
|
+
- Dropbox API calls are mocked using Jest mock functions
|
|
248
|
+
- File system operations are mocked to avoid touching real files
|
|
249
|
+
- Socket.IO is mocked to test event emission and handling
|
|
250
|
+
|
|
251
|
+
### Test Coverage
|
|
252
|
+
|
|
253
|
+
We aim for high test coverage, especially for the core functionality:
|
|
254
|
+
|
|
255
|
+
| Module | Coverage |
|
|
256
|
+
| -------- | -------- |
|
|
257
|
+
| Core | >90% |
|
|
258
|
+
| Auth | >95% |
|
|
259
|
+
| Sync | >90% |
|
|
260
|
+
| Socket | >90% |
|
|
261
|
+
| Adapters | >85% |
|
|
262
|
+
|
|
57
263
|
## Framework-Specific Usage
|
|
58
264
|
|
|
59
265
|
### Next.js
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Manual mock for nuxt/app module
|
|
2
|
+
module.exports = {
|
|
3
|
+
useRuntimeConfig: jest.fn().mockReturnValue({
|
|
4
|
+
public: {
|
|
5
|
+
dropboxAppKey: 'test-app-key',
|
|
6
|
+
appUrl: 'https://example.com'
|
|
7
|
+
},
|
|
8
|
+
dropboxAppSecret: 'test-app-secret',
|
|
9
|
+
dropboxRedirectUri: 'https://example.com/api/dropbox/auth/callback'
|
|
10
|
+
}),
|
|
11
|
+
useCookie: jest.fn().mockImplementation((name) => {
|
|
12
|
+
if (name === 'dropbox_access_token') {
|
|
13
|
+
return { value: 'test-access-token' }
|
|
14
|
+
}
|
|
15
|
+
if (name === 'dropbox_refresh_token') {
|
|
16
|
+
return { value: 'test-refresh-token' }
|
|
17
|
+
}
|
|
18
|
+
return { value: null }
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// Mock Angular modules before importing the code under test
|
|
2
|
+
jest.mock('@angular/core', () => ({
|
|
3
|
+
Injectable: () => jest.fn(),
|
|
4
|
+
}));
|
|
5
|
+
jest.mock('@angular/common/http', () => ({
|
|
6
|
+
HttpClient: jest.fn(),
|
|
7
|
+
HttpHeaders: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
// Now import the code that uses Angular modules
|
|
10
|
+
import { DropboxSyncService, getCredentialsFromEnvironment } from '../angular';
|
|
11
|
+
import { createDropboxSyncClient } from '../../core/client';
|
|
12
|
+
import { of } from 'rxjs';
|
|
13
|
+
import { Observable } from 'rxjs';
|
|
14
|
+
// Mock the core client
|
|
15
|
+
jest.mock('../../core/client');
|
|
16
|
+
describe('Angular adapter', () => {
|
|
17
|
+
let service;
|
|
18
|
+
const mockHttpClient = {
|
|
19
|
+
get: jest.fn(),
|
|
20
|
+
post: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
service = new DropboxSyncService(mockHttpClient);
|
|
25
|
+
});
|
|
26
|
+
describe('DropboxSyncService', () => {
|
|
27
|
+
it('should be created', () => {
|
|
28
|
+
expect(service).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
it('should initialize with credentials', () => {
|
|
31
|
+
const mockCredentials = {
|
|
32
|
+
clientId: 'test-client-id',
|
|
33
|
+
clientSecret: 'test-client-secret',
|
|
34
|
+
accessToken: 'test-access-token',
|
|
35
|
+
refreshToken: 'test-refresh-token',
|
|
36
|
+
};
|
|
37
|
+
const mockClient = { mock: 'client' };
|
|
38
|
+
createDropboxSyncClient.mockReturnValue(mockClient);
|
|
39
|
+
const result = service.initialize(mockCredentials);
|
|
40
|
+
expect(createDropboxSyncClient).toHaveBeenCalledWith(mockCredentials);
|
|
41
|
+
expect(result).toEqual(mockClient);
|
|
42
|
+
});
|
|
43
|
+
it('should get client after initialization', () => {
|
|
44
|
+
const mockCredentials = { clientId: 'test-id' };
|
|
45
|
+
const mockClient = { mock: 'client' };
|
|
46
|
+
createDropboxSyncClient.mockReturnValue(mockClient);
|
|
47
|
+
service.initialize(mockCredentials);
|
|
48
|
+
const client = service.getClient();
|
|
49
|
+
expect(client).toEqual(mockClient);
|
|
50
|
+
});
|
|
51
|
+
it('should throw error if getClient called before initialization', () => {
|
|
52
|
+
expect(() => service.getClient()).toThrow('DropboxSyncService not initialized. Call initialize() first.');
|
|
53
|
+
});
|
|
54
|
+
it('should check connection status via HTTP', () => {
|
|
55
|
+
mockHttpClient.get.mockReturnValue(of({ connected: true }));
|
|
56
|
+
service.checkConnection().subscribe((result) => {
|
|
57
|
+
expect(result).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
expect(mockHttpClient.get).toHaveBeenCalledWith('/api/dropbox/status');
|
|
60
|
+
});
|
|
61
|
+
it('should handle connection error', () => {
|
|
62
|
+
mockHttpClient.get.mockReturnValue(of(null)); // Simulate error
|
|
63
|
+
service.checkConnection().subscribe((result) => {
|
|
64
|
+
expect(result).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it('should disconnect via HTTP', () => {
|
|
68
|
+
mockHttpClient.post.mockReturnValue(of({ success: true }));
|
|
69
|
+
service.disconnectDropbox().subscribe((result) => {
|
|
70
|
+
expect(result).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
expect(mockHttpClient.post).toHaveBeenCalledWith('/api/dropbox/logout', {});
|
|
73
|
+
});
|
|
74
|
+
it('should start sync process', () => {
|
|
75
|
+
const mockOptions = { localDir: '/test-dir' };
|
|
76
|
+
const mockResult = { uploaded: [], downloaded: [] };
|
|
77
|
+
const mockClient = {
|
|
78
|
+
sync: {
|
|
79
|
+
syncFiles: jest.fn().mockResolvedValue(mockResult),
|
|
80
|
+
},
|
|
81
|
+
socket: {
|
|
82
|
+
connect: jest.fn(),
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
createDropboxSyncClient.mockReturnValue(mockClient);
|
|
86
|
+
service.initialize({ clientId: 'test-id' });
|
|
87
|
+
// Mock setupSocketListeners to return an observable
|
|
88
|
+
service.setupSocketListeners = jest.fn().mockReturnValue(of({}));
|
|
89
|
+
service.startSync(mockOptions).subscribe((result) => {
|
|
90
|
+
expect(result).toEqual(mockResult);
|
|
91
|
+
});
|
|
92
|
+
expect(mockClient.socket.connect).toHaveBeenCalled();
|
|
93
|
+
expect(mockClient.sync.syncFiles).toHaveBeenCalledWith(mockOptions);
|
|
94
|
+
expect(service.setupSocketListeners).toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
it('should cancel sync', () => {
|
|
97
|
+
const mockClient = {
|
|
98
|
+
sync: {
|
|
99
|
+
cancelSync: jest.fn(),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
createDropboxSyncClient.mockReturnValue(mockClient);
|
|
103
|
+
service.initialize({ clientId: 'test-id' });
|
|
104
|
+
service.cancelSync();
|
|
105
|
+
expect(mockClient.sync.cancelSync).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
// Mock the implementation of setupSocketListeners for testing
|
|
108
|
+
it('should setup socket listeners', () => {
|
|
109
|
+
const mockSocket = {
|
|
110
|
+
on: jest.fn(),
|
|
111
|
+
off: jest.fn(),
|
|
112
|
+
};
|
|
113
|
+
const mockClient = {
|
|
114
|
+
socket: mockSocket,
|
|
115
|
+
};
|
|
116
|
+
createDropboxSyncClient.mockReturnValue(mockClient);
|
|
117
|
+
service.initialize({ clientId: 'test-id' });
|
|
118
|
+
const observable = service.setupSocketListeners();
|
|
119
|
+
// Should return an Observable
|
|
120
|
+
expect(observable).toBeInstanceOf(Observable);
|
|
121
|
+
// Get the subscriber function that was used to create the Observable
|
|
122
|
+
const observer = {
|
|
123
|
+
next: jest.fn(),
|
|
124
|
+
error: jest.fn(),
|
|
125
|
+
complete: jest.fn(),
|
|
126
|
+
};
|
|
127
|
+
const subscription = observable.subscribe(observer);
|
|
128
|
+
// Since we have direct access to the socket mock, we can verify its behavior
|
|
129
|
+
expect(mockSocket.on).toHaveBeenCalledWith('sync:progress', expect.any(Function));
|
|
130
|
+
expect(mockSocket.on).toHaveBeenCalledWith('sync:queue', expect.any(Function));
|
|
131
|
+
expect(mockSocket.on).toHaveBeenCalledWith('sync:complete', expect.any(Function));
|
|
132
|
+
expect(mockSocket.on).toHaveBeenCalledWith('sync:error', expect.any(Function));
|
|
133
|
+
// Clean up the subscription
|
|
134
|
+
subscription.unsubscribe();
|
|
135
|
+
});
|
|
136
|
+
it('should handle OAuth callback', () => {
|
|
137
|
+
const mockCode = 'test-auth-code';
|
|
138
|
+
const mockTokens = {
|
|
139
|
+
accessToken: 'new-access-token',
|
|
140
|
+
refreshToken: 'new-refresh-token',
|
|
141
|
+
};
|
|
142
|
+
const mockClient = {
|
|
143
|
+
auth: {
|
|
144
|
+
exchangeCodeForToken: jest
|
|
145
|
+
.fn()
|
|
146
|
+
.mockResolvedValue(mockTokens),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
createDropboxSyncClient.mockReturnValue(mockClient);
|
|
150
|
+
service.initialize({ clientId: 'test-id' });
|
|
151
|
+
// Create a proper localStorage mock
|
|
152
|
+
const mockLocalStorage = {
|
|
153
|
+
setItem: jest.fn(),
|
|
154
|
+
getItem: jest.fn(),
|
|
155
|
+
removeItem: jest.fn(),
|
|
156
|
+
};
|
|
157
|
+
Object.defineProperty(global, 'localStorage', {
|
|
158
|
+
value: mockLocalStorage,
|
|
159
|
+
writable: true,
|
|
160
|
+
});
|
|
161
|
+
// Mock window.location.origin
|
|
162
|
+
Object.defineProperty(window, 'location', {
|
|
163
|
+
value: {
|
|
164
|
+
origin: 'http://localhost',
|
|
165
|
+
},
|
|
166
|
+
writable: true,
|
|
167
|
+
});
|
|
168
|
+
// Subscribe to the observable to trigger the code
|
|
169
|
+
let receivedResult;
|
|
170
|
+
service.handleOAuthCallback(mockCode).subscribe((result) => {
|
|
171
|
+
receivedResult = result;
|
|
172
|
+
});
|
|
173
|
+
// Verify the exchangeCodeForToken was called with the expected args
|
|
174
|
+
expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalledWith(mockCode, 'http://localhost/api/dropbox/auth/callback');
|
|
175
|
+
// Create and resolve the promise to trigger the localStorage set calls
|
|
176
|
+
const resolvePromise = Promise.resolve(mockTokens);
|
|
177
|
+
// Return a promise that will resolve after our mocked promise
|
|
178
|
+
return resolvePromise.then(() => {
|
|
179
|
+
// Now we can check localStorage was called
|
|
180
|
+
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('dropbox_access_token', mockTokens.accessToken);
|
|
181
|
+
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('dropbox_refresh_token', mockTokens.refreshToken);
|
|
182
|
+
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('dropbox_connected', 'true');
|
|
183
|
+
// Verify we received the tokens
|
|
184
|
+
expect(receivedResult).toEqual(mockTokens);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('getCredentialsFromEnvironment', () => {
|
|
189
|
+
it('should extract credentials from environment', () => {
|
|
190
|
+
const mockEnvironment = {
|
|
191
|
+
dropboxAppKey: 'env-app-key',
|
|
192
|
+
dropboxAppSecret: 'env-app-secret',
|
|
193
|
+
};
|
|
194
|
+
// Mock localStorage
|
|
195
|
+
const mockLocalStorage = {
|
|
196
|
+
getItem: jest.fn().mockImplementation((key) => {
|
|
197
|
+
if (key === 'dropbox_access_token')
|
|
198
|
+
return 'storage-access-token';
|
|
199
|
+
if (key === 'dropbox_refresh_token')
|
|
200
|
+
return 'storage-refresh-token';
|
|
201
|
+
return null;
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
Object.defineProperty(global, 'localStorage', {
|
|
205
|
+
value: mockLocalStorage,
|
|
206
|
+
writable: true,
|
|
207
|
+
});
|
|
208
|
+
const credentials = getCredentialsFromEnvironment(mockEnvironment);
|
|
209
|
+
expect(credentials).toEqual({
|
|
210
|
+
clientId: 'env-app-key',
|
|
211
|
+
clientSecret: 'env-app-secret',
|
|
212
|
+
accessToken: 'storage-access-token',
|
|
213
|
+
refreshToken: 'storage-refresh-token',
|
|
214
|
+
});
|
|
215
|
+
expect(mockLocalStorage.getItem).toHaveBeenCalledWith('dropbox_access_token');
|
|
216
|
+
expect(mockLocalStorage.getItem).toHaveBeenCalledWith('dropbox_refresh_token');
|
|
217
|
+
});
|
|
218
|
+
it('should handle missing environment values', () => {
|
|
219
|
+
const mockEnvironment = {};
|
|
220
|
+
// Mock localStorage with no tokens
|
|
221
|
+
const mockLocalStorage = {
|
|
222
|
+
getItem: jest.fn().mockReturnValue(null),
|
|
223
|
+
};
|
|
224
|
+
Object.defineProperty(global, 'localStorage', {
|
|
225
|
+
value: mockLocalStorage,
|
|
226
|
+
writable: true,
|
|
227
|
+
});
|
|
228
|
+
const credentials = getCredentialsFromEnvironment(mockEnvironment);
|
|
229
|
+
expect(credentials).toEqual({
|
|
230
|
+
clientId: '',
|
|
231
|
+
clientSecret: undefined,
|
|
232
|
+
accessToken: undefined,
|
|
233
|
+
refreshToken: undefined,
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|