516 lines
21 KiB
JavaScript
516 lines
21 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { promisify } from "node:util";
|
|
|
|
import {
|
|
buildDevRuntimeMigrationReport,
|
|
parseArgs
|
|
} from "./dev-runtime-migration.mjs";
|
|
import {
|
|
CLOUD_CORE_MIGRATION_ID,
|
|
CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION,
|
|
CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS
|
|
} from "../../internal/db/schema.ts";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../..");
|
|
const bunCommand = process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");
|
|
|
|
test("source check is non-secret and does not attempt live DB access", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(parseArgs(["--check"]), {
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:secret-password@db.example.test:5432/hwlab",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
});
|
|
|
|
assert.equal(report.conclusion.status, "ready");
|
|
assert.equal(report.actions.liveDbReadAttempted, false);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.status, "not_checked");
|
|
assert.equal(report.gates.schema.status, "ready");
|
|
assert.equal(report.gates.migration.status, "ready");
|
|
assert.equal(report.gates.readiness.status, "not_checked");
|
|
assert.equal(report.safety.sourceStoresSecretValues, false);
|
|
assert.equal(report.safety.printsSecretValues, false);
|
|
assert.equal(JSON.stringify(report).includes("secret-password"), false);
|
|
assert.equal(JSON.stringify(report).includes("db.example.test"), false);
|
|
});
|
|
|
|
test("cloud-api image migration entrypoint exposes the same non-secret source check", async () => {
|
|
const { stdout } = await execFileAsync(
|
|
bunCommand,
|
|
["cmd/hwlab-cloud-api/migrate.ts", "--check"],
|
|
{
|
|
cwd: repoRoot,
|
|
env: {
|
|
...process.env,
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:secret-password@db.example.test:5432/hwlab?sslmode=require",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
}
|
|
}
|
|
);
|
|
const report = JSON.parse(stdout);
|
|
|
|
assert.equal(report.conclusion.status, "ready");
|
|
assert.equal(report.actions.liveDbReadAttempted, false);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.safety.printsSecretValues, false);
|
|
assert.equal(report.safety.dbUrlValueRedacted, true);
|
|
assert.equal(report.migration.id, CLOUD_CORE_MIGRATION_ID);
|
|
assert.equal(JSON.stringify(report).includes("secret-password"), false);
|
|
assert.equal(JSON.stringify(report).includes("db.example.test"), false);
|
|
});
|
|
|
|
test("cloud-api image migration entrypoint redacts invalid DSN-shaped argv failures", async () => {
|
|
const secretArg = "postgresql://hwlab:secret-password@db.example.test:5432/hwlab?sslmode=require";
|
|
await assert.rejects(
|
|
execFileAsync(bunCommand, ["cmd/hwlab-cloud-api/migrate.ts", secretArg], { cwd: repoRoot }),
|
|
(error) => {
|
|
const report = JSON.parse(error.stdout);
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.error, "unknown argument: [redacted-postgres-url]");
|
|
assert.match(report.traceId, /^[0-9a-f]{16}$/u);
|
|
assert.equal(report.safety.secretValuesPrinted, false);
|
|
assert.equal(JSON.stringify(report).includes("secret-password"), false);
|
|
assert.equal(JSON.stringify(report).includes("db.example.test"), false);
|
|
return true;
|
|
}
|
|
);
|
|
});
|
|
|
|
test("apply mode refuses missing DEV confirmation before touching query client", async () => {
|
|
const queryClient = createFakeQueryClient({ migrationReady: false });
|
|
const report = await buildDevRuntimeMigrationReport(parseArgs(["--apply"]), {
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient,
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
});
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.safetyRefusal, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(queryClient.calls.length, 0);
|
|
});
|
|
|
|
test("live dry-run separates migration ledger blocker from auth and schema", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient: createFakeQueryClient({ migrationReady: false }),
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.liveDbReadAttempted, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.status, "ready");
|
|
assert.equal(report.gates.schema.status, "ready");
|
|
assert.equal(report.gates.migration.status, "blocked");
|
|
assert.equal(report.gates.readiness.status, "not_checked");
|
|
assert.equal(report.blockers[0].scope, "runtime-migration-ledger");
|
|
assert.equal(report.runtime.migration.missing, true);
|
|
});
|
|
|
|
test("live dry-run separates SSL negotiation blocker from auth and schema", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
|
},
|
|
queryClient: {
|
|
async query(sql) {
|
|
assert.match(sql, /information_schema\.columns/u);
|
|
const error = new Error("The server does not support SSL connections");
|
|
error.code = "08P01";
|
|
throw error;
|
|
}
|
|
},
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.liveDbReadAttempted, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.status, "not_checked");
|
|
assert.equal(report.gates.schema.status, "not_checked");
|
|
assert.equal(report.gates.migration.status, "not_checked");
|
|
assert.equal(report.gates.readiness.status, "not_checked");
|
|
assert.equal(report.blockers[0].scope, "runtime-migration-ssl");
|
|
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_ssl_blocked");
|
|
assert.equal(report.runtime.blocker, "runtime_durable_adapter_ssl_blocked");
|
|
assert.equal(report.runtime.connection.queryResult, "ssl_negotiation_blocked");
|
|
assert.equal(JSON.stringify(report).includes("The server does not support SSL connections"), false);
|
|
});
|
|
|
|
test("live dry-run classifies fixture 28P01 as auth before schema or migration", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient: {
|
|
async query(sql) {
|
|
assert.match(sql, /information_schema\.columns/u);
|
|
const error = new Error("fixture auth failure");
|
|
error.code = "28P01";
|
|
throw error;
|
|
}
|
|
},
|
|
now: () => "2026-05-23T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.liveDbReadAttempted, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.checked, true);
|
|
assert.equal(report.gates.auth.status, "blocked");
|
|
assert.equal(report.gates.schema.checked, false);
|
|
assert.equal(report.gates.schema.status, "not_checked");
|
|
assert.equal(report.gates.migration.checked, false);
|
|
assert.equal(report.gates.migration.status, "not_checked");
|
|
assert.equal(report.gates.readiness.checked, false);
|
|
assert.equal(report.gates.readiness.status, "not_checked");
|
|
assert.equal(report.blockers[0].scope, "runtime-migration-auth");
|
|
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_auth_blocked");
|
|
assert.equal(report.runtime.blocker, "runtime_durable_adapter_auth_blocked");
|
|
assert.equal(report.runtime.connection.queryResult, "auth_blocked");
|
|
assert.equal(report.runtime.connection.errorCode, "28P01");
|
|
assert.equal(report.safety.fixtureEvidenceAllowed, false);
|
|
assert.equal(JSON.stringify(report).includes("fixture auth failure"), false);
|
|
});
|
|
|
|
test("live dry-run classifies pg_hba no-encryption rejection as SSL with ssl disabled", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient: {
|
|
async query(sql) {
|
|
assert.match(sql, /information_schema\.columns/u);
|
|
const error = new Error('no pg_hba.conf entry for host "[redacted]", user "[redacted]", database "[redacted]", no encryption');
|
|
error.code = "28000";
|
|
throw error;
|
|
}
|
|
},
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.liveDbReadAttempted, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.status, "not_checked");
|
|
assert.equal(report.gates.schema.status, "not_checked");
|
|
assert.equal(report.gates.migration.status, "not_checked");
|
|
assert.equal(report.gates.readiness.status, "not_checked");
|
|
assert.equal(report.blockers[0].scope, "runtime-migration-ssl");
|
|
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_ssl_blocked");
|
|
assert.equal(report.runtime.blocker, "runtime_durable_adapter_ssl_blocked");
|
|
assert.equal(report.runtime.connection.queryResult, "ssl_negotiation_blocked");
|
|
assert.equal(report.runtime.connection.errorCode, "28000");
|
|
assert.equal(JSON.stringify(report).includes("pg_hba.conf"), false);
|
|
});
|
|
|
|
test("live dry-run classifies missing runtime schema before migration ledger", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient: {
|
|
async query(sql) {
|
|
assert.match(sql, /information_schema\.columns/u);
|
|
return { rows: [] };
|
|
}
|
|
},
|
|
now: () => "2026-05-23T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.liveDbReadAttempted, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.status, "ready");
|
|
assert.equal(report.gates.schema.status, "blocked");
|
|
assert.equal(report.gates.migration.status, "not_checked");
|
|
assert.equal(report.gates.readiness.status, "not_checked");
|
|
assert.equal(report.blockers[0].scope, "runtime-migration-schema");
|
|
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_schema_blocked");
|
|
assert.equal(report.runtime.schema.ready, false);
|
|
assert.ok(report.runtime.schema.missingTables.includes("gateway_sessions"));
|
|
});
|
|
|
|
test("live dry-run classifies durability read query blocker after schema and ledger are ready", async () => {
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient: createFakeQueryClient({ migrationReady: true, countErrorCode: "57014" }),
|
|
now: () => "2026-05-23T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.liveDbReadAttempted, true);
|
|
assert.equal(report.actions.liveDbWriteAttempted, false);
|
|
assert.equal(report.gates.auth.status, "ready");
|
|
assert.equal(report.gates.schema.status, "ready");
|
|
assert.equal(report.gates.migration.status, "ready");
|
|
assert.equal(report.gates.readiness.status, "blocked");
|
|
assert.equal(report.blockers[0].scope, "runtime-migration-readiness");
|
|
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_query_blocked");
|
|
assert.equal(report.blockers[0].evidence.queryResult, "query_blocked");
|
|
assert.equal(report.runtime.migration.ready, true);
|
|
assert.equal(report.runtime.connection.queryResult, "query_blocked");
|
|
});
|
|
|
|
test("apply mode records ledger and verifies durable runtime readiness", async () => {
|
|
const queryClient = createFakeQueryClient({ migrationReady: false });
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--apply", "--confirm-dev", "--confirmed-non-production"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient,
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "ready");
|
|
assert.equal(report.actions.liveDbWriteAttempted, true);
|
|
assert.equal(report.actions.migrationApplied, true);
|
|
assert.equal(report.actions.readinessVerified, true);
|
|
assert.equal(report.gates.auth.status, "ready");
|
|
assert.equal(report.gates.schema.status, "ready");
|
|
assert.equal(report.gates.migration.status, "ready");
|
|
assert.equal(report.gates.readiness.status, "ready");
|
|
assert.equal(report.runtime.migration.appliedMigrationId, CLOUD_CORE_MIGRATION_ID);
|
|
assert.equal(report.runtime.migration.appliedSchemaVersion, CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION);
|
|
assert.ok(queryClient.calls.some((call) => call.sql === "BEGIN"));
|
|
assert.ok(queryClient.calls.some((call) => call.sql.includes("INSERT INTO hwlab_schema_migrations")));
|
|
assert.ok(queryClient.calls.some((call) => call.sql === "COMMIT"));
|
|
assert.equal(JSON.stringify(report).includes("redacted@example.invalid"), false);
|
|
});
|
|
|
|
test("apply mode reports sanitized migration diagnostics", async () => {
|
|
const migrationApplyError = new Error("duplicate key blocked postgresql://hwlab:secret-password@db.example.test:5432/hwlab");
|
|
migrationApplyError.code = "23505";
|
|
migrationApplyError.constraint = "idx_workbench_trace_events_trace_source_event";
|
|
migrationApplyError.table = "workbench_trace_events";
|
|
migrationApplyError.detail = "Key (trace_id, source_event_id)=(trc_1, password=secret) already exists.";
|
|
const queryClient = createFakeQueryClient({ migrationReady: false, migrationApplyError });
|
|
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--apply", "--confirm-dev", "--confirmed-non-production"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
queryClient,
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "blocked");
|
|
assert.equal(report.actions.migrationApplied, false);
|
|
assert.equal(report.blockers[0].evidence.errorCode, "23505");
|
|
assert.equal(report.blockers[0].evidence.errorConstraint, "idx_workbench_trace_events_trace_source_event");
|
|
assert.equal(report.blockers[0].evidence.errorTable, "workbench_trace_events");
|
|
assert.match(report.blockers[0].evidence.errorMessage, /\[redacted-postgres-url\]/u);
|
|
assert.match(report.blockers[0].evidence.errorDetail, /password=\[redacted\]/u);
|
|
assert.equal(JSON.stringify(report.blockers[0].evidence).includes("secret-password"), false);
|
|
assert.ok(queryClient.calls.some((call) => call.sql === "ROLLBACK"));
|
|
});
|
|
|
|
test("live dry-run closes pg pool after readiness verification", async () => {
|
|
const backingClient = createFakeQueryClient({ migrationReady: true });
|
|
const pools = [];
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
pgModuleLoader: async () => createFakePgModule({ backingClient, pools }),
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "ready");
|
|
assert.equal(pools.length, 1);
|
|
assert.ok(pools[0].calls.some((call) => call.target === "pool" && call.sql.includes("information_schema.columns")));
|
|
assert.ok(pools[0].calls.some((call) => call.type === "end"));
|
|
});
|
|
|
|
test("live dry-run normalizes pg pool ssl config before readiness verification", async () => {
|
|
const backingClient = createFakeQueryClient({ migrationReady: true });
|
|
const pools = [];
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:fixture-pass@db.example.test:5432/hwlab?sslmode=require",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable",
|
|
HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS: "1600"
|
|
},
|
|
pgModuleLoader: async () => createFakePgModule({ backingClient, pools }),
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "ready");
|
|
assert.equal(pools.length, 1);
|
|
assert.equal(pools[0].config.ssl, false);
|
|
assert.equal(pools[0].config.connectionTimeoutMillis, 1600);
|
|
assert.equal(new URL(pools[0].config.connectionString).searchParams.get("sslmode"), null);
|
|
assert.equal(JSON.stringify(report).includes("fixture-pass"), false);
|
|
assert.equal(JSON.stringify(report).includes("db.example.test"), false);
|
|
});
|
|
|
|
test("apply mode uses a dedicated pg client transaction", async () => {
|
|
const backingClient = createFakeQueryClient({ migrationReady: false });
|
|
const pools = [];
|
|
const report = await buildDevRuntimeMigrationReport(
|
|
parseArgs(["--apply", "--confirm-dev", "--confirmed-non-production"]),
|
|
{
|
|
env: {
|
|
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
|
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
|
},
|
|
pgModuleLoader: async () => createFakePgModule({ backingClient, pools }),
|
|
now: () => "2026-05-22T00:00:00.000Z"
|
|
}
|
|
);
|
|
|
|
assert.equal(report.conclusion.status, "ready");
|
|
assert.equal(pools.length, 2);
|
|
assert.ok(pools[0].calls.some((call) => call.type === "connect"));
|
|
assert.ok(pools[0].calls.some((call) => call.target === "client" && call.sql === "BEGIN"));
|
|
assert.ok(pools[0].calls.some((call) => call.target === "client" && call.sql.includes("INSERT INTO hwlab_schema_migrations")));
|
|
assert.ok(pools[0].calls.some((call) => call.target === "client" && call.sql === "COMMIT"));
|
|
assert.equal(pools[0].calls.some((call) => call.target === "pool"), false);
|
|
assert.ok(pools[0].calls.some((call) => call.type === "release"));
|
|
assert.ok(pools[0].calls.some((call) => call.type === "end"));
|
|
assert.ok(pools[1].calls.some((call) => call.target === "pool" && call.sql.includes("information_schema.columns")));
|
|
assert.ok(pools[1].calls.some((call) => call.type === "end"));
|
|
});
|
|
|
|
function createFakeQueryClient({ migrationReady, countErrorCode = null, migrationApplyError = null }) {
|
|
const state = {
|
|
migrationReady,
|
|
calls: []
|
|
};
|
|
return {
|
|
get calls() {
|
|
return state.calls;
|
|
},
|
|
async query(sql, params = []) {
|
|
state.calls.push({ sql, params });
|
|
if (sql === "BEGIN" || sql === "COMMIT" || sql === "ROLLBACK") {
|
|
return { rows: [] };
|
|
}
|
|
if (migrationApplyError && sql.includes("CREATE TABLE IF NOT EXISTS projects")) {
|
|
throw migrationApplyError;
|
|
}
|
|
if (sql.includes("INSERT INTO hwlab_schema_migrations")) {
|
|
state.migrationReady = true;
|
|
return { rows: [] };
|
|
}
|
|
if (sql.includes("information_schema.columns")) {
|
|
return { rows: schemaRows() };
|
|
}
|
|
if (sql.startsWith("SELECT id, schema_version FROM hwlab_schema_migrations")) {
|
|
return {
|
|
rows: state.migrationReady
|
|
? [
|
|
{
|
|
id: CLOUD_CORE_MIGRATION_ID,
|
|
schema_version: CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION
|
|
}
|
|
]
|
|
: []
|
|
};
|
|
}
|
|
if (sql.startsWith("SELECT COUNT(*)::int AS count FROM ")) {
|
|
if (countErrorCode) {
|
|
const error = new Error("durability count blocked");
|
|
error.code = countErrorCode;
|
|
throw error;
|
|
}
|
|
return { rows: [{ count: 0 }] };
|
|
}
|
|
if (sql.startsWith("CREATE INDEX IF NOT EXISTS ")) {
|
|
return { rows: [] };
|
|
}
|
|
throw new Error(`unexpected query: ${sql}`);
|
|
}
|
|
};
|
|
}
|
|
|
|
function createFakePgModule({ backingClient, pools }) {
|
|
return {
|
|
Pool: class FakePool {
|
|
constructor(config) {
|
|
this.config = config;
|
|
this.calls = [];
|
|
pools.push(this);
|
|
}
|
|
|
|
async connect() {
|
|
this.calls.push({ type: "connect" });
|
|
return {
|
|
query: async (sql, params = []) => {
|
|
this.calls.push({ target: "client", sql, params });
|
|
return backingClient.query(sql, params);
|
|
},
|
|
release: () => {
|
|
this.calls.push({ type: "release" });
|
|
}
|
|
};
|
|
}
|
|
|
|
async query(sql, params = []) {
|
|
this.calls.push({ target: "pool", sql, params });
|
|
return backingClient.query(sql, params);
|
|
}
|
|
|
|
async end() {
|
|
this.calls.push({ type: "end" });
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function schemaRows() {
|
|
return Object.entries(CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS).flatMap(([table, columns]) =>
|
|
columns.map((column) => ({
|
|
table_name: table,
|
|
column_name: column
|
|
}))
|
|
);
|
|
}
|