test: add isolated production acceptance harness
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createRequire } from "node:module";
|
||||
import { readFile, rm, writeFile } from "node:fs/promises";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const { ProductionAcceptanceHarness } = require("../src/main/production-acceptance-harness.cjs");
|
||||
|
||||
async function fixture(t) {
|
||||
const harness = await ProductionAcceptanceHarness.create();
|
||||
t.after(() => harness.cleanup());
|
||||
return harness;
|
||||
}
|
||||
|
||||
test("production harness proves clean install, portable start and configuration migration", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
assert.equal((await harness.install("0.10.0", "portable")).mode, "portable");
|
||||
const migration = await harness.migrate("1.0.0-rc.1");
|
||||
assert.equal(migration.previousVersion, "0.10.0");
|
||||
assert.equal(JSON.parse(await readFile(harness.paths.config, "utf8")).schemaVersion, 13);
|
||||
assert.equal(JSON.parse(await readFile(migration.backup, "utf8")).schemaVersion, 12);
|
||||
});
|
||||
|
||||
test("authentication fixtures cover token rotation, password, SSH keys and changed host keys", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
assert.equal(harness.rotateToken().tokenVersion, 2);
|
||||
assert.equal(harness.authenticate("password").authenticated, true);
|
||||
assert.equal(harness.authenticate("ssh-key", { hostFingerprint: "SHA256:fixture-host" }).authenticated, true);
|
||||
assert.throws(() => harness.authenticate("ssh-key", { hostFingerprint: "SHA256:changed" }), /fingerprint changed/);
|
||||
});
|
||||
|
||||
test("new repositories deploy by server pull and Direct Copy at the exact Gitea SHA", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
await harness.install();
|
||||
harness.authenticate("ssh-key", { hostFingerprint: harness.state.hostFingerprint });
|
||||
const serverSha = await harness.createCommit();
|
||||
assert.equal((await harness.deploy(harness.plan(serverSha, "server-git"))).status, "success");
|
||||
const copySha = await harness.createCommit("fix: direct copy fixture");
|
||||
assert.equal((await harness.deploy(harness.plan(copySha, "push-bundle"))).status, "success");
|
||||
assert.equal(harness.state.liveSha, copySha);
|
||||
});
|
||||
|
||||
test("existing deployments are adopted, externally updated and reconciled without replacement", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
assert.deepEqual(harness.adoptExisting(), { linked: true, liveSha: harness.initialSha, preserved: true });
|
||||
const sha = await harness.createCommit();
|
||||
assert.equal(harness.externalUpdate(sha).liveSha, sha);
|
||||
assert.equal(harness.state.healthy, true);
|
||||
});
|
||||
|
||||
test("deploy key rotation, revocation, restore and writable-key rejection fail closed", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
harness.authenticate("password");
|
||||
assert.equal(harness.rotateDeployKey().rotated, true);
|
||||
assert.equal(harness.revokeDeployKey().deploymentBlocked, true);
|
||||
assert.equal(harness.restoreDeployKey().restored, true);
|
||||
harness.setKeyAccess(false);
|
||||
assert.throws(() => harness.rotateDeployKey(), /writable/);
|
||||
await assert.rejects(() => harness.deploy(harness.plan(harness.initialSha)), /Writable deploy key/);
|
||||
});
|
||||
|
||||
test("unhealthy activation rolls back only to the exact recorded previous SHA", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
harness.authenticate("password");
|
||||
harness.adoptExisting();
|
||||
const sha = await harness.createCommit();
|
||||
assert.equal((await harness.deploy(harness.plan(sha), "unhealthy")).status, "failed");
|
||||
assert.throws(() => harness.rollback("0".repeat(40)), /exact recorded/);
|
||||
const rollback = harness.rollback(harness.initialSha);
|
||||
assert.equal(rollback.status, "rolled-back");
|
||||
assert.equal(rollback.liveSha, harness.initialSha);
|
||||
});
|
||||
|
||||
test("network failures distinguish fetch from partial activation and preserve recovery", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
harness.authenticate("password");
|
||||
const sha = await harness.createCommit();
|
||||
let outcome = await harness.deploy(harness.plan(sha), "fetch-network");
|
||||
assert.deepEqual(outcome.failure, { message: "Network interrupted during fetch", partial: false });
|
||||
outcome = await harness.deploy(harness.plan(sha), "activation-network");
|
||||
assert.equal(outcome.failure.partial, true);
|
||||
assert.ok(harness.state.recovery);
|
||||
});
|
||||
|
||||
test("application shutdown is recoverable and stale plans cannot mutate state", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
harness.authenticate("password");
|
||||
const plan = harness.plan(harness.initialSha);
|
||||
harness.state.liveSha = "1".repeat(40);
|
||||
await assert.rejects(() => harness.deploy(plan), /Stale reconciliation plan/);
|
||||
const current = harness.plan(harness.initialSha);
|
||||
assert.equal((await harness.deploy(current, "shutdown")).status, "interrupted");
|
||||
assert.equal(harness.recover().status, "failed");
|
||||
});
|
||||
|
||||
test("corrupt configuration is recoverable from the migration backup", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
await harness.install();
|
||||
await harness.migrate();
|
||||
const backup = `${harness.paths.config}.backup`;
|
||||
await writeFile(harness.paths.config, "{broken", "utf8");
|
||||
await assert.rejects(() => readFile(harness.paths.config, "utf8").then(JSON.parse));
|
||||
await writeFile(harness.paths.config, await readFile(backup));
|
||||
assert.equal(JSON.parse(await readFile(harness.paths.config, "utf8")).version, "0.10.0");
|
||||
});
|
||||
|
||||
test("release verification rejects checksum, signer, timestamp, missing asset and drafts", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
await harness.publishRelease("1.0.0-ok");
|
||||
assert.equal((await harness.verifyRelease("1.0.0-ok")).verified, true);
|
||||
for (const [version, options, error] of [
|
||||
["1.0.0-checksum", { badChecksum: true }, /checksum/],
|
||||
["1.0.0-signer", { signer: "CN=Wrong" }, /signer/],
|
||||
["1.0.0-time", { timestamped: false }, /timestamp/],
|
||||
["1.0.0-missing", { missingAsset: true }, /asset is missing/],
|
||||
["1.0.0-draft", { draft: true }, /draft release/],
|
||||
]) {
|
||||
await harness.publishRelease(version, options);
|
||||
await assert.rejects(() => harness.verifyRelease(version), error);
|
||||
}
|
||||
});
|
||||
|
||||
test("large and partial inventory fixtures expose duplicates without touching production data", async (t) => {
|
||||
const harness = await fixture(t);
|
||||
const inventory = harness.inventory(24, true);
|
||||
assert.equal(inventory.workloads.length, 24);
|
||||
assert.equal(inventory.workloads.filter((item) => item.classification === "duplicate").length, 1);
|
||||
assert.equal(inventory.partial, true);
|
||||
assert.match(inventory.warnings[0], /unavailable/);
|
||||
await rm(harness.paths.server, { recursive: true, force: true });
|
||||
});
|
||||
Reference in New Issue
Block a user