fix(test): fix CI test failures on Windows and macOS

- Windows: ArchiveStoreService now includes complete DB upgrade logic
  to create all object stores when it opens the database first. This
  fixes 411 IndexedDB "object store not found" errors that occurred
  when browser state was cleared between test runs.

- macOS: Increase timing threshold in lock.service.spec.ts from 200ms
  to 500ms to account for CI machine variability in the "should not
  starve any request" test.
This commit is contained in:
Johannes Millan 2026-01-11 16:12:52 +01:00
parent 9d77aa2d3f
commit b987fecb7c
2 changed files with 51 additions and 15 deletions

View file

@ -1,7 +1,13 @@
import { Injectable } from '@angular/core';
import { IDBPDatabase, openDB } from 'idb';
import { ArchiveModel } from '../../features/time-tracking/time-tracking.model';
import { DB_NAME, DB_VERSION, STORE_NAMES, SINGLETON_KEY } from './db-keys.const';
import {
DB_NAME,
DB_VERSION,
STORE_NAMES,
SINGLETON_KEY,
OPS_INDEXES,
} from './db-keys.const';
/**
* Entry stored in archive_young or archive_old object stores.
@ -56,23 +62,51 @@ export class ArchiveStoreService {
/**
* Opens the SUP_OPS database for archive operations.
*
* IMPORTANT: This includes an upgrade callback to ensure archive stores are created
* even if ArchiveStoreService opens the database before OperationLogStoreService.
* IMPORTANT: This includes the COMPLETE upgrade callback to ensure ALL stores
* are created if ArchiveStoreService opens the database before OperationLogStoreService.
* IndexedDB only runs ONE upgrade callback per version transition, so whichever
* service opens the DB first will create the stores.
* service opens the DB first MUST create all stores.
*
* This is critical for test environments where browser state may be cleared between
* test runs, causing a fresh database to be created by whichever service initializes first.
*/
private async _init(): Promise<void> {
this._db = await openDB<ArchiveDBSchema>(DB_NAME, DB_VERSION, {
upgrade: (db, oldVersion) => {
// Version 4: Add archive stores (same logic as OperationLogStoreService)
// This ensures stores are created even if this service opens the DB first.
upgrade: (db, oldVersion, _newVersion, transaction) => {
// IMPORTANT: This must mirror OperationLogStoreService.init() upgrade logic exactly!
// If this service opens the DB first, it must create ALL stores.
// Version 1: Create initial stores
if (oldVersion < 1) {
const opStore = db.createObjectStore(STORE_NAMES.OPS, {
keyPath: 'seq',
autoIncrement: true,
});
opStore.createIndex(OPS_INDEXES.BY_ID, 'op.id', { unique: true });
opStore.createIndex(OPS_INDEXES.BY_SYNCED_AT, 'syncedAt');
db.createObjectStore(STORE_NAMES.STATE_CACHE, { keyPath: 'id' });
db.createObjectStore(STORE_NAMES.IMPORT_BACKUP, { keyPath: 'id' });
}
// Version 2: Add vector_clock store for atomic writes
if (oldVersion < 2) {
db.createObjectStore(STORE_NAMES.VECTOR_CLOCK);
}
// Version 3: Add compound index for efficient source+status queries
if (oldVersion < 3) {
const opStore = transaction.objectStore(STORE_NAMES.OPS);
opStore.createIndex(OPS_INDEXES.BY_SOURCE_AND_STATUS, [
'source',
'applicationStatus',
]);
}
// Version 4: Add archive stores for archiveYoung and archiveOld
if (oldVersion < 4) {
if (!db.objectStoreNames.contains(STORE_NAMES.ARCHIVE_YOUNG)) {
db.createObjectStore(STORE_NAMES.ARCHIVE_YOUNG, { keyPath: 'id' });
}
if (!db.objectStoreNames.contains(STORE_NAMES.ARCHIVE_OLD)) {
db.createObjectStore(STORE_NAMES.ARCHIVE_OLD, { keyPath: 'id' });
}
db.createObjectStore(STORE_NAMES.ARCHIVE_YOUNG, { keyPath: 'id' });
db.createObjectStore(STORE_NAMES.ARCHIVE_OLD, { keyPath: 'id' });
}
},
});

View file

@ -316,9 +316,11 @@ describe('LockService', () => {
expect(startTimes.size).toBe(10);
expect(endTimes.size).toBe(10);
// Check no request waited unreasonably long (more than 200ms for 10 requests)
// Check no request waited unreasonably long (more than 500ms for 10 requests)
// Note: Using 500ms threshold to account for CI machine variability
// Theoretical max is ~63ms (sum of work times), but CI can add significant overhead
const maxWait = Math.max(...Array.from(startTimes.values()));
expect(maxWait).toBeLessThan(200);
expect(maxWait).toBeLessThan(500);
});
it('should maintain correct execution order for FIFO-like behavior', async () => {