fix(test): close v9 db fixture pools per URL and seed via admin role
This commit is contained in:
@@ -74,6 +74,14 @@ function localDataPool(connectionString: string): Pool {
|
||||
return pool;
|
||||
}
|
||||
|
||||
export async function closeLocalPostgresDataPool(connectionString: string): Promise<void> {
|
||||
const pools = poolGlobal.jyotishaLocalDataPools;
|
||||
const pool = pools?.get(connectionString);
|
||||
if (!pool) return;
|
||||
pools?.delete(connectionString);
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
export async function closeLocalPostgresDataPools(): Promise<void> {
|
||||
const pools = poolGlobal.jyotishaLocalDataPools;
|
||||
poolGlobal.jyotishaLocalDataPools = new Map();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
closeLocalPostgresDataPool,
|
||||
closeLocalPostgresDataPools,
|
||||
createLocalPostgresDataClient,
|
||||
} from "../src/lib/db/local-postgres-client-core.ts";
|
||||
|
||||
/**
|
||||
* Non-Docker unit tests for the per-key pool close helper. pg Pool is lazy:
|
||||
* constructing a client only registers the pool in the per-URL cache and
|
||||
* never opens a socket, so these tests run without PostgreSQL and never
|
||||
* produce 57P01 teardown noise.
|
||||
*/
|
||||
test("closeLocalPostgresDataPool is a safe no-op for an unknown key", async () => {
|
||||
await assert.doesNotReject(
|
||||
closeLocalPostgresDataPool("postgresql://never-registered@127.0.0.1:1/nope"),
|
||||
);
|
||||
});
|
||||
|
||||
test("closeLocalPostgresDataPool closes only the requested per-key pool", async () => {
|
||||
const urlA = "postgresql://unit-a@127.0.0.1:1/jyotisha-unit-a";
|
||||
const urlB = "postgresql://unit-b@127.0.0.1:1/jyotisha-unit-b";
|
||||
createLocalPostgresDataClient(urlA, null, "service_role");
|
||||
createLocalPostgresDataClient(urlB, null, "service_role");
|
||||
|
||||
// Closing one key must never reject or affect the other key's close.
|
||||
await assert.doesNotReject(closeLocalPostgresDataPool(urlA));
|
||||
await assert.doesNotReject(closeLocalPostgresDataPool(urlB));
|
||||
|
||||
// Re-closing the same key is idempotent.
|
||||
await assert.doesNotReject(closeLocalPostgresDataPool(urlA));
|
||||
});
|
||||
|
||||
test("the per-key cache accepts a fresh pool for a closed key", async () => {
|
||||
const url = "postgresql://unit-c@127.0.0.1:1/jyotisha-unit-c";
|
||||
createLocalPostgresDataClient(url, null, "service_role");
|
||||
await closeLocalPostgresDataPool(url);
|
||||
// A later client for the same URL registers a new pool (the close deleted
|
||||
// the entry before ending the old pool); close again must resolve.
|
||||
createLocalPostgresDataClient(url, null, "service_role");
|
||||
await assert.doesNotReject(closeLocalPostgresDataPool(url));
|
||||
});
|
||||
|
||||
test("per-key close after a global close is a safe no-op", async () => {
|
||||
const url = "postgresql://unit-d@127.0.0.1:1/jyotisha-unit-d";
|
||||
createLocalPostgresDataClient(url, null, "service_role");
|
||||
await closeLocalPostgresDataPools();
|
||||
await assert.doesNotReject(closeLocalPostgresDataPool(url));
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
import { createLocalPostgresDataClient } from "../src/lib/db/local-postgres-client-core.ts";
|
||||
import { closeLocalPostgresDataPool, createLocalPostgresDataClient } from "../src/lib/db/local-postgres-client-core.ts";
|
||||
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
|
||||
|
||||
const runnerPath = fileURLToPath(
|
||||
@@ -222,7 +222,17 @@ test("v9 open is atomic, idempotent and resumes instead of duplicating", { skip:
|
||||
assert.equal((sessionOpen.data as Record<string, unknown>).case_id, caseId);
|
||||
assert.equal((sessionOpen.data as Record<string, unknown>).session_id, sessionId);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
// The service client registered a per-URL pool in the global cache; close
|
||||
// only this test's own service URL before destroying PostgreSQL, otherwise
|
||||
// the async pool teardown surfaces 57P01 after the fixture is gone. The
|
||||
// fixture must still stop even if pool close fails.
|
||||
try {
|
||||
await closeLocalPostgresDataPool(
|
||||
fixture.connectionUrl("service_runtime", "service-runtime-test-password"),
|
||||
);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -374,7 +384,13 @@ test("v9 enforces profile gating, ownership and terminal read-only", { skip: ski
|
||||
});
|
||||
assert.match(rpcError(proposal.error), /agentic_rectification_case_terminal/);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
try {
|
||||
await closeLocalPostgresDataPool(
|
||||
fixture.connectionUrl("service_runtime", "service-runtime-test-password"),
|
||||
);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -542,7 +558,13 @@ test("v9 evidence lifecycle: quote grounding, idempotency, confirm and revision
|
||||
"2",
|
||||
);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
try {
|
||||
await closeLocalPostgresDataPool(
|
||||
fixture.connectionUrl("service_runtime", "service-runtime-test-password"),
|
||||
);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -756,7 +778,13 @@ test("v9 legacy backfill maps statuses, keeps one resumable per user and is idem
|
||||
assert.equal(again.error, null, rpcError(again.error));
|
||||
assert.equal((again.data as Record<string, unknown>).cases_created, 0);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
try {
|
||||
await closeLocalPostgresDataPool(
|
||||
fixture.connectionUrl("service_runtime", "service-runtime-test-password"),
|
||||
);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -785,18 +813,27 @@ test("v9 agent api migration applies, seeds the runtime flag and guards consent"
|
||||
);
|
||||
|
||||
// Set up a profile + case + pending turn, then exercise the turn
|
||||
// finalize and run-phase receipt RPCs.
|
||||
// finalize and run-phase receipt RPCs. The identity row is seeded through
|
||||
// the legitimate identity_runtime role (syncs to auth.users, satisfying
|
||||
// the profiles FK); the public.* fixture rows are seeded through the
|
||||
// postgres admin connection because identity_runtime has no grants on
|
||||
// these tables (production least privilege). Tests must never widen
|
||||
// runtime grants or change migrations.
|
||||
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
|
||||
insert into identity.users (id, name, email, email_verified, email_verified_at)
|
||||
values ('66666666-6666-4666-8666-666666666666', 'V9 Agent API Fixture', 'v9-agent-api-fixture@example.com', true, now())
|
||||
`);
|
||||
fixture.psql(`
|
||||
insert into public.profiles (id, birth_date, reported_birth_time, active_birth_time, birth_time_source,
|
||||
uncertainty_before_minutes, uncertainty_after_minutes, latitude, longitude, timezone_offset)
|
||||
values ('66666666-6666-4666-8666-666666666666', '1997-08-08', '05:00', null, 'family_exact',
|
||||
10, 10, 36.420487, 114.209936, 8);
|
||||
`);
|
||||
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
|
||||
fixture.psql(`
|
||||
insert into public.chat_sessions (id, user_id, title, theme, session_type, messages)
|
||||
values ('22222222-2222-4222-8222-222222222222', '66666666-6666-4666-8666-666666666666', '生时校正', 'general', 'birth_time_rectification', '[]'::jsonb);
|
||||
`);
|
||||
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
|
||||
fixture.psql(`
|
||||
insert into public.agentic_rectification_cases (
|
||||
id, user_id, session_id, status, skill_name, skill_version,
|
||||
baseline_profile_fingerprint, baseline_birth_snapshot, candidate_range
|
||||
@@ -808,7 +845,7 @@ test("v9 agent api migration applies, seeds the runtime flag and guards consent"
|
||||
'{"start_time":"04:50","end_time":"05:10"}'::jsonb
|
||||
);
|
||||
`);
|
||||
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
|
||||
fixture.psql(`
|
||||
insert into public.agentic_rectification_turns (id, case_id, user_message, status, model_name)
|
||||
values ('33333333-3333-4333-8333-333333333333', '11111111-1111-4111-8111-111111111111',
|
||||
'2016年9月离开家去北京开始工作', 'pending', 'gpt-4o-mini');
|
||||
@@ -861,6 +898,12 @@ test("v9 agent api migration applies, seeds the runtime flag and guards consent"
|
||||
const consentRow = consent.data as { error?: unknown };
|
||||
assert.equal(consentRow.error, "agentic_rectification_candidate_not_found");
|
||||
} finally {
|
||||
fixture.stop();
|
||||
try {
|
||||
await closeLocalPostgresDataPool(
|
||||
fixture.connectionUrl("service_runtime", "service-runtime-test-password"),
|
||||
);
|
||||
} finally {
|
||||
fixture.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user