@rsweeten/dropbox-sync
Version:
Reusable Dropbox synchronization module with framework adapters
339 lines (274 loc) • 11.3 kB
text/typescript
// 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: DropboxSyncService
const mockHttpClient = {
get: jest.fn(),
post: jest.fn(),
}
beforeEach(() => {
jest.clearAllMocks()
service = new DropboxSyncService(mockHttpClient as any)
})
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 as jest.Mock).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 as jest.Mock).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 as jest.Mock).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 as jest.Mock).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 as jest.Mock).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 as jest.Mock).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: any
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,
})
})
})
})