📄 src/downloader/test/meta-db.test.ts
D-OPEN SOVEREIGN
1import 'fake-indexeddb/auto';
2import { describe, it, beforeEach, expect } from 'vitest';
3import {
4    openMetaDb, closeMetaDb, _resetDbForTest,
5    getBookData, storeBookData, getAllBookData,
6    getDownloadRecord, putDownloadRecord, getAllDownloadRecords, deleteDownloadRecord,
7    getMigrationCheckpoint, saveMigrationCheckpoint, clearMigrationCheckpoint,
8    type BookData, type DownloadRecord,
9} from '../meta-db';
10
11beforeEach(async () => {
12    closeMetaDb();
13    _resetDbForTest();
14    // Fresh database per test — delete then let openMetaDb() recreate the stores.
15    await new Promise<void>((resolve) => {
16        const req = indexedDB.deleteDatabase('dl2_meta');
17        req.onsuccess = () => resolve();
18        req.onerror = () => resolve();
19        req.onblocked = () => resolve();
20    });
21});
22
23describe('meta-db', () => {
24    it('openMetaDb creates book_data, downloads and migration_checkpoints stores', async () => {
25        const db = await openMetaDb();
26        expect(db.objectStoreNames.contains('book_data')).toBe(true);
27        expect(db.objectStoreNames.contains('downloads')).toBe(true);
28        expect(db.objectStoreNames.contains('migration_checkpoints')).toBe(true);
29    });
30
31    it('openMetaDb returns the cached singleton on repeat calls', async () => {
32        const a = await openMetaDb();
33        const b = await openMetaDb();
34        expect(a).toBe(b);
35    });
36
37    it('storeBookData/getBookData round-trip', async () => {
38        const data: BookData = { txId: 'book1', dimensions: [{ width: 100, height: 200 }], parsed: true };
39        await storeBookData(data);
40        expect(await getBookData('book1')).toEqual(data);
41    });
42
43    it('getBookData returns undefined for a miss', async () => {
44        expect(await getBookData('nope')).toBeUndefined();
45    });
46
47    it('getAllBookData returns every stored record', async () => {
48        await storeBookData({ txId: 'b1', dimensions: [], parsed: false });
49        await storeBookData({ txId: 'b2', dimensions: [], parsed: true });
50        const all = await getAllBookData();
51        expect(all.map(b => b.txId).sort()).toEqual(['b1', 'b2']);
52    });
53
54    it('putDownloadRecord/getDownloadRecord round-trip', async () => {
55        const rec: DownloadRecord = {
56            txId: 'dl1', fileType: 'pdf', sizeBytes: 1234,
57            downloadedAt: 1000, lastAccessedAt: 2000,
58        };
59        await putDownloadRecord(rec);
60        expect(await getDownloadRecord('dl1')).toEqual(rec);
61    });
62
63    it('putDownloadRecord overwrites an existing record with the same txId', async () => {
64        await putDownloadRecord({ txId: 'dl2', fileType: 'pdf', sizeBytes: 1, downloadedAt: 0, lastAccessedAt: 0 });
65        await putDownloadRecord({ txId: 'dl2', fileType: 'pdf', sizeBytes: 999, downloadedAt: 5, lastAccessedAt: 5 });
66        const rec = await getDownloadRecord('dl2');
67        expect(rec?.sizeBytes).toBe(999);
68    });
69
70    it('getAllDownloadRecords returns every record', async () => {
71        await putDownloadRecord({ txId: 'a', fileType: 'pdf', sizeBytes: 1, downloadedAt: 0, lastAccessedAt: 0 });
72        await putDownloadRecord({ txId: 'b', fileType: 'preview', sizeBytes: 2, downloadedAt: 0, lastAccessedAt: 0 });
73        const all = await getAllDownloadRecords();
74        expect(all.length).toBe(2);
75    });
76
77    it('deleteDownloadRecord removes the entry', async () => {
78        await putDownloadRecord({ txId: 'to-delete', fileType: 'pdf', sizeBytes: 1, downloadedAt: 0, lastAccessedAt: 0 });
79        await deleteDownloadRecord('to-delete');
80        expect(await getDownloadRecord('to-delete')).toBeUndefined();
81    });
82
83    it('getMigrationCheckpoint defaults to 0 when unset', async () => {
84        expect(await getMigrationCheckpoint('cache-x')).toBe(0);
85    });
86
87    it('saveMigrationCheckpoint/getMigrationCheckpoint round-trip', async () => {
88        await saveMigrationCheckpoint('cache-x', 42);
89        expect(await getMigrationCheckpoint('cache-x')).toBe(42);
90    });
91
92    it('clearMigrationCheckpoint resets to the default', async () => {
93        await saveMigrationCheckpoint('cache-y', 7);
94        await clearMigrationCheckpoint('cache-y');
95        expect(await getMigrationCheckpoint('cache-y')).toBe(0);
96    });
97
98    it('closeMetaDb + _resetDbForTest allow a subsequent openMetaDb to reconnect', async () => {
99        const first = await openMetaDb();
100        closeMetaDb();
101        _resetDbForTest();
102        const second = await openMetaDb();
103        expect(second).not.toBe(first);
104    });
105});
106