mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +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.
92 lines
4.0 KiB
TypeScript
92 lines
4.0 KiB
TypeScript
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import Database from 'better-sqlite3';
|
|
import { createSnapshotTestDb } from '../../../helpers/db-mock';
|
|
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
|
|
import { MaintenanceRepository } from '../../../../src/db/repositories/MaintenanceRepository';
|
|
|
|
const testDb = createSnapshotTestDb();
|
|
let t: TestOrm;
|
|
let maintenance: MaintenanceRepository;
|
|
|
|
beforeAll(async () => {
|
|
t = await createTestOrm(testDb);
|
|
maintenance = new MaintenanceRepository(t.em);
|
|
});
|
|
afterAll(async () => { await t.close(); testDb.close(); });
|
|
|
|
describe('MaintenanceRepository', () => {
|
|
it('MAINTREPO-001: walCheckpoint renders PRAGMA wal_checkpoint(TRUNCATE) — pinned text, equivalent to the legacy raw call', async () => {
|
|
// Rendered-SQL-equivalence: the exact text a direct statement on the
|
|
// handle executes without throwing, string-for-string what the legacy
|
|
// `db.exec('PRAGMA wal_checkpoint(TRUNCATE)')` issued.
|
|
const RENDERED = 'PRAGMA wal_checkpoint(TRUNCATE)';
|
|
// test-sql-allow: the pragma text itself is under test, run as the raw statement it renders to.
|
|
expect(() => testDb.prepare(RENDERED).run()).not.toThrow();
|
|
await expect(maintenance.walCheckpoint()).resolves.toBeUndefined();
|
|
});
|
|
|
|
describe('vacuumInto', () => {
|
|
let scratchPath: string;
|
|
|
|
afterEach(() => {
|
|
if (scratchPath && fs.existsSync(scratchPath)) fs.rmSync(scratchPath, { force: true });
|
|
});
|
|
|
|
it('MAINTREPO-002: vacuumInto renders `VACUUM INTO \'<path>\'` — pinned text, produces a readable snapshot at the given path', async () => {
|
|
scratchPath = path.join(os.tmpdir(), `maintrepo-vacuum-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
await maintenance.vacuumInto(scratchPath);
|
|
expect(fs.existsSync(scratchPath)).toBe(true);
|
|
|
|
const snap = new Database(scratchPath, { readonly: true });
|
|
try {
|
|
// test-sql-allow: a snapshot file no ORM is bound to, read through sqlite_master.
|
|
const row = snap.prepare('SELECT name FROM sqlite_master WHERE type = ? AND name = ?').get('table', 'users');
|
|
expect(row).toBeTruthy();
|
|
} finally {
|
|
snap.close();
|
|
}
|
|
});
|
|
|
|
it('MAINTREPO-003: single quotes in the path are escaped by doubling, matching the legacy `dbSnap.replaceAll("\'", "\'\'")` shape', async () => {
|
|
const dirWithQuote = path.join(os.tmpdir(), `maintrepo-o'brien-${Date.now()}`);
|
|
fs.mkdirSync(dirWithQuote, { recursive: true });
|
|
scratchPath = path.join(dirWithQuote, 'snap.db');
|
|
try {
|
|
await maintenance.vacuumInto(scratchPath);
|
|
expect(fs.existsSync(scratchPath)).toBe(true);
|
|
|
|
// Rendered-SQL-equivalence: the SAME escaped text, run directly
|
|
// against another target path, succeeds identically.
|
|
const directPath = path.join(dirWithQuote, 'snap-direct.db');
|
|
const escaped = directPath.replaceAll("'", "''");
|
|
// test-sql-allow: the VACUUM INTO text itself is under test, run as the raw statement it renders to.
|
|
expect(() => testDb.prepare(`VACUUM INTO '${escaped}'`).run()).not.toThrow();
|
|
expect(fs.existsSync(directPath)).toBe(true);
|
|
fs.rmSync(directPath, { force: true });
|
|
} finally {
|
|
fs.rmSync(dirWithQuote, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
it('MAINTREPO-004: both methods throw outside a request context (fail-closed, matching TrekRepository)', async () => {
|
|
const { MikroORM } = await import('@mikro-orm/core');
|
|
const fresh = await MikroORM.init({
|
|
entities: [],
|
|
driver: (await import('../../../../src/db/orm-driver')).createBoundSqliteDriver(() => testDb),
|
|
dbName: ':memory:',
|
|
allowGlobalContext: false,
|
|
discovery: { warnWhenNoEntities: false },
|
|
});
|
|
try {
|
|
const unwrapped = new MaintenanceRepository(fresh.em);
|
|
await expect(unwrapped.walCheckpoint()).rejects.toThrow();
|
|
} finally {
|
|
await fresh.close(false);
|
|
}
|
|
});
|
|
});
|