@rsweeten/dropbox-sync
Version:
Reusable Dropbox synchronization module with framework adapters
238 lines (237 loc) • 10.6 kB
JavaScript
// Mock Angular modules before importing the code under test
jest.mock('@angular/core', () => ({
Injectable: () => jest.fn(),
}));
jest.mock('@angular/common/http', () => ({
HttpClient: jest.fn(),
HttpHeaders: jest.fn(),
}));
// Now import the code that uses Angular modules
import { DropboxSyncService, getCredentialsFromEnvironment } from '../angular';
import { createDropboxSyncClient } from '../../core/client';
import { of } from 'rxjs';
import { Observable } from 'rxjs';
// Mock the core client
jest.mock('../../core/client');
describe('Angular adapter', () => {
let service;
const mockHttpClient = {
get: jest.fn(),
post: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
service = new DropboxSyncService(mockHttpClient);
});
describe('DropboxSyncService', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should initialize with credentials', () => {
const mockCredentials = {
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
accessToken: 'test-access-token',
refreshToken: 'test-refresh-token',
};
const mockClient = { mock: 'client' };
createDropboxSyncClient.mockReturnValue(mockClient);
const result = service.initialize(mockCredentials);
expect(createDropboxSyncClient).toHaveBeenCalledWith(mockCredentials);
expect(result).toEqual(mockClient);
});
it('should get client after initialization', () => {
const mockCredentials = { clientId: 'test-id' };
const mockClient = { mock: 'client' };
createDropboxSyncClient.mockReturnValue(mockClient);
service.initialize(mockCredentials);
const client = service.getClient();
expect(client).toEqual(mockClient);
});
it('should throw error if getClient called before initialization', () => {
expect(() => service.getClient()).toThrow('DropboxSyncService not initialized. Call initialize() first.');
});
it('should check connection status via HTTP', () => {
mockHttpClient.get.mockReturnValue(of({ connected: true }));
service.checkConnection().subscribe((result) => {
expect(result).toBe(true);
});
expect(mockHttpClient.get).toHaveBeenCalledWith('/api/dropbox/status');
});
it('should handle connection error', () => {
mockHttpClient.get.mockReturnValue(of(null)); // Simulate error
service.checkConnection().subscribe((result) => {
expect(result).toBe(false);
});
});
it('should disconnect via HTTP', () => {
mockHttpClient.post.mockReturnValue(of({ success: true }));
service.disconnectDropbox().subscribe((result) => {
expect(result).toBe(true);
});
expect(mockHttpClient.post).toHaveBeenCalledWith('/api/dropbox/logout', {});
});
it('should start sync process', () => {
const mockOptions = { localDir: '/test-dir' };
const mockResult = { uploaded: [], downloaded: [] };
const mockClient = {
sync: {
syncFiles: jest.fn().mockResolvedValue(mockResult),
},
socket: {
connect: jest.fn(),
},
};
createDropboxSyncClient.mockReturnValue(mockClient);
service.initialize({ clientId: 'test-id' });
// Mock setupSocketListeners to return an observable
service.setupSocketListeners = jest.fn().mockReturnValue(of({}));
service.startSync(mockOptions).subscribe((result) => {
expect(result).toEqual(mockResult);
});
expect(mockClient.socket.connect).toHaveBeenCalled();
expect(mockClient.sync.syncFiles).toHaveBeenCalledWith(mockOptions);
expect(service.setupSocketListeners).toHaveBeenCalled();
});
it('should cancel sync', () => {
const mockClient = {
sync: {
cancelSync: jest.fn(),
},
};
createDropboxSyncClient.mockReturnValue(mockClient);
service.initialize({ clientId: 'test-id' });
service.cancelSync();
expect(mockClient.sync.cancelSync).toHaveBeenCalled();
});
// Mock the implementation of setupSocketListeners for testing
it('should setup socket listeners', () => {
const mockSocket = {
on: jest.fn(),
off: jest.fn(),
};
const mockClient = {
socket: mockSocket,
};
createDropboxSyncClient.mockReturnValue(mockClient);
service.initialize({ clientId: 'test-id' });
const observable = service.setupSocketListeners();
// Should return an Observable
expect(observable).toBeInstanceOf(Observable);
// Get the subscriber function that was used to create the Observable
const observer = {
next: jest.fn(),
error: jest.fn(),
complete: jest.fn(),
};
const subscription = observable.subscribe(observer);
// Since we have direct access to the socket mock, we can verify its behavior
expect(mockSocket.on).toHaveBeenCalledWith('sync:progress', expect.any(Function));
expect(mockSocket.on).toHaveBeenCalledWith('sync:queue', expect.any(Function));
expect(mockSocket.on).toHaveBeenCalledWith('sync:complete', expect.any(Function));
expect(mockSocket.on).toHaveBeenCalledWith('sync:error', expect.any(Function));
// Clean up the subscription
subscription.unsubscribe();
});
it('should handle OAuth callback', () => {
const mockCode = 'test-auth-code';
const mockTokens = {
accessToken: 'new-access-token',
refreshToken: 'new-refresh-token',
};
const mockClient = {
auth: {
exchangeCodeForToken: jest
.fn()
.mockResolvedValue(mockTokens),
},
};
createDropboxSyncClient.mockReturnValue(mockClient);
service.initialize({ clientId: 'test-id' });
// Create a proper localStorage mock
const mockLocalStorage = {
setItem: jest.fn(),
getItem: jest.fn(),
removeItem: jest.fn(),
};
Object.defineProperty(global, 'localStorage', {
value: mockLocalStorage,
writable: true,
});
// Mock window.location.origin
Object.defineProperty(window, 'location', {
value: {
origin: 'http://localhost',
},
writable: true,
});
// Subscribe to the observable to trigger the code
let receivedResult;
service.handleOAuthCallback(mockCode).subscribe((result) => {
receivedResult = result;
});
// Verify the exchangeCodeForToken was called with the expected args
expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalledWith(mockCode, 'http://localhost/api/dropbox/auth/callback');
// Create and resolve the promise to trigger the localStorage set calls
const resolvePromise = Promise.resolve(mockTokens);
// Return a promise that will resolve after our mocked promise
return resolvePromise.then(() => {
// Now we can check localStorage was called
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('dropbox_access_token', mockTokens.accessToken);
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('dropbox_refresh_token', mockTokens.refreshToken);
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('dropbox_connected', 'true');
// Verify we received the tokens
expect(receivedResult).toEqual(mockTokens);
});
});
});
describe('getCredentialsFromEnvironment', () => {
it('should extract credentials from environment', () => {
const mockEnvironment = {
dropboxAppKey: 'env-app-key',
dropboxAppSecret: 'env-app-secret',
};
// Mock localStorage
const mockLocalStorage = {
getItem: jest.fn().mockImplementation((key) => {
if (key === 'dropbox_access_token')
return 'storage-access-token';
if (key === 'dropbox_refresh_token')
return 'storage-refresh-token';
return null;
}),
};
Object.defineProperty(global, 'localStorage', {
value: mockLocalStorage,
writable: true,
});
const credentials = getCredentialsFromEnvironment(mockEnvironment);
expect(credentials).toEqual({
clientId: 'env-app-key',
clientSecret: 'env-app-secret',
accessToken: 'storage-access-token',
refreshToken: 'storage-refresh-token',
});
expect(mockLocalStorage.getItem).toHaveBeenCalledWith('dropbox_access_token');
expect(mockLocalStorage.getItem).toHaveBeenCalledWith('dropbox_refresh_token');
});
it('should handle missing environment values', () => {
const mockEnvironment = {};
// Mock localStorage with no tokens
const mockLocalStorage = {
getItem: jest.fn().mockReturnValue(null),
};
Object.defineProperty(global, 'localStorage', {
value: mockLocalStorage,
writable: true,
});
const credentials = getCredentialsFromEnvironment(mockEnvironment);
expect(credentials).toEqual({
clientId: '',
clientSecret: undefined,
accessToken: undefined,
refreshToken: undefined,
});
});
});
});