Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import '../../../../test/mock-angular';
import { $q } from '../../../../test/mock-services';
import {
DailyNewSyncLimitReachedError,
DataOutOfSyncError,
InvalidServiceError,
NetworkConnectionError,
NotAcceptingNewSyncsError,
RequestEntityTooLargeError,
ServiceOfflineError,
SyncNotFoundError,
TooManyRequestsError
} from '../../errors/errors';
import { ApiXbrowsersyncService } from './api-xbrowsersync.service';

describe('ApiXbrowsersyncService', () => {
let apiSvc: ApiXbrowsersyncService;
const mock$injector = {
get: jest.fn(),
annotate: jest.fn(),
has: jest.fn(),
instantiate: jest.fn(),
invoke: jest.fn(),
loadNewModules: jest.fn(),
modules: {},
strictDi: false
} as any;
const mock$httpFn = jest.fn();
const mock$http = Object.assign(mock$httpFn, {
get: jest.fn(),
post: jest.fn(),
put: jest.fn()
}) as any;
const mockNetworkSvc = { isNetworkConnected: jest.fn(), getErrorFromHttpResponse: jest.fn() } as any;
const mockStoreSvc = { get: jest.fn(), set: jest.fn() } as any;
const mockUtilitySvc = {
checkSyncCredentialsExist: jest.fn(),
compareVersions: jest.fn()
} as any;

beforeEach(() => {
apiSvc = new ApiXbrowsersyncService(mock$injector, mock$http, $q, mockNetworkSvc, mockStoreSvc, mockUtilitySvc);
});

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

test('apiRequestSucceeded: Returns resolved promise with response', async () => {
const testResponse = { data: 'test' };

const result = await apiSvc.apiRequestSucceeded(testResponse);

expect(result).toStrictEqual(testResponse);
});

test('checkNetworkConnection: Resolves when network is connected', async () => {
mockNetworkSvc.isNetworkConnected.mockReturnValue(true);

await expect(apiSvc.checkNetworkConnection()).resolves.toBeUndefined();
});

test('checkNetworkConnection: Rejects with NetworkConnectionError when not connected', async () => {
mockNetworkSvc.isNetworkConnected.mockReturnValue(false);

await expect(apiSvc.checkNetworkConnection()).rejects.toThrow(NetworkConnectionError);
});

test('getServiceUrl: Returns service URL from store', async () => {
mockStoreSvc.get.mockResolvedValue({ serviceUrl: 'https://api.example.com' });

const result = await apiSvc.getServiceUrl();

expect(result).toBe('https://api.example.com');
});

// getErrorFromHttpResponse tests
test('getErrorFromHttpResponse: Returns SyncNotFoundError for 401', () => {
const response = { status: 401, data: { message: 'Not found' } } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(SyncNotFoundError);
});

test('getErrorFromHttpResponse: Returns InvalidServiceError for 404', () => {
const response = { status: 404, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(InvalidServiceError);
});

test('getErrorFromHttpResponse: Returns NotAcceptingNewSyncsError for 405', () => {
const response = { status: 405, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(NotAcceptingNewSyncsError);
});

test('getErrorFromHttpResponse: Returns DailyNewSyncLimitReachedError for 406', () => {
const response = { status: 406, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(DailyNewSyncLimitReachedError);
});

test('getErrorFromHttpResponse: Returns DataOutOfSyncError for 409', () => {
const response = { status: 409, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(DataOutOfSyncError);
});

test('getErrorFromHttpResponse: Returns RequestEntityTooLargeError for 413', () => {
const response = { status: 413, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(RequestEntityTooLargeError);
});

test('getErrorFromHttpResponse: Returns TooManyRequestsError for 429', () => {
const response = { status: 429, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(TooManyRequestsError);
});

test('getErrorFromHttpResponse: Returns ServiceOfflineError for 500+', () => {
const response = { status: 500, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(ServiceOfflineError);
});

test('getErrorFromHttpResponse: Returns ServiceOfflineError for 503', () => {
const response = { status: 503, data: {} } as any;

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBeInstanceOf(ServiceOfflineError);
});

test('getErrorFromHttpResponse: Delegates to networkSvc for other status codes', () => {
const response = { status: 400, data: {} } as any;
const mockError = new Error('network error');
mockNetworkSvc.getErrorFromHttpResponse.mockReturnValue(mockError);

const result = apiSvc.getErrorFromHttpResponse(response);

expect(result).toBe(mockError);
});

test('handleFailedRequest: Throws error from getErrorFromHttpResponse', () => {
const response = { status: 401, data: { message: 'Unauthorized' } } as any;

expect(() => apiSvc.handleFailedRequest(response)).toThrow(SyncNotFoundError);
});

test('checkServiceStatus: Rejects when not connected', async () => {
mockNetworkSvc.isNetworkConnected.mockReturnValue(false);

await expect(apiSvc.checkServiceStatus('https://api.example.com')).rejects.toThrow(NetworkConnectionError);
});

test('checkServiceStatus: Validates service response', async () => {
mockNetworkSvc.isNetworkConnected.mockReturnValue(true);
mockUtilitySvc.compareVersions.mockReturnValue(false);
mock$httpFn.mockResolvedValue({
data: { status: 1, version: '1.1.13' }
});

const result = await apiSvc.checkServiceStatus('https://api.example.com');

expect(result.status).toBe(1);
expect(result.version).toBe('1.1.13');
});

test('checkServiceStatus: Throws InvalidServiceError for invalid response', async () => {
mockNetworkSvc.isNetworkConnected.mockReturnValue(true);
mock$httpFn.mockResolvedValue({
data: {}
});

await expect(apiSvc.checkServiceStatus('https://api.example.com')).rejects.toThrow(InvalidServiceError);
});

test('checkServiceStatus: Throws UnsupportedApiVersionError for old API version', async () => {
mockNetworkSvc.isNetworkConnected.mockReturnValue(true);
mockUtilitySvc.compareVersions.mockReturnValue(true);
mock$httpFn.mockResolvedValue({
data: { status: 1, version: '1.0.0' }
});

const { UnsupportedApiVersionError } = require('../../errors/errors');
await expect(apiSvc.checkServiceStatus('https://api.example.com')).rejects.toThrow(UnsupportedApiVersionError);
});

test('formatServiceInfo: Returns undefined for no input', () => {
const result = apiSvc.formatServiceInfo(undefined);

expect(result).toBeUndefined();
});

test('formatServiceInfo: Converts maxSyncSize from bytes to KB', () => {
const result = apiSvc.formatServiceInfo({
status: 1,
version: '1.1.13',
maxSyncSize: 524288
} as any);

expect(result.maxSyncSize).toBe(512);
});
});
155 changes: 155 additions & 0 deletions src/modules/shared/backup-restore/backup-restore.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import '../../../test/mock-angular';
import { $q } from '../../../test/mock-services';
import { FailedRestoreDataError } from '../errors/errors';
import { BackupRestoreService } from './backup-restore.service';

describe('BackupRestoreService', () => {
let backupRestoreSvc: BackupRestoreService;
const mockBookmarkSvc = { getBookmarksForExport: jest.fn() } as any;
const mockLogSvc = { logInfo: jest.fn() } as any;
const mockPlatformSvc = {
downloadFile: jest.fn(),
getAppVersion: jest.fn(),
queueSync: jest.fn()
} as any;
const mockStoreSvc = { get: jest.fn(), set: jest.fn() } as any;
const mockUpgradeSvc = { upgradeBookmarks: jest.fn() } as any;
const mockUtilitySvc = {
getDateTimeString: jest.fn().mockReturnValue('20230115103045'),
isSyncEnabled: jest.fn(),
getApiService: jest.fn()
} as any;

beforeEach(() => {
backupRestoreSvc = new BackupRestoreService(
$q,
mockBookmarkSvc,
mockLogSvc,
mockPlatformSvc,
mockStoreSvc,
mockUpgradeSvc,
mockUtilitySvc
);
});

afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

test('createBackupData: Creates backup object with correct structure', () => {
const bookmarks = [{ id: 1, title: 'Test', url: 'https://test.com' }];
const syncInfo = { id: 'sync-id', serviceType: 'xbrowsersync' } as any;

const result = backupRestoreSvc.createBackupData(bookmarks, syncInfo);

expect(result.xbrowsersync).toBeDefined();
expect(result.xbrowsersync.date).toBe('20230115103045');
expect(result.xbrowsersync.sync).toStrictEqual(syncInfo);
expect(result.xbrowsersync.data.bookmarks).toStrictEqual(bookmarks);
});

test('getBackupFilename: Returns filename with timestamp', () => {
const result = backupRestoreSvc.getBackupFilename();

expect(result).toBe('xbs_backup_20230115103045.txt');
});

test('getSyncInfo: Returns sync info without password', async () => {
mockStoreSvc.get.mockResolvedValue({
id: 'sync-id',
password: 'secret',
serviceType: 'xbrowsersync',
version: '1.5.0'
});

const result = await backupRestoreSvc.getSyncInfo();

expect(result.id).toBe('sync-id');
expect(result.serviceType).toBe('xbrowsersync');
expect(result.version).toBe('1.5.0');
expect((result as any).password).toBeUndefined();
});

test('getSetAutoBackUpSchedule: Gets value from store when no argument', async () => {
const schedule = { autoBackUpHour: 2, autoBackUpMinute: 0, autoBackUpNumber: 1, autoBackUpUnit: 'day' };
mockStoreSvc.get.mockResolvedValue(schedule);

const result = await backupRestoreSvc.getSetAutoBackUpSchedule();

expect(result).toStrictEqual(schedule);
});

test('getSetAutoBackUpSchedule: Sets and returns value when argument provided', async () => {
const schedule = { autoBackUpHour: 3, autoBackUpMinute: 30, autoBackUpNumber: 2, autoBackUpUnit: 'day' };
mockStoreSvc.set.mockResolvedValue();

const result = await backupRestoreSvc.getSetAutoBackUpSchedule(schedule as any);

expect(mockStoreSvc.set).toBeCalled();
expect(result).toStrictEqual(schedule);
});

test('getSetAutoBackUpSchedule: Logs message when clearing schedule', async () => {
mockStoreSvc.set.mockResolvedValue();

await backupRestoreSvc.getSetAutoBackUpSchedule(null);

expect(mockLogSvc.logInfo).toBeCalledWith('Auto back up schedule cleared');
});

test('restoreBackupData: Throws FailedRestoreDataError for invalid backup format', () => {
const badData = { invalid: 'data' } as any;

expect(() => backupRestoreSvc.restoreBackupData(badData)).toThrow(FailedRestoreDataError);
});

test('restoreBackupData: Handles v1.5.0+ backup format with xbrowsersync key', async () => {
const backupData = {
xbrowsersync: {
date: '20230101120000',
sync: { id: 'sync-id', serviceType: 'xbrowsersync', version: '1.5.0' },
data: {
bookmarks: [{ id: 1, title: 'Test', url: 'https://test.com' }]
}
}
} as any;
mockPlatformSvc.getAppVersion.mockResolvedValue('1.5.0');
mockUpgradeSvc.upgradeBookmarks.mockResolvedValue(backupData.xbrowsersync.data.bookmarks);
mockUtilitySvc.isSyncEnabled.mockResolvedValue(false);
mockStoreSvc.set.mockResolvedValue();
mockPlatformSvc.queueSync.mockResolvedValue();

await backupRestoreSvc.restoreBackupData(backupData);

expect(mockPlatformSvc.queueSync).toBeCalled();
});

test('restoreBackupData: Handles pre-v1.5.0 backup format with xBrowserSync key', async () => {
const backupData = {
xBrowserSync: {
bookmarks: [{ id: 1, title: 'Test', url: 'https://test.com' }]
}
} as any;
mockPlatformSvc.getAppVersion.mockResolvedValue('1.5.0');
mockUpgradeSvc.upgradeBookmarks.mockResolvedValue(backupData.xBrowserSync.bookmarks);
mockUtilitySvc.isSyncEnabled.mockResolvedValue(false);
mockStoreSvc.set.mockResolvedValue();
mockPlatformSvc.queueSync.mockResolvedValue();

await backupRestoreSvc.restoreBackupData(backupData);

expect(mockPlatformSvc.queueSync).toBeCalled();
});

test('saveBackupFile: Creates and downloads backup file', async () => {
mockBookmarkSvc.getBookmarksForExport.mockResolvedValue([{ id: 1, title: 'Test' }]);
mockStoreSvc.get.mockResolvedValue({ id: 'sync-id', serviceType: 'xbrowsersync' });
mockUtilitySvc.isSyncEnabled.mockResolvedValue(true);
mockPlatformSvc.downloadFile.mockResolvedValue();

await backupRestoreSvc.saveBackupFile(true);

expect(mockPlatformSvc.downloadFile).toBeCalled();
});
});
Loading
Loading