250 lines
15 KiB
TypeScript
250 lines
15 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { readFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { resolve } from "node:path";
|
|
|
|
import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
|
|
import { renderAgentRunPipelineManifest } from "./agentrun-manifests";
|
|
import { renderPlatformInfraGiteaDesiredFragments } from "./platform-infra-gitea-desired-fragments";
|
|
import { pacConsumerRbacDesiredIdentity, renderPacConsumerRbacDesiredFragment } from "./platform-infra-pac-consumer-rbac";
|
|
import { parsePacAdmissionContract, readPacAdmissionContract, renderPacAdmissionDesiredFragment } from "./platform-infra-pac-provenance";
|
|
import { pacDebugFixtureDisclosure } from "./platform-infra-pipelines-as-code";
|
|
|
|
const root = resolve(import.meta.dir, "../..");
|
|
const evaluator = createRequire(import.meta.url)("../native/cicd/pac-status-evaluator.cjs") as {
|
|
evaluatePacAdmissionState: (input: Record<string, unknown>) => Record<string, any>;
|
|
evaluatePacStatus: (input: Record<string, unknown>) => Record<string, any>;
|
|
extractPacArtifactEvidence: (records: unknown[], logText: string) => Record<string, any>;
|
|
taskTerminalRecord: (taskRun: Record<string, unknown>) => Record<string, any> | null;
|
|
runPacStatusFixtureChecks: () => { ok: boolean; checks: Array<{ id: string; ok: boolean }> };
|
|
};
|
|
|
|
function documents(value: string): any[] {
|
|
return value.split(/^---\s*$/mu).map((item) => item.trim()).filter(Boolean).map((item) => Bun.YAML.parse(item));
|
|
}
|
|
|
|
test("admission contract rejects malformed required declarations instead of silently downgrading", () => {
|
|
const source = Bun.YAML.parse(readFileSync(resolve(root, "config/platform-infra/pipelines-as-code.yaml"), "utf8")) as any;
|
|
const consumer = source.consumers.find((item: any) => item.deliveryProvenance !== undefined);
|
|
consumer.deliveryProvenance.required = "true";
|
|
expect(() => parsePacAdmissionContract(source)).toThrow("deliveryProvenance.required must be boolean true or false");
|
|
});
|
|
|
|
test("versioned admission desired state protects controller proof, candidate identity, params, and execution SA", () => {
|
|
const contract = readPacAdmissionContract();
|
|
const owningYaml = Bun.YAML.parse(readFileSync(resolve(root, "config/platform-infra/pipelines-as-code.yaml"), "utf8")) as any;
|
|
expect(owningYaml.deliveryProvenance.terminalRoles).toBeUndefined();
|
|
expect(contract.policyName).toEndWith("-v1");
|
|
expect(contract.bindingName).toEndWith("-v1");
|
|
expect(contract.child.enabled).toBe(false);
|
|
expect(contract.consumers.map((item) => item.id)).toEqual(["agentrun-nc01-v02"]);
|
|
const items = documents(renderPacAdmissionDesiredFragment("NC01"));
|
|
expect(items.map((item) => item.kind)).toEqual(["ValidatingAdmissionPolicy", "ValidatingAdmissionPolicyBinding"]);
|
|
const policy = items[0];
|
|
const expressions = policy.spec.validations.map((item: any) => item.expression).join("\n");
|
|
const messages = policy.spec.validations.map((item: any) => item.message).join("\n");
|
|
expect(policy.spec.failurePolicy).toBe("Fail");
|
|
expect(policy.spec.matchConstraints.matchPolicy).toBe("Equivalent");
|
|
expect(items[1].spec.validationActions).toEqual(["Deny"]);
|
|
expect(policy.metadata.annotations["argocd.argoproj.io/sync-wave"]).toBe("0");
|
|
expect(items[1].metadata.annotations["argocd.argoproj.io/sync-wave"]).toBe("1");
|
|
expect(expressions).toContain("request.userInfo.username");
|
|
expect(expressions).toContain("spec.pipelineRef.name");
|
|
expect(expressions).toContain("spec.params == oldObject.spec.params");
|
|
expect(expressions).toContain("object.spec.pipelineRef == oldObject.spec.pipelineRef");
|
|
expect(expressions).toContain("object.spec == oldObject.spec");
|
|
expect(expressions).toContain("taskRunTemplate.serviceAccountName == oldObject.spec.taskRunTemplate.serviceAccountName");
|
|
expect(expressions).toContain("object.metadata.name.startsWith");
|
|
expect(expressions).toContain('object.metadata.labels["tekton.dev/pipeline"]');
|
|
expect(expressions).toContain("agentrun-nc01-v02-tekton-runner");
|
|
expect(messages).toContain("execution ServiceAccount");
|
|
expect(expressions).toContain(contract.child.parentUidAnnotation);
|
|
expect(expressions).toContain("oldObject");
|
|
expect(JSON.stringify(items)).not.toContain('"kind":"ServiceAccount"');
|
|
const specGuard = policy.spec.validations.find((item: any) => item.message.includes("PipelineRun spec is immutable"));
|
|
const oldRun = { spec: { pipelineSpec: { tasks: [{ name: "plan-artifacts" }] }, workspaces: [], timeouts: { pipeline: "1h" } } };
|
|
const mutatedRun = structuredClone(oldRun);
|
|
mutatedRun.spec.pipelineSpec.tasks[0].name = "forged-task";
|
|
expect(specGuard.expression).toMatch(/object\.spec == oldObject\.spec/u);
|
|
expect(mutatedRun.spec).not.toEqual(oldRun.spec);
|
|
});
|
|
|
|
test("required consumer default RBAC has one automatic desired owner and no Tekton mutation verbs", () => {
|
|
const rbac = documents(renderPacConsumerRbacDesiredFragment("NC01"));
|
|
expect(rbac.map((item) => item.kind)).toEqual(["Role", "RoleBinding"]);
|
|
const role = rbac[0];
|
|
expect(role.metadata.annotations["argocd.argoproj.io/sync-wave"]).toBe("-1");
|
|
expect(role.metadata.annotations["unidesk.ai/effective-config-sha256"]).toBe(pacConsumerRbacDesiredIdentity("NC01").configSha256);
|
|
const verbs = role.rules.flatMap((rule: any) => rule.verbs);
|
|
for (const verb of ["create", "patch", "update", "delete", "deletecollection"]) expect(verbs).not.toContain(verb);
|
|
const desiredKinds = documents(renderPlatformInfraGiteaDesiredFragments("NC01")).map((item) => item.kind);
|
|
expect(desiredKinds).toEqual(["Role", "RoleBinding", "ValidatingAdmissionPolicy", "ValidatingAdmissionPolicyBinding"]);
|
|
});
|
|
|
|
test("live admission evaluator requires exact desired hashes, observed generation, default-SA loss, and a new resource epoch", () => {
|
|
const contract = readPacAdmissionContract();
|
|
const admission = documents(renderPacAdmissionDesiredFragment("NC01"));
|
|
const rbac = documents(renderPacConsumerRbacDesiredFragment("NC01"));
|
|
const policy = { ...admission[0], metadata: { ...admission[0].metadata, uid: "policy-uid", generation: 2, creationTimestamp: "2026-01-01T00:00:00Z" }, status: { observedGeneration: 2, typeChecking: { expressionWarnings: [] } } };
|
|
const binding = { ...admission[1], metadata: { ...admission[1].metadata, uid: "binding-uid", creationTimestamp: "2026-01-01T00:00:01Z" } };
|
|
const expected = {
|
|
targetId: "NC01",
|
|
policyName: contract.policyName,
|
|
bindingName: contract.bindingName,
|
|
version: contract.version,
|
|
controllerIdentity: contract.controllerUsername,
|
|
configSha256: policy.metadata.annotations["unidesk.ai/effective-config-sha256"],
|
|
rbacConfigSha256: rbac[0].metadata.annotations["unidesk.ai/effective-config-sha256"],
|
|
};
|
|
const input = { policy, binding, role: rbac[0], roleBinding: rbac[1], namespace: "agentrun-ci", defaultCanMutate: false, expected };
|
|
expect(evaluator.evaluatePacAdmissionState(input)).toMatchObject({ ready: true, activeAfter: "2026-01-01T00:00:01Z", defaultCanMutate: false });
|
|
expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: true })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["default-serviceaccount-can-mutate-tekton-runs"]) });
|
|
expect(evaluator.evaluatePacAdmissionState({ ...input, defaultCanMutate: null })).toMatchObject({ ready: false, defaultCanMutate: null, reasons: expect.arrayContaining(["default-serviceaccount-mutation-probe-unavailable"]) });
|
|
expect(evaluator.evaluatePacAdmissionState({ ...input, role: { ...rbac[0], rules: [rbac[0].rules[0]] } })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["default-role-rules-mismatch"]) });
|
|
expect(evaluator.evaluatePacAdmissionState({ ...input, expected: { ...expected, configSha256: `sha256:${"0".repeat(64)}` } })).toMatchObject({ ready: false, reasons: expect.arrayContaining(["policy-config-sha256-mismatch"]) });
|
|
const mutatedPolicy = {
|
|
...policy,
|
|
spec: {
|
|
...policy.spec,
|
|
validations: policy.spec.validations.map((item: any, index: number) => index === 0 ? { ...item, expression: "true" } : item),
|
|
},
|
|
};
|
|
expect(evaluator.evaluatePacAdmissionState({ ...input, policy: mutatedPolicy })).toMatchObject({
|
|
ready: false,
|
|
reasons: expect.arrayContaining(["admission-live-spec-sha256-mismatch"]),
|
|
});
|
|
expect(evaluator.evaluatePacAdmissionState({
|
|
...input,
|
|
policy: { ...policy, spec: { ...policy.spec, matchConditions: [{ name: "disable-enforcement", expression: "false" }] } },
|
|
})).toMatchObject({ ready: false, reasons: expect.arrayContaining(["admission-live-spec-sha256-mismatch"]) });
|
|
expect(evaluator.evaluatePacAdmissionState({
|
|
...input,
|
|
binding: { ...binding, spec: { ...binding.spec, matchResources: { namespaceSelector: { matchLabels: { disabled: "true" } } } } },
|
|
})).toMatchObject({ ready: false, reasons: expect.arrayContaining(["admission-live-spec-sha256-mismatch"]) });
|
|
});
|
|
|
|
test("AgentRun owning renderer emits ordered terminal roles over the shared workspace without changing build and publish steps", () => {
|
|
const spec = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" }).spec;
|
|
const pipeline = renderAgentRunPipelineManifest(spec) as any;
|
|
const tasks = pipeline.spec.tasks;
|
|
expect(tasks.map((item: any) => item.name)).toEqual(["plan-artifacts", "collect-artifacts", "gitops-promote"]);
|
|
expect(tasks[0].runAfter).toBeUndefined();
|
|
expect(tasks[1].runAfter).toEqual(["plan-artifacts"]);
|
|
expect(tasks[2].runAfter).toEqual(["collect-artifacts"]);
|
|
for (const task of tasks) expect(task.workspaces).toEqual([{ name: "source", workspace: "source" }]);
|
|
expect(tasks[0].taskSpec.steps.map((item: any) => item.name)).toEqual(["source-checkout", "probe-env-image"]);
|
|
expect(tasks[1].taskSpec.steps.map((item: any) => item.name)).toEqual(["build-env-image"]);
|
|
expect(tasks[2].taskSpec.steps.map((item: any) => item.name)).toEqual(["publish-gitops"]);
|
|
expect(tasks[0].taskSpec.steps[1].script).toContain('"event":"pac-delivery-plan"');
|
|
expect(tasks[1].taskSpec.steps[0].script).toContain("buildctl-daemonless.sh");
|
|
expect(tasks[2].taskSpec.steps[0].script).toContain("gitops-publish");
|
|
});
|
|
|
|
test("AgentRun rendered delivery plan and real TaskRun terminals close the status evaluator gate", () => {
|
|
const spec = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" }).spec;
|
|
const pipeline = renderAgentRunPipelineManifest(spec) as any;
|
|
const tasks = pipeline.spec.tasks as any[];
|
|
const sourceCommit = "c".repeat(40);
|
|
const gitopsCommit = "b".repeat(40);
|
|
const digest = `sha256:${"a".repeat(64)}`;
|
|
const planPrintf = String(tasks[0].taskSpec.steps[1].script)
|
|
.split("\n")
|
|
.find((line) => line.startsWith("printf '{\"event\":\"pac-delivery-plan\""));
|
|
expect(planPrintf).toBeDefined();
|
|
const planResult = Bun.spawnSync({
|
|
cmd: ["sh", "-c", `build_services='["agentrun-mgr"]'; reused_services='[]'; ${planPrintf!.replaceAll("$(params.revision)", sourceCommit)}`],
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
expect(planResult.exitCode).toBe(0);
|
|
const plan = JSON.parse(planResult.stdout.toString().trim());
|
|
expect(plan).toMatchObject({ event: "pac-delivery-plan", sourceCommitId: sourceCommit, buildServices: ["agentrun-mgr"] });
|
|
|
|
const terminals = tasks.map((task) => evaluator.taskTerminalRecord({
|
|
apiVersion: "tekton.dev/v1",
|
|
kind: "TaskRun",
|
|
metadata: {
|
|
name: `fixture-${task.name}`,
|
|
labels: { "tekton.dev/pipelineTask": task.name },
|
|
},
|
|
status: {
|
|
conditions: [{ type: "Succeeded", status: "True", reason: "Succeeded" }],
|
|
results: [],
|
|
},
|
|
}));
|
|
expect(terminals).not.toContain(null);
|
|
const publish = {
|
|
ok: true,
|
|
status: "succeeded",
|
|
phase: "gitops-publish",
|
|
gitopsCommit,
|
|
sourceCommit,
|
|
imageStatus: "built",
|
|
digest,
|
|
valuesPrinted: false,
|
|
};
|
|
const artifact = evaluator.extractPacArtifactEvidence([plan, ...terminals, publish], "");
|
|
expect(artifact.sourceObservation).toMatchObject({
|
|
contract: "pac-delivery-plan-v1",
|
|
mode: "delivery",
|
|
valid: true,
|
|
terminal: {
|
|
planArtifacts: "succeeded",
|
|
collectArtifacts: "succeeded",
|
|
gitopsPromote: "succeeded",
|
|
},
|
|
terminalSources: {
|
|
planArtifacts: { markerSource: "pac-delivery-plan-structured-log" },
|
|
},
|
|
});
|
|
expect(evaluator.evaluatePacStatus({
|
|
sourceCommit,
|
|
artifact,
|
|
registryPresent: true,
|
|
argo: {
|
|
sync: "Synced",
|
|
health: "Healthy",
|
|
revision: gitopsCommit,
|
|
revisionRelation: { relation: "exact", expectedRevision: gitopsCommit, observedRevision: gitopsCommit },
|
|
},
|
|
runtime: { image: `registry/agentrun-mgr@${digest}`, digest, replicas: 1, readyReplicas: 1 },
|
|
})).toMatchObject({ ok: true, code: "pac-ready-gitops-exact" });
|
|
});
|
|
|
|
test("provenance fixtures fail closed for pre-bootstrap, forged marker, wrong SA, and policy mismatch", () => {
|
|
const result = evaluator.runPacStatusFixtureChecks();
|
|
expect(result.ok).toBe(true);
|
|
for (const id of ["admission-provenance-verified", "pre-bootstrap-run-fails-closed", "forged-name-candidate-fails-closed", "forged-label-candidate-fails-closed", "forged-pipeline-ref-candidate-fails-closed", "wrong-service-account-fails-closed", "policy-identity-mismatch-fails-closed"]) {
|
|
expect(result.checks.find((item) => item.id === id)?.ok).toBe(true);
|
|
}
|
|
});
|
|
|
|
test("history evaluates admission and default RBAC in each consumer namespace", () => {
|
|
const remote = readFileSync(resolve(root, "scripts/src/platform-infra-pipelines-as-code-remote.sh"), "utf8");
|
|
expect(remote).toContain("consumer.admissionState = admissionStateForConsumer(consumer)");
|
|
expect(remote).toContain("kubectlJson(['-n', consumer.namespace, 'get', 'role', 'unidesk-pac-consumer-runner'");
|
|
expect(remote).toContain("kubectlJson(['-n', consumer.namespace, 'get', 'rolebinding', 'unidesk-pac-consumer-runner-default'");
|
|
expect(remote).toContain("defaultCanMutate(consumer.namespace)");
|
|
expect(remote).toContain("for verb in create patch update delete deletecollection; do");
|
|
expect(remote).toContain('timeout "$UNIDESK_PAC_WAIT_TIMEOUT_SECONDS" kubectl auth can-i');
|
|
expect(remote).toContain("for (const verb of ['create', 'patch', 'update', 'delete', 'deletecollection'])");
|
|
const defaultMutationProbe = remote.slice(remote.indexOf("function defaultCanMutate(namespace)"), remote.indexOf("function admissionStateForConsumer(consumer)"));
|
|
expect(defaultMutationProbe).toContain("timeout: waitTimeoutSeconds * 1000");
|
|
expect(defaultMutationProbe).not.toContain("timeout: 12000");
|
|
expect(remote).toContain("'delivery plan structured evidence is missing'");
|
|
expect(remote).toContain("'delivery plan source commit does not match the selected PipelineRun'");
|
|
expect(remote).not.toContain("'g14-ci-plan structured evidence is missing'");
|
|
expect(remote).not.toContain("for (const consumer of consumers) consumer.admissionState = admissionState");
|
|
});
|
|
|
|
test("id-specific PaC debug keeps fixture failures but does not dump every passing check", () => {
|
|
const passing = Array.from({ length: 23 }, (_, index) => ({ id: `pass-${index}`, ok: true }));
|
|
expect(pacDebugFixtureDisclosure(passing, "agentrun-run")).toMatchObject({
|
|
fixtureGate: { ok: true, total: 23, passed: 23, failed: 0, disclosure: "failed-checks-only-for-id-specific-debug" },
|
|
checks: [],
|
|
});
|
|
const withFailure = [...passing, { id: "failed", ok: false }];
|
|
expect(pacDebugFixtureDisclosure(withFailure, "agentrun-run").checks).toEqual([{ id: "failed", ok: false }]);
|
|
expect(pacDebugFixtureDisclosure(passing, null).checks).toHaveLength(23);
|
|
});
|