mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
d28e91d056
The repository suites now write their fixtures and read back what a call left behind through the factories and readers, not hand-written SQLite on the better-sqlite3 handle. What stays raw is marked with test-sql-allow: the legacy statements the parity cases hold a repository read to, rows read as the table stores them (a BLOB, an integer the entity maps to a boolean), and a write that has to land on the connection an open transaction holds. Cases that pinned a "raw UPDATE" between two repository reads now make that write through a context of their own, which the repository's identity map knows as little about, so they still prove the same thing.
159 lines
6.8 KiB
TypeScript
159 lines
6.8 KiB
TypeScript
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
|
import { createSnapshotTestDb } from '../../../helpers/db-mock';
|
|
import { resetTestDb } from '../../../helpers/test-db';
|
|
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
|
|
import { Addons } from '../../../../src/db/entities/Addons.entity';
|
|
import { deleteRows, insertRow, updateRows } from '../../../helpers/factories/rows';
|
|
import type { AddonsRepository } from '../../../../src/db/repositories/Addons.repository';
|
|
|
|
const testDb = createSnapshotTestDb();
|
|
let t: TestOrm;
|
|
let addons: AddonsRepository;
|
|
|
|
beforeAll(async () => {
|
|
t = await createTestOrm(testDb);
|
|
addons = t.repo(Addons);
|
|
});
|
|
beforeEach(async () => {
|
|
resetTestDb(testDb);
|
|
// resetTestDb deliberately keeps the seeded addons catalogue (test-db.ts's
|
|
// KEEP_TABLES) — these tests want a known, empty table so every case
|
|
// controls its own rows.
|
|
await deleteRows(t, Addons);
|
|
t.clear();
|
|
});
|
|
afterAll(async () => {
|
|
await t.close();
|
|
testDb.close();
|
|
});
|
|
|
|
function rawRow(id: string): unknown {
|
|
// test-sql-allow: the stored integer is under test, which the entity reads back as a boolean.
|
|
return testDb.prepare('SELECT * FROM addons WHERE id = ?').get(id);
|
|
}
|
|
|
|
async function insertAddon(row: {
|
|
id: string;
|
|
name: string;
|
|
description?: string | null;
|
|
type?: string;
|
|
icon?: string | null;
|
|
enabled: 0 | 1;
|
|
sort_order?: number;
|
|
}): Promise<void> {
|
|
await insertRow(t, Addons, {
|
|
id: row.id,
|
|
name: row.name,
|
|
description: row.description ?? null,
|
|
type: row.type ?? 'global',
|
|
icon: row.icon ?? null,
|
|
enabled: row.enabled === 1,
|
|
sort_order: row.sort_order ?? 0,
|
|
});
|
|
}
|
|
|
|
describe('AddonsRepository', () => {
|
|
describe('isEnabled', () => {
|
|
it('ADDONSREPO-001: reads a stored 1 as true', async () => {
|
|
await insertAddon({ id: 'budget', name: 'Budget', enabled: 1 });
|
|
expect(await addons.isEnabled('budget')).toBe(true);
|
|
});
|
|
|
|
it('ADDONSREPO-002: reads a stored 0 as false', async () => {
|
|
await insertAddon({ id: 'budget', name: 'Budget', enabled: 0 });
|
|
expect(await addons.isEnabled('budget')).toBe(false);
|
|
});
|
|
|
|
it('ADDONSREPO-003: a missing row reads false', async () => {
|
|
expect(await addons.isEnabled('does-not-exist')).toBe(false);
|
|
});
|
|
|
|
// I1 (Task 0 review, carried forward): a primary-key findOne answers a
|
|
// repeat call from the identity map unless refresh: true is set —
|
|
// invisible to a write on the same id in the same request.
|
|
describe('sees a write on the same id in the same request (I1, identity-map regression)', () => {
|
|
it('ADDONSREPO-005: an UPDATE from another context on the same id then isEnabled reads the new value', async () => {
|
|
await insertAddon({ id: 'budget', name: 'Budget', enabled: 0 });
|
|
expect(await addons.isEnabled('budget')).toBe(false); // populate the identity map
|
|
await updateRows(t, Addons, { id: 'budget' }, { enabled: true });
|
|
expect(await addons.isEnabled('budget')).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('listEnabled', () => {
|
|
it('ADDONSREPO-006: returns only enabled addons, ordered by sort_order', async () => {
|
|
await insertAddon({ id: 'atlas', name: 'Atlas', type: 'page', icon: 'globe', enabled: 1, sort_order: 2 });
|
|
await insertAddon({ id: 'budget', name: 'Costs', type: 'trip', icon: 'wallet', enabled: 1, sort_order: 1 });
|
|
await insertAddon({ id: 'vacay', name: 'Vacay', type: 'page', icon: 'sun', enabled: 0, sort_order: 0 });
|
|
|
|
const rows = await addons.listEnabled();
|
|
expect(rows.map((r) => r.id)).toEqual(['budget', 'atlas']);
|
|
});
|
|
|
|
it('ADDONSREPO-007: the enabled column comes back as a JS boolean, not the stored int', async () => {
|
|
await insertAddon({ id: 'atlas', name: 'Atlas', enabled: 1 });
|
|
const [row] = await addons.listEnabled();
|
|
expect(row.enabled).toBe(true);
|
|
expect(rawRow('atlas')).toMatchObject({ enabled: 1 });
|
|
});
|
|
|
|
it('ADDONSREPO-008: an empty table returns an empty array', async () => {
|
|
expect(await addons.listEnabled()).toEqual([]);
|
|
});
|
|
|
|
it('ADDONSREPO-009: carries the full row shape (id, name, description, type, icon, enabled, config, sort_order)', async () => {
|
|
await insertAddon({ id: 'atlas', name: 'Atlas', description: 'Visited countries map', type: 'page', icon: 'globe', enabled: 1, sort_order: 3 });
|
|
const [row] = await addons.listEnabled();
|
|
expect(row).toEqual({
|
|
id: 'atlas',
|
|
name: 'Atlas',
|
|
description: 'Visited countries map',
|
|
type: 'page',
|
|
icon: 'globe',
|
|
enabled: true,
|
|
config: {},
|
|
sort_order: 3,
|
|
});
|
|
});
|
|
});
|
|
|
|
// Plan 3i Task 4 fix wave (should-land 5): AD26 (listAllOrdered) and
|
|
// AD30/AD39 (findById) shipped in Task 1 with no repository-level test —
|
|
// full-key parity against the exact legacy SQL each method's own docstring
|
|
// names.
|
|
describe('listAllOrdered (AD26)', () => {
|
|
it('ADDONSREPO-010: matches SELECT * FROM addons ORDER BY sort_order, id — unfiltered, including a disabled addon', async () => {
|
|
await insertAddon({ id: 'vacay', name: 'Vacay', type: 'page', icon: 'sun', enabled: 0, sort_order: 1 });
|
|
await insertAddon({ id: 'budget', name: 'Costs', type: 'trip', icon: 'wallet', enabled: 1, sort_order: 0 });
|
|
// test-sql-allow: the legacy statement is the oracle the repository read is held to.
|
|
const legacy = testDb.prepare('SELECT * FROM addons ORDER BY sort_order, id').all();
|
|
const rows = await addons.listAllOrdered();
|
|
expect(rows.map((r) => r.id)).toEqual(['budget', 'vacay']); // includes the disabled one, unlike listEnabled
|
|
expect(rows).toEqual(
|
|
(legacy as Array<{ enabled: number; config: string | null }>).map((r) => ({ ...r, enabled: !!r.enabled, config: JSON.parse(r.config ?? '{}') })),
|
|
);
|
|
});
|
|
|
|
it('ADDONSREPO-011: an empty table returns an empty array', async () => {
|
|
expect(await addons.listAllOrdered()).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('findById (AD30/AD39)', () => {
|
|
it('ADDONSREPO-012: matches SELECT * FROM addons WHERE id = ?, on both a pre-write read and a post-write re-select (byte-identical text at both call sites)', async () => {
|
|
await insertAddon({ id: 'budget', name: 'Costs', description: 'Track spend', type: 'trip', icon: 'wallet', enabled: 0, sort_order: 4 });
|
|
const preWrite = await addons.findById('budget');
|
|
expect(preWrite).toEqual({ id: 'budget', name: 'Costs', description: 'Track spend', type: 'trip', icon: 'wallet', enabled: false, config: {}, sort_order: 4 });
|
|
|
|
await updateRows(t, Addons, { id: 'budget' }, { enabled: true });
|
|
const postWrite = await addons.findById('budget');
|
|
expect(postWrite?.enabled).toBe(true);
|
|
});
|
|
|
|
it('ADDONSREPO-013: a missing id returns null', async () => {
|
|
expect(await addons.findById('does-not-exist')).toBeNull();
|
|
});
|
|
});
|
|
});
|