import '@angular/compiler'; import { Injector, runInInjectionContext } from '@angular/core'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { PlatformService } from '../../core/platform'; import { BrowserDatabaseService } from './browser-database.service'; import { CapacitorDatabaseService } from './capacitor-database.service'; import { DatabaseService } from './database.service'; import { ElectronDatabaseService } from './electron-database.service'; function installLocalStorageMock(): void { const store = new Map(); vi.stubGlobal('localStorage', { getItem: (key: string) => store.get(key) ?? null, setItem: (key: string, value: string) => store.set(key, String(value)), removeItem: (key: string) => store.delete(key), clear: () => store.clear(), key: (index: number) => Array.from(store.keys())[index] ?? null, get length() { return store.size; } }); } describe('DatabaseService', () => { let browserDatabase: { getBansForRoom: ReturnType; initialize: ReturnType; }; let capacitorDatabase: { getBansForRoom: ReturnType; initialize: ReturnType; }; let electronDatabase: { getBansForRoom: ReturnType; initialize: ReturnType; }; beforeEach(() => { installLocalStorageMock(); localStorage.clear(); browserDatabase = { getBansForRoom: vi.fn(() => Promise.resolve([])), initialize: vi.fn(() => Promise.resolve()) }; capacitorDatabase = { getBansForRoom: vi.fn(() => Promise.resolve([])), initialize: vi.fn(() => Promise.resolve()) }; electronDatabase = { getBansForRoom: vi.fn(() => Promise.resolve([])), initialize: vi.fn(() => Promise.resolve()) }; }); function createService(platform: Pick): DatabaseService { const injector = Injector.create({ providers: [ DatabaseService, { provide: PlatformService, useValue: platform }, { provide: BrowserDatabaseService, useValue: browserDatabase }, { provide: CapacitorDatabaseService, useValue: capacitorDatabase }, { provide: ElectronDatabaseService, useValue: electronDatabase } ] }); return runInInjectionContext(injector, () => injector.get(DatabaseService)); } it('initializes the browser backend before the first delegated read', async () => { const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false }); await service.getBansForRoom('room-1'); expect(browserDatabase.initialize).toHaveBeenCalledTimes(1); expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-1'); expect(capacitorDatabase.initialize).not.toHaveBeenCalled(); expect(service.isReady()).toBe(true); }); it('rechecks backend initialization when the user scope changes during an in-flight initialize call', async () => { let finishInitialInitialize!: () => void; browserDatabase.initialize = vi.fn() .mockImplementationOnce(() => new Promise((resolve) => { finishInitialInitialize = resolve; })) .mockResolvedValue(undefined); localStorage.setItem('metoyou_currentUserId', 'user-a'); const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false }); const initialInitialize = service.initialize(); localStorage.setItem('metoyou_currentUserId', 'user-b'); const initializeAfterScopeChange = service.initialize(); expect(browserDatabase.initialize).toHaveBeenCalledTimes(1); finishInitialInitialize(); await Promise.all([initialInitialize, initializeAfterScopeChange]); expect(browserDatabase.initialize).toHaveBeenCalledTimes(2); expect(service.isReady()).toBe(true); }); it('does not reinitialize the browser backend for repeated reads in the same user scope', async () => { const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false }); await service.getBansForRoom('room-1'); await service.getBansForRoom('room-2'); expect(browserDatabase.initialize).toHaveBeenCalledTimes(1); expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-1'); expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-2'); }); it('reinitializes the browser backend when the stored user scope changes', async () => { localStorage.setItem('metoyou_currentUserId', 'user-a'); const service = createService({ isBrowser: true, isElectron: false, isCapacitor: false }); await service.getBansForRoom('room-1'); localStorage.setItem('metoyou_currentUserId', 'user-b'); await service.getBansForRoom('room-2'); expect(browserDatabase.initialize).toHaveBeenCalledTimes(2); expect(browserDatabase.getBansForRoom).toHaveBeenCalledWith('room-2'); }); it('routes Capacitor shells to native SQLite instead of IndexedDB', async () => { const service = createService({ isBrowser: false, isElectron: false, isCapacitor: true }); await service.getBansForRoom('room-1'); expect(capacitorDatabase.initialize).toHaveBeenCalledTimes(1); expect(capacitorDatabase.getBansForRoom).toHaveBeenCalledWith('room-1'); expect(browserDatabase.initialize).not.toHaveBeenCalled(); }); it('routes Electron shells to the IPC SQLite backend', async () => { const service = createService({ isBrowser: false, isElectron: true, isCapacitor: false }); await service.getBansForRoom('room-1'); expect(electronDatabase.initialize).toHaveBeenCalledTimes(1); expect(electronDatabase.getBansForRoom).toHaveBeenCalledWith('room-1'); expect(browserDatabase.initialize).not.toHaveBeenCalled(); expect(capacitorDatabase.initialize).not.toHaveBeenCalled(); }); });