Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
// Zero-residue guard: the e2e suite must leave the dev database exactly as
|
|
// it found it. globalSetup snapshots row counts, globalTeardown re-counts
|
|
// and fails the run on any difference.
|
|
//
|
|
// Documented exception: done/failed rows in `jobs` are execution history
|
|
// (the queue's audit trail); only unprocessed jobs count as residue.
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const TABLES = [
|
|
'users',
|
|
'departments',
|
|
'templates',
|
|
'conversations',
|
|
'messages',
|
|
'documents',
|
|
'chunks',
|
|
'doc_permissions',
|
|
'auth_sessions',
|
|
'llm_settings'
|
|
];
|
|
|
|
const SNAPSHOT = path.join(import.meta.dirname, '..', 'test-results', 'row-counts.json');
|
|
|
|
function query(sql: string): string {
|
|
return execSync(`docker exec pablan-dev-postgres-1 psql -tA -U pablan -d pablan -c "${sql}"`)
|
|
.toString()
|
|
.trim();
|
|
}
|
|
|
|
export function snapshotCounts(): Record<string, number> {
|
|
const counts: Record<string, number> = {};
|
|
for (const table of TABLES) {
|
|
counts[table] = Number(query(`SELECT count(*) FROM ${table}`));
|
|
}
|
|
counts['jobs (unprocessed)'] = Number(
|
|
query(`SELECT count(*) FROM jobs WHERE status IN ('pending','running')`)
|
|
);
|
|
return counts;
|
|
}
|
|
|
|
export async function globalSetup(): Promise<void> {
|
|
fs.mkdirSync(path.dirname(SNAPSHOT), { recursive: true });
|
|
fs.writeFileSync(SNAPSHOT, JSON.stringify(snapshotCounts(), null, 2));
|
|
}
|
|
|
|
export async function globalTeardown(): Promise<void> {
|
|
const before = JSON.parse(fs.readFileSync(SNAPSHOT, 'utf-8')) as Record<string, number>;
|
|
const after = snapshotCounts();
|
|
const diffs = Object.keys(after)
|
|
.filter((key) => before[key] !== after[key])
|
|
.map((key) => ` ${key}: ${before[key]} -> ${after[key]}`);
|
|
if (diffs.length > 0) {
|
|
throw new Error(`e2e suite left residue in the database:\n${diffs.join('\n')}`);
|
|
}
|
|
}
|