test(op-log): fix clock drift tests by adding missing service mocks

- Add missing mocks for OperationEncryptionService, SuperSyncStatusService, and CLIENT_ID_PROVIDER
- Fix async handling by replacing flushMicrotasks() with tick() and flush()
- Update test expectation to match actual clientId parameter
- Reset hasWarnedClockDrift flag in beforeEach to prevent test interference
- Re-enable previously disabled test suite

Root cause: Without all required service mocks, async code in lock callback
never executed in fakeAsync context, preventing clock drift detection logic
from running.
This commit is contained in:
Johannes Millan 2026-01-18 02:20:14 +01:00
parent 7d5bb1a709
commit dca674cdf4

View file

@ -1,4 +1,4 @@
import { fakeAsync, flushMicrotasks, TestBed, tick } from '@angular/core/testing';
import { fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { OperationLogDownloadService } from './operation-log-download.service';
import { OperationLogStoreService } from '../persistence/operation-log-store.service';
import { LockService } from './lock.service';
@ -12,12 +12,18 @@ import { ActionType, OpType } from '../core/operation.types';
import { CLOCK_DRIFT_THRESHOLD_MS } from '../core/operation-log.const';
import { OpLog } from '../../core/log';
import { T } from '../../t.const';
import { OperationEncryptionService } from './operation-encryption.service';
import { SuperSyncStatusService } from './super-sync-status.service';
import { CLIENT_ID_PROVIDER } from '../util/client-id.provider';
xdescribe('OperationLogDownloadService', () => {
describe('OperationLogDownloadService', () => {
let service: OperationLogDownloadService;
let mockOpLogStore: jasmine.SpyObj<OperationLogStoreService>;
let mockLockService: jasmine.SpyObj<LockService>;
let mockSnackService: jasmine.SpyObj<SnackService>;
let mockEncryptionService: jasmine.SpyObj<OperationEncryptionService>;
let mockSuperSyncStatusService: jasmine.SpyObj<SuperSyncStatusService>;
let mockClientIdProvider: { loadClientId: jasmine.Spy };
beforeEach(() => {
mockOpLogStore = jasmine.createSpyObj('OperationLogStoreService', [
@ -26,17 +32,27 @@ xdescribe('OperationLogDownloadService', () => {
]);
mockLockService = jasmine.createSpyObj('LockService', ['request']);
mockSnackService = jasmine.createSpyObj('SnackService', ['open']);
mockEncryptionService = jasmine.createSpyObj('OperationEncryptionService', [
'decryptOperations',
]);
mockSuperSyncStatusService = jasmine.createSpyObj('SuperSyncStatusService', [
'markRemoteChecked',
]);
mockClientIdProvider = {
loadClientId: jasmine
.createSpy('loadClientId')
.and.returnValue(Promise.resolve('test-client-id')),
};
// Mock OpLog
spyOn(OpLog, 'warn');
spyOn(OpLog, 'normal');
// Default mock implementations
mockLockService.request.and.callFake(
async (_name: string, fn: () => Promise<void>) => {
await fn();
},
);
// Note: Don't use async/await here as it breaks fakeAsync zone context
mockLockService.request.and.callFake((_name: string, fn: () => Promise<void>) => {
return fn();
});
mockOpLogStore.getAppliedOpIds.and.returnValue(Promise.resolve(new Set<string>()));
mockOpLogStore.hasSyncedOps.and.returnValue(Promise.resolve(false));
@ -46,10 +62,15 @@ xdescribe('OperationLogDownloadService', () => {
{ provide: OperationLogStoreService, useValue: mockOpLogStore },
{ provide: LockService, useValue: mockLockService },
{ provide: SnackService, useValue: mockSnackService },
{ provide: OperationEncryptionService, useValue: mockEncryptionService },
{ provide: SuperSyncStatusService, useValue: mockSuperSyncStatusService },
{ provide: CLIENT_ID_PROVIDER, useValue: mockClientIdProvider },
],
});
service = TestBed.inject(OperationLogDownloadService);
// Reset the clock drift warning flag for each test
(service as any).hasWarnedClockDrift = false;
});
describe('downloadRemoteOps', () => {
@ -125,13 +146,13 @@ xdescribe('OperationLogDownloadService', () => {
);
service.downloadRemoteOps(mockApiProvider);
flushMicrotasks(); // Flush all pending promises
tick(); // Resolve promises
// Warning should not be shown immediately
expect(OpLog.warn).not.toHaveBeenCalled();
// After 1 second retry, warning should appear
tick(1000);
flush(); // Flush all pending timers
expect(OpLog.warn).toHaveBeenCalledWith(
'OperationLogDownloadService: Clock drift detected',
@ -269,14 +290,14 @@ xdescribe('OperationLogDownloadService', () => {
// First call - should warn after retry
service.downloadRemoteOps(mockApiProvider);
flushMicrotasks(); // Flush all pending promises
tick(1000); // Wait for retry
tick(); // Resolve promises
flush(); // Flush all pending timers
expect(OpLog.warn).toHaveBeenCalledTimes(1);
// Second call - should NOT warn again
service.downloadRemoteOps(mockApiProvider);
flushMicrotasks(); // Flush all pending promises
tick(1000); // Wait for retry (if any)
tick(); // Resolve promises
flush(); // Flush all pending timers (if any)
expect(OpLog.warn).toHaveBeenCalledTimes(1);
}));
@ -361,8 +382,8 @@ xdescribe('OperationLogDownloadService', () => {
);
service.downloadRemoteOps(mockApiProvider);
flushMicrotasks(); // Flush all pending promises
tick(1000); // Wait for retry
tick(); // Resolve promises
flush(); // Flush all pending timers
// Should warn because serverTime differs from client time
expect(OpLog.warn).toHaveBeenCalledWith(
@ -562,7 +583,11 @@ xdescribe('OperationLogDownloadService', () => {
await service.downloadRemoteOps(mockApiProvider, { forceFromSeq0: true });
// Should start from 0, not from lastServerSeq (100)
expect(mockApiProvider.downloadOps).toHaveBeenCalledWith(0, undefined, 500);
expect(mockApiProvider.downloadOps).toHaveBeenCalledWith(
0,
'test-client-id',
500,
);
});
it('should collect all op clocks when forceFromSeq0 is true', async () => {