feat: harden authenticated deployment
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const root = path.resolve(import.meta.dirname, "..");
|
||||
|
||||
async function dockerAvailable() {
|
||||
try {
|
||||
await execFileAsync("docker", ["info"], { timeout: 15_000 });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
test("Docker image starts against PostgreSQL and applies migrations", async (t) => {
|
||||
if (!(await dockerAvailable())) {
|
||||
t.skip("Docker daemon is unavailable");
|
||||
return;
|
||||
}
|
||||
const temp = await mkdtemp(path.join(os.tmpdir(), "roast-planner-container-"));
|
||||
const agentDir = path.join(temp, "pi-agent");
|
||||
await mkdir(agentDir);
|
||||
const envFile = path.join(temp, "compose.env");
|
||||
await writeFile(
|
||||
envFile,
|
||||
`POSTGRES_PASSWORD=container-test-password\nBOOTSTRAP_SETUP_TOKEN=\nPI_AGENT_CONFIG_DIR=${agentDir}\n`,
|
||||
);
|
||||
const project = `roastplanner${Date.now()}`;
|
||||
const compose = (args, options = {}) =>
|
||||
execFileAsync(
|
||||
"docker",
|
||||
["compose", "--project-name", project, "--env-file", envFile, ...args],
|
||||
{ cwd: root, timeout: 120_000, ...options },
|
||||
);
|
||||
t.after(async () => {
|
||||
try {
|
||||
await compose(["down", "--volumes", "--remove-orphans"]);
|
||||
} catch {
|
||||
// Preserve the startup failure rather than masking it with cleanup.
|
||||
}
|
||||
});
|
||||
await compose(["up", "--build", "--detach"]);
|
||||
|
||||
let lastError;
|
||||
for (let attempt = 0; attempt < 30; attempt++) {
|
||||
try {
|
||||
await compose([
|
||||
"exec",
|
||||
"-T",
|
||||
"app",
|
||||
"node",
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
"const response = await fetch('http://127.0.0.1:8090/'); process.exit(response.ok ? 0 : 1)",
|
||||
]);
|
||||
lastError = null;
|
||||
break;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
||||
}
|
||||
}
|
||||
assert.equal(lastError, null, "application never became ready in its container");
|
||||
const migration = await compose([
|
||||
"exec",
|
||||
"-T",
|
||||
"db",
|
||||
"psql",
|
||||
"-U",
|
||||
"roast",
|
||||
"-d",
|
||||
"roast",
|
||||
"-tAc",
|
||||
"SELECT count(*) FROM schema_migrations WHERE filename = '001_auth.sql'",
|
||||
]);
|
||||
assert.equal(migration.stdout.trim(), "1");
|
||||
});
|
||||
Reference in New Issue
Block a user