Files

207 lines
8.4 KiB
TypeScript

/**
* Fleet regression: run the detector over snapshots of REAL repos and assert
* the exact config it produces.
*
* `trees.json` is generated by `bin/snapshot-trees.ts` from real repos.
* `cluster-index.json` is a small synthetic index in the shape
* `bin/cluster-index.ts` emits: just the workloads these trees join against,
* plus non-production and plain-Deployment decoys the join must rank below or
* refuse. The test touches no disk outside this package and no network.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { memTree } from "../src/tree.ts";
import { detect, withCluster } from "../src/detect.ts";
import { stacksPresent } from "../src/stack.ts";
import type { ClusterIndex } from "../src/deploy.ts";
import type { DetectResult, PlatformConfig } from "../src/types.ts";
const here = new URL(".", import.meta.url).pathname;
const trees = JSON.parse(readFileSync(`${here}fixtures/trees.json`, "utf8")) as Record<
string,
{ org: string; repo: string; files: Record<string, string> }
>;
const index = JSON.parse(readFileSync(`${here}fixtures/cluster-index.json`, "utf8")) as ClusterIndex;
/** Full two-pass detection for a fixture repo. */
function run(key: string): DetectResult {
const fx = trees[key];
assert.ok(fx, `fixture ${key} missing — regenerate test/fixtures/trees.json`);
const ctx = { org: fx.org, repo: fx.repo };
return withCluster(detect(memTree(fx.files), ctx), index, ctx);
}
/** `[name, image, dockerfile, context]` per build, in order. */
function shape(c: PlatformConfig): string[][] {
return c.builds.map((b) => [b.name, b.image, b.dockerfile, b.context]);
}
// --- repos whose whole hanzo.yml is redundant --------------------------------
test("hanzoai/tasks — one image, no deploy: exactly its hanzo.yml", () => {
const r = run("hanzoai/tasks");
assert.deepEqual(shape(r.config), [["tasks", "ghcr.io/hanzoai/tasks", "Dockerfile", "."]]);
assert.equal(r.stack, "go");
});
test("hanzoai/world — polyglot repo, one image, deploy joined from the App CR", () => {
const r = run("hanzoai/world");
assert.deepEqual(shape(r.config), [["world", "ghcr.io/hanzoai/world", "Dockerfile", "."]]);
// This repo is genuinely BOTH: a Vite SPA and a Go server in one image
// (`FROM node` builds the SPA, `FROM golang` builds cmd/world, alpine runs
// it). Manifest order alone would call it Go; the Dockerfile's first stage
// is what the rule reads, so it reports node — and both stacks are present.
assert.equal(r.stack, "node");
assert.deepEqual(stacksPresent(memTree(trees["hanzoai/world"]?.files ?? {})), ["go", "node"]);
assert.deepEqual(r.config.deploy?.target, {
cluster: "cluster-a", namespace: "hanzo",
operator: "hanzo-operator", crd: "App", name: "world",
});
});
test("hanzoai/git — image `git`, deployed as the CR named hanzo-git", () => {
const r = run("hanzoai/git");
assert.deepEqual(shape(r.config), [["git", "ghcr.io/hanzoai/git", "Dockerfile", "."]]);
assert.equal(r.config.deploy?.target.name, "hanzo-git");
assert.equal(r.config.deploy?.target.namespace, "hanzo");
});
test("hanzoai/studio — Python service", () => {
const r = run("hanzoai/studio");
assert.deepEqual(shape(r.config), [["studio", "ghcr.io/hanzoai/studio", "Dockerfile", "."]]);
assert.equal(r.stack, "python");
assert.equal(r.config.deploy?.target.name, "studio");
});
test("luxfi/node — the luxfi registry, and no deploy (the lux operator owns it)", () => {
const r = run("luxfi/node");
assert.deepEqual(shape(r.config), [["node", "ghcr.io/luxfi/node", "Dockerfile", "."]]);
// Six other Dockerfiles in this repo (bootnode, custom, simple, …) are held back.
assert.equal(r.config.builds.length, 1);
assert.equal(r.config.deploy, undefined, "luxd rollout is the lux operator's, not the hanzo one's");
});
test("hanzoai/app — image name the repo name alone cannot give", () => {
const r = run("hanzoai/app");
assert.deepEqual(shape(r.config), [
["hanzo-app", "ghcr.io/hanzoai/hanzo-app", "Dockerfile.production", "."],
]);
assert.equal(r.config.deploy?.target.name, "hanzo-app");
// The rename is recorded, not silent.
assert.ok(
r.provenance.some((p) => p.source === "cluster" && /hanzo-app/.test(p.because)),
"the rename must be explained in provenance",
);
});
// --- multi-image repos -------------------------------------------------------
test("hanzoai/commerce — 3 of its 4 declared images, from 15 Dockerfiles", () => {
const r = run("hanzoai/commerce");
assert.deepEqual(shape(r.config), [
["commerce", "ghcr.io/hanzoai/commerce", "Dockerfile.production", "."],
["commerce-admin", "ghcr.io/hanzoai/commerce-admin", "Dockerfile.commerce-admin", "."],
["commerce-site", "ghcr.io/hanzoai/commerce-site", "Dockerfile.site", "."],
]);
// commerce-store IS declared by hand but nothing in the fleet runs it, so the
// detector will not invent it. That gap is the honest one.
assert.ok(!r.config.builds.some((b) => b.name === "commerce-store"));
// …and neither are the dead variants that sit right beside the live ones.
for (const dead of ["commerce-sqfix", "commerce-prebuilt", "commerce-proxy"]) {
assert.ok(!r.config.builds.some((b) => b.name === dead), `${dead} must not be built`);
}
});
test("luxfi/exchange — both images, deploy correctly absent (plain Deployments)", () => {
const r = run("luxfi/exchange");
assert.deepEqual(shape(r.config), [
["exchange", "ghcr.io/luxfi/exchange", "Dockerfile", "."],
["exchange-api", "ghcr.io/luxfi/exchange-api", "Dockerfile.api", "."],
]);
assert.equal(r.config.deploy, undefined);
});
test("hanzoai/otel-collector — no root image; falls back to the cmd/ binaries", () => {
const r = run("hanzoai/otel-collector");
assert.deepEqual(
r.config.builds.map((b) => b.dockerfile),
["cmd/o11yotelcollector/Dockerfile", "cmd/o11yschemamigrator/Dockerfile"],
);
// The `.multi-arch` / `.selfbuild` siblings must not become images too.
assert.equal(r.config.builds.length, 2);
});
// --- repos that must produce NO build ---------------------------------------
test("luxfi/consensus — a Go library builds nothing", () => {
const r = run("luxfi/consensus");
assert.equal(r.deployable, false);
assert.deepEqual(r.config.builds, []);
assert.match(r.reason ?? "", /no Dockerfile/);
});
test("hanzoai/console — the embed artifact is not invented", () => {
const r = run("hanzoai/console");
assert.deepEqual(shape(r.config), [["console", "ghcr.io/hanzoai/console", "Dockerfile", "."]]);
assert.equal(r.config.deploy?.target.name, "console");
});
test("hanzoai/cloud — root image derived; the flags staticlib is not", () => {
const r = run("hanzoai/cloud");
// cloud-flags is a build-time artifact no workload runs, so it stays an
// override. Deriving it would be a guess.
assert.deepEqual(shape(r.config), [["cloud", "ghcr.io/hanzoai/cloud", "Dockerfile", "."]]);
assert.equal(r.config.deploy?.target.name, "cloud");
});
// --- global invariants over every fixture ------------------------------------
test("every fixture is deterministic and never emits an empty build list", () => {
for (const key of Object.keys(trees)) {
const a = run(key);
const b = run(key);
assert.deepEqual(a, b, `${key} is not deterministic`);
if (a.deployable) {
assert.ok(a.config.builds.length > 0, `${key} deployable but no builds`);
} else {
assert.equal(a.config.builds.length, 0, `${key} not deployable but has builds`);
}
}
});
test("no derived image ever crosses org registries", () => {
const nsOf: Record<string, string> = { hanzoai: "hanzoai", luxfi: "luxfi", zooai: "zooai" };
for (const [key, fx] of Object.entries(trees)) {
for (const b of run(key).config.builds) {
assert.ok(
b.image.startsWith(`ghcr.io/${nsOf[fx.org]}/`),
`${key}: ${b.image} is not in ghcr.io/${nsOf[fx.org]}`,
);
}
}
});
test("a derived deploy target always names a workload that really exists", () => {
for (const key of Object.keys(trees)) {
const d = run(key).config.deploy;
if (!d) continue;
const hit = index.find(
(w) =>
w.namespace === d.target.namespace &&
w.name === d.target.name &&
w.kind === d.target.crd,
);
assert.ok(hit, `${key}: deploy target ${d.target.namespace}/${d.target.name} is not in the fleet`);
}
});
test("every build's dockerfile path exists in the repo it came from", () => {
for (const [key, fx] of Object.entries(trees)) {
for (const b of run(key).config.builds) {
assert.ok(b.dockerfile in fx.files, `${key}: ${b.dockerfile} is not a real path`);
}
}
});