Files
git-website-engine-control-…/src/backend/releaseLifecycle.test.ts
T
Labyricorn 72e8b2de91
CI / verify (pull_request) Canceled after 0s
Harden control plane boundaries
2026-07-25 10:13:22 -07:00

162 lines
5.8 KiB
TypeScript

import assert from "node:assert/strict";
import { ChildProcess, spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { BuildEngine } from "./buildEngine";
import {
commit,
createRepository,
loadFixture,
} from "./testing/repositoryFixture";
const waitForExit = (child: ChildProcess): Promise<string> =>
new Promise((resolve, reject) => {
let stdout = "";
let stderr = "";
child.stdout?.on("data", (chunk) => {
stdout += chunk;
});
child.stderr?.on("data", (chunk) => {
stderr += chunk;
});
child.on("error", reject);
child.on("exit", (code) => {
if (code === 0) resolve(stdout.trim());
else reject(new Error(`worker exited ${code}: ${stderr}`));
});
});
const waitFor = async (predicate: () => boolean): Promise<void> => {
const deadline = Date.now() + 10_000;
while (!predicate()) {
if (Date.now() > deadline) throw new Error("Timed out waiting for workers.");
await new Promise((resolve) => setTimeout(resolve, 10));
}
};
test("concurrent writers converge on one verified immutable release", async () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "concurrent-release-"));
const barrier = path.join(output, "barrier");
const worker = path.resolve("src/backend/testing/concurrentBuildWorker.ts");
try {
const children = [1, 2].map(() =>
spawn(process.execPath, ["--import", "tsx", worker, root, output, barrier], {
cwd: process.cwd(),
stdio: ["ignore", "pipe", "pipe"],
}),
);
const completions = children.map(waitForExit);
await waitFor(
() =>
fs
.readdirSync(output)
.filter((entry) => entry.startsWith("barrier.") && entry.endsWith(".ready"))
.length === 2,
);
fs.writeFileSync(`${barrier}.go`, "");
const checksums = await Promise.all(completions);
assert.equal(new Set(checksums).size, 1);
const releases = fs.readdirSync(path.join(output, "releases"));
assert.equal(releases.length, 1);
new BuildEngine(output).verifyRelease(releases[0]);
assert.equal(
fs.readdirSync(output).some((entry) => entry.startsWith(".release-")),
false,
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
test("partial rendering failure leaves no release or temporary directory", () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "partial-release-"));
try {
const template = path.join(
root,
"packages/site-definition/.theme/templates/home.liquid",
);
fs.appendFileSync(template, "\n{{ undefined.value }}\n");
commit(root, "break rendering");
const fixture = loadFixture(root, "partial-failure");
try {
const engine = new BuildEngine(output);
assert.throws(() => engine.build(fixture.input), /undefined|not defined/i);
assert.equal(fs.existsSync(engine.buildDirectory(fixture.input.artifactBuildId)), false);
assert.equal(
fs.readdirSync(output).some((entry) => entry.startsWith(".release-")),
false,
);
} finally {
fixture.manager.dispose();
}
} finally {
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
test("staging and current pointers promote only checksum-verified releases", () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "promotion-release-"));
const first = loadFixture(root, "first");
try {
const engine = new BuildEngine(output);
const firstResult = engine.build(first.input);
engine.stage(first.input.artifactBuildId);
engine.activate(first.input.artifactBuildId);
const css = path.join(
root,
"packages/site-definition/.theme/assets/styles/theme.css",
);
fs.appendFileSync(css, "\n.second-release{display:block}\n");
commit(root, "second release");
const second = loadFixture(root, "second");
try {
const secondResult = engine.build(second.input);
engine.stage(second.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.currentDirectory())), first.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.stagingDirectory())), second.input.artifactBuildId);
engine.activate(second.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.currentDirectory())), second.input.artifactBuildId);
assert.equal(path.basename(fs.realpathSync(engine.stagingDirectory())), second.input.artifactBuildId);
fs.appendFileSync(path.join(firstResult.outputDirectory, "index.html"), "tamper");
assert.throws(() => engine.activate(first.input.artifactBuildId), /E_RELEASE_CHECKSUM/);
assert.equal(path.basename(fs.realpathSync(engine.currentDirectory())), second.input.artifactBuildId);
assert.ok(fs.existsSync(secondResult.outputDirectory));
} finally {
second.manager.dispose();
}
} finally {
first.manager.dispose();
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});
test("repeated identical builds retain identical artifact checksums", () => {
const root = createRepository();
const output = fs.mkdtempSync(path.join(os.tmpdir(), "idempotent-release-"));
const fixture = loadFixture(root, "idempotent");
try {
const engine = new BuildEngine(output);
const checksums = Array.from(
{ length: 20 },
() => engine.build(fixture.input).artifactChecksum,
);
assert.equal(new Set(checksums).size, 1);
} finally {
fixture.manager.dispose();
fs.rmSync(root, { recursive: true, force: true });
fs.rmSync(output, { recursive: true, force: true });
}
});