829 lines
41 KiB
TypeScript
829 lines
41 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { spawnSync } from "node:child_process";
|
|
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { resolve } from "node:path";
|
|
|
|
import { rootPath } from "./config";
|
|
import { applyNodeRuntimeDeployYamlOverlay, nodeRuntimeDeployYamlOverlayShellScript } from "./hwlab-node/deploy-overlay";
|
|
import { hwlabRuntimePipelineProvenance } from "./hwlab-node/pipeline-provenance";
|
|
import { nodeRuntimePipelineProvenancePostprocessScript } from "./hwlab-node/render";
|
|
import { nodeRuntimeRenderOverlay } from "./hwlab-node/web-probe";
|
|
import { hwlabRuntimeLaneSpecForNode, parseHwlabRuntimePipelineProvenance } from "./hwlab-node-lanes";
|
|
import { pipelineProvenanceAnnotationKeys, pipelineProvenanceAnnotations } from "./pipeline-provenance";
|
|
import {
|
|
assertPacSourceArtifactVerifyWorktree,
|
|
canonicalSha256,
|
|
canonicalizePacPipelineSpec,
|
|
firstPacSourceArtifactDrift,
|
|
pacSourceArtifactSafeError,
|
|
pacValueEvidence,
|
|
pacSourceArtifactYaml,
|
|
parsePacSourceArtifactOptions,
|
|
sourceArtifactRuntimeAlignment,
|
|
} from "./platform-infra-pipelines-as-code-source-artifact";
|
|
import {
|
|
pacRuntimeSafeReason,
|
|
pacRuntimeSafePath,
|
|
pacRuntimeSourceArtifactItem,
|
|
pacRuntimeTransportFailureReason,
|
|
renderPacSourceArtifactRuntimeObserverShell,
|
|
} from "./platform-infra-pipelines-as-code";
|
|
import {
|
|
canonicalPacPipelineSha256 as nativeCanonicalSha256,
|
|
canonicalizePacPipelineSpec as nativeCanonicalize,
|
|
observePacSourceArtifactRuntime,
|
|
selectPipelineRunBySourceCommit,
|
|
} from "../native/cicd/pac-source-artifact-runtime.mjs";
|
|
|
|
const sourceCommit = "a".repeat(40);
|
|
const provenance = {
|
|
configRef: "config/example.yaml#lanes.nc01",
|
|
effectiveConfigSha256: "sha256:config",
|
|
renderer: "agentrun-control-plane",
|
|
mode: "embedded-pipeline-spec",
|
|
};
|
|
const desiredSpec = { params: [], tasks: [] };
|
|
const runtimeWrapperSpec = {
|
|
taskRunTemplate: {
|
|
serviceAccountName: "agentrun-nc01-v02-tekton-runner",
|
|
podTemplate: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", securityContext: { fsGroup: 1000 } },
|
|
},
|
|
params: [],
|
|
workspaces: [],
|
|
};
|
|
|
|
function manifestAnnotations(): Record<string, string> {
|
|
return {
|
|
"unidesk.ai/owning-config-ref": provenance.configRef,
|
|
"unidesk.ai/effective-config-sha256": provenance.effectiveConfigSha256,
|
|
"unidesk.ai/source-artifact-renderer": provenance.renderer,
|
|
"unidesk.ai/source-artifact-mode": provenance.mode,
|
|
};
|
|
}
|
|
|
|
function pipeline(): Record<string, unknown> {
|
|
return {
|
|
metadata: { name: "agentrun-nc01-v02-ci-image-publish", annotations: manifestAnnotations() },
|
|
spec: desiredSpec,
|
|
};
|
|
}
|
|
|
|
function pipelineRun(name: string, creationTimestamp: string, spec: Record<string, unknown> = desiredSpec): Record<string, unknown> {
|
|
return {
|
|
metadata: {
|
|
name,
|
|
creationTimestamp,
|
|
labels: { "pipelinesascode.tekton.dev/sha": sourceCommit },
|
|
annotations: manifestAnnotations(),
|
|
},
|
|
spec: { pipelineSpec: spec, ...structuredClone(runtimeWrapperSpec) },
|
|
};
|
|
}
|
|
|
|
function runtimeInput(commit: string | null = sourceCommit): Record<string, unknown> {
|
|
return {
|
|
namespace: "agentrun-ci",
|
|
pipeline: "agentrun-nc01-v02-ci-image-publish",
|
|
pipelineRunPrefix: "agentrun-nc01-v02-ci",
|
|
desiredSpec,
|
|
desiredWrapper: { mode: "embedded-pipeline-spec", executionMode: "pipeline-spec", spec: runtimeWrapperSpec },
|
|
provenance,
|
|
sourceCommit: commit,
|
|
};
|
|
}
|
|
|
|
describe("PaC source artifact CLI contract", () => {
|
|
test("verify-runtime requires and normalizes a full source commit", () => {
|
|
expect(() => parsePacSourceArtifactOptions([
|
|
"verify-runtime", "--target", "NC01", "--consumer", "agentrun-nc01-v02", "--source-worktree", "/tmp/repo",
|
|
])).toThrow("source-commit-required");
|
|
const parsed = parsePacSourceArtifactOptions([
|
|
"verify-runtime", "--target", "NC01", "--consumer", "agentrun-nc01-v02", "--source-worktree", "/tmp/repo", "--source-commit", sourceCommit.toUpperCase(),
|
|
]);
|
|
expect(parsed.sourceCommit).toBe(sourceCommit);
|
|
expect(() => parsePacSourceArtifactOptions([
|
|
"plan", "--target", "NC01", "--consumer", "agentrun-nc01-v02", "--source-worktree", "/tmp/repo", "--source-commit", sourceCommit,
|
|
])).toThrow("accepted only");
|
|
});
|
|
|
|
test("real outer CLI returns bounded scoped help for both help positions", () => {
|
|
for (const tail of [["--help"], ["check", "--help"]]) {
|
|
const result = spawnSync("bun", ["scripts/cli.ts", "platform-infra", "pipelines-as-code", "source-artifact", ...tail], {
|
|
cwd: rootPath(), encoding: "utf8", timeout: 30_000,
|
|
});
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toContain("PAC SOURCE ARTIFACT");
|
|
expect(result.stdout).toContain("verify-runtime");
|
|
expect(result.stdout).toContain("--source-commit <full-sha>");
|
|
expect(result.stdout.split("\n").length).toBeLessThan(25);
|
|
expect(result.stdout).not.toContain("platform-infra sub2api plan");
|
|
}
|
|
});
|
|
|
|
test("predictable validation failures omit stack and rendered specs", () => {
|
|
const result = spawnSync("bun", [
|
|
"scripts/cli.ts", "platform-infra", "pipelines-as-code", "source-artifact", "plan",
|
|
"--target", "NC01", "--consumer", "agentrun-nc01-v02", "--source-worktree", rootPath(),
|
|
], { cwd: rootPath(), encoding: "utf8", timeout: 30_000 });
|
|
expect(result.status).toBe(1);
|
|
expect(result.stdout).toContain("code=source-worktree-origin-mismatch");
|
|
expect(result.stdout).toContain("observedUrlFingerprint=sha256:");
|
|
expect(result.stdout).not.toContain("observedOwnerRepo");
|
|
expect(result.stdout).not.toContain("stack");
|
|
expect(result.stdout).not.toContain("taskSpec");
|
|
expect(result.stdout.length).toBeLessThan(1500);
|
|
});
|
|
|
|
test("default, JSON, and full failures never disclose a malicious origin", () => {
|
|
const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-pac-origin-test-"));
|
|
const secrets = [
|
|
"review-user",
|
|
"REVIEW_SUPER_SECRET",
|
|
"QUERY_SUPER_SECRET",
|
|
"Authorization",
|
|
"https://review-user:REVIEW_SUPER_SECRET@example.invalid/pikasTech/agentrun.git?token=QUERY_SUPER_SECRET",
|
|
];
|
|
try {
|
|
for (const args of [
|
|
["init"],
|
|
["config", "user.email", "pac-test@example.invalid"],
|
|
["config", "user.name", "PaC Test"],
|
|
]) expect(spawnSync("git", args, { cwd: temporary, encoding: "utf8" }).status).toBe(0);
|
|
writeFileSync(resolve(temporary, "README.md"), "fixture\n");
|
|
expect(spawnSync("git", ["add", "README.md"], { cwd: temporary, encoding: "utf8" }).status).toBe(0);
|
|
expect(spawnSync("git", ["commit", "-m", "fixture"], { cwd: temporary, encoding: "utf8" }).status).toBe(0);
|
|
expect(spawnSync("git", ["remote", "add", "origin", secrets[4] as string], { cwd: temporary, encoding: "utf8" }).status).toBe(0);
|
|
const origins = [
|
|
secrets[4] as string,
|
|
"audit@example.invalid:pikasTech/agentrun.git?token=SCP_QUERY_SUPER_SECRET",
|
|
"https://review-user:REVIEW_SUPER_SECRET@github.com/pikasTech/agentrun.git",
|
|
"https://github.com/pikasTech/agentrun.git?token=QUERY_SUPER_SECRET",
|
|
"https://github.com/pikasTech/agentrun.git#QUERY_SUPER_SECRET",
|
|
];
|
|
for (const origin of origins) {
|
|
expect(spawnSync("git", ["remote", "set-url", "origin", origin], { cwd: temporary, encoding: "utf8" }).status).toBe(0);
|
|
for (const outputFlag of [null, "--json", "--full"] as const) {
|
|
const args = [
|
|
"scripts/cli.ts", "platform-infra", "pipelines-as-code", "source-artifact", "plan",
|
|
"--target", "NC01", "--consumer", "agentrun-nc01-v02", "--source-worktree", temporary,
|
|
];
|
|
if (outputFlag !== null) args.push(outputFlag);
|
|
const result = spawnSync("bun", args, { cwd: rootPath(), encoding: "utf8", timeout: 30_000 });
|
|
const output = `${result.stdout}\n${result.stderr}`;
|
|
expect(result.status).toBe(1);
|
|
expect(output).toContain("source-worktree-origin-mismatch");
|
|
expect(output).toContain("observedUrlFingerprint");
|
|
expect(output).not.toContain("observedOwnerRepo");
|
|
for (const secret of [...secrets, "SCP_QUERY_SUPER_SECRET"]) expect(output).not.toContain(secret);
|
|
}
|
|
}
|
|
} finally {
|
|
rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
}, 15_000);
|
|
|
|
test("verification requires a clean worktree at the exact requested commit", () => {
|
|
const exact = { head: sourceCommit, clean: true, matchesSourceCommit: true };
|
|
expect(() => assertPacSourceArtifactVerifyWorktree("verify-runtime", sourceCommit, exact)).not.toThrow();
|
|
expect(() => assertPacSourceArtifactVerifyWorktree("status", sourceCommit, { ...exact, clean: false })).not.toThrow();
|
|
expect(() => assertPacSourceArtifactVerifyWorktree("verify-runtime", sourceCommit, { ...exact, clean: false })).toThrow("source-worktree-not-clean");
|
|
expect(() => assertPacSourceArtifactVerifyWorktree("verify-runtime", sourceCommit, { ...exact, head: "b".repeat(40), matchesSourceCommit: false })).toThrow("source-worktree-commit-mismatch");
|
|
});
|
|
|
|
test("taskRunTemplate operational facts are declared in owning YAML", () => {
|
|
const document = Bun.YAML.parse(readFileSync(rootPath("config", "platform-infra", "pipelines-as-code.yaml"), "utf8")) as Record<string, any>;
|
|
const consumers = document.consumers as Array<Record<string, any>>;
|
|
for (const template of ["templates.consumers.agentrunV02", "templates.consumers.hwlabV03"]) {
|
|
const sourceArtifact = consumers.find((item) => item.extends === template && item.variables?.NODE === "NC01")?.sourceArtifact;
|
|
expect(sourceArtifact?.taskRunTemplate).toEqual({ hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", fsGroup: 1000 });
|
|
}
|
|
for (const id of ["platform-infra-sub2rank-nc01", "selfmedia-nc01"]) {
|
|
const sourceArtifact = consumers.find((item) => item.id === id)?.sourceArtifact;
|
|
expect(sourceArtifact?.taskRunTemplate).toEqual({ hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", fsGroup: 1000 });
|
|
}
|
|
});
|
|
|
|
test("undeclared JD01 source artifact owner fails closed", () => {
|
|
const result = spawnSync("bun", [
|
|
"scripts/cli.ts", "platform-infra", "pipelines-as-code", "source-artifact", "plan",
|
|
"--target", "JD01", "--consumer", "agentrun-jd01-v02", "--source-worktree", rootPath(),
|
|
], { cwd: rootPath(), encoding: "utf8", timeout: 30_000 });
|
|
expect(result.status).toBe(1);
|
|
expect(result.stdout).toContain("code=source-artifact-operation-failed");
|
|
expect(result.stdout).toContain("evidence=type=string");
|
|
});
|
|
});
|
|
|
|
describe("HWLAB YAML-owned Pipeline provenance", () => {
|
|
const ownerPath = "lanes.v03.targets.NC01.pipelineProvenance";
|
|
const configRef = "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01";
|
|
|
|
test("target owner parser fails closed for missing or inconsistent contracts", () => {
|
|
expect(() => parseHwlabRuntimePipelineProvenance(undefined, ownerPath, configRef)).toThrow("must be declared by the target owner");
|
|
expect(() => parseHwlabRuntimePipelineProvenance({
|
|
configRef: "config/hwlab-node-lanes.yaml#lanes.v03.targets.JD01",
|
|
renderer: "hwlab-runtime-lane",
|
|
mode: "remote-pipeline-annotation",
|
|
}, ownerPath, configRef)).toThrow(`must equal ${configRef}`);
|
|
expect(() => parseHwlabRuntimePipelineProvenance({
|
|
configRef,
|
|
renderer: "another-renderer",
|
|
mode: "remote-pipeline-annotation",
|
|
}, ownerPath, configRef)).toThrow("renderer must be one of hwlab-runtime-lane");
|
|
expect(() => parseHwlabRuntimePipelineProvenance({
|
|
configRef,
|
|
renderer: "hwlab-runtime-lane",
|
|
mode: "embedded-pipeline-spec",
|
|
}, ownerPath, configRef)).toThrow("mode must be one of remote-pipeline-annotation");
|
|
});
|
|
|
|
test("normal control-plane postprocess writes exactly the shared four provenance fields", () => {
|
|
const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-hwlab-pipeline-provenance-"));
|
|
try {
|
|
const spec = hwlabRuntimeLaneSpecForNode("v03", "NC01");
|
|
const overlay = nodeRuntimeRenderOverlay(spec);
|
|
const pipelineDir = resolve(temporary, spec.tektonDir);
|
|
const pipelinePath = resolve(pipelineDir, "pipeline.yaml");
|
|
mkdirSync(pipelineDir, { recursive: true });
|
|
writeFileSync(pipelinePath, Bun.YAML.stringify({
|
|
apiVersion: "tekton.dev/v1",
|
|
kind: "Pipeline",
|
|
metadata: { name: spec.pipeline, namespace: "hwlab-ci", annotations: { "fixture.example/keep": "true" } },
|
|
spec: { params: [], tasks: [] },
|
|
}));
|
|
const overlayBase64 = Buffer.from(JSON.stringify(overlay), "utf8").toString("base64");
|
|
const shell = [
|
|
`render_dir=${JSON.stringify(temporary)}`,
|
|
`overlay_b64=${JSON.stringify(overlayBase64)}`,
|
|
...nodeRuntimePipelineProvenancePostprocessScript(),
|
|
].join("\n");
|
|
const result = spawnSync("sh", [], { cwd: rootPath(), input: shell, encoding: "utf8", timeout: 30_000 });
|
|
expect(result.status).toBe(0);
|
|
const rendered = Bun.YAML.parse(readFileSync(pipelinePath, "utf8")) as Record<string, any>;
|
|
const annotations = rendered.metadata.annotations as Record<string, string>;
|
|
const provenanceKeySet = new Set<string>(Object.values(pipelineProvenanceAnnotationKeys));
|
|
const renderedProvenance = Object.fromEntries(Object.entries(annotations).filter(([key]) => provenanceKeySet.has(key)));
|
|
expect(renderedProvenance).toEqual(overlay.pipelineProvenanceAnnotations);
|
|
expect(Object.keys(renderedProvenance).sort()).toEqual(Object.values(pipelineProvenanceAnnotationKeys).sort());
|
|
expect(annotations["fixture.example/keep"]).toBe("true");
|
|
|
|
const invalidOverlay = { ...overlay, pipelineProvenanceAnnotations: { a: "1", b: "2", c: "3", d: "4" } };
|
|
const invalidShell = [
|
|
`render_dir=${JSON.stringify(temporary)}`,
|
|
`overlay_b64=${JSON.stringify(Buffer.from(JSON.stringify(invalidOverlay), "utf8").toString("base64"))}`,
|
|
...nodeRuntimePipelineProvenancePostprocessScript(),
|
|
].join("\n");
|
|
const invalid = spawnSync("sh", [], { cwd: rootPath(), input: invalidShell, encoding: "utf8", timeout: 30_000 });
|
|
expect(invalid.status).not.toBe(0);
|
|
expect(`${invalid.stdout}\n${invalid.stderr}`).toContain("shared four-key contract");
|
|
} finally {
|
|
rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("Tekton admission canonicalization", () => {
|
|
const admitted = {
|
|
tasks: [{
|
|
name: "build",
|
|
taskSpec: {
|
|
metadata: {},
|
|
spec: null,
|
|
params: [{ name: "input", type: "string" }],
|
|
results: [{ name: "output", type: "string" }],
|
|
steps: [{ name: "step", computeResources: {} }],
|
|
sidecars: [{ name: "sidecar", computeResources: {} }],
|
|
},
|
|
}],
|
|
};
|
|
const desired = {
|
|
tasks: [{
|
|
name: "build",
|
|
taskSpec: {
|
|
params: [{ name: "input" }],
|
|
results: [{ name: "output" }],
|
|
steps: [{ name: "step" }],
|
|
sidecars: [{ name: "sidecar" }],
|
|
},
|
|
}],
|
|
};
|
|
|
|
test("removes exactly the six proven defaults and keeps local/native hashes equal", () => {
|
|
expect(canonicalizePacPipelineSpec(admitted)).toEqual(desired);
|
|
expect(nativeCanonicalize(admitted)).toEqual(desired);
|
|
expect(canonicalSha256(admitted)).toBe(canonicalSha256(desired));
|
|
expect(nativeCanonicalSha256(admitted)).toBe(canonicalSha256(admitted));
|
|
});
|
|
|
|
test("does not strip same-name fields outside the three Pipeline spec roots", () => {
|
|
const negative = {
|
|
unrelated: admitted,
|
|
tasks: [{ taskSpec: {
|
|
metadata: { labels: {} },
|
|
spec: {},
|
|
params: [{ type: "array" }],
|
|
results: [{ type: "object" }],
|
|
steps: [{ computeResources: { requests: { cpu: "1" } } }],
|
|
sidecars: [{ computeResources: { limits: { memory: "1Gi" } } }],
|
|
} }],
|
|
};
|
|
expect(canonicalizePacPipelineSpec(negative)).toEqual(negative);
|
|
expect(nativeCanonicalize(negative)).toEqual(negative);
|
|
});
|
|
|
|
test("normalizes equivalent PipelineRun timeout spellings without hiding different values", () => {
|
|
const seconds = { spec: { timeouts: { pipeline: "600s" } } };
|
|
const admitted = { spec: { timeouts: { pipeline: "10m0s" } } };
|
|
const different = { spec: { timeouts: { pipeline: "601s" } } };
|
|
const unrelated = { timeouts: { pipeline: "600s" } };
|
|
expect(canonicalizePacPipelineSpec(seconds)).toEqual(canonicalizePacPipelineSpec(admitted));
|
|
expect(nativeCanonicalize(seconds)).toEqual(nativeCanonicalize(admitted));
|
|
expect(canonicalSha256(seconds)).toBe(canonicalSha256(admitted));
|
|
expect(nativeCanonicalSha256(admitted)).toBe(canonicalSha256(seconds));
|
|
expect(firstPacSourceArtifactDrift(seconds, admitted)).toBeNull();
|
|
expect(firstPacSourceArtifactDrift(seconds, different)?.path).toBe("$.spec.timeouts.pipeline");
|
|
expect(canonicalizePacPipelineSpec(unrelated)).toEqual(unrelated);
|
|
expect(nativeCanonicalize(unrelated)).toEqual(unrelated);
|
|
});
|
|
|
|
test("removes only empty workspace claim metadata and status added by admission", () => {
|
|
const desired = { spec: { workspaces: [{ name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"] } } }] } };
|
|
const admitted = { spec: { workspaces: [{ name: "source", volumeClaimTemplate: { metadata: {}, spec: { accessModes: ["ReadWriteOnce"] }, status: {} } }] } };
|
|
const labeled = { spec: { workspaces: [{ name: "source", volumeClaimTemplate: { metadata: { labels: { owner: "test" } }, spec: { accessModes: ["ReadWriteOnce"] } } }] } };
|
|
expect(canonicalizePacPipelineSpec(admitted)).toEqual(canonicalizePacPipelineSpec(desired));
|
|
expect(nativeCanonicalize(admitted)).toEqual(nativeCanonicalize(desired));
|
|
expect(firstPacSourceArtifactDrift(desired, admitted)).toBeNull();
|
|
expect(firstPacSourceArtifactDrift(desired, labeled)?.path).toBe("$.spec.workspaces[0].volumeClaimTemplate.metadata");
|
|
});
|
|
});
|
|
|
|
describe("source artifact serialization", () => {
|
|
test("remote Pipeline artifacts are deterministic reviewable multi-line YAML", () => {
|
|
const manifest = {
|
|
apiVersion: "tekton.dev/v1",
|
|
kind: "Pipeline",
|
|
metadata: { name: "fixture" },
|
|
spec: { params: [{ name: "revision", type: "string" }], tasks: [{ name: "build", taskSpec: { steps: [{ name: "build", script: "set -eu\necho ok\n" }] } }] },
|
|
};
|
|
const first = pacSourceArtifactYaml(manifest);
|
|
const second = pacSourceArtifactYaml(manifest);
|
|
expect(first).toBe(second);
|
|
expect(first.split("\n").length).toBeGreaterThan(10);
|
|
expect(first).toContain("apiVersion: tekton.dev/v1\nkind: Pipeline\n");
|
|
expect(Bun.YAML.parse(first)).toEqual(manifest);
|
|
});
|
|
|
|
test("empty mappings and sequences have stable whitespace without changing structure", () => {
|
|
const manifest = {
|
|
apiVersion: "tekton.dev/v1",
|
|
kind: "PipelineRun",
|
|
metadata: {},
|
|
spec: { params: [], workspaces: [], pipelineSpec: { tasks: [] } },
|
|
};
|
|
const serialized = pacSourceArtifactYaml(manifest);
|
|
expect(serialized).not.toMatch(/[\t ]+$/m);
|
|
expect(serialized.endsWith("\n")).toBe(true);
|
|
expect(serialized.endsWith("\n\n")).toBe(false);
|
|
expect(Bun.YAML.parse(serialized)).toEqual(manifest);
|
|
});
|
|
|
|
test("multi-line script scalar preserves trailing spaces and tabs exactly", () => {
|
|
const script = [
|
|
"#!/bin/sh ",
|
|
"printf 'mapping-looking scalar: ' ",
|
|
"metadata: ",
|
|
" {}",
|
|
"printf 'tab follows'\t",
|
|
"",
|
|
].join("\n");
|
|
const manifest = {
|
|
apiVersion: "tekton.dev/v1",
|
|
kind: "Pipeline",
|
|
metadata: { name: "scalar-round-trip" },
|
|
spec: { tasks: [{ name: "build", taskSpec: { steps: [{ name: "build", script }] } }] },
|
|
};
|
|
const serialized = pacSourceArtifactYaml(manifest);
|
|
const parsed = Bun.YAML.parse(serialized) as typeof manifest;
|
|
expect(parsed).toEqual(manifest);
|
|
expect(parsed.spec.tasks[0]?.taskSpec.steps[0]?.script).toBe(script);
|
|
expect(Buffer.from(parsed.spec.tasks[0]?.taskSpec.steps[0]?.script ?? "")).toEqual(Buffer.from(script));
|
|
expect(serialized.endsWith("\n")).toBe(true);
|
|
expect(serialized.endsWith("\n\n")).toBe(false);
|
|
});
|
|
|
|
test("round-trip failure exposes only secret-safe drift evidence", () => {
|
|
const secret = "ROUND_TRIP_SCRIPT_SECRET_DO_NOT_PRINT";
|
|
let reads = 0;
|
|
const spec: Record<string, unknown> = {};
|
|
Object.defineProperty(spec, "script", {
|
|
enumerable: true,
|
|
get: () => {
|
|
reads += 1;
|
|
return reads <= 2 ? secret : "changed-after-serialization";
|
|
},
|
|
});
|
|
|
|
let thrown: unknown;
|
|
try {
|
|
pacSourceArtifactYaml({ apiVersion: "tekton.dev/v1", kind: "Pipeline", spec });
|
|
} catch (error) {
|
|
thrown = error;
|
|
}
|
|
expect(thrown).toBeInstanceOf(Error);
|
|
const safe = pacSourceArtifactSafeError(thrown);
|
|
const serializedError = JSON.stringify({
|
|
message: thrown instanceof Error ? thrown.message : String(thrown),
|
|
direct: thrown,
|
|
safe,
|
|
});
|
|
expect(safe.code).toBe("source-artifact-yaml-round-trip-failed");
|
|
expect(serializedError).not.toContain(secret);
|
|
expect(serializedError).not.toContain("changed-after-serialization");
|
|
expect(serializedError).toContain("sha256");
|
|
});
|
|
});
|
|
|
|
describe("runtime source-commit selection", () => {
|
|
test("same-commit retries select the newest only when spec and provenance agree", () => {
|
|
const oldRun = pipelineRun("agentrun-nc01-v02-ci-old", "2026-07-10T00:00:00Z");
|
|
const newRun = pipelineRun("agentrun-nc01-v02-ci-new", "2026-07-11T00:00:00Z");
|
|
const selected = selectPipelineRunBySourceCommit([oldRun, newRun], "agentrun-nc01-v02-ci", sourceCommit);
|
|
expect(selected.kind).toBe("ok");
|
|
expect(selected.run.metadata.name).toBe("agentrun-nc01-v02-ci-new");
|
|
const observed = observePacSourceArtifactRuntime(runtimeInput(), (args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: pipeline() }
|
|
: { kind: "ok", value: { items: [oldRun, newRun] } });
|
|
expect(observed.embedded.status).toBe("aligned");
|
|
expect(observed.embedded.name).toBe("agentrun-nc01-v02-ci-new");
|
|
expect(observed.embedded.candidateCount).toBe(2);
|
|
expect(observed.embedded.sourceCommit).toBe(sourceCommit);
|
|
});
|
|
|
|
test("same-commit conflicting embedded specs fail closed", () => {
|
|
const observed = observePacSourceArtifactRuntime(runtimeInput(), (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: pipeline() }
|
|
: { kind: "ok", value: { items: [
|
|
pipelineRun("agentrun-nc01-v02-ci-old", "2026-07-10T00:00:00Z"),
|
|
pipelineRun("agentrun-nc01-v02-ci-new", "2026-07-11T00:00:00Z", { params: [], tasks: [{ name: "changed" }] }),
|
|
] } });
|
|
expect(observed.embedded.status).toBe("unavailable");
|
|
expect(observed.embedded.reason).toBe("source-commit-run-conflict:2");
|
|
expect(observed.embedded.candidateCount).toBe(2);
|
|
});
|
|
|
|
test("embedded mode compares the complete PipelineRun wrapper", () => {
|
|
const run = pipelineRun("agentrun-nc01-v02-ci-wrapper", "2026-07-11T00:00:00Z");
|
|
(run.spec as Record<string, any>).taskRunTemplate.serviceAccountName = "wrong-service-account";
|
|
const observed = observePacSourceArtifactRuntime(runtimeInput(), (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: pipeline() }
|
|
: { kind: "ok", value: { items: [run] } });
|
|
expect(observed.embedded.status).toBe("drifted");
|
|
expect(observed.embedded.firstDrift?.path).toBe("$.spec.taskRunTemplate.serviceAccountName");
|
|
});
|
|
|
|
test("remote mode validates Pipeline spec live and wrapper on the exact-commit Run", () => {
|
|
const spec = hwlabRuntimeLaneSpecForNode("v03", "NC01");
|
|
const remoteProvenance = hwlabRuntimePipelineProvenance(spec);
|
|
const overlay = nodeRuntimeRenderOverlay(spec);
|
|
const remoteAnnotations = overlay.pipelineProvenanceAnnotations as Record<string, string>;
|
|
expect(spec.pipelineProvenance).toEqual({
|
|
configRef: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01",
|
|
renderer: "hwlab-runtime-lane",
|
|
mode: "remote-pipeline-annotation",
|
|
});
|
|
expect(remoteAnnotations).toEqual(pipelineProvenanceAnnotations(remoteProvenance));
|
|
expect(Object.keys(remoteAnnotations).sort()).toEqual(Object.values(pipelineProvenanceAnnotationKeys).sort());
|
|
expect(readFileSync(rootPath("scripts", "src", "hwlab-node", "render.ts"), "utf8")).not.toContain("overlay.sourceArtifactProvenance");
|
|
expect(readFileSync(rootPath("scripts", "src", "platform-infra-pipelines-as-code-source-artifact.ts"), "utf8")).not.toContain('"unidesk.ai/owning-config-ref"');
|
|
const wrapper = {
|
|
mode: "remote-pipeline-annotation",
|
|
executionMode: "pipeline-spec",
|
|
spec: { ...runtimeWrapperSpec, timeouts: { pipeline: "1h0m0s" } },
|
|
};
|
|
const run = {
|
|
metadata: {
|
|
name: "hwlab-nc01-v03-ci-poll-remote",
|
|
creationTimestamp: "2026-07-11T00:00:00Z",
|
|
labels: { "pipelinesascode.tekton.dev/sha": sourceCommit },
|
|
annotations: remoteAnnotations,
|
|
},
|
|
spec: { pipelineSpec: desiredSpec, ...wrapper.spec },
|
|
};
|
|
const input = {
|
|
...runtimeInput(),
|
|
pipeline: "hwlab-nc01-v03-ci-image-publish",
|
|
pipelineRunPrefix: "hwlab-nc01-v03-ci-poll",
|
|
provenance: remoteProvenance,
|
|
desiredWrapper: wrapper,
|
|
};
|
|
const observed = observePacSourceArtifactRuntime(input, (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: { metadata: { name: input.pipeline, annotations: remoteAnnotations }, spec: desiredSpec } }
|
|
: { kind: "ok", value: { items: [run] } });
|
|
expect(observed.live.status).toBe("aligned");
|
|
expect(observed.live.provenanceAligned).toBe(true);
|
|
expect(observed.embedded.status).toBe("aligned");
|
|
expect(observed.embedded.provenanceAligned).toBe(true);
|
|
expect(observed.embedded.firstDrift).toBeNull();
|
|
});
|
|
|
|
test("NotFound, missing commit, and transport failures remain distinct", () => {
|
|
const noCommit = observePacSourceArtifactRuntime(runtimeInput(null), () => ({ kind: "not-found", reason: "pipeline-get-not-found" }));
|
|
expect(noCommit.live.status).toBe("missing");
|
|
expect(noCommit.live.reason).toBe("pipeline-get-not-found");
|
|
expect(noCommit.embedded.status).toBe("unavailable");
|
|
expect(noCommit.embedded.reason).toBe("source-commit-not-specified");
|
|
|
|
const secret = "Authorization: Bearer REVIEW_RUNTIME_SECRET https://review:password@example.invalid/path?token=QUERY_SECRET";
|
|
const transport = observePacSourceArtifactRuntime(runtimeInput(), () => ({ kind: "unavailable", reason: secret }));
|
|
expect(transport.live.status).toBe("unavailable");
|
|
expect(transport.embedded.status).toBe("unavailable");
|
|
expect(transport.live.reason).toMatch(/^runtime-observer-reason:type=string,length=\d+,sha256=[0-9a-f]{64}$/u);
|
|
expect(transport.embedded.reason).toMatch(/^runtime-observer-reason:type=string,length=\d+,sha256=[0-9a-f]{64}$/u);
|
|
expect(JSON.stringify(transport)).not.toContain(secret);
|
|
});
|
|
|
|
test("first drift reports structural evidence without script or token values", () => {
|
|
const expectedSecret = "Authorization: Bearer EXPECTED_SUPER_SECRET";
|
|
const actualSecret = "https://review-user:ACTUAL_SUPER_SECRET@example.invalid/path?token=QUERY_SUPER_SECRET";
|
|
const input = {
|
|
...runtimeInput(),
|
|
desiredSpec: { tasks: [{ taskSpec: { steps: [{ script: expectedSecret }] } }] },
|
|
};
|
|
const observed = observePacSourceArtifactRuntime(input, (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: { metadata: { name: "pipeline", annotations: manifestAnnotations() }, spec: { tasks: [{ taskSpec: { steps: [{ script: actualSecret }] } }] } } }
|
|
: { kind: "ok", value: { items: [pipelineRun("agentrun-nc01-v02-ci-secret", "2026-07-11T00:00:00Z", { tasks: [{ taskSpec: { steps: [{ script: actualSecret }] } }] })] } });
|
|
const serialized = JSON.stringify(observed);
|
|
expect(observed.live.firstDrift?.path).toBe("$.tasks[0].taskSpec.steps[0].script");
|
|
expect(observed.live.firstDrift?.expected).toBe(pacValueEvidence(expectedSecret));
|
|
expect(observed.live.firstDrift?.actual).toBe(pacValueEvidence(actualSecret));
|
|
expect(serialized).not.toContain(expectedSecret);
|
|
expect(serialized).not.toContain(actualSecret);
|
|
expect(serialized).not.toContain("Authorization");
|
|
expect(serialized).not.toContain("QUERY_SUPER_SECRET");
|
|
});
|
|
|
|
test("unknown runtime keys and forged paths are fingerprinted", () => {
|
|
const maliciousKey = "HEADER_QUERY_SUPER_SECRET";
|
|
const local = firstPacSourceArtifactDrift({ tasks: [], [maliciousKey]: "expected" }, { tasks: [], [maliciousKey]: "actual" });
|
|
expect(local?.path).not.toContain(maliciousKey);
|
|
expect(local?.path).toContain("type=string,length=");
|
|
|
|
const maliciousInput = { ...runtimeInput(), desiredSpec: { ...desiredSpec, [maliciousKey]: "expected" } };
|
|
const observed = observePacSourceArtifactRuntime(maliciousInput, (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: { ...pipeline(), spec: { ...desiredSpec, [maliciousKey]: "actual" } } }
|
|
: { kind: "ok", value: { items: [pipelineRun("agentrun-nc01-v02-ci-key", "2026-07-11T00:00:00Z", { ...desiredSpec, [maliciousKey]: "actual" })] } });
|
|
expect(JSON.stringify(observed)).not.toContain(maliciousKey);
|
|
expect(pacRuntimeSafePath(`$.${maliciousKey}`)).toBe(`path(${pacValueEvidence(`$.${maliciousKey}`)})`);
|
|
});
|
|
|
|
test("untrusted runtime annotations and identities are never echoed", () => {
|
|
const secret = "Authorization: Bearer OBSERVER_SUPER_SECRET https://u:p@example.invalid/x?token=QUERY_SECRET";
|
|
const annotations = {
|
|
...manifestAnnotations(),
|
|
"unidesk.ai/owning-config-ref": secret,
|
|
"unidesk.ai/effective-config-sha256": secret,
|
|
"unidesk.ai/source-artifact-renderer": secret,
|
|
};
|
|
const observed = observePacSourceArtifactRuntime(runtimeInput(), (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: { metadata: { name: secret, annotations }, spec: desiredSpec } }
|
|
: { kind: "ok", value: { items: [{ ...pipelineRun("agentrun-nc01-v02-ci-annotation", "2026-07-11T00:00:00Z"), metadata: { name: secret, creationTimestamp: "2026-07-11T00:00:00Z", labels: { "pipelinesascode.tekton.dev/sha": sourceCommit }, annotations } }] } });
|
|
const serialized = JSON.stringify(observed);
|
|
expect(observed.live.provenanceAligned).toBe(false);
|
|
expect(observed.live.configRef).toBeNull();
|
|
expect(observed.live.effectiveConfigSha256).toBeNull();
|
|
expect(observed.live.name).toBeNull();
|
|
expect(serialized).not.toContain(secret);
|
|
expect(serialized).not.toContain("OBSERVER_SUPER_SECRET");
|
|
expect(serialized).not.toContain("QUERY_SECRET");
|
|
|
|
const forged = pacRuntimeSourceArtifactItem({
|
|
status: "drifted",
|
|
name: "observer-super-secret",
|
|
canonicalSha256: `sha256:${"c".repeat(64)}`,
|
|
firstDrift: { path: "$.HEADER_QUERY_SUPER_SECRET", expected: secret, actual: secret },
|
|
provenanceAligned: false,
|
|
configRef: "SECRET/TOKEN#QUERY",
|
|
effectiveConfigSha256: `sha256:${"d".repeat(64)}`,
|
|
renderer: provenance.renderer,
|
|
mode: provenance.mode,
|
|
sourceCommit: "e".repeat(40),
|
|
reason: secret,
|
|
candidateCount: 1,
|
|
}, {
|
|
configRef: provenance.configRef,
|
|
effectiveConfigSha256: `sha256:${"f".repeat(64)}`,
|
|
renderer: provenance.renderer,
|
|
mode: provenance.mode,
|
|
sourceCommit,
|
|
exactName: null,
|
|
namePrefix: "agentrun-nc01-v02-ci",
|
|
});
|
|
const forgedSerialized = JSON.stringify(forged);
|
|
expect(forged.name).toBeNull();
|
|
expect(forged.configRef).toBeNull();
|
|
expect(forged.effectiveConfigSha256).toBeNull();
|
|
expect(forged.provenanceAligned).toBe(false);
|
|
expect(forged.sourceCommit).toBeNull();
|
|
expect(forged.firstDrift?.path).not.toContain("HEADER_QUERY_SUPER_SECRET");
|
|
expect(forgedSerialized).not.toContain(secret);
|
|
expect(forgedSerialized).not.toContain("SECRET/TOKEN#QUERY");
|
|
|
|
const forgedAligned = pacRuntimeSourceArtifactItem({
|
|
status: "aligned",
|
|
provenanceAligned: true,
|
|
configRef: provenance.configRef,
|
|
effectiveConfigSha256: provenance.effectiveConfigSha256,
|
|
renderer: "forged-renderer",
|
|
mode: provenance.mode,
|
|
sourceCommit,
|
|
candidateCount: 1,
|
|
}, {
|
|
configRef: provenance.configRef,
|
|
effectiveConfigSha256: provenance.effectiveConfigSha256,
|
|
renderer: provenance.renderer,
|
|
mode: provenance.mode,
|
|
sourceCommit,
|
|
exactName: null,
|
|
namePrefix: null,
|
|
});
|
|
expect(forgedAligned.provenanceAligned).toBe(false);
|
|
});
|
|
|
|
test("typed runtime alignment never reports a synthetic pending state", () => {
|
|
const aligned = observePacSourceArtifactRuntime(runtimeInput(), (_args: string[], label: string) => label === "pipeline-get"
|
|
? { kind: "ok", value: pipeline() }
|
|
: { kind: "ok", value: { items: [pipelineRun("agentrun-nc01-v02-ci-aligned", "2026-07-11T00:00:00Z")] } });
|
|
const drifted = structuredClone(aligned);
|
|
drifted.embedded.status = "drifted";
|
|
const exact = { head: sourceCommit, clean: true, matchesSourceCommit: true };
|
|
expect(sourceArtifactRuntimeAlignment(null, exact, sourceCommit)).toBe("not-requested");
|
|
expect(sourceArtifactRuntimeAlignment(aligned, exact, sourceCommit)).toBe("aligned");
|
|
expect(sourceArtifactRuntimeAlignment(drifted, exact, sourceCommit)).toBe("drifted");
|
|
expect(sourceArtifactRuntimeAlignment(aligned, { ...exact, clean: false }, sourceCommit)).toBe("unavailable");
|
|
const embeddedOnly = { ...structuredClone(aligned), live: { ...aligned.live, status: "missing" as const, provenanceAligned: false } };
|
|
expect(sourceArtifactRuntimeAlignment(embeddedOnly, exact, sourceCommit, "embedded-pipeline-spec")).toBe("aligned");
|
|
expect(sourceArtifactRuntimeAlignment(embeddedOnly, exact, sourceCommit, "remote-pipeline-annotation")).toBe("missing");
|
|
expect(JSON.stringify([aligned, drifted])).not.toContain("pending");
|
|
});
|
|
|
|
test("local transport and remote observer failures retain only bounded evidence", () => {
|
|
const secret = "Authorization: Bearer TRANSPORT_SUPER_SECRET https://review:password@example.invalid/path?token=QUERY_SECRET";
|
|
const localReason = pacRuntimeTransportFailureReason({ exitCode: 126, stdout: secret, stderr: secret });
|
|
expect(localReason).toContain(pacValueEvidence(secret));
|
|
expect(localReason).not.toContain(secret);
|
|
expect(pacRuntimeSafeReason(secret)).toBe(`runtime-observer-reason:${pacValueEvidence(secret)}`);
|
|
|
|
const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-pac-runtime-redaction-test-"));
|
|
try {
|
|
const bin = resolve(temporary, "bin");
|
|
mkdirSync(bin);
|
|
const kubectl = resolve(bin, "kubectl");
|
|
writeFileSync(kubectl, `#!/bin/sh\nprintf '%s\\n' '${secret}' >&2\nexit 126\n`);
|
|
chmodSync(kubectl, 0o755);
|
|
const shell = renderPacSourceArtifactRuntimeObserverShell(runtimeInput());
|
|
const result = spawnSync("sh", [], {
|
|
input: shell,
|
|
encoding: "utf8",
|
|
timeout: 30_000,
|
|
env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}` },
|
|
});
|
|
expect(result.status).toBe(0);
|
|
expect(result.stdout).toContain("command-failed:126:type=string");
|
|
expect(result.stdout).not.toContain(secret);
|
|
expect(result.stdout).not.toContain("Authorization");
|
|
expect(result.stdout).not.toContain("QUERY_SECRET");
|
|
} finally {
|
|
rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("temp-file transport handles a payload larger than the current 203 KiB HWLAB Pipeline", () => {
|
|
const observerSource = readFileSync(rootPath("scripts", "native", "cicd", "pac-source-artifact-runtime.mjs"), "utf8");
|
|
expect(observerSource).not.toMatch(/process\.env\.PAC_SOURCE_ARTIFACT_INPUT(?!_FILE)/u);
|
|
const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-pac-runtime-test-"));
|
|
try {
|
|
const bin = resolve(temporary, "bin");
|
|
mkdirSync(bin);
|
|
const hugeSpec = { params: [], tasks: [{ name: "render", taskSpec: { steps: [{ name: "render", script: "x".repeat(220 * 1024) }] } }] };
|
|
const input = {
|
|
namespace: "hwlab-ci",
|
|
pipeline: "hwlab-nc01-v03-ci-image-publish",
|
|
pipelineRunPrefix: "hwlab-nc01-v03-ci-poll",
|
|
desiredSpec: hugeSpec,
|
|
desiredWrapper: { mode: "embedded-pipeline-spec", executionMode: "pipeline-spec", spec: runtimeWrapperSpec },
|
|
provenance,
|
|
sourceCommit,
|
|
};
|
|
expect(Buffer.byteLength(JSON.stringify(input), "utf8")).toBeGreaterThan(203 * 1024);
|
|
const pipelineFile = resolve(temporary, "pipeline.json");
|
|
const runsFile = resolve(temporary, "runs.json");
|
|
writeFileSync(pipelineFile, JSON.stringify({ metadata: { name: input.pipeline, annotations: manifestAnnotations() }, spec: hugeSpec }));
|
|
writeFileSync(runsFile, JSON.stringify({ items: [pipelineRun("hwlab-nc01-v03-ci-poll-large", "2026-07-11T00:00:00Z", hugeSpec)] }));
|
|
const kubectl = resolve(bin, "kubectl");
|
|
writeFileSync(kubectl, "#!/bin/sh\nset -eu\ncase \"$2\" in pipeline) cat \"$PAC_TEST_PIPELINE\" ;; pipelinerun) cat \"$PAC_TEST_RUNS\" ;; *) exit 2 ;; esac\n");
|
|
chmodSync(kubectl, 0o755);
|
|
const shell = renderPacSourceArtifactRuntimeObserverShell(input);
|
|
expect(shell).toContain("observer_input=$(mktemp)");
|
|
expect(shell).toContain("PAC_SOURCE_ARTIFACT_INPUT_FILE=\"$observer_input\"");
|
|
expect(shell).not.toContain("PAC_SOURCE_ARTIFACT_INPUT=");
|
|
const result = spawnSync("sh", [], {
|
|
input: shell,
|
|
encoding: "utf8",
|
|
timeout: 30_000,
|
|
maxBuffer: 8 * 1024 * 1024,
|
|
env: {
|
|
...process.env,
|
|
PATH: `${bin}:${process.env.PATH ?? ""}`,
|
|
PAC_TEST_PIPELINE: pipelineFile,
|
|
PAC_TEST_RUNS: runsFile,
|
|
},
|
|
});
|
|
expect(result.status).toBe(0);
|
|
const observed = JSON.parse(result.stdout) as Record<string, any>;
|
|
expect(observed.live.status).toBe("aligned");
|
|
expect(observed.embedded.status).toBe("aligned");
|
|
expect(observed.embedded.sourceCommit).toBe(sourceCommit);
|
|
} finally {
|
|
rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("shared HWLAB deploy overlay", () => {
|
|
test("preserves exact undefined/null semantics without mutating its input", () => {
|
|
const document = {
|
|
nodes: { NC01: { keep: "node" } },
|
|
lanes: { v03: {
|
|
keep: "lane",
|
|
externalPostgres: { old: true },
|
|
runtimeStore: { old: true },
|
|
codeAgentRuntime: { old: true },
|
|
gitMirror: { old: true },
|
|
envRecipe: { keep: "recipe", downloadStack: { keep: "download" } },
|
|
} },
|
|
};
|
|
const overlay = {
|
|
nodeId: "NC01",
|
|
lane: "v03",
|
|
gitopsRoot: "deploy/gitops/node/nc01",
|
|
gitUrl: "git@example/HWLAB.git",
|
|
sourceBranch: "v0.3",
|
|
gitopsBranch: "v0.3-gitops",
|
|
runtimeNamespace: "hwlab-v03",
|
|
publicApiUrl: "https://api.example",
|
|
publicWebUrl: "https://web.example",
|
|
catalogPath: "deploy/artifacts/nc01.yaml",
|
|
runtimePath: "deploy/gitops/node/nc01/hwlab-v03",
|
|
observability: { enabled: true },
|
|
dockerProxyHttp: null,
|
|
dockerProxyHttps: null,
|
|
dockerNoProxyList: ["localhost"],
|
|
externalPostgres: null,
|
|
runtimeStore: null,
|
|
codeAgentRuntime: null,
|
|
deployYamlGitMirror: null,
|
|
};
|
|
const rendered = applyNodeRuntimeDeployYamlOverlay(document, overlay);
|
|
expect(document.lanes.v03.externalPostgres).toEqual({ old: true });
|
|
expect(rendered.lanes.v03).not.toHaveProperty("externalPostgres");
|
|
expect(rendered.lanes.v03.runtimeStore).toBeNull();
|
|
expect(rendered.lanes.v03.codeAgentRuntime).toBeNull();
|
|
expect(rendered.lanes.v03.gitMirror).toBeNull();
|
|
expect(rendered.lanes.v03.keep).toBe("lane");
|
|
expect(nodeRuntimeDeployYamlOverlayShellScript().join("\n")).not.toContain(": Record<string");
|
|
});
|
|
|
|
test("the runtime shell helper produces the same deploy.yaml object as the pure renderer", () => {
|
|
const temporary = mkdtempSync(resolve(tmpdir(), "unidesk-pac-overlay-test-"));
|
|
try {
|
|
mkdirSync(resolve(temporary, "deploy"));
|
|
const document = { nodes: { NC01: {} }, lanes: { v03: { envRecipe: { downloadStack: {} } } } };
|
|
const overlay = {
|
|
nodeId: "NC01", lane: "v03", gitopsRoot: "deploy/gitops/node/nc01", gitUrl: "git@example/HWLAB.git",
|
|
sourceBranch: "v0.3", gitopsBranch: "v0.3-gitops", runtimeNamespace: "hwlab-v03",
|
|
publicApiUrl: "https://api.example", publicWebUrl: "https://web.example", catalogPath: "deploy/artifacts/nc01.yaml",
|
|
runtimePath: "deploy/gitops/node/nc01/hwlab-v03", observability: { enabled: true },
|
|
dockerProxyHttp: "http://proxy", dockerProxyHttps: "http://proxy", dockerNoProxyList: ["localhost"],
|
|
externalPostgres: { host: "postgres" }, runtimeStore: { mode: "postgres" }, codeAgentRuntime: { enabled: true },
|
|
deployYamlGitMirror: { readUrl: "http://mirror" },
|
|
};
|
|
writeFileSync(resolve(temporary, "deploy", "deploy.yaml"), Bun.YAML.stringify(document));
|
|
const overlayBase64 = Buffer.from(JSON.stringify(overlay), "utf8").toString("base64");
|
|
const shell = [`overlay_b64='${overlayBase64}'`, ...nodeRuntimeDeployYamlOverlayShellScript()].join("\n");
|
|
const result = spawnSync("sh", [], {
|
|
cwd: temporary,
|
|
input: shell,
|
|
encoding: "utf8",
|
|
timeout: 30_000,
|
|
env: { ...process.env, NODE_PATH: resolve(rootPath(), "node_modules") },
|
|
});
|
|
expect(result.status).toBe(0);
|
|
const shellRendered = Bun.YAML.parse(readFileSync(resolve(temporary, "deploy", "deploy.yaml"), "utf8")) as Record<string, unknown>;
|
|
expect(shellRendered).toEqual(applyNodeRuntimeDeployYamlOverlay(document, overlay));
|
|
} finally {
|
|
rmSync(temporary, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|