Files
Jyotisha/frontend/tests/local-postgres-roundtrips.test.ts
T
Jesse_ChenandClaude Opus 5.5 503a16fb92
Independent Staging Quality Gate / validate (push) Failing after 18m15s
Independent Staging Quality Gate / publish (push) Skipped
fix(db): add gte and lt to the self-hosted query builder so synastry reports can be created
createSynastryFullReport counted today's reports with .gte(), which the
self-hosted client did not implement; every create threw before any credit
was reserved and surfaced as a generic 500 (BUG-1399). The synastry create
path now uses the shared DataQueryBuilder type, and a scan test requires every
builder method used in src to exist on the self-hosted client.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-21 14:15:59 +08:00

314 lines
12 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import type { Pool } from "pg";
import {
clearLocalPostgresMetadataCacheForTests,
closeLocalPostgresDataPool,
createLocalPostgresDataClient,
installLocalPostgresPoolForTests,
setLocalPostgresMetadataClockForTests,
type LocalDatabaseRole,
} from "../src/lib/db/local-postgres-client-core.ts";
type QueryCall = { sql: string; params: readonly unknown[] };
function tag(sql: string): string {
const compact = sql.replace(/\s+/g, " ").trim().toLowerCase();
if (compact === "begin") return "begin";
if (compact === "commit") return "commit";
if (compact === "rollback") return "rollback";
if (compact.startsWith("set local role")) return "set local role";
if (compact.includes("set_config('role'")) return "set_config(role)";
if (compact.includes("set_config('request.jwt.claim.sub'")) return "set_config";
if (compact.includes("information_schema.columns")) return "information_schema";
if (compact.includes("from pg_index")) return "primary_key";
if (compact.includes("from pg_proc")) return "pg_proc";
return "business";
}
function installFakePool(
url: string,
options: {
columns?: ReadonlyArray<{ column_name: string; udt_name: string }> | null;
primaryKeys?: readonly string[] | null;
functions?: ReadonlyArray<Record<string, unknown>> | null;
failBusinessCode?: () => string | null;
} = {},
): QueryCall[] {
const calls: QueryCall[] = [];
const client = {
async query(sql: string, params: readonly unknown[] = []) {
calls.push({ sql, params });
const label = tag(sql);
if (label === "information_schema") {
const rows = options.columns === null
? []
: [...(options.columns ?? [{ column_name: "id", udt_name: "uuid" }])];
return { rows, rowCount: rows.length };
}
if (label === "primary_key") {
const names = options.primaryKeys === null ? [] : [...(options.primaryKeys ?? ["id"])];
const rows = names.map((column_name) => ({ column_name }));
return { rows, rowCount: rows.length };
}
if (label === "pg_proc") {
const rows = options.functions === null
? []
: [...(options.functions ?? [{
proretset: false,
return_type: "text",
argument_names: ["left_name", "right_name"],
argument_types: ["text", "text"],
}])];
return { rows, rowCount: rows.length };
}
if (label === "business") {
const code = options.failBusinessCode?.() ?? null;
if (code) {
const error = new Error("business sql failed") as Error & { code: string };
error.code = code;
throw error;
}
if (sql.toLowerCase().includes(" as value")) return { rows: [{ value: "ok" }], rowCount: 1 };
}
return { rows: [], rowCount: 0 };
},
release() {},
};
const pool = {
async connect() {
return client;
},
async end() {},
};
installLocalPostgresPoolForTests(url, pool as unknown as Pool);
return calls;
}
async function withSwitches(
values: Record<string, string | undefined>,
run: () => Promise<void>,
): Promise<void> {
const previous = new Map<string, string | undefined>();
for (const [key, value] of Object.entries(values)) {
previous.set(key, process.env[key]);
if (value === undefined) delete process.env[key];
else process.env[key] = value;
}
clearLocalPostgresMetadataCacheForTests();
setLocalPostgresMetadataClockForTests(null);
try {
await run();
} finally {
for (const [key, value] of previous) {
if (value === undefined) delete process.env[key];
else process.env[key] = value;
}
clearLocalPostgresMetadataCacheForTests();
setLocalPostgresMetadataClockForTests(null);
}
}
const identity = { id: "018f4e6d-7a11-7000-8000-0000000000aa", email: "person@example.com" };
function clientFor(url: string, role: LocalDatabaseRole | null = "authenticated") {
return createLocalPostgresDataClient(url, role === "service_role" ? null : identity, role);
}
test("metadata cache and merged session setup stay off the baseline query sequence", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "off",
JYOTISHA_PG_MERGED_SESSION_SETUP: "off",
}, async () => {
const url = "postgresql://roundtrip-off@127.0.0.1:1/jyotisha";
const calls = installFakePool(url);
const client = clientFor(url);
const first = await client.from("profiles").select("id").eq("id", identity.id);
assert.equal(first.error, null);
assert.deepEqual(calls.map((call) => tag(call.sql)), [
"begin",
"set local role",
"set_config",
"information_schema",
"business",
"commit",
]);
await client.from("profiles").select("id").eq("id", identity.id);
assert.equal(calls.filter((call) => tag(call.sql) === "information_schema").length, 2);
});
});
test("cached metadata and merged session setup drop a repeat select and rpc to four roundtrips", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "on",
JYOTISHA_PG_MERGED_SESSION_SETUP: "on",
}, async () => {
const url = "postgresql://roundtrip-on@127.0.0.1:1/jyotisha";
const calls = installFakePool(url);
const client = clientFor(url);
await client.from("profiles").select("id").eq("id", identity.id);
calls.length = 0;
await client.from("profiles").select("id").eq("id", identity.id);
assert.deepEqual(calls.map((call) => tag(call.sql)), [
"begin",
"set_config(role)",
"business",
"commit",
]);
await client.rpc("sample_probe", { left_name: "a", right_name: "b" });
calls.length = 0;
const rpc = await client.rpc("sample_probe", { left_name: "a", right_name: "b" });
assert.equal(rpc.error, null);
assert.deepEqual(calls.map((call) => tag(call.sql)), [
"begin",
"set_config(role)",
"business",
"commit",
]);
await client.from("profiles").upsert({ id: identity.id });
calls.length = 0;
await client.from("profiles").upsert({ id: identity.id });
assert.deepEqual(calls.map((call) => tag(call.sql)), [
"begin",
"set_config(role)",
"business",
"commit",
]);
});
});
test("column cache is per role and function cache is per argument names", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "on",
JYOTISHA_PG_MERGED_SESSION_SETUP: "on",
}, async () => {
const url = "postgresql://roundtrip-keys@127.0.0.1:1/jyotisha";
const calls = installFakePool(url);
const authenticated = clientFor(url, "authenticated");
const service = clientFor(url, "service_role");
await authenticated.from("profiles").select("id");
calls.length = 0;
await service.from("profiles").select("id");
assert.equal(calls.filter((call) => tag(call.sql) === "information_schema").length, 1);
await authenticated.rpc("sample_probe", { left_name: "a" });
calls.length = 0;
await authenticated.rpc("sample_probe", { right_name: "b" });
assert.equal(calls.filter((call) => tag(call.sql) === "pg_proc").length, 1);
});
});
test("undefined column, expiry, pool close, and empty metadata all miss the cache", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "on",
JYOTISHA_PG_MERGED_SESSION_SETUP: "off",
}, async () => {
const url = "postgresql://roundtrip-invalidate@127.0.0.1:1/jyotisha";
let failuresLeft = 1;
const calls = installFakePool(url, {
failBusinessCode: () => {
if (failuresLeft <= 0) return null;
failuresLeft -= 1;
return "42703";
},
});
const client = clientFor(url);
const failed = await client.from("profiles").select("id");
assert.equal(failed.error?.code, "42703");
calls.length = 0;
const retried = await client.from("profiles").select("id");
assert.equal(retried.error, null);
assert.equal(calls.filter((call) => tag(call.sql) === "information_schema").length, 1);
});
});
test("metadata older than 300 seconds is queried again", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "on",
JYOTISHA_PG_MERGED_SESSION_SETUP: "off",
}, async () => {
const url = "postgresql://roundtrip-ttl@127.0.0.1:1/jyotisha";
let now = 10_000;
setLocalPostgresMetadataClockForTests(() => now);
const calls = installFakePool(url);
const client = clientFor(url);
await client.from("profiles").select("id");
calls.length = 0;
await client.from("profiles").select("id");
assert.equal(calls.filter((call) => tag(call.sql) === "information_schema").length, 0);
now += 300_000;
calls.length = 0;
await client.from("profiles").select("id");
assert.equal(calls.filter((call) => tag(call.sql) === "information_schema").length, 1);
});
});
test("an empty column list and a missing function are not cached", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "on",
JYOTISHA_PG_MERGED_SESSION_SETUP: "off",
}, async () => {
const url = "postgresql://roundtrip-empty@127.0.0.1:1/jyotisha";
const calls = installFakePool(url, { columns: null, functions: null, primaryKeys: null });
const client = clientFor(url);
await client.from("profiles").select("id");
calls.length = 0;
await client.from("profiles").select("id");
assert.equal(calls.filter((call) => tag(call.sql) === "information_schema").length, 1);
const missing = await client.rpc("missing_probe", { left_name: "a" });
assert.match(missing.error?.message ?? "", /not found/);
calls.length = 0;
await client.rpc("missing_probe", { left_name: "a" });
assert.equal(calls.filter((call) => tag(call.sql) === "pg_proc").length, 1);
});
});
test("closing the pool drops that url from the metadata cache", async () => {
await withSwitches({
JYOTISHA_PG_METADATA_CACHE: "on",
JYOTISHA_PG_MERGED_SESSION_SETUP: "off",
}, async () => {
const url = "postgresql://roundtrip-close@127.0.0.1:1/jyotisha";
const calls = installFakePool(url);
const client = clientFor(url);
await client.from("profiles").select("id");
await closeLocalPostgresDataPool(url);
const reopenedCalls = installFakePool(url);
const reopened = clientFor(url);
await reopened.from("profiles").select("id");
assert.equal(reopenedCalls.filter((call) => tag(call.sql) === "information_schema").length, 1);
assert.ok(calls.length > 0);
});
});
test("gte and lt compile to >= and < with the value bound as a parameter (BUG-1399)", async () => {
await withSwitches({}, async () => {
const url = "postgresql://range-filters@127.0.0.1:1/jyotisha";
const calls = installFakePool(url, {
columns: [
{ column_name: "id", udt_name: "uuid" },
{ column_name: "user_id", udt_name: "uuid" },
{ column_name: "created_at", udt_name: "timestamptz" },
],
});
const client = clientFor(url, "service_role");
const since = "2026-10-21T00:00:00.000Z";
const until = "2026-10-22T00:00:00.000Z";
const result = await client.from("personal_reports").select("created_at")
.eq("user_id", identity.id)
.gte("created_at", since)
.lt("created_at", until);
assert.equal(result.error, null);
const business = calls.filter((call) => tag(call.sql) === "business");
assert.equal(business.length, 1);
const sql = business[0].sql.replace(/\s+/g, " ");
assert.match(sql, /"created_at" >= \$2/);
assert.match(sql, /"created_at" < \$3/);
assert.deepEqual(business[0].params.slice(0, 3).map(String), [identity.id, since, until].map(String));
});
});