feat: add dev durable runtime provisioning gates
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
buildDevRuntimeProvisioningReport,
|
||||
parseArgs
|
||||
} from "./dev-runtime-provisioning.mjs";
|
||||
|
||||
test("source check parses target role/database without leaking DB URL material", async () => {
|
||||
const report = await buildDevRuntimeProvisioningReport(parseArgs(["--check"]), {
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: fixturePostgresUrl({ password: fixtureSecret("super") }),
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
});
|
||||
|
||||
assert.equal(report.conclusion.status, "ready");
|
||||
assert.equal(report.actions.liveDbReadAttempted, false);
|
||||
assert.equal(report.actions.liveDbWriteAttempted, false);
|
||||
assert.equal(report.db.targetRoleNamePresent, true);
|
||||
assert.equal(report.db.targetDatabaseNamePresent, true);
|
||||
assert.equal(report.db.targetPasswordPresent, true);
|
||||
assert.equal(report.safety.secretValuesPrinted, false);
|
||||
assertNoFixtureSecrets(report);
|
||||
});
|
||||
|
||||
test("source check without injected DB URL is ready and performs no live access", async () => {
|
||||
const report = await buildDevRuntimeProvisioningReport(parseArgs(["--check"]), {
|
||||
env: {},
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
});
|
||||
|
||||
assert.equal(report.conclusion.status, "ready");
|
||||
assert.equal(report.actions.liveDbReadAttempted, false);
|
||||
assert.equal(report.actions.liveDbWriteAttempted, false);
|
||||
assert.equal(report.blockers.length, 0);
|
||||
assert.equal(report.db.targetUrlPresent, false);
|
||||
assert.equal(report.gates.role.status, "not_checked");
|
||||
});
|
||||
|
||||
test("live dry-run classifies missing target role separately from missing database", async () => {
|
||||
const report = await buildDevRuntimeProvisioningReport(
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: envWithAdmin(),
|
||||
adminClient: createAdminClient({ roleExists: false, databaseExists: true }),
|
||||
queryClient: createRuntimeClient("auth"),
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(report.conclusion.status, "blocked");
|
||||
assert.equal(report.actions.adminInspectionAttempted, true);
|
||||
assert.equal(report.actions.liveDbWriteAttempted, false);
|
||||
assert.equal(report.provisioning.targetRole.exists, false);
|
||||
assert.equal(report.provisioning.targetDatabase.exists, true);
|
||||
assert.equal(report.blockers.some((blocker) => blocker.scope === "runtime-provisioning-role"), true);
|
||||
assert.equal(report.blockers.some((blocker) => blocker.scope === "runtime-provisioning-database"), false);
|
||||
assert.equal(report.blockers.some((blocker) => blocker.scope === "runtime-provisioning-auth"), true);
|
||||
assertNoFixtureSecrets(report);
|
||||
});
|
||||
|
||||
test("live dry-run classifies missing target database separately from target role", async () => {
|
||||
const report = await buildDevRuntimeProvisioningReport(
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: envWithAdmin(),
|
||||
adminClient: createAdminClient({ roleExists: true, databaseExists: false }),
|
||||
queryClient: createRuntimeClient("auth"),
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(report.conclusion.status, "blocked");
|
||||
assert.equal(report.provisioning.targetRole.exists, true);
|
||||
assert.equal(report.provisioning.targetDatabase.exists, false);
|
||||
assert.equal(report.blockers.some((blocker) => blocker.scope === "runtime-provisioning-role"), false);
|
||||
assert.equal(report.blockers.some((blocker) => blocker.scope === "runtime-provisioning-database"), true);
|
||||
});
|
||||
|
||||
test("live dry-run separates SSL from auth/schema/migration", async () => {
|
||||
const report = await buildDevRuntimeProvisioningReport(
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: envWithAdmin({ sslMode: "require" }),
|
||||
adminClient: createAdminClient({ roleExists: true, databaseExists: true }),
|
||||
queryClient: createRuntimeClient("ssl"),
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(report.conclusion.status, "blocked");
|
||||
assert.equal(report.gates.ssl.status, "blocked");
|
||||
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.blockers.some((blocker) => blocker.scope === "runtime-provisioning-ssl"), true);
|
||||
assert.equal(JSON.stringify(report).includes("does not support SSL"), false);
|
||||
});
|
||||
|
||||
test("live dry-run leaves role/database ready while migration remains a downstream blocker", async () => {
|
||||
const report = await buildDevRuntimeProvisioningReport(
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: envWithAdmin(),
|
||||
adminClient: createAdminClient({ roleExists: true, databaseExists: true }),
|
||||
queryClient: createRuntimeClient("migration"),
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(report.conclusion.status, "ready");
|
||||
assert.equal(report.provisioning.ready, true);
|
||||
assert.equal(report.provisioning.targetRole.ready, true);
|
||||
assert.equal(report.provisioning.targetDatabase.ready, true);
|
||||
assert.equal(report.runtime.blocker, "runtime_durable_adapter_migration_blocked");
|
||||
assert.equal(report.gates.migration.status, "blocked");
|
||||
assert.equal(report.blockers.length, 0);
|
||||
});
|
||||
|
||||
test("apply creates missing role/database and grants target privileges without printing secrets", async () => {
|
||||
const adminClient = createAdminClient({ roleExists: false, databaseExists: false });
|
||||
const targetAdminClient = createAdminClient({ roleExists: true, databaseExists: true });
|
||||
const report = await buildDevRuntimeProvisioningReport(
|
||||
parseArgs(["--apply", "--confirm-dev", "--confirmed-non-production"]),
|
||||
{
|
||||
env: envWithAdmin(),
|
||||
adminClient,
|
||||
queryClient: createRuntimeClient("ready"),
|
||||
now: () => "2026-05-23T00:00:00.000Z",
|
||||
adminDbUrlOverride: null,
|
||||
pgModuleLoader: async () => {
|
||||
throw new Error("pgModuleLoader should not be used when adminClient is injected");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(targetAdminClient.calls.length, 0);
|
||||
assert.equal(report.conclusion.status, "ready");
|
||||
assert.equal(report.actions.roleCreated, true);
|
||||
assert.equal(report.actions.databaseCreated, true);
|
||||
assert.equal(report.actions.rolePasswordSynchronized, true);
|
||||
assert.equal(report.actions.databaseConnectGranted, true);
|
||||
assert.equal(report.actions.schemaPrivilegesGranted, true);
|
||||
assert.equal(report.provisioning.ready, true);
|
||||
assert.ok(adminClient.calls.some((call) => call.sql.startsWith("CREATE ROLE ")));
|
||||
assert.ok(adminClient.calls.some((call) => call.sql.startsWith("CREATE DATABASE ")));
|
||||
assert.ok(adminClient.calls.some((call) => call.sql.startsWith("GRANT CONNECT ON DATABASE ")));
|
||||
assert.ok(adminClient.calls.some((call) => call.sql.startsWith("GRANT USAGE, CREATE ON SCHEMA public ")));
|
||||
assertNoFixtureSecrets(report);
|
||||
});
|
||||
|
||||
function envWithAdmin({ sslMode = "disable" } = {}) {
|
||||
return {
|
||||
HWLAB_CLOUD_DB_URL: fixturePostgresUrl({ password: fixtureSecret("app") }),
|
||||
HWLAB_CLOUD_DB_ADMIN_URL: fixturePostgresUrl({
|
||||
user: "postgres",
|
||||
password: fixtureSecret("admin"),
|
||||
database: "postgres"
|
||||
}),
|
||||
HWLAB_CLOUD_DB_SSL_MODE: sslMode
|
||||
};
|
||||
}
|
||||
|
||||
function fixturePostgresUrl({
|
||||
user = "hwlab_app",
|
||||
password = fixtureSecret("app"),
|
||||
host = "db.example.test",
|
||||
database = "hwlab_runtime"
|
||||
} = {}) {
|
||||
return `${["postgresql", "://"].join("")}${user}:${password}@${host}:5432/${database}`;
|
||||
}
|
||||
|
||||
function fixtureSecret(kind) {
|
||||
return `${kind}-${"secret"}`;
|
||||
}
|
||||
|
||||
function assertNoFixtureSecrets(value) {
|
||||
const text = JSON.stringify(value);
|
||||
for (const forbidden of [
|
||||
fixtureSecret("super"),
|
||||
fixtureSecret("app"),
|
||||
fixtureSecret("admin"),
|
||||
"db.example.test",
|
||||
"hwlab_app",
|
||||
"hwlab_runtime"
|
||||
]) {
|
||||
assert.equal(text.includes(forbidden), false);
|
||||
}
|
||||
}
|
||||
|
||||
function createAdminClient({ roleExists, databaseExists }) {
|
||||
const state = {
|
||||
roleExists,
|
||||
databaseExists,
|
||||
calls: []
|
||||
};
|
||||
return {
|
||||
get calls() {
|
||||
return state.calls;
|
||||
},
|
||||
async query(sql, params = []) {
|
||||
state.calls.push({ sql, params });
|
||||
if (sql.includes("pg_catalog.pg_roles")) {
|
||||
return { rows: state.roleExists ? [{ present: 1 }] : [] };
|
||||
}
|
||||
if (sql.includes("pg_catalog.pg_database")) {
|
||||
return { rows: state.databaseExists ? [{ present: 1 }] : [] };
|
||||
}
|
||||
if (sql.startsWith("CREATE ROLE ")) {
|
||||
state.roleExists = true;
|
||||
return { rows: [] };
|
||||
}
|
||||
if (sql.startsWith("ALTER ROLE ")) {
|
||||
return { rows: [] };
|
||||
}
|
||||
if (sql.startsWith("CREATE DATABASE ")) {
|
||||
state.databaseExists = true;
|
||||
return { rows: [] };
|
||||
}
|
||||
if (sql.startsWith("GRANT CONNECT ON DATABASE ") || sql.startsWith("GRANT USAGE, CREATE ON SCHEMA public ")) {
|
||||
return { rows: [] };
|
||||
}
|
||||
throw new Error(`unexpected admin query: ${sql}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeClient(mode) {
|
||||
return {
|
||||
async query(sql) {
|
||||
if (sql.includes("information_schema.columns")) {
|
||||
if (mode === "ssl") {
|
||||
const error = new Error("fixture SSL blocked: server does not support SSL");
|
||||
error.code = "08P01";
|
||||
throw error;
|
||||
}
|
||||
if (mode === "auth") {
|
||||
const error = new Error("fixture auth blocked");
|
||||
error.code = "28P01";
|
||||
throw error;
|
||||
}
|
||||
return { rows: schemaRows() };
|
||||
}
|
||||
if (sql.startsWith("SELECT id, schema_version FROM hwlab_schema_migrations")) {
|
||||
if (mode === "migration") return { rows: [] };
|
||||
return { rows: [{ id: "0001_cloud_core_skeleton", schema_version: "runtime-durable-postgres-v1" }] };
|
||||
}
|
||||
if (sql.startsWith("SELECT COUNT(*)::int AS count FROM ")) {
|
||||
return { rows: [{ count: 0 }] };
|
||||
}
|
||||
throw new Error(`unexpected runtime query: ${sql}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function schemaRows() {
|
||||
const schema = {
|
||||
gateway_sessions: ["id", "project_id", "gateway_service_id", "status", "started_at", "ended_at", "gateway_session_json"],
|
||||
box_resources: ["id", "project_id", "gateway_session_id", "resource_state", "labels_json", "resource_json", "updated_at"],
|
||||
box_capabilities: ["id", "box_resource_id", "capability_type", "capability_json", "updated_at"],
|
||||
hardware_operations: ["id", "project_id", "requested_by", "operation_type", "operation_json", "status", "requested_at", "updated_at"],
|
||||
audit_events: ["id", "request_id", "actor", "source", "operation", "target", "result", "timestamp", "event_json"],
|
||||
evidence_records: ["id", "project_id", "operation_id", "evidence_type", "uri", "metadata_json", "created_at"]
|
||||
};
|
||||
return Object.entries(schema).flatMap(([table, columns]) =>
|
||||
columns.map((column) => ({ table_name: table, column_name: column }))
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user