mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
bef8fc20f1
- A restore (admin panel or RESTORE_FROM_BACKUP) now leaves data/.trek-reclaim-reset before it swaps the database in. Boot reconcile resets the reclaim state when it finds the file, whatever install id the database names, and removes it afterwards; a successful reopen removes it too. A restart after a failed reopen, or a boot restore of this installation's own backup, no longer keeps the old switch, marks and confirmations. The id mismatch reset stays. - The release job skips a tick (install_mismatch) while the database names another installation or a reset is still pending. - An allowed owner verdict is cached for 15 minutes, a refusal for 26 hours. The local driver reads a list prefix as a folder, so the category root is still listed whole, at most once per category per window. - Each tick reads every allowed category's due rows separately, an equal share of the 200 each, so a category whose deletes keep failing cannot starve the others. - Rows dropped after 30 days overdue and releases refused by a full queue are logged at warn with their counts.
97 lines
4.4 KiB
TypeScript
97 lines
4.4 KiB
TypeScript
/**
|
|
* StorageReleaseQueueRepository: the objects a delete or a replace left
|
|
* unnamed, each with the time it may go. RELQ-001 to RELQ-005.
|
|
*/
|
|
import { StorageReleaseQueue } from '../../../../src/db/entities/StorageReleaseQueue.entity';
|
|
import type { StorageReleaseQueueRepository } from '../../../../src/db/repositories/StorageReleaseQueue.repository';
|
|
import { createSnapshotTestDb } from '../../../helpers/db-mock';
|
|
import { deleteRows } from '../../../helpers/factories/rows';
|
|
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
|
|
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
const testDb = createSnapshotTestDb();
|
|
let t: TestOrm;
|
|
let queue: StorageReleaseQueueRepository;
|
|
|
|
beforeAll(async () => {
|
|
t = await createTestOrm(testDb);
|
|
queue = t.repo(StorageReleaseQueue);
|
|
});
|
|
beforeEach(async () => {
|
|
await deleteRows(t, StorageReleaseQueue);
|
|
t.clear();
|
|
});
|
|
afterAll(async () => {
|
|
await t.close();
|
|
testDb.close();
|
|
});
|
|
|
|
describe('StorageReleaseQueueRepository', () => {
|
|
it('RELQ-001: queues keys per category and answers the due ones of one category, the longest waiting first, up to a limit', async () => {
|
|
await queue.enqueue([{ category: 'covers', key: 'b.jpg' }], '2026-01-02 00:00:00');
|
|
await queue.enqueue([{ category: 'covers', key: 'a.jpg' }], '2026-01-01 00:00:00');
|
|
await queue.enqueue([{ category: 'places', key: 'a.jpg' }], '2026-01-01 00:00:00');
|
|
await queue.enqueue([{ category: 'covers', key: 'later.jpg' }], '2026-02-01 00:00:00');
|
|
expect(await queue.size()).toBe(4);
|
|
expect(await queue.dueIn('covers', '2026-01-15 00:00:00', 10)).toEqual([
|
|
{ category: 'covers', key: 'a.jpg', due_at: '2026-01-01 00:00:00' },
|
|
{ category: 'covers', key: 'b.jpg', due_at: '2026-01-02 00:00:00' },
|
|
]);
|
|
expect(await queue.dueIn('covers', '2026-01-15 00:00:00', 1)).toEqual([
|
|
{ category: 'covers', key: 'a.jpg', due_at: '2026-01-01 00:00:00' },
|
|
]);
|
|
expect(await queue.dueIn('places', '2026-01-15 00:00:00', 10)).toEqual([
|
|
{ category: 'places', key: 'a.jpg', due_at: '2026-01-01 00:00:00' },
|
|
]);
|
|
expect(await queue.dueIn('files', '2026-01-15 00:00:00', 10)).toEqual([]);
|
|
expect(await queue.dueIn('covers', '2026-01-15 00:00:00', 0)).toEqual([]);
|
|
});
|
|
|
|
it('RELQ-002: a key queued again waits from the later time, and an empty batch writes nothing', async () => {
|
|
await queue.enqueue([{ category: 'covers', key: 'a.jpg' }], '2026-01-01 00:00:00');
|
|
await queue.enqueue([{ category: 'covers', key: 'a.jpg' }], '2026-03-01 00:00:00');
|
|
await queue.enqueue([], '2026-04-01 00:00:00');
|
|
expect(await queue.size()).toBe(1);
|
|
expect(await queue.dueIn('covers', '2026-02-01 00:00:00', 10)).toEqual([]);
|
|
expect(await queue.dueIn('covers', '2026-03-01 00:00:00', 10)).toHaveLength(1);
|
|
});
|
|
|
|
it('RELQ-003: answers the categories holding a due row, each once', async () => {
|
|
await queue.enqueue(
|
|
[
|
|
{ category: 'covers', key: 'a.jpg' },
|
|
{ category: 'covers', key: 'b.jpg' },
|
|
],
|
|
'2026-01-01 00:00:00',
|
|
);
|
|
await queue.enqueue([{ category: 'places', key: 'a.jpg' }], '2026-05-01 00:00:00');
|
|
expect(await queue.dueCategories('2026-02-01 00:00:00')).toEqual(['covers']);
|
|
expect((await queue.dueCategories('2026-06-01 00:00:00')).sort()).toEqual(['covers', 'places']);
|
|
});
|
|
|
|
it('RELQ-004: drops the rows of the keys named, in their category only, and the rows due before a time', async () => {
|
|
await queue.enqueue(
|
|
[
|
|
{ category: 'covers', key: 'a.jpg' },
|
|
{ category: 'covers', key: 'b.jpg' },
|
|
],
|
|
'2026-01-01 00:00:00',
|
|
);
|
|
await queue.enqueue([{ category: 'places', key: 'a.jpg' }], '2026-03-01 00:00:00');
|
|
expect(await queue.deleteKeys('covers', ['a.jpg', 'nope.jpg'])).toBe(1);
|
|
expect(await queue.deleteKeys('covers', [])).toBe(0);
|
|
expect(await queue.deleteDueBefore('2026-02-01 00:00:00')).toBe(1);
|
|
expect(await queue.dueIn('places', '2026-03-01 00:00:00', 10)).toEqual([
|
|
{ category: 'places', key: 'a.jpg', due_at: '2026-03-01 00:00:00' },
|
|
]);
|
|
});
|
|
|
|
it('RELQ-005: drops every row, for a database another installation brought', async () => {
|
|
await queue.enqueue([{ category: 'covers', key: 'a.jpg' }], '2026-01-01 00:00:00');
|
|
await queue.enqueue([{ category: 'places', key: 'a.jpg' }], '2026-01-01 00:00:00');
|
|
expect(await queue.deleteAll()).toBe(2);
|
|
expect(await queue.size()).toBe(0);
|
|
});
|
|
});
|