@ultimat3/testing 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ // Real parallel database isolation: N workers, each owning a Postgres database cloned from one
2
+ // migrated template with `CREATE DATABASE ... TEMPLATE`. Never mock the database — clone it. No
3
+ // transaction-rollback wrapper (which breaks anything that commits), no shared schema with a
4
+ // `truncate` between tests (which serialises the suite and still leaks sequences).
5
+
6
+ import { TestDatabaseUnavailableError } from './errors';
7
+
8
+ export type DbKind = 'postgres' | 'pglite';
9
+
10
+ export interface SqlRunner {
11
+ /** Run a statement that returns nothing. */
12
+ exec(sql: string): Promise<void>;
13
+ close(): Promise<void>;
14
+ }
15
+
16
+ export interface TemplateDbConfig {
17
+ /** Connection URL with rights to CREATE DATABASE. Absent means "fall back to PGlite". */
18
+ readonly adminUrl?: string | undefined;
19
+ readonly templateName?: string;
20
+ /** Applied once, into the template, under an advisory lock. */
21
+ readonly migrate?: (url: string) => Promise<void>;
22
+ }
23
+
24
+ export interface WorkerDatabase {
25
+ readonly kind: DbKind;
26
+ readonly worker: number;
27
+ readonly database: string;
28
+ readonly url: string;
29
+ drop(): Promise<void>;
30
+ }
31
+
32
+ export const DEFAULT_TEMPLATE = 'ultimate_test_template';
33
+
34
+ /**
35
+ * Bun exposes the test worker index in the environment; a plain `bun test` run is worker 0. The
36
+ * pid fallback keeps two hand-run processes from colliding on the same database.
37
+ */
38
+ export function workerId(env: Readonly<Record<string, string | undefined>>, pid = 0): number {
39
+ // ULTIMATE_TEST_WORKER is checked FIRST because `x test` assigns it deliberately, one index per
40
+ // shard. A runner-set value must beat anything the runtime invents: if Bun ever populates
41
+ // BUN_TEST_WORKER_ID itself, two shards could resolve to the same index and then race on the
42
+ // same cloned database — a data-dependent failure that would look like a flaky test.
43
+ for (const key of ['ULTIMATE_TEST_WORKER', 'BUN_TEST_WORKER_ID', 'JEST_WORKER_ID']) {
44
+ const raw = env[key];
45
+ if (raw === undefined) continue;
46
+ const parsed = Number.parseInt(raw, 10);
47
+ if (Number.isFinite(parsed)) return parsed;
48
+ }
49
+ return pid === 0 ? 0 : pid % 1024;
50
+ }
51
+
52
+ export const databaseNameFor = (template: string, worker: number): string =>
53
+ `${template}_w${worker}`;
54
+
55
+ /** Swap the database segment of a Postgres URL, keeping credentials and query parameters. */
56
+ export function urlFor(adminUrl: string, database: string): string {
57
+ const url = new URL(adminUrl);
58
+ url.pathname = `/${database}`;
59
+ return url.toString();
60
+ }
61
+
62
+ export const createTemplateSql = (template: string): string =>
63
+ `CREATE DATABASE "${template}" TEMPLATE template0`;
64
+
65
+ export const cloneSql = (template: string, target: string): string =>
66
+ `CREATE DATABASE "${target}" TEMPLATE "${template}"`;
67
+
68
+ export const dropSql = (target: string): string =>
69
+ `DROP DATABASE IF EXISTS "${target}" WITH (FORCE)`;
70
+
71
+ /** hashtext-based advisory lock: one worker migrates the template, the rest wait for it. */
72
+ export const lockSql = (template: string): string =>
73
+ `SELECT pg_advisory_lock(hashtext('${template}'))`;
74
+
75
+ export const unlockSql = (template: string): string =>
76
+ `SELECT pg_advisory_unlock(hashtext('${template}'))`;
77
+
78
+ export interface TemplateDbDeps {
79
+ /** Opens an admin connection. Injected so the unit tests never need a server. */
80
+ readonly connect: (url: string) => SqlRunner;
81
+ readonly env: Readonly<Record<string, string | undefined>>;
82
+ readonly pid?: number;
83
+ }
84
+
85
+ const defaultConnect = (url: string): SqlRunner => {
86
+ // Bun.SQL is the blessed Postgres client; no driver package, no pool config to get wrong.
87
+ const sql = new Bun.SQL(url);
88
+ return {
89
+ exec: async (statement: string) => {
90
+ await sql.unsafe(statement);
91
+ },
92
+ close: async () => {
93
+ await sql.close();
94
+ },
95
+ };
96
+ };
97
+
98
+ /**
99
+ * Give this worker its own database. The first caller creates and migrates the template; every
100
+ * caller clones it. Returns a PGlite-backed handle when no Postgres is configured, so `bun test`
101
+ * works on a laptop with nothing installed.
102
+ */
103
+ export async function acquireWorkerDatabase(
104
+ config: TemplateDbConfig = {},
105
+ deps: Partial<TemplateDbDeps> = {},
106
+ ): Promise<WorkerDatabase> {
107
+ const env = deps.env ?? (Bun.env as Readonly<Record<string, string | undefined>>);
108
+ const connect = deps.connect ?? defaultConnect;
109
+ const template = config.templateName ?? DEFAULT_TEMPLATE;
110
+ const worker = workerId(env, deps.pid ?? process.pid);
111
+ const database = databaseNameFor(template, worker);
112
+ const adminUrl = config.adminUrl ?? env['TEST_DATABASE_URL'] ?? env['DATABASE_URL'];
113
+
114
+ if (adminUrl === undefined || adminUrl.length === 0) {
115
+ return {
116
+ kind: 'pglite',
117
+ worker,
118
+ database,
119
+ url: `pglite://memory/${database}`,
120
+ drop: async () => undefined,
121
+ };
122
+ }
123
+
124
+ const admin = connect(adminUrl);
125
+ try {
126
+ await admin.exec(lockSql(template));
127
+ try {
128
+ await admin.exec(createTemplateSql(template));
129
+ if (config.migrate !== undefined) await config.migrate(urlFor(adminUrl, template));
130
+ } catch (error) {
131
+ // "already exists" means another worker migrated it first — the lock made that safe.
132
+ if (!alreadyExists(error)) throw error;
133
+ } finally {
134
+ await admin.exec(unlockSql(template));
135
+ }
136
+ await admin.exec(dropSql(database));
137
+ await admin.exec(cloneSql(template, database));
138
+ } catch (error) {
139
+ await admin.close();
140
+ throw new TestDatabaseUnavailableError({
141
+ cause: `could not clone ${template} for worker ${worker}: ${messageOf(error)}`,
142
+ });
143
+ }
144
+ await admin.close();
145
+
146
+ return {
147
+ kind: 'postgres',
148
+ worker,
149
+ database,
150
+ url: urlFor(adminUrl, database),
151
+ drop: async () => {
152
+ const cleanup = connect(adminUrl);
153
+ await cleanup.exec(dropSql(database));
154
+ await cleanup.close();
155
+ },
156
+ };
157
+ }
158
+
159
+ const messageOf = (error: unknown): string =>
160
+ error instanceof Error ? error.message : String(error);
161
+
162
+ const alreadyExists = (error: unknown): boolean =>
163
+ messageOf(error).toLowerCase().includes('already exists');
@@ -0,0 +1,145 @@
1
+ // The six first-class test types. `x verify` selects a suite by FILENAME — `*.job.test.ts`, and
2
+ // so on — so these helpers only prefix the reported name with its type, which is what makes a
3
+ // failure line say which of the six steps it belongs to. See packages/cli/src/verify-tests.ts.
4
+
5
+ import { test } from 'bun:test';
6
+ import { TestEvalThresholdError } from './errors';
7
+
8
+ export const TEST_TYPES = ['unit', 'contract', 'live', 'job', 'e2e', 'eval'] as const;
9
+
10
+ export type TestType = (typeof TEST_TYPES)[number];
11
+
12
+ export const SEPARATOR = ' · ';
13
+
14
+ export const testName = (type: TestType, name: string): string => `${type}${SEPARATOR}${name}`;
15
+
16
+ export type TestBody = () => void | Promise<void>;
17
+
18
+ /** Pure logic: no database, no clock, no network. The cheapest test that can fail for real. */
19
+ export const unitTest = (name: string, body: TestBody): void => {
20
+ test(testName('unit', name), body);
21
+ };
22
+
23
+ /** The published surface: OpenAPI diff against the committed spec, MCP exposure, error codes. */
24
+ export const contractTest = (name: string, body: TestBody): void => {
25
+ test(testName('contract', name), body);
26
+ };
27
+
28
+ /** Live queries: assert exactly what each subscriber receives, snapshot then incremental patch. */
29
+ export const liveTest = (name: string, body: TestBody): void => {
30
+ test(testName('live', name), body);
31
+ };
32
+
33
+ /** Jobs: step sequence, retries, idempotency. Never wall-clock sleeps — advance the frozen clock. */
34
+ export const jobTest = (name: string, body: TestBody): void => {
35
+ test(testName('job', name), body);
36
+ };
37
+
38
+ /** One element selection, resolved when it is used rather than when it is built. */
39
+ export interface LocatorLike {
40
+ count(): Promise<number>;
41
+ click(): Promise<void>;
42
+ first(): LocatorLike;
43
+ isVisible(): Promise<boolean>;
44
+ }
45
+
46
+ /**
47
+ * The browser surface an e2e test drives. Every member is one the reference app's e2e suite
48
+ * already calls — this is the observed contract, not a wish list, and the driver that implements
49
+ * it (a browser, at milestone 11) is the only thing that may add to it.
50
+ */
51
+ export interface PageLike {
52
+ goto(url: string): Promise<unknown>;
53
+ /** The first flush of a streamed response — the shell, before the holes resolve. */
54
+ gotoStreamed(url: string): Promise<{ readonly html: string }>;
55
+ reload(): Promise<unknown>;
56
+ /** Resolves once the service worker controls the page, so the offline assertions are not racy. */
57
+ waitForServiceWorker(): Promise<void>;
58
+ title(): Promise<string>;
59
+ content(): Promise<string>;
60
+ url(): string;
61
+ evaluate<T>(fn: () => T): Promise<T>;
62
+ locator(selector: string): LocatorLike;
63
+ getByRole(
64
+ role: string,
65
+ options?: { readonly name?: string; readonly level?: number },
66
+ ): LocatorLike;
67
+ getByText(text: string): LocatorLike;
68
+ }
69
+
70
+ export interface E2eFixtures {
71
+ readonly page: PageLike;
72
+ /** Drop the network so the service worker has to answer. */
73
+ offline(): Promise<void>;
74
+ online(): Promise<void>;
75
+ /** Publish a new build id so the SW update path runs. */
76
+ update(): Promise<void>;
77
+ }
78
+
79
+ export type E2eBody = (fixtures: E2eFixtures) => Promise<void>;
80
+
81
+ let e2eDriver: ((name: string, body: E2eBody) => void) | undefined;
82
+
83
+ /**
84
+ * Register the Playwright-backed driver. The e2e package wires this up; without it e2e tests are
85
+ * skipped loudly rather than failing on a missing browser, and `x verify` reports the step as
86
+ * skipped rather than green.
87
+ */
88
+ export function useE2eDriver(driver: (name: string, body: E2eBody) => void): void {
89
+ e2eDriver = driver;
90
+ }
91
+
92
+ export const e2eTest = (name: string, body: E2eBody): void => {
93
+ if (e2eDriver === undefined) {
94
+ test.skip(
95
+ testName('e2e', `${name} (no browser driver: x build --target static && x e2e)`),
96
+ () => undefined,
97
+ );
98
+ return;
99
+ }
100
+ e2eDriver(testName('e2e', name), body);
101
+ };
102
+
103
+ export interface EvalCase<TInput, TOutput> {
104
+ readonly name: string;
105
+ readonly input: TInput;
106
+ /** 0..1. Below the threshold fails the test. */
107
+ score(output: TOutput): number | Promise<number>;
108
+ }
109
+
110
+ export interface EvalOptions<TInput, TOutput> {
111
+ readonly threshold: number;
112
+ readonly cases: readonly EvalCase<TInput, TOutput>[];
113
+ run(input: TInput): Promise<TOutput>;
114
+ }
115
+
116
+ /**
117
+ * LLM output scoring with a threshold. Deterministic by construction: the model call goes through
118
+ * the sealed network, so an eval either uses a recorded response or declares its allowlist.
119
+ */
120
+ export function evalTest<TInput, TOutput>(
121
+ name: string,
122
+ options: EvalOptions<TInput, TOutput>,
123
+ ): void {
124
+ test(testName('eval', name), async () => {
125
+ const scores: { readonly name: string; readonly score: number }[] = [];
126
+ for (const testCase of options.cases) {
127
+ const output = await options.run(testCase.input);
128
+ scores.push({ name: testCase.name, score: await testCase.score(output) });
129
+ }
130
+ const failures = scores.filter((entry) => entry.score < options.threshold);
131
+ if (failures.length > 0) {
132
+ const detail = failures.map((entry) => `${entry.name}=${entry.score.toFixed(2)}`).join(', ');
133
+ throw new TestEvalThresholdError({ name, threshold: options.threshold, detail });
134
+ }
135
+ });
136
+ }
137
+
138
+ export interface OpenApiOperationLike {
139
+ readonly operationId: string;
140
+ readonly required?: readonly string[];
141
+ }
142
+
143
+ export interface OpenApiLike {
144
+ readonly operations: readonly OpenApiOperationLike[];
145
+ }