paperclip/scripts/service-onboard-smoke.test.mjs

53 lines
2.7 KiB
JavaScript

import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { accessSync, constants, readFileSync } from "node:fs";
import { join } from "node:path";
import test from "node:test";
// Pins the wiring that makes the background-service smoke an effective gate.
// The service leg exists because v2026.824.0 shipped a service install that
// crash-looped on a missing shim while the Docker smoke stayed green; these
// assertions keep the job from being silently disconnected or weakened.
const repoRoot = new URL("..", import.meta.url).pathname.replace(/\/$/, "");
const scriptPath = join(repoRoot, "scripts", "service-onboard-smoke.sh");
const script = readFileSync(scriptPath, "utf8");
const smokeWorkflow = readFileSync(join(repoRoot, ".github", "workflows", "release-smoke.yml"), "utf8");
const releaseWorkflow = readFileSync(join(repoRoot, ".github", "workflows", "release.yml"), "utf8");
test("smoke script is executable and parses", () => {
accessSync(scriptPath, constants.X_OK);
execFileSync("bash", ["-n", scriptPath]);
});
test("smoke script keeps its load-bearing assertions", () => {
assert.match(script, /^set -euo pipefail$/m);
// Onboards the published artifact with the service leg forced on.
assert.match(script, /onboard --yes --install-service/);
// Fails when the shim never materialized.
assert.match(script, /no executable shim at .*after onboarding/);
// Fails when the unit dies instead of serving.
assert.match(script, /entered the failed state/);
// Fails when health answers but the service is not what is serving --
// the exact signature of the v2026.824.0 defect.
assert.match(script, /something other than the service is serving/);
// Refuses to smoke over a real install unless forced.
assert.match(script, /SMOKE_FORCE/);
});
test("release-smoke workflow runs the service leg against the input version", () => {
assert.match(smokeWorkflow, /^ smoke_service:$/m);
assert.match(smokeWorkflow, /scripts\/service-onboard-smoke\.sh/);
const serviceJob = smokeWorkflow.split(/^ smoke:$/m)[0];
assert.match(serviceJob, /PAPERCLIPAI_VERSION: \$\{\{ inputs\.paperclip_version \}\}/);
// Diagnostics must survive the run: cleanup stays off in CI and the
// artifact name cannot collide with the Docker job's upload.
assert.match(serviceJob, /SMOKE_CLEANUP: "false"/);
assert.match(serviceJob, /\$\{\{ inputs\.artifact_name \}\}-service/);
});
test("nightly and beta smokes still route through the reusable workflow", () => {
const calls = releaseWorkflow.match(/uses: \.\/\.github\/workflows\/release-smoke\.yml/g) ?? [];
assert.ok(calls.length >= 2, "smoke_nightly and smoke_beta must call release-smoke.yml so smoke_service gates them");
});