151 lines
6.6 KiB
JavaScript
151 lines
6.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { promisify } from "node:util";
|
|
|
|
import {
|
|
classifyGlobalChange,
|
|
componentModelsForServices,
|
|
createG14CiPlan,
|
|
matchingPaths
|
|
} from "./src/g14-ci-plan-lib.mjs";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
test("G14 CI planner maps bridge changes to cloud-api only", async () => {
|
|
const repo = await createFixtureRepo();
|
|
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge v2');\n");
|
|
await git(repo, ["add", "."]);
|
|
await git(repo, ["commit", "-m", "change bridge"]);
|
|
|
|
const plan = await createG14CiPlan({ repoRoot: repo, baseRef: "HEAD~1", targetRef: "HEAD" });
|
|
assert.deepEqual(plan.affectedServices, ["hwlab-cloud-api"]);
|
|
assert.equal(plan.reusedServices.includes("hwlab-cloud-web"), true);
|
|
const cloudApi = plan.services.find((service) => service.serviceId === "hwlab-cloud-api");
|
|
assert.equal(cloudApi.affected, true);
|
|
assert.deepEqual(cloudApi.reason, ["component-path-changed"]);
|
|
});
|
|
|
|
test("G14 CI planner treats docs-only changes as no image build", async () => {
|
|
const repo = await createFixtureRepo();
|
|
await mkdir(path.join(repo, "docs/reference"), { recursive: true });
|
|
await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n");
|
|
await git(repo, ["add", "."]);
|
|
await git(repo, ["commit", "-m", "change docs"]);
|
|
|
|
const plan = await createG14CiPlan({ repoRoot: repo, baseRef: "HEAD~1", targetRef: "HEAD" });
|
|
assert.deepEqual(plan.affectedServices, []);
|
|
assert.equal(plan.imageBuildRequired, false);
|
|
assert.equal(plan.changedPathSummary.docsOnly, true);
|
|
});
|
|
|
|
test("planner rebuilds when catalog digest is missing instead of blocking reuse", async () => {
|
|
const repo = await createFixtureRepo({ catalog: "missing-digest" });
|
|
await mkdir(path.join(repo, "docs/reference"), { recursive: true });
|
|
await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n");
|
|
await git(repo, ["add", "."]);
|
|
await git(repo, ["commit", "-m", "change docs"]);
|
|
|
|
const plan = await createG14CiPlan({ repoRoot: repo, baseRef: "HEAD~1", targetRef: "HEAD" });
|
|
assert.deepEqual(plan.affectedServices, ["hwlab-cloud-api", "hwlab-cloud-web"]);
|
|
assert.equal(plan.imageBuildRequired, true);
|
|
assert.equal(plan.services[0].reason.includes("catalog-digest-missing"), true);
|
|
});
|
|
|
|
test("component model uses built-in service paths", () => {
|
|
const models = componentModelsForServices(["hwlab-cloud-api"]);
|
|
assert.deepEqual(models[0].componentPaths, [
|
|
"cmd/hwlab-cloud-api/",
|
|
"cmd/hwlab-codex-api-responses-forwarder/",
|
|
"cmd/hwlab-deepseek-responses-bridge/",
|
|
"internal/audit/",
|
|
"internal/cloud/",
|
|
"internal/db/",
|
|
"skills/",
|
|
"tools/"
|
|
]);
|
|
assert.deepEqual(matchingPaths(["cmd/hwlab-cloud-api/main.ts"], models[0].componentPaths), ["cmd/hwlab-cloud-api/main.ts"]);
|
|
});
|
|
|
|
test("planner remains compatible with deploy.k3s.serviceMappings shape", async () => {
|
|
const repo = await createFixtureRepo({ deployServices: false, k3sServiceMappings: true });
|
|
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge v2');\n");
|
|
await git(repo, ["add", "."]);
|
|
await git(repo, ["commit", "-m", "change bridge"]);
|
|
|
|
const plan = await createG14CiPlan({ repoRoot: repo, baseRef: "HEAD~1", targetRef: "HEAD" });
|
|
assert.equal(plan.compatibility.currentRenderCompatible, true);
|
|
assert.equal(plan.compatibility.serviceIdSource, "deploy.k3s.serviceMappings");
|
|
assert.deepEqual(plan.affectedServices, ["hwlab-cloud-api"]);
|
|
assert.equal(plan.inputs.serviceCount, 2);
|
|
});
|
|
|
|
test("global change classifier distinguishes gitops-only and test-only", () => {
|
|
assert.equal(classifyGlobalChange(["deploy/gitops/g14/runtime-dev/workloads.yaml"]).gitopsOnly, true);
|
|
assert.equal(classifyGlobalChange(["cmd/hwlab-cloud-api/main.test.mjs"]).testOnly, true);
|
|
});
|
|
|
|
async function createFixtureRepo(options = {}) {
|
|
const deployServices = options.deployServices !== false;
|
|
const k3sServiceMappings = options.k3sServiceMappings === true;
|
|
const repo = await mkdtemp(path.join(os.tmpdir(), "hwlab-g14-ci-plan-"));
|
|
await mkdir(path.join(repo, "cmd/hwlab-cloud-api"), { recursive: true });
|
|
await mkdir(path.join(repo, "cmd/hwlab-deepseek-responses-bridge"), { recursive: true });
|
|
await mkdir(path.join(repo, "web/hwlab-cloud-web"), { recursive: true });
|
|
await mkdir(path.join(repo, "deploy"), { recursive: true });
|
|
await writeFile(path.join(repo, "deploy/deploy.json"), JSON.stringify(createDeployFixture({ deployServices, k3sServiceMappings }), null, 2));
|
|
const catalogMode = options.catalog ?? "ready";
|
|
if (catalogMode !== false) {
|
|
await writeFile(path.join(repo, "deploy/artifact-catalog.dev.json"), JSON.stringify(createCatalogFixture(catalogMode), null, 2));
|
|
}
|
|
await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2));
|
|
await writeFile(path.join(repo, "package-lock.json"), JSON.stringify({ lockfileVersion: 3 }, null, 2));
|
|
await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.ts"), "console.log('api');\n");
|
|
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge');\n");
|
|
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.test.mjs"), "console.log('test');\n");
|
|
await writeFile(path.join(repo, "web/hwlab-cloud-web/index.html"), "<html></html>\n");
|
|
await git(repo, ["init"]);
|
|
await git(repo, ["add", "."]);
|
|
await git(repo, ["commit", "-m", "initial"]);
|
|
return repo;
|
|
}
|
|
|
|
function createDeployFixture({ deployServices, k3sServiceMappings }) {
|
|
const deploy = {};
|
|
if (deployServices) {
|
|
deploy.services = [
|
|
{ serviceId: "hwlab-cloud-api" },
|
|
{ serviceId: "hwlab-cloud-web" }
|
|
];
|
|
}
|
|
if (k3sServiceMappings) {
|
|
deploy.k3s = {
|
|
serviceMappings: [
|
|
{ serviceId: "hwlab-cloud-api", name: "hwlab-cloud-api" },
|
|
{ serviceId: "hwlab-cloud-web", name: "hwlab-cloud-web" }
|
|
]
|
|
};
|
|
}
|
|
return deploy;
|
|
}
|
|
|
|
function createCatalogFixture(mode) {
|
|
const digest = mode === "ready" ? `sha256:${"1".repeat(64)}` : "not_published";
|
|
return {
|
|
services: ["hwlab-cloud-api", "hwlab-cloud-web"].map((serviceId) => ({
|
|
serviceId,
|
|
commitId: "abc1234",
|
|
image: `127.0.0.1:5000/hwlab/${serviceId}:abc1234`,
|
|
imageTag: "abc1234",
|
|
digest
|
|
}))
|
|
};
|
|
}
|
|
|
|
async function git(cwd, args) {
|
|
return execFileAsync("git", ["-c", "user.name=HWLAB Test", "-c", "user.email=hwlab-test@example.invalid", ...args], { cwd });
|
|
}
|