Files
Ekklesia/core/tests/module-database-abi.integration.ts
T
ekklesiaandClaude Opus 5.5 6db50516b7
CI / verify (pull_request) Successful in 59s
CI / infrastructure-smoke (pull_request) Successful in 27s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 23s
CI / deploy-smoke (same-origin) (pull_request) Successful in 41s
CI / deploy-smoke (split) (pull_request) Successful in 1m29s
feat: Core's Module Database ABI v1 executor, proven against PostgreSQL (ADR 0050)
Implements the first ADR 0050 slice: Core's ORM-neutral executor and its
PostgreSQL evidence. Nothing is wired to modules yet; manifest v8, SDK
adapters, schema metadata, packaging, and Signage are later slices. The
legacy ModuleDatabase facade is unchanged.

Contracts (@ekklesia/contracts, no imports at all):
- ABI version 1; query { text, values, rowMode } returning
  { command, rowCount, rows, fields: [{ name, typeOid }] };
- executor, transaction, and savepoint types with exactly the ABI's
  members; parameter and result value types;
- ModuleDatabaseError { code, sqlState? }: eight stable codes, and a
  SQLSTATE only when PostgreSQL supplied one; static messages, no cause,
  frozen; isModuleDatabaseError recognises it by shape.

Core (createModuleDatabaseExecutor over the API's existing pool):
- one statement per query, enforced by forcing the extended query
  protocol so PostgreSQL itself refuses a second statement (42601);
  no SQL parsing;
- parameters: null, boolean, number, bigint, string, Uint8Array, and
  rectangular arrays of them; everything else refused before sending;
- a Core-owned decoding table that never consults pg's global parser
  registry: int8, numeric, temporal, and JSON as exact text; bytea as a
  plain Uint8Array; built-in arrays element by element;
- every operation pins TimeZone, DateStyle, IntervalStyle, bytea_output,
  and extra_float_digits, then resets with DISCARD ALL before returning
  the connection, or destroys it when it cannot be reset cleanly;
- callback-scoped transactions with explicit isolation and access mode;
  a COMMIT that PostgreSQL answers with ROLLBACK is reported as failed;
- savepoints named by Core; only the innermost scope is active; scopes
  are revoked when their callback settles;
- a callback that settles with work outstanding is rolled back, never
  committed or released;
- transaction-control statements sent through query are refused by
  command tag and transaction status, and fail the transaction;
- the module ID is carried for Core's attribution only.

Tests:
- 83 integration tests on a database with hostile defaults: queries,
  the one-statement rule, parameters, decoding, errors, transactions,
  savepoints, forgotten awaits, pool safety after every failure class,
  and session state;
- unit tests for encoding, decoding, options, sanitization, and scope
  bookkeeping;
- type-level and source checks that no ORM or driver object crosses
  the boundary.

ADR 0050 is Accepted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 16:01:46 -05:00

1327 lines
48 KiB
TypeScript

import { randomBytes } from "node:crypto";
import {
isModuleDatabaseError,
type ModuleDatabaseErrorCode,
type ModuleDatabaseExecutor,
type ModuleDatabaseIsolationLevel,
type ModuleDatabaseTransaction,
} from "@ekklesia/contracts";
import { Client, Pool, type PoolClient } from "pg";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { createValidatedPostgresPool } from "../src/database/index.js";
import { createModuleDatabaseExecutor } from "../src/modules/index.js";
/**
* Module Database ABI v1 (ADR 0050) against real PostgreSQL.
*
* Everything the ABI promises that depends on PostgreSQL itself is proven here, not mocked: type
* OIDs and decoding, the one-statement rule, SQLSTATEs, transactions, isolation, savepoints,
* session pinning and reset, and that every failure leaves the pool's next borrower a clean
* connection.
*
* The disposable database is created with deliberately hostile defaults (a non-UTC `TimeZone`, a
* non-ISO `DateStyle`, `sql_standard` intervals, `escape` bytea output, and reduced float digits),
* so every decoding assertion also proves the executor pins its own session settings, and every
* pool-safety assertion proves it gives the database's defaults back afterwards. The fixture table
* is created directly in that disposable database; no migration runs.
*/
const integrationDatabaseUrlEnvironment = "EKKLESIA_MIGRATION_TEST_DATABASE_URL";
const trackedDatabases = new Set<string>();
class IntegrationHarnessError extends Error {
constructor(message: string) {
super(message);
this.name = "IntegrationHarnessError";
this.stack = `${this.name}: ${this.message}`;
}
}
function baseDatabaseUrl(): string {
const value = process.env[integrationDatabaseUrlEnvironment];
if (typeof value !== "string" || value.trim() === "") {
throw new IntegrationHarnessError(
"The module database ABI 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 new IntegrationHarnessError(
"The module database ABI integration database configuration is invalid.",
);
}
return value;
}
function quoteDatabaseName(databaseName: string): string {
if (!/^[a-z][a-z0-9_]{0,62}$/u.test(databaseName)) {
throw new IntegrationHarnessError("The generated integration database name is invalid.");
}
return `"${databaseName}"`;
}
async function withAdministrativeClient<T>(
connectionString: string,
run: (client: Client) => Promise<T>,
): Promise<T> {
const client = new Client({ connectionString });
client.on("error", () => undefined);
try {
await client.connect();
} catch {
await client.end().catch(() => undefined);
throw new IntegrationHarnessError("The integration database connection failed.");
}
try {
return await run(client);
} finally {
await client.end().catch(() => undefined);
}
}
async function createDisposableDatabase(): Promise<string> {
const name = `ekk_db_abi_${process.pid}_${randomBytes(4).toString("hex")}`;
const quoted = quoteDatabaseName(name);
await withAdministrativeClient(baseDatabaseUrl(), async (admin) => {
await admin.query(`CREATE DATABASE ${quoted} TEMPLATE template0`);
trackedDatabases.add(name);
// Hostile defaults: everything the ABI pins is set to something else here.
await admin.query(`ALTER DATABASE ${quoted} SET TimeZone TO 'America/Chicago'`);
await admin.query(`ALTER DATABASE ${quoted} SET DateStyle TO 'SQL, DMY'`);
await admin.query(`ALTER DATABASE ${quoted} SET IntervalStyle TO 'sql_standard'`);
await admin.query(`ALTER DATABASE ${quoted} SET bytea_output TO 'escape'`);
await admin.query(`ALTER DATABASE ${quoted} SET extra_float_digits TO -3`);
});
const parsed = new URL(baseDatabaseUrl());
parsed.pathname = `/${name}`;
parsed.searchParams.delete("application_name");
return parsed.toString();
}
async function dropDatabase(name: string): Promise<void> {
await withAdministrativeClient(baseDatabaseUrl(), async (admin) => {
await admin.query(`DROP DATABASE IF EXISTS ${quoteDatabaseName(name)} WITH (FORCE)`);
});
trackedDatabases.delete(name);
}
let databaseUrl: string;
let pool: Pool;
let closePool: () => Promise<void>;
let database: ModuleDatabaseExecutor;
beforeAll(async () => {
databaseUrl = await createDisposableDatabase();
await withAdministrativeClient(databaseUrl, async (admin) => {
await admin.query(
"CREATE TABLE example_records (id integer PRIMARY KEY, value text NOT NULL, secret text UNIQUE)",
);
await admin.query(
"CREATE TABLE example_deferred (id integer, CONSTRAINT example_deferred_id_key UNIQUE (id) DEFERRABLE INITIALLY DEFERRED)",
);
});
// The production pool: validated, with Core's own per-statement bounds.
({ close: closePool, pool } = createValidatedPostgresPool(databaseUrl, {
connectionTimeoutMs: 10_000,
operationTimeoutMs: 20_000,
}));
database = createModuleDatabaseExecutor(pool, { moduleId: "example" });
});
afterAll(async () => {
await closePool?.();
for (const name of [...trackedDatabases]) {
await dropDatabase(name).catch(() => undefined);
}
});
async function rejection(promise: Promise<unknown>): Promise<unknown> {
try {
await promise;
} catch (error) {
return error;
}
throw new Error("Expected the operation to reject.");
}
/** Asserts a sanitized ABI error: the code, PostgreSQL's SQLSTATE or none, and nothing else. */
async function expectAbiError(
promise: Promise<unknown>,
code: ModuleDatabaseErrorCode,
sqlState?: string,
secrets: readonly string[] = [],
): Promise<void> {
const error = await rejection(promise);
expect(isModuleDatabaseError(error)).toBe(true);
expect((error as { code: string }).code).toBe(code);
expect((error as { sqlState?: string }).sqlState).toBe(sqlState);
expect(Object.keys(error as object).sort()).toEqual(
sqlState === undefined ? ["code", "name"] : ["code", "name", "sqlState"],
);
expect((error as Error).cause).toBeUndefined();
expect((error as Error).stack).toBe(`ModuleDatabaseError: ${(error as Error).message}`);
const serialized = `${JSON.stringify(error)} ${String(error)}`;
for (const secret of [...secrets, databaseUrl, "example_records", "postgres://"]) {
expect(serialized).not.toContain(secret);
}
}
async function reset(): Promise<void> {
await withAdministrativeClient(databaseUrl, async (admin) => {
await admin.query("TRUNCATE example_records, example_deferred");
});
}
async function storedIds(): Promise<number[]> {
return withAdministrativeClient(databaseUrl, async (admin) => {
const result = await admin.query<{ id: number }>("SELECT id FROM example_records ORDER BY id");
return result.rows.map((row) => row.id);
});
}
// --- Queries ------------------------------------------------------------------------------------
describe("query", () => {
it("runs a simple select in object mode by default", async () => {
const result = await database.query({ text: "SELECT 1 AS one, 'two' AS two" });
expect(result).toEqual({
command: "SELECT",
fields: [
{ name: "one", typeOid: 23 },
{ name: "two", typeOid: 25 },
],
rowCount: 1,
rows: [{ one: 1, two: "two" }],
});
});
it("binds parameters and never interpolates them", async () => {
const hostile = "x'); DROP TABLE example_records; --";
const result = await database.query({ text: "SELECT $1::text AS v", values: [hostile] });
expect(result.rows).toEqual([{ v: hostile }]);
expect(await storedIds()).toEqual([]);
});
it("keeps array-mode columns positional, and returns metadata even with no rows", async () => {
const result = await database.query({
rowMode: "array",
text: "SELECT 1 AS a, 'b' AS b WHERE false",
});
expect(result.rows).toEqual([]);
expect(result.rowCount).toBe(0);
expect(result.fields).toEqual([
{ name: "a", typeOid: 23 },
{ name: "b", typeOid: 25 },
]);
});
it("returns duplicate column names positionally in array mode, and refuses them in object mode", async () => {
const arrayResult = await database.query({ rowMode: "array", text: "SELECT 1 AS a, 2 AS a" });
expect(arrayResult.rows).toEqual([[1, 2]]);
expect(arrayResult.fields.map((field) => field.name)).toEqual(["a", "a"]);
await expectAbiError(
database.query({ text: "SELECT 1 AS a, 2 AS a" }),
"MODULE_DATABASE_REQUEST_INVALID",
);
});
it("keeps a column named __proto__ a column", async () => {
const result = await database.query({ text: 'SELECT 1 AS "__proto__"' });
const row = result.rows[0] as Record<string, unknown>;
expect(Object.hasOwn(row, "__proto__")).toBe(true);
expect(Object.getPrototypeOf(row)).toBe(Object.prototype);
});
it("reports command tags and row counts for writes, and RETURNING rows as ordinary rows", async () => {
await reset();
const inserted = await database.query({
text: "INSERT INTO example_records (id, value) VALUES ($1, $2), ($3, $4) RETURNING id, value",
values: [1, "a", 2, "b"],
});
expect(inserted.command).toBe("INSERT");
expect(inserted.rowCount).toBe(2);
expect(inserted.rows).toEqual([
{ id: 1, value: "a" },
{ id: 2, value: "b" },
]);
const updated = await database.query({
text: "UPDATE example_records SET value = 'c' WHERE id > $1",
values: [0],
});
expect(updated).toMatchObject({ command: "UPDATE", rowCount: 2, rows: [] });
const deleted = await database.query({ text: "DELETE FROM example_records" });
expect(deleted).toMatchObject({ command: "DELETE", rowCount: 2 });
const created = await database.query({ text: "CREATE TEMPORARY TABLE scratch (x int)" });
expect(created).toMatchObject({ command: "CREATE", rowCount: null });
});
});
describe("one statement per query", () => {
it("runs exactly one statement, a trailing semicolon included", async () => {
expect((await database.query({ text: "SELECT 1 AS n" })).rows).toEqual([{ n: 1 }]);
expect((await database.query({ text: "SELECT 1 AS n;" })).rows).toEqual([{ n: 1 }]);
});
it("refuses two statements, with PostgreSQL's own SQLSTATE, and runs neither", async () => {
await reset();
await expectAbiError(
database.query({ text: "SELECT 1; SELECT 2" }),
"MODULE_DATABASE_QUERY_FAILED",
"42601",
);
await expectAbiError(
database.query({
text: "INSERT INTO example_records (id, value) VALUES (1, 'a'); INSERT INTO example_records (id, value) VALUES (2, 'b')",
}),
"MODULE_DATABASE_QUERY_FAILED",
"42601",
);
await expectAbiError(
database.query({ text: "SELECT $1::int; SELECT 2", values: [1] }),
"MODULE_DATABASE_QUERY_FAILED",
"42601",
);
expect(await storedIds()).toEqual([]);
});
it("never mistakes semicolons in literals, identifiers, or comments for a second statement", async () => {
const result = await database.query({
rowMode: "array",
text: [
"SELECT ';' AS a,",
" $$;SELECT 2;$$ AS b,",
" $tag$;$tag$ AS c,",
" E'\\x3B' AS d,",
' 1 AS "semi;colon" -- ; SELECT 3',
"/* ; SELECT 4; */",
].join("\n"),
});
expect(result.rows).toEqual([[";", ";SELECT 2;", ";", ";", 1]]);
expect(result.fields.map((field) => field.name)).toEqual(["a", "b", "c", "d", "semi;colon"]);
});
it("refuses statement text that holds no statement", async () => {
for (const text of [" ", "-- only a comment", "/* nothing */", ";"]) {
await expectAbiError(database.query({ text }), "MODULE_DATABASE_REQUEST_INVALID");
}
});
});
// --- Parameters ---------------------------------------------------------------------------------
describe("parameters", () => {
it("binds every allowed category with its exact value", async () => {
const result = await database.query({
rowMode: "array",
text: [
"SELECT $1::text IS NULL, $2::bool, $3::float8, $4::float8, $5::float8, $6::float8,",
" $7::int8, $8::numeric, $9::text, $10::bytea, $11::bytea, $12::int4",
].join(" "),
values: [
null,
true,
0.1,
Number.NaN,
Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY,
9_223_372_036_854_775_807n,
12_345_678_901_234_567_890n,
'it\'s "quoted" \\ and ünïcödé 🙂',
new Uint8Array([0, 1, 254, 255]),
Buffer.from([7]),
-2_147_483_648,
],
});
const row = result.rows[0] as readonly unknown[];
expect(row.slice(0, 3)).toEqual([true, true, 0.1]);
expect(row[3]).toBeNaN();
expect(row.slice(4, 10)).toEqual([
Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY,
"9223372036854775807",
"12345678901234567890",
'it\'s "quoted" \\ and ünïcödé 🙂',
new Uint8Array([0, 1, 254, 255]),
]);
expect(row.slice(10)).toEqual([new Uint8Array([7]), -2_147_483_648]);
});
it("binds arrays, nulls inside them, and more dimensions", async () => {
const result = await database.query({
rowMode: "array",
text: "SELECT $1::int[], $2::text[], $3::bytea[], $4::int[], $5::int8[], $6::text[], $7::bool[]",
values: [
[1, null, 3],
['a"b', "c,d", "e\\f", "", "NULL", null, "{x}"],
[new Uint8Array([0, 255]), null],
[
[1, 2],
[3, 4],
],
[9_223_372_036_854_775_807n],
[],
[true, false],
],
});
expect(result.rows[0]).toEqual([
[1, null, 3],
['a"b', "c,d", "e\\f", "", "NULL", null, "{x}"],
[new Uint8Array([0, 255]), null],
[
[1, 2],
[3, 4],
],
["9223372036854775807"],
[],
[true, false],
]);
});
it("finds rows with an array parameter", async () => {
await reset();
await database.query({
text: "INSERT INTO example_records (id, value) VALUES (1, 'a'), (2, 'b'), (3, 'c')",
});
const result = await database.query({
text: "SELECT id FROM example_records WHERE id = ANY($1) ORDER BY id",
values: [[1, 3]],
});
expect(result.rows).toEqual([{ id: 1 }, { id: 3 }]);
});
const refused: readonly [string, unknown][] = [
["a Date", new Date("2026-10-07T00:00:00Z")],
["a plain object", { secret: "hunter2" }],
["undefined", undefined],
["a function", () => "hunter2"],
["a symbol", Symbol("hunter2")],
["a class instance", new URL("https://hunter2.invalid")],
["an array holding a Date", [new Date("2026-10-07T00:00:00Z")]],
["an array holding an object", [{ secret: "hunter2" }]],
["an array holding undefined", ["hunter2", undefined]],
["ragged sub-arrays", [["hunter2"], ["a", "b"]]],
];
for (const [label, value] of refused) {
it(`refuses ${label} before PostgreSQL sees anything`, async () => {
await reset();
await expectAbiError(
database.query({
text: "INSERT INTO example_records (id, value) VALUES (1, $1::text)",
values: [value as never],
}),
"MODULE_DATABASE_PARAMETER_INVALID",
undefined,
["hunter2", "2026-10-07", "INSERT"],
);
expect(await storedIds()).toEqual([]);
});
}
});
// --- Results ------------------------------------------------------------------------------------
describe("result decoding", () => {
it("decodes every type in ADR 0050's table deterministically, despite hostile database defaults", async () => {
const result = await database.query({
text: [
"SELECT",
" NULL::text AS null_value,",
" true AS bool, false AS bool_false,",
" (-32768)::int2 AS int2, 2147483647::int4 AS int4,",
" 9223372036854775807::int8 AS int8,",
" 1.5::float4 AS float4, 0.1::float8 AS float8, 'NaN'::float8 AS nan,",
" 'Infinity'::float8 AS inf, '-Infinity'::float4 AS neg_inf,",
" 12345678901234567890.123456789::numeric AS numeric, 'NaN'::numeric AS numeric_nan,",
" 1.10::decimal(5,2) AS decimal,",
" 'text'::text AS text, 'var'::varchar(10) AS varchar, 'ch'::char(3) AS bpchar,",
" 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid AS uuid,",
" '2026-10-07 12:34:56.789'::timestamp AS timestamp,",
" '2026-10-07 12:34:56.789+02'::timestamptz AS timestamptz,",
" '2026-10-07'::date AS date,",
" '12:34:56.5'::time AS time, '12:34:56+05:30'::timetz AS timetz,",
" '1 year 2 mons 3 days 04:05:06.7'::interval AS interval,",
" '-1 day'::interval AS negative_interval,",
` '{"big": 12345678901234567890, "f": 0.10000000000000000001}'::json AS json,`,
` '{"big": 12345678901234567890}'::jsonb AS jsonb,`,
" '\\x00ff10'::bytea AS bytea, ''::bytea AS empty_bytea",
].join("\n"),
});
const row = result.rows[0] as Record<string, unknown>;
expect(row).toMatchObject({
bool: true,
bool_false: false,
bpchar: "ch ",
date: "2026-10-07",
decimal: "1.10",
float4: 1.5,
float8: 0.1,
inf: Number.POSITIVE_INFINITY,
int2: -32_768,
int4: 2_147_483_647,
int8: "9223372036854775807",
interval: "1 year 2 mons 3 days 04:05:06.7",
json: '{"big": 12345678901234567890, "f": 0.10000000000000000001}',
jsonb: '{"big": 12345678901234567890}',
neg_inf: Number.NEGATIVE_INFINITY,
negative_interval: "-1 days",
null_value: null,
numeric: "12345678901234567890.123456789",
numeric_nan: "NaN",
text: "text",
time: "12:34:56.5",
timestamp: "2026-10-07 12:34:56.789",
timestamptz: "2026-10-07 10:34:56.789+00",
timetz: "12:34:56+05:30",
uuid: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
varchar: "var",
});
expect(row.nan).toBeNaN();
expect(row.bytea).toEqual(new Uint8Array([0, 255, 16]));
expect(Object.getPrototypeOf(row.bytea)).toBe(Uint8Array.prototype);
expect(row.empty_bytea).toEqual(new Uint8Array([]));
expect(result.fields.map((field) => [field.name, field.typeOid])).toEqual([
["null_value", 25],
["bool", 16],
["bool_false", 16],
["int2", 21],
["int4", 23],
["int8", 20],
["float4", 700],
["float8", 701],
["nan", 701],
["inf", 701],
["neg_inf", 700],
["numeric", 1700],
["numeric_nan", 1700],
["decimal", 1700],
["text", 25],
["varchar", 1043],
["bpchar", 1042],
["uuid", 2950],
["timestamp", 1114],
["timestamptz", 1184],
["date", 1082],
["time", 1083],
["timetz", 1266],
["interval", 1186],
["negative_interval", 1186],
["json", 114],
["jsonb", 3802],
["bytea", 17],
["empty_bytea", 17],
]);
});
it("keeps float precision exact: the shortest text that round-trips", async () => {
const values = [0.1, 1 / 3, 2 ** -1074, Number.MAX_VALUE, 123_456_789.123_456_78];
const result = await database.query({
rowMode: "array",
text: "SELECT unnest($1::float8[])",
values: [values],
});
expect(result.rows.map((row) => row[0])).toEqual(values);
});
it("decodes representative arrays element by element", async () => {
const result = await database.query({
text: [
"SELECT",
" ARRAY[1, NULL, 9223372036854775807]::int8[] AS int8s,",
" ARRAY[1.10, 2]::numeric[] AS numerics,",
" ARRAY[true, false, NULL] AS bools,",
" ARRAY[[1, 2], [3, 4]]::int4[] AS matrix,",
" ARRAY[0.5, 'NaN']::float8[] AS floats,",
` ARRAY['a"b', 'c,d', '', 'NULL', NULL, '{}', 'back\\slash']::text[] AS texts,`,
" ARRAY['\\x00ff'::bytea, NULL] AS byteas,",
" ARRAY['2026-10-07 12:00:00+02'::timestamptz] AS instants,",
" ARRAY['2026-10-07'::date] AS dates,",
" ARRAY['1 day'::interval] AS intervals,",
` ARRAY['{"n": 12345678901234567890}'::jsonb] AS documents,`,
" ARRAY['a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid] AS uuids,",
" '{}'::int[] AS empty,",
" '[0:1]={5,6}'::int[] AS lower_bound",
].join("\n"),
});
const row = result.rows[0] as Record<string, unknown>;
expect(row).toEqual({
bools: [true, false, null],
byteas: [new Uint8Array([0, 255]), null],
dates: ["2026-10-07"],
documents: ['{"n": 12345678901234567890}'],
empty: [],
floats: [0.5, Number.NaN],
instants: ["2026-10-07 10:00:00+00"],
int8s: ["1", null, "9223372036854775807"],
intervals: ["1 day"],
lower_bound: [5, 6],
matrix: [
[1, 2],
[3, 4],
],
numerics: ["1.10", "2"],
texts: ['a"b', "c,d", "", "NULL", null, "{}", "back\\slash"],
uuids: ["a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"],
});
});
it("decodes the same instant to the same text whatever the session started as", async () => {
const instant = "2026-03-08 08:30:00+00";
const first = await database.query({ text: "SELECT $1::timestamptz AS t", values: [instant] });
const second = await database.transaction((tx) =>
tx.query({ text: "SELECT $1::timestamptz AS t", values: [instant] }),
);
expect(first.rows).toEqual([{ t: instant }]);
expect(second.rows).toEqual([{ t: instant }]);
});
});
// --- Errors -------------------------------------------------------------------------------------
describe("errors", () => {
it("reports a syntax error as a failed query with PostgreSQL's SQLSTATE and nothing more", async () => {
await expectAbiError(
database.query({ text: "SELEC secret_column FROM example_records" }),
"MODULE_DATABASE_QUERY_FAILED",
"42601",
["SELEC", "secret_column", "syntax"],
);
await expectAbiError(
database.query({ text: "SELECT missing_column FROM example_records" }),
"MODULE_DATABASE_QUERY_FAILED",
"42703",
["missing_column"],
);
});
it("keeps a unique violation's SQLSTATE and hides its message, detail, constraint, and value", async () => {
await reset();
await database.query({
text: "INSERT INTO example_records (id, value, secret) VALUES (1, 'a', $1)",
values: ["hunter2"],
});
await expectAbiError(
database.query({
text: "INSERT INTO example_records (id, value, secret) VALUES (2, 'b', $1)",
values: ["hunter2"],
}),
"MODULE_DATABASE_QUERY_FAILED",
"23505",
["hunter2", "example_records_secret_key", "duplicate", "Key ("],
);
});
it("reports a primary-key conflict inside a transaction the same way", async () => {
await reset();
await database.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
await expectAbiError(
database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'b')" });
}),
"MODULE_DATABASE_QUERY_FAILED",
"23505",
["example_records_pkey"],
);
});
});
// --- Transactions -------------------------------------------------------------------------------
describe("transactions", () => {
it("commits a resolved callback and returns its value", async () => {
await reset();
const value = await database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
return "committed";
});
expect(value).toBe("committed");
expect(await storedIds()).toEqual([1, 2]);
});
it("rolls back a callback that throws, and rejects with the callback's own error", async () => {
await reset();
const own = new Error("module failure");
const error = await rejection(
database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
throw own;
}),
);
expect(error).toBe(own);
expect(await storedIds()).toEqual([]);
});
it("sees its own writes, and other connections do not until it commits", async () => {
await reset();
await database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
expect(
(await tx.query({ text: "SELECT count(*)::int AS n FROM example_records" })).rows,
).toEqual([{ n: 1 }]);
expect(await storedIds()).toEqual([]);
});
expect(await storedIds()).toEqual([1]);
});
it("never retries: a serialization failure reaches the callback once and rejects the transaction", async () => {
await reset();
await database.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
let attempts = 0;
await expectAbiError(
database.transaction(
async (tx) => {
attempts += 1;
await tx.query({ text: "SELECT value FROM example_records WHERE id = 1" });
// Another connection commits a change to the same row after this snapshot was taken.
await database.query({ text: "UPDATE example_records SET value = 'b' WHERE id = 1" });
await tx.query({ text: "UPDATE example_records SET value = 'c' WHERE id = 1" });
},
{ isolation: "repeatable read" },
),
"MODULE_DATABASE_QUERY_FAILED",
"40001",
);
expect(attempts).toBe(1);
const rows = await database.query({ text: "SELECT value FROM example_records WHERE id = 1" });
expect(rows.rows).toEqual([{ value: "b" }]);
});
it("reports a failure at COMMIT, such as a deferred constraint, with its SQLSTATE", async () => {
await reset();
let attempts = 0;
await expectAbiError(
database.transaction(async (tx) => {
attempts += 1;
await tx.query({ text: "INSERT INTO example_deferred (id) VALUES (1), (1)" });
}),
"MODULE_DATABASE_TRANSACTION_FAILED",
"23505",
["example_deferred_id_key"],
);
expect(attempts).toBe(1);
const count = await database.query({ text: "SELECT count(*)::int AS n FROM example_deferred" });
expect(count.rows).toEqual([{ n: 0 }]);
});
it("never commits a transaction a failed statement aborted, even if the callback carries on", async () => {
await reset();
await expectAbiError(
database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
await expectAbiError(
tx.query({ text: "SELECT 1/0" }),
"MODULE_DATABASE_QUERY_FAILED",
"22012",
);
await expectAbiError(
tx.query({ text: "SELECT 1" }),
"MODULE_DATABASE_QUERY_FAILED",
"25P02",
);
return "carried on";
}),
"MODULE_DATABASE_TRANSACTION_FAILED",
);
expect(await storedIds()).toEqual([]);
});
it("revokes an escaped executor, which then reaches nothing", async () => {
await reset();
let escaped: ModuleDatabaseTransaction | undefined;
await database.transaction((tx) => {
escaped = tx;
});
await expectAbiError(
(escaped as ModuleDatabaseTransaction).query({
text: "INSERT INTO example_records (id, value) VALUES (1, 'a')",
}),
"MODULE_DATABASE_SCOPE_CLOSED",
);
await expectAbiError(
(escaped as ModuleDatabaseTransaction).savepoint(async () => undefined),
"MODULE_DATABASE_SCOPE_CLOSED",
);
expect(await storedIds()).toEqual([]);
});
it("refuses writes in a read-only transaction", async () => {
await reset();
await expectAbiError(
database.transaction(
async (tx) => {
expect((await tx.query({ text: "SHOW transaction_read_only" })).rows).toEqual([
{ transaction_read_only: "on" },
]);
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
},
{ readOnly: true },
),
"MODULE_DATABASE_QUERY_FAILED",
"25006",
);
expect(await storedIds()).toEqual([]);
});
const levels: readonly [ModuleDatabaseIsolationLevel | undefined, string][] = [
[undefined, "read committed"],
["read committed", "read committed"],
["repeatable read", "repeatable read"],
["serializable", "serializable"],
];
for (const [isolation, expected] of levels) {
it(`runs ${isolation ?? "the default"} isolation as ${expected}, read-write`, async () => {
const rows = await database.transaction(
async (tx) =>
(
await tx.query({
text: "SELECT current_setting('transaction_isolation') AS level, current_setting('transaction_read_only') AS ro",
})
).rows,
isolation === undefined ? undefined : { isolation },
);
expect(rows).toEqual([{ level: expected, ro: "off" }]);
});
}
it("overrides a database whose default isolation is different", async () => {
const name = new URL(databaseUrl).pathname.slice(1);
await withAdministrativeClient(databaseUrl, async (admin) => {
await admin.query(
`ALTER DATABASE ${quoteDatabaseName(name)} SET default_transaction_isolation TO 'serializable'`,
);
});
// A new pool, so its connections start with the new database default.
const freshPool = new Pool({ connectionString: databaseUrl, max: 1 });
freshPool.on("error", () => undefined);
try {
const raw = await freshPool.query<{ level: string }>(
"SELECT current_setting('default_transaction_isolation') AS level",
);
expect(raw.rows).toEqual([{ level: "serializable" }]);
const fresh = createModuleDatabaseExecutor(freshPool, { moduleId: "example" });
const rows = await fresh.transaction(
async (tx) =>
(await tx.query({ text: "SELECT current_setting('transaction_isolation') AS level" }))
.rows,
);
expect(rows).toEqual([{ level: "read committed" }]);
} finally {
await freshPool.end();
await withAdministrativeClient(databaseUrl, async (admin) => {
await admin.query(
`ALTER DATABASE ${quoteDatabaseName(name)} RESET default_transaction_isolation`,
);
});
}
});
it("refuses invalid options before it starts anything", async () => {
let called = false;
for (const options of [
{ isolation: "read uncommitted" },
{ deferrable: true },
{ readOnly: "yes" },
{ isolation: "serializable", timeout: 5 },
]) {
await expectAbiError(
database.transaction(async () => {
called = true;
}, options as never),
"MODULE_DATABASE_TRANSACTION_OPTION_INVALID",
);
}
expect(called).toBe(false);
});
it("refuses transaction control sent through query, and never commits the transaction", async () => {
await reset();
for (const statements of [
["SAVEPOINT mine"],
["SAVEPOINT mine", "RELEASE SAVEPOINT mine"],
["SAVEPOINT mine", "ROLLBACK TO SAVEPOINT mine"],
["BEGIN"],
["START TRANSACTION"],
]) {
await expectAbiError(
database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
for (const statement of statements) {
// PostgreSQL has run each statement by the time it is refused.
await expectAbiError(tx.query({ text: statement }), "MODULE_DATABASE_REQUEST_INVALID");
}
return "carried on";
}),
"MODULE_DATABASE_REQUEST_INVALID",
);
expect(await storedIds()).toEqual([]);
}
});
it("refuses a COMMIT sent through query and fails the transaction", async () => {
await reset();
await expectAbiError(
database.transaction(async (tx) => {
await expectAbiError(tx.query({ text: "COMMIT" }), "MODULE_DATABASE_REQUEST_INVALID");
return "carried on";
}),
"MODULE_DATABASE_REQUEST_INVALID",
);
});
it("applies Core's statement bound inside a transaction, and transaction-local settings work", async () => {
await expectAbiError(
database.transaction(async (tx) => {
await tx.query({ text: "SET LOCAL statement_timeout = 50" });
await tx.query({ text: "SELECT pg_sleep(2)" });
}),
"MODULE_DATABASE_QUERY_FAILED",
"57014",
);
});
});
// --- Forgotten await ----------------------------------------------------------------------------
describe("outstanding work", () => {
it("never commits a transaction whose callback forgot to await a write", async () => {
await reset();
let forgotten: Promise<unknown> | undefined;
await expectAbiError(
database.transaction((tx) => {
forgotten = tx.query({
text: "INSERT INTO example_records (id, value) SELECT 1, 'a' FROM pg_sleep(0.2)",
});
}),
"MODULE_DATABASE_SCOPE_WORK_OUTSTANDING",
);
// The forgotten statement itself ran (Core let it settle before rolling back) but its write
// was rolled back with the transaction.
await expect(forgotten).resolves.toMatchObject({ command: "INSERT", rowCount: 1 });
expect(await storedIds()).toEqual([]);
});
it("never commits when the callback awaited some writes and forgot one", async () => {
await reset();
await expectAbiError(
database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
void tx.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
}),
"MODULE_DATABASE_SCOPE_WORK_OUTSTANDING",
);
expect(await storedIds()).toEqual([]);
});
it("treats an unawaited savepoint as outstanding work", async () => {
await reset();
let forgotten: Promise<unknown> | undefined;
await expectAbiError(
database.transaction((tx) => {
forgotten = tx.savepoint(async (sp) => {
await sp.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
});
// Observed below; handled now so its rejection is never briefly unhandled.
forgotten.catch(() => undefined);
}),
"MODULE_DATABASE_SCOPE_WORK_OUTSTANDING",
);
await expect(forgotten).rejects.toMatchObject({ code: "MODULE_DATABASE_SCOPE_CLOSED" });
expect(await storedIds()).toEqual([]);
});
});
// --- Savepoints ---------------------------------------------------------------------------------
describe("savepoints", () => {
it("releases a resolved savepoint, keeping its work", async () => {
await reset();
const value = await database.transaction(async (tx) =>
tx.savepoint(async (sp) => {
await sp.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
return "kept";
}),
);
expect(value).toBe("kept");
expect(await storedIds()).toEqual([1]);
});
it("rolls back a rejected savepoint and lets the transaction carry on and commit", async () => {
await reset();
const own = new Error("savepoint failure");
await database.transaction(async (tx) => {
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
const error = await rejection(
tx.savepoint(async (sp) => {
await sp.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
throw own;
}),
);
expect(error).toBe(own);
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (3, 'c')" });
});
expect(await storedIds()).toEqual([1, 3]);
});
it("recovers from a failed statement inside a savepoint, the usual way to continue after one", async () => {
await reset();
await database.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
await database.transaction(async (tx) => {
await expectAbiError(
tx.savepoint(async (sp) => {
await sp.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'dup')" });
}),
"MODULE_DATABASE_QUERY_FAILED",
"23505",
);
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
});
expect(await storedIds()).toEqual([1, 2]);
});
it("reports a savepoint whose callback swallowed a failed statement, and keeps the transaction usable", async () => {
await reset();
await database.transaction(async (tx) => {
await expectAbiError(
tx.savepoint(async (sp) => {
await sp.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
await rejection(sp.query({ text: "SELECT 1/0" }));
return "swallowed";
}),
"MODULE_DATABASE_TRANSACTION_FAILED",
"25P02",
);
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
});
expect(await storedIds()).toEqual([2]);
});
it("nests and sequences savepoints, keeping exactly the work that was released", async () => {
await reset();
await database.transaction(async (tx) => {
await tx.savepoint(async (outer) => {
await outer.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" });
await rejection(
outer.savepoint(async (inner) => {
await inner.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
throw new Error("inner failure");
}),
);
await outer.savepoint(async (inner) => {
await inner.query({ text: "INSERT INTO example_records (id, value) VALUES (3, 'c')" });
});
});
await tx.savepoint(async (second) => {
await second.query({ text: "INSERT INTO example_records (id, value) VALUES (4, 'd')" });
});
});
expect(await storedIds()).toEqual([1, 3, 4]);
});
it("revokes an escaped savepoint executor", async () => {
await reset();
await database.transaction(async (tx) => {
let escaped: ModuleDatabaseTransaction | undefined;
await tx.savepoint((sp) => {
escaped = sp;
});
await expectAbiError(
(escaped as ModuleDatabaseTransaction).query({
text: "INSERT INTO example_records (id, value) VALUES (1, 'a')",
}),
"MODULE_DATABASE_SCOPE_CLOSED",
);
});
expect(await storedIds()).toEqual([]);
});
it("refuses overlapping scopes: the parent is inactive while a savepoint is open", async () => {
await reset();
await database.transaction(async (tx) => {
await tx.savepoint(async (sp) => {
await expectAbiError(
tx.query({ text: "INSERT INTO example_records (id, value) VALUES (1, 'a')" }),
"MODULE_DATABASE_SCOPE_INACTIVE",
);
await expectAbiError(
tx.savepoint(async () => undefined),
"MODULE_DATABASE_SCOPE_INACTIVE",
);
await sp.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
});
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (3, 'c')" });
});
expect(await storedIds()).toEqual([2, 3]);
});
it("rolls back, never releases, a savepoint whose callback forgot to await", async () => {
await reset();
await database.transaction(async (tx) => {
await expectAbiError(
tx.savepoint((sp) => {
void sp.query({
text: "INSERT INTO example_records (id, value) SELECT 1, 'a' FROM pg_sleep(0.2)",
});
}),
"MODULE_DATABASE_SCOPE_WORK_OUTSTANDING",
);
await tx.query({ text: "INSERT INTO example_records (id, value) VALUES (2, 'b')" });
});
expect(await storedIds()).toEqual([2]);
});
});
// --- Pool safety and session state --------------------------------------------------------------
interface BorrowerState {
readonly bytea: string;
readonly datestyle: string;
readonly listening: number;
readonly locks: number;
readonly status: string | null;
readonly temporaryTables: number;
readonly timezone: string;
readonly workMem: string;
}
/** What the next borrower of a one-connection pool finds, through the raw driver, as Core's own
* stores would. */
async function nextBorrower(onePool: Pool): Promise<BorrowerState> {
const client = (await onePool.connect()) as PoolClient & {
getTransactionStatus(): string | null;
};
try {
const status = client.getTransactionStatus();
const result = await client.query<{
bytea: string;
datestyle: string;
listening: number;
locks: number;
temporary_tables: number;
timezone: string;
work_mem: string;
}>(
[
"SELECT current_setting('TimeZone') AS timezone, current_setting('DateStyle') AS datestyle,",
" current_setting('bytea_output') AS bytea, current_setting('work_mem') AS work_mem,",
" (SELECT count(*)::int FROM pg_listening_channels()) AS listening,",
" (SELECT count(*)::int FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid()) AS locks,",
" (SELECT count(*)::int FROM pg_class WHERE relnamespace = pg_my_temp_schema()) AS temporary_tables",
].join(" "),
);
const row = result.rows[0];
if (row === undefined) {
throw new IntegrationHarnessError("The borrower probe returned no row.");
}
return {
bytea: row.bytea,
datestyle: row.datestyle,
listening: row.listening,
locks: row.locks,
status,
temporaryTables: row.temporary_tables,
timezone: row.timezone,
workMem: row.work_mem,
};
} finally {
client.release();
}
}
/** Exactly what a fresh connection to the hostile database has. */
const clean: BorrowerState = {
bytea: "escape",
datestyle: "SQL, DMY",
listening: 0,
locks: 0,
status: "I",
temporaryTables: 0,
timezone: "America/Chicago",
workMem: "4MB",
};
describe("pool safety", () => {
let onePool: Pool;
let single: ModuleDatabaseExecutor;
beforeAll(() => {
onePool = new Pool({ connectionString: databaseUrl, max: 1 });
onePool.on("error", () => undefined);
single = createModuleDatabaseExecutor(onePool, { moduleId: "example" });
});
afterAll(async () => {
await onePool?.end();
});
const failures: readonly [string, (executor: ModuleDatabaseExecutor) => Promise<unknown>][] = [
["a successful query", (executor) => executor.query({ text: "SELECT 1" })],
[
"a successful transaction",
(executor) => executor.transaction(async (tx) => tx.query({ text: "SELECT 1" })),
],
[
"a callback throw",
(executor) => executor.transaction(async () => Promise.reject(new Error("x"))),
],
["a SQL error", (executor) => executor.query({ text: "SELECT 1/0" })],
[
"a SQL error inside a transaction",
(executor) => executor.transaction((tx) => tx.query({ text: "SELECT 1/0" })),
],
[
"an aborted transaction the callback ignored",
(executor) =>
executor.transaction(async (tx) => {
await rejection(tx.query({ text: "SELECT 1/0" }));
}),
],
[
"escaped executor use",
async (executor) => {
let escaped: ModuleDatabaseTransaction | undefined;
await executor.transaction((tx) => {
escaped = tx;
});
return (escaped as ModuleDatabaseTransaction).query({ text: "SELECT 1" });
},
],
[
"outstanding work",
(executor) =>
executor.transaction((tx) => {
void tx.query({ text: "SELECT pg_sleep(0.1)" });
}),
],
[
"a savepoint rollback",
(executor) =>
executor.transaction(async (tx) => {
await rejection(tx.savepoint(async () => Promise.reject(new Error("x"))));
}),
],
[
"an outstanding savepoint",
(executor) =>
executor.transaction((tx) => {
// The module ignores the savepoint's own rejection; it is handled here only so the test
// run has no unhandled rejection.
tx.savepoint((sp) => sp.query({ text: "SELECT pg_sleep(0.1)" })).catch(() => undefined);
}),
],
["a refused top-level BEGIN", (executor) => executor.query({ text: "BEGIN" })],
[
"a refused COMMIT inside a transaction",
(executor) => executor.transaction((tx) => tx.query({ text: "COMMIT" })),
],
["session-level settings", (executor) => executor.query({ text: "SET work_mem = '64MB'" })],
[
"session-level settings inside a transaction",
(executor) =>
executor.transaction(async (tx) => {
await tx.query({ text: "SET work_mem = '64MB'" });
await tx.query({ text: "SET TimeZone = 'Asia/Tokyo'" });
}),
],
[
"a temporary table",
(executor) => executor.query({ text: "CREATE TEMPORARY TABLE scratch (x int)" }),
],
["LISTEN", (executor) => executor.query({ text: "LISTEN example_channel" })],
[
"LISTEN inside a transaction",
(executor) => executor.transaction((tx) => tx.query({ text: "LISTEN example_channel" })),
],
[
"a session advisory lock",
(executor) => executor.query({ text: "SELECT pg_advisory_lock(42)" }),
],
["a prepared statement", (executor) => executor.query({ text: "PREPARE mine AS SELECT 1" })],
[
"a terminated connection",
(executor) =>
executor.transaction(async (tx) => {
await tx.query({ text: "SELECT pg_terminate_backend(pg_backend_pid())" });
}),
],
];
for (const [label, run] of failures) {
it(`hands the next borrower a clean connection after ${label}`, async () => {
await run(single).catch(() => undefined);
expect(await nextBorrower(onePool)).toEqual(clean);
// And the ABI keeps working on it.
expect((await single.query({ text: "SELECT 1 AS n" })).rows).toEqual([{ n: 1 }]);
});
}
it("reports a terminated connection without a SQLSTATE of its own making", async () => {
const error = await rejection(
single.transaction(async (tx) => {
await tx.query({ text: "SELECT pg_terminate_backend(pg_backend_pid())" });
}),
);
expect(isModuleDatabaseError(error)).toBe(true);
// PostgreSQL may report the termination itself (57P01) or the driver may only see the socket
// close; either way the code is Core's, and any SQLSTATE is PostgreSQL's.
expect((error as { code: string }).code).toMatch(
/^MODULE_DATABASE_(QUERY_FAILED|TRANSACTION_FAILED)$/u,
);
expect([undefined, "57P01"]).toContain((error as { sqlState?: string }).sqlState);
expect(await nextBorrower(onePool)).toEqual(clean);
});
});
describe("session state", () => {
it("lets no session setting, temporary table, or listener carry from one query to the next", async () => {
await database.query({ text: "SET work_mem = '64MB'" });
await database.query({ text: "CREATE TEMPORARY TABLE scratch (x int)" });
await database.query({ text: "LISTEN example_channel" });
const result = await database.query({
text: [
"SELECT current_setting('work_mem') AS work_mem,",
" to_regclass('pg_temp.scratch') IS NULL AS no_scratch,",
" (SELECT count(*)::int FROM pg_listening_channels()) AS listening",
].join(" "),
});
expect(result.rows).toEqual([{ listening: 0, no_scratch: true, work_mem: "4MB" }]);
});
it("keeps transaction-local settings for the rest of the transaction, and no longer", async () => {
const inside = await database.transaction(async (tx) => {
await tx.query({ text: "SET LOCAL work_mem = '64MB'" });
return (await tx.query({ text: "SELECT current_setting('work_mem') AS work_mem" })).rows;
});
expect(inside).toEqual([{ work_mem: "64MB" }]);
const after = await database.query({ text: "SELECT current_setting('work_mem') AS work_mem" });
expect(after.rows).toEqual([{ work_mem: "4MB" }]);
});
it("pins the ABI's settings for every operation, and never leaves them for Core's own borrowers", async () => {
const pinned = await database.query({
text: [
"SELECT current_setting('TimeZone') AS tz, current_setting('DateStyle') AS ds,",
" current_setting('IntervalStyle') AS iv, current_setting('bytea_output') AS bytea,",
" current_setting('extra_float_digits') AS digits",
].join(" "),
});
expect(pinned.rows).toEqual([
{ bytea: "hex", digits: "1", ds: "ISO, MDY", iv: "postgres", tz: "UTC" },
]);
const core = await pool.query<{ tz: string }>("SELECT current_setting('TimeZone') AS tz");
expect(core.rows).toEqual([{ tz: "America/Chicago" }]);
});
it("keeps Core's own connection-level statement bound after resetting a session", async () => {
await database.query({ text: "SET statement_timeout = 0" });
const bound = await pool.query<{ timeout: string }>(
"SELECT current_setting('statement_timeout') AS timeout",
);
expect(bound.rows).toEqual([{ timeout: "20s" }]);
});
});