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 failures, missing assets and drafts without requiring paid signing", 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-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 }); });