import { randomUUID } from "node:crypto"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { applyModuleMigrations, createPostgresMigrationDatabase, discoverAllMigrations, } from "@ekklesia/core/migrations"; import { Client, type QueryResultRow } from "pg"; import { afterAll, describe, expect, it } from "vitest"; import { createApiRuntimeConfig } from "../src/config.js"; import { createApiDatabase } from "../src/database.js"; import { type RunningApi, startApi } from "../src/server.js"; /** * Proves `ModuleRuntimeServices.database` against real PostgreSQL: an installed test module * (`core/tests/fixtures/module-database-integration/db-facade/`) whose lifecycle hooks and * permissioned HTTP handlers perform real typed Drizzle operations — insert, select, update, * transaction, execute — against its own migrated table, exclusively through the facade the * platform hands it. Follows the exact harness conventions `apps/api/tests/api-http.integration.ts` * and `core/tests/postgres-migration.integration.ts` already establish (self-contained per-file * helpers, one disposable PostgreSQL database per scenario, `describe.sequential` blocks sharing a * running API across related assertions) rather than introducing a second integration-test * framework or Docker orchestration path. * * The fixture module is committed under `core/tests/fixtures/...`, not `apps/api/tests/fixtures/`, * for one concrete reason: its schema imports `drizzle-orm/pg-core` and `@ekklesia/contracts`, * neither of which `@ekklesia/api` declares as a direct dependency (pnpm's strict linker will not * resolve them from anywhere under `apps/api/`), while `@ekklesia/core` already depends on both * directly, so any file physically located under `core/`'s directory tree resolves them correctly * regardless of which package's test actually points discovery at it. It is never registered as a * pnpm workspace package (it matches none of `pnpm-workspace.yaml`'s glob patterns), never sits in * the repository's live `modules/` directory, and is only ever discovered when this file explicitly * passes its directory as `modulesDirectoryOverride` — normal production startup, Doctor, and * `pnpm modules:validate` never see it. */ const integrationDatabaseUrlEnvironment = "EKKLESIA_MIGRATION_TEST_DATABASE_URL"; const thisDirectory = path.dirname(fileURLToPath(import.meta.url)); const coreMigrationsDirectory = path.join(thisDirectory, "..", "..", "..", "core", "drizzle"); const fixtureModulesDirectory = path.join( thisDirectory, "..", "..", "..", "core", "tests", "fixtures", "module-database-integration", ); const RECORDS_TABLE = "db_facade_records"; const RECORDER_GLOBAL_KEY = "ekklesia.integration.db-facade.recorder"; // Must match `STARTUP_RECORD_ID` in the fixture's `server/index.js` exactly. const STARTUP_RECORD_ID = "11111111-1111-1111-1111-111111111111"; const identityBaseUrl = "https://identity.example.test"; const dashboardOrigin = "https://dashboard.example.test"; const untrustedOrigin = "https://evil.example.test"; class IntegrationHarnessError extends Error { constructor(message: string) { super(message); this.name = "IntegrationHarnessError"; this.stack = `${this.name}: ${this.message}`; } } function harnessError(message: string): IntegrationHarnessError { return new IntegrationHarnessError(message); } function baseDatabaseUrl(): string { const value = process.env[integrationDatabaseUrlEnvironment]; if (typeof value !== "string" || value.trim() === "") { throw harnessError("The module database integration database is not configured."); } try { const parsed = new URL(value); if (parsed.protocol !== "postgres:" && parsed.protocol !== "postgresql:") { throw new TypeError("Unsupported protocol."); } } catch { throw harnessError("The module database integration database configuration is invalid."); } return value; } function quoteDatabaseName(databaseName: string): string { if (!/^[a-z][a-z0-9_]{0,62}$/u.test(databaseName)) { throw harnessError("The generated integration database name is invalid."); } return `"${databaseName}"`; } let databaseCounter = 0; function makeDatabaseName(label: string): string { const safeLabel = label .toLowerCase() .replaceAll(/[^a-z0-9]+/gu, "_") .replaceAll(/^_+|_+$/gu, "") .slice(0, 14); databaseCounter += 1; return `ekk_dbf_${process.pid}_${databaseCounter}_${safeLabel || "scenario"}_${randomUUID().slice(0, 8)}`; } function databaseUrl(databaseName: string): string { const parsed = new URL(baseDatabaseUrl()); parsed.pathname = `/${databaseName}`; parsed.searchParams.delete("application_name"); return parsed.toString(); } async function openAdministrativeClient(connectionString: string): Promise { const client = new Client({ connectionString }); client.on("error", () => undefined); try { await client.connect(); return client; } catch { try { await client.end(); } catch { // The public harness failure remains static when connection cleanup also fails. } throw harnessError("The module database integration connection failed."); } } async function safeQuery( client: Client, text: string, values: unknown[] = [], ): Promise { try { const result = await client.query(text, values); return result.rows; } catch { throw harnessError("An integration database administration operation failed."); } } async function safeEnd(client: Client): Promise { try { await client.end(); } catch { throw harnessError("An integration database connection could not be closed."); } } const trackedDatabases = new Set(); async function createDatabase(databaseName: string): Promise { const admin = await openAdministrativeClient(baseDatabaseUrl()); try { await safeQuery(admin, `CREATE DATABASE ${quoteDatabaseName(databaseName)} TEMPLATE template0`); } finally { await safeEnd(admin); } } async function dropDatabase(databaseName: string): Promise { const admin = await openAdministrativeClient(baseDatabaseUrl()); try { await safeQuery( admin, "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()", [databaseName], ); await safeQuery( admin, `DROP DATABASE IF EXISTS ${quoteDatabaseName(databaseName)} WITH (FORCE)`, ); trackedDatabases.delete(databaseName); } finally { await safeEnd(admin); } } async function activeConnectionCount(databaseName: string): Promise { const admin = await openAdministrativeClient(baseDatabaseUrl()); try { const rows = await safeQuery<{ count: string }>( admin, "SELECT count(*)::text AS count FROM pg_stat_activity WHERE datname = $1", [databaseName], ); return Number(rows[0]?.count ?? "0"); } finally { await safeEnd(admin); } } async function createDisposableDatabase(label: string): Promise<{ name: string; url: string }> { const name = makeDatabaseName(label); await createDatabase(name); trackedDatabases.add(name); return { name, url: databaseUrl(name) }; } /** Applies only Core's own migrations, against an empty modules directory — the module fixture's * migration is deliberately absent, used only by the "no auto-migration" negative scenario below. */ async function applyCoreMigrations(url: string): Promise { const emptyModulesDirectory = await mkdtemp( path.join(tmpdir(), "ekklesia-db-facade-no-modules-"), ); try { const aggregate = await discoverAllMigrations({ coreMigrationsDirectory, moduleDiscovery: { moduleSdkVersion: "0.1.0", modulesDirectory: emptyModulesDirectory, platformVersion: "0.1.0", }, }); expect(aggregate.migrations.map((entry) => entry.identity)).toEqual([ "core:0000_identity_core_tables", "core:0001_authorization_core_tables", "core:0002_authorization_integrity", "core:0003_dashboard_navigation_tables", "core:0004_module_lifecycle_state", "core:0005_module_configuration", "core:0006_device_identity", "core:0007_verified_bootstrap_owners", ]); await applyModuleMigrations({ aggregate, database: createPostgresMigrationDatabase(url) }); } finally { await rm(emptyModulesDirectory, { recursive: true, force: true }); } } /** Applies Core's migrations plus the `db-facade` fixture module's own migration — explicitly, * before any module runtime exists — proving the platform never infers or auto-generates this * table. Pass-1's committed core migrations and checksums must not have drifted while this pass * was built. */ async function applyMigrationsWithModule(url: string): Promise { const aggregate = await discoverAllMigrations({ coreMigrationsDirectory, moduleDiscovery: { moduleSdkVersion: "0.1.0", modulesDirectory: fixtureModulesDirectory, platformVersion: "0.1.0", }, }); // Core migrations aggregate first, exactly once each, and always before any module migration. expect(aggregate.migrations.map((entry) => entry.identity)).toEqual([ "core:0000_identity_core_tables", "core:0001_authorization_core_tables", "core:0002_authorization_integrity", "core:0003_dashboard_navigation_tables", "core:0004_module_lifecycle_state", "core:0005_module_configuration", "core:0006_device_identity", "core:0007_verified_bootstrap_owners", "db-facade:0000_initial", ]); expect(aggregate.migrations[0]?.checksum).toBe( "f8f2086ef06f7a6943e8ffc4350bb010b37aed04c0a72030d2b153c986b1325f", ); expect(aggregate.migrations[1]?.checksum).toBe( "830e1fbc26d05af08df8d8710c3d10e1f3016d32c41b8363cb661c49afcbe81e", ); expect(aggregate.migrations[2]?.checksum).toBe( "63429473118065dfe3877a1baec61b8f61663b88127d9cb17707c859f27f6036", ); await applyModuleMigrations({ aggregate, database: createPostgresMigrationDatabase(url) }); } async function startMigratedApi( url: string, modulesDirectoryOverride?: string, ): Promise { const config = createApiRuntimeConfig( { databaseUrl: url, host: "127.0.0.1", identity: { baseUrl: identityBaseUrl, publicSignupEnabled: true, secret: "integration-test-secret-0123456789abcdefgh", trustedOrigins: [identityBaseUrl, dashboardOrigin], }, port: 0, }, // Development validation: this suite signs an owner account up over HTTP to exercise real // authorized module database access, and production configuration refuses to enable public // signup at all until durable abuse controls exist // (`PUBLIC_SIGNUP_UNSUPPORTED_IN_PRODUCTION`, `core/src/identity/config.ts`). `environment` // affects only Ekklesia's own config validation — it never reaches Better Auth — and the base // URL and trusted origins here are identical either way. { environment: "development" }, ); return startApi(config, { rateLimitOverride: { max: 1000, window: 1 }, ...(modulesDirectoryOverride === undefined ? {} : { modulesDirectoryOverride }), }); } function apiOrigin(running: RunningApi): string { const address = running.server.address(); if (!address || typeof address !== "object") { throw harnessError("The API integration server has no bound address."); } return `http://127.0.0.1:${address.port}`; } interface SignUpResult { readonly userId: string; } async function signUp(origin: string, email: string, password: string): Promise { const response = await fetch(`${origin}/api/auth/sign-up/email`, { body: JSON.stringify({ email, name: "Integration Test User", password }), headers: { "content-type": "application/json", origin: dashboardOrigin }, method: "POST", }); const body = (await response.json()) as { user: { id: string } }; return { userId: body.user.id }; } /** * Marks an address verified out of band, the same trusted step the initial-owner bootstrap performs. * * A verified address is a precondition for holding a session (ADR 0038), and this suite is about * module database access rather than identity, so it completes that step directly rather than * standing up an SMTP server. That the policy is genuinely enforced is asserted in * `email-verification.integration.ts`. */ async function markEmailVerified(url: string, email: string): Promise { const client = new Client({ connectionString: url }); await client.connect(); try { await client.query( 'update "ekklesia_core_user" set "email_verified" = true where lower("email") = $1', [email.trim().toLowerCase()], ); } finally { await client.end().catch(() => undefined); } } async function signIn(origin: string, email: string, password: string): Promise { const response = await fetch(`${origin}/api/auth/sign-in/email`, { body: JSON.stringify({ email, password }), headers: { "content-type": "application/json", origin: dashboardOrigin }, method: "POST", }); const setCookie = response.headers.get("set-cookie"); if (!setCookie) { throw harnessError("Sign-in did not return a session cookie."); } return setCookie.split(",")[0]?.split(";")[0]?.trim() ?? ""; } interface RecordedEvent { readonly type: "register" | "start" | "stop" | "handler"; readonly services?: { readonly database: unknown }; readonly route?: string; readonly contextKeys?: readonly string[]; } function globalRecord(): Record { return globalThis as unknown as Record; } function resetRecorder(): void { globalRecord()[RECORDER_GLOBAL_KEY] = []; } function readRecorder(): RecordedEvent[] { return (globalRecord()[RECORDER_GLOBAL_KEY] as RecordedEvent[] | undefined) ?? []; } async function tableExists(url: string): Promise { const admin = await openAdministrativeClient(url); try { const rows = await safeQuery<{ exists: boolean }>( admin, "select exists (select 1 from information_schema.tables where table_schema = 'public' and table_name = $1) as exists", [RECORDS_TABLE], ); return rows[0]?.exists ?? false; } finally { await safeEnd(admin); } } async function fetchRecordRow( url: string, id: string, ): Promise<{ id: string; value: string } | undefined> { const admin = await openAdministrativeClient(url); try { const rows = await safeQuery<{ id: string; value: string }>( admin, `select id, value from "${RECORDS_TABLE}" where id = $1`, [id], ); return rows[0]; } finally { await safeEnd(admin); } } async function recordExists(url: string, id: string): Promise { return (await fetchRecordRow(url, id)) !== undefined; } async function migrationHistoryCount(url: string, identity: string): Promise { const admin = await openAdministrativeClient(url); try { const rows = await safeQuery<{ count: string }>( admin, `select count(*)::text as count from "ekklesia_core_migrations" where identity = $1`, [identity], ); return Number(rows[0]?.count ?? "0"); } finally { await safeEnd(admin); } } afterAll(async () => { delete globalRecord()[RECORDER_GLOBAL_KEY]; for (const databaseName of [...trackedDatabases]) { await dropDatabase(databaseName).catch(() => undefined); } }); describe.sequential("live API — module database explicit migration ownership", () => { it("creates no table merely by applying core migrations or constructing the pool and facade — only an explicit module migration does", async () => { const database = await createDisposableDatabase("mig_absent"); try { expect(await tableExists(database.url)).toBe(false); await applyCoreMigrations(database.url); expect(await tableExists(database.url)).toBe(false); const handle = createApiDatabase(database.url); expect(handle.module).toBeDefined(); await handle.close(); expect(await tableExists(database.url)).toBe(false); await applyMigrationsWithModule(database.url); expect(await tableExists(database.url)).toBe(true); } finally { await dropDatabase(database.name); } }); it("applies the module's migration exactly once, never regenerating or reapplying it across repeated startups", async () => { const database = await createDisposableDatabase("mig_once"); try { await applyMigrationsWithModule(database.url); expect(await migrationHistoryCount(database.url, "db-facade:0000_initial")).toBe(1); const first = await startMigratedApi(database.url, fixtureModulesDirectory); await first.shutdown(); const second = await startMigratedApi(database.url, fixtureModulesDirectory); await second.shutdown(); expect(await migrationHistoryCount(database.url, "db-facade:0000_initial")).toBe(1); expect(await migrationHistoryCount(database.url, "core:0000_identity_core_tables")).toBe(1); expect(await migrationHistoryCount(database.url, "core:0001_authorization_core_tables")).toBe( 1, ); expect(await migrationHistoryCount(database.url, "core:0002_authorization_integrity")).toBe( 1, ); expect( await migrationHistoryCount(database.url, "core:0003_dashboard_navigation_tables"), ).toBe(1); expect(await migrationHistoryCount(database.url, "core:0004_module_lifecycle_state")).toBe(1); } finally { await dropDatabase(database.name); } }); it("fails module startup rather than silently creating the table when the module's own migration was never applied", async () => { const database = await createDisposableDatabase("mig_skip"); try { await applyCoreMigrations(database.url); resetRecorder(); // The module whose `start` failed is now rolled back through its own `stop` hook first. That // hook also writes to the table its missing migration would have created, so it fails too — // which is exactly the documented reason a `stop` hook must tolerate partial initialization. // Both outcomes are reported, in order, and neither is swallowed. await expect(startMigratedApi(database.url, fixtureModulesDirectory)).rejects.toMatchObject({ issues: [ expect.objectContaining({ code: "MODULE_START_FAILED", moduleId: "db-facade" }), expect.objectContaining({ code: "MODULE_ROLLBACK_FAILED", moduleId: "db-facade" }), ], }); // No schema inference, no automatic table creation as a side effect of the failed attempt. expect(await tableExists(database.url)).toBe(false); } finally { await dropDatabase(database.name); } }); }); describe.sequential("live API — module database access through ModuleRuntimeServices", () => { let database: { name: string; url: string }; let running: RunningApi; let origin: string; let ownerCookie: string; it("starts a freshly migrated API; register receives no services; start writes the startup record through the real facade", async () => { database = await createDisposableDatabase("db_facade"); await applyMigrationsWithModule(database.url); resetRecorder(); running = await startMigratedApi(database.url, fixtureModulesDirectory); origin = apiOrigin(running); const events = readRecorder(); const registerEvents = events.filter((event) => event.type === "register"); const startEvents = events.filter((event) => event.type === "start"); expect(registerEvents).toHaveLength(1); expect(registerEvents[0]?.services).toBeUndefined(); expect(startEvents).toHaveLength(1); expect(startEvents[0]?.services?.database).toBeDefined(); const startupRow = await fetchRecordRow(database.url, STARTUP_RECORD_ID); expect(startupRow).toEqual({ id: STARTUP_RECORD_ID, value: "startup-value" }); const signedUp = await signUp(origin, "db-facade-owner@example.test", "correct horse battery"); await markEmailVerified(database.url, "db-facade-owner@example.test"); ownerCookie = await signIn(origin, "db-facade-owner@example.test", "correct horse battery"); expect(signedUp.userId).toBeDefined(); }); it("authorized read reflects real PostgreSQL state, exposes the exact scoped handler context, and the handler's database is reference-equal to start's", async () => { const response = await fetch(`${origin}/api/v1/modules/db-facade/records/${STARTUP_RECORD_ID}`); expect(response.status).toBe(200); expect(await response.json()).toEqual({ id: STARTUP_RECORD_ID, value: "startup-value" }); const events = readRecorder(); const startEvent = events.find((event) => event.type === "start"); const readHandlerEvent = [...events] .reverse() .find((event) => event.type === "handler" && event.route === "GET /records/:id"); // The complete handler-context surface, asserted exactly so a new field is a deliberate change // here rather than an unnoticed one: `config` (ADR 0029), `storage` (ADR 0030), and `realtime` // (ADR 0032) are scoped, per-module additions; everything else a module might want — a Hono // Context, the authorization store, raw PostgreSQL, the storage provider, an endpoint or // credential — remains unreachable. expect(readHandlerEvent?.contextKeys).toEqual([ "config", "moduleId", "params", "principal", "realtime", "services", "storage", ]); expect(readHandlerEvent?.services).toBeDefined(); expect(readHandlerEvent?.services?.database).toBe(startEvent?.services?.database); }); it("requires a trusted Origin for the module's mutating route, performs a real insert, and a later read observes it", async () => { const id = randomUUID(); const noOrigin = await fetch(`${origin}/api/v1/modules/db-facade/records`, { body: JSON.stringify({ id, value: "no-origin" }), headers: { "content-type": "application/json", cookie: ownerCookie }, method: "POST", }); expect(noOrigin.status).toBe(403); expect(await noOrigin.json()).toEqual({ code: "UNTRUSTED_ORIGIN" }); expect(await recordExists(database.url, id)).toBe(false); const created = await fetch(`${origin}/api/v1/modules/db-facade/records`, { body: JSON.stringify({ id, value: "created-value" }), headers: { "content-type": "application/json", cookie: ownerCookie, origin: dashboardOrigin, }, method: "POST", }); expect(created.status).toBe(201); expect(await created.json()).toEqual({ id, value: "created-value" }); const readBack = await fetch(`${origin}/api/v1/modules/db-facade/records/${id}`); expect(readBack.status).toBe(200); expect(await readBack.json()).toEqual({ id, value: "created-value" }); const patched = await fetch(`${origin}/api/v1/modules/db-facade/records/${id}`, { body: JSON.stringify({ value: "patched-value" }), headers: { "content-type": "application/json", cookie: ownerCookie, origin: dashboardOrigin, }, method: "PATCH", }); expect(patched.status).toBe(200); expect(await patched.json()).toEqual({ id, value: "patched-value" }); expect(await fetchRecordRow(database.url, id)).toEqual({ id, value: "patched-value" }); }); it("denies the mutating route to an unauthenticated caller, invoking neither the handler nor a database mutation", async () => { const id = randomUUID(); const eventsBefore = readRecorder().length; const response = await fetch(`${origin}/api/v1/modules/db-facade/records`, { body: JSON.stringify({ id, value: "should-not-persist" }), headers: { "content-type": "application/json", origin: dashboardOrigin, }, method: "POST", }); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "AUTHORIZATION_DENIED" }); expect(readRecorder().length).toBe(eventsBefore); expect(await recordExists(database.url, id)).toBe(false); }); it("rejects an untrusted-Origin mutation before principal resolution, invoking neither the handler nor a database mutation", async () => { const id = randomUUID(); const eventsBefore = readRecorder().length; const response = await fetch(`${origin}/api/v1/modules/db-facade/records`, { body: JSON.stringify({ id, value: "should-not-persist" }), headers: { "content-type": "application/json", cookie: ownerCookie, origin: untrustedOrigin, }, method: "POST", }); expect(response.status).toBe(403); expect(await response.json()).toEqual({ code: "UNTRUSTED_ORIGIN" }); expect(readRecorder().length).toBe(eventsBefore); expect(await recordExists(database.url, id)).toBe(false); }); it("keeps OPTIONS platform-owned: never invokes module code, never mutates the table, and returns correct final CORS/Vary", async () => { const id = randomUUID(); const eventsBefore = readRecorder().length; const response = await fetch(`${origin}/api/v1/modules/db-facade/records`, { headers: { "access-control-request-method": "POST", origin: dashboardOrigin, }, method: "OPTIONS", }); expect(response.status).toBe(204); expect(response.headers.get("access-control-allow-origin")).toBe(dashboardOrigin); const vary = (response.headers.get("vary") ?? "").split(",").map((token) => token.trim()); expect(vary.filter((token) => token.toLowerCase() === "origin")).toHaveLength(1); expect(readRecorder().length).toBe(eventsBefore); expect(await recordExists(database.url, id)).toBe(false); }); it("cannot let HEAD bypass the authorized GET chain, and creates no mutation", async () => { const anonymousHead = await fetch(`${origin}/api/v1/modules/db-facade/records-count`, { method: "HEAD", }); expect(anonymousHead.status).toBe(403); expect(await anonymousHead.text()).toBe(""); const authorizedHead = await fetch(`${origin}/api/v1/modules/db-facade/records-count`, { headers: { cookie: ownerCookie }, method: "HEAD", }); expect(authorizedHead.status).toBe(200); expect(await authorizedHead.text()).toBe(""); const countBefore = await countRecordRowsViaGet(origin, ownerCookie); const countAfter = await countRecordRowsViaGet(origin, ownerCookie); expect(countAfter).toBe(countBefore); }); it("commits a real transaction and persists its result", async () => { const id = randomUUID(); const response = await fetch(`${origin}/api/v1/modules/db-facade/records/transactional`, { body: JSON.stringify({ id, value: "committed-value" }), headers: { "content-type": "application/json", cookie: ownerCookie, origin: dashboardOrigin, }, method: "POST", }); expect(response.status).toBe(201); expect(await response.json()).toEqual({ committed: true }); expect(await fetchRecordRow(database.url, id)).toEqual({ id, value: "committed-value" }); }); it("rolls back a real transaction on a unique-constraint failure, leaving no partial mutation", async () => { const id = randomUUID(); const conflictId = randomUUID(); const response = await fetch(`${origin}/api/v1/modules/db-facade/records/transactional`, { body: JSON.stringify({ id, value: "should-roll-back", conflictId }), headers: { "content-type": "application/json", cookie: ownerCookie, origin: dashboardOrigin, }, method: "POST", }); expect(response.status).toBe(409); expect(await response.json()).toEqual({ committed: false }); // The whole transaction rolled back atomically — including the first, otherwise-valid insert. expect(await recordExists(database.url, id)).toBe(false); expect(await recordExists(database.url, conflictId)).toBe(false); }); it("preserves a module's own sanitized response for a database failure it deliberately catches", async () => { const response = await fetch(`${origin}/api/v1/modules/db-facade/safe-boom`); expect(response.status).toBe(409); expect(await response.json()).toEqual({ code: "BOOM_HANDLED" }); }); it("sanitizes an uncaught real PostgreSQL failure to the existing MODULE_HANDLER_FAILED boundary, leaking no detail", async () => { const response = await fetch(`${origin}/api/v1/modules/db-facade/boom`); const text = await response.text(); expect(response.status).toBe(500); expect(text).toBe(JSON.stringify({ code: "MODULE_HANDLER_FAILED" })); expect(response.headers.get("cache-control")).toBe("no-store"); const lowered = text.toLowerCase(); for (const forbidden of [ "division", "sql", "postgres", "22012", "select", "relation", "stack", "node_modules", fixtureModulesDirectory.toLowerCase(), ]) { expect(lowered).not.toContain(forbidden); } }); it("stops cleanly: stop receives the same services and database as start, its write completes before the pool closes, and the API shuts down", async () => { const events = readRecorder(); const startEvent = events.find((event) => event.type === "start"); await running.shutdown(); const remainingConnections = await activeConnectionCount(database.name); expect(remainingConnections).toBe(0); const eventsAfterShutdown = readRecorder(); const stopEvents = eventsAfterShutdown.filter((event) => event.type === "stop"); expect(stopEvents).toHaveLength(1); expect(stopEvents[0]?.services?.database).toBe(startEvent?.services?.database); // Read via a separate administrative connection, entirely independent of the API's own // (now-closed) pool — proving the stop write completed and persisted before that pool closed. const stoppedRow = await fetchRecordRow(database.url, STARTUP_RECORD_ID); expect(stoppedRow).toEqual({ id: STARTUP_RECORD_ID, value: "stopped-by-shutdown" }); await dropDatabase(database.name); }); }); async function countRecordRowsViaGet(origin: string, cookie: string): Promise { const response = await fetch(`${origin}/api/v1/modules/db-facade/records-count`, { headers: { cookie }, }); const body = (await response.json()) as { count: number }; return body.count; }