578 lines
30 KiB
TypeScript
578 lines
30 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { spawnSync } from "node:child_process";
|
|
import { readFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
|
|
import { runAgentRunCommand, agentRunHelpText } from "./agentrun/entry";
|
|
import { cicdHelp as branchFollowerHelp, runCicdCommand as runBranchFollowerCommand } from "./cicd-branch-follower";
|
|
import { branchFollowerReadOnlyNext } from "./cicd-branch-follower-authority";
|
|
import { cicdGiteaActionsPocHelp, runGiteaActionsPocCommand } from "./cicd-gitea-actions-poc";
|
|
import { unsupported as unsupportedAgentRunCommand } from "./agentrun/utils";
|
|
import {
|
|
PAC_AUTOMATIC_DELIVERY_REFERENCE,
|
|
decideCicdDeliveryMutation,
|
|
gitRepositoryIdentity,
|
|
pacReadOnlyNext,
|
|
readCicdDeliveryAuthorityCatalog,
|
|
resolveCicdDeliveryAuthority,
|
|
resolveCicdDeliveryAuthorityFromCatalog,
|
|
type CicdDeliveryAuthorityCatalog,
|
|
} from "./cicd-delivery-authority";
|
|
import { hwlabNodeHelp } from "./hwlab-node-help";
|
|
import { runHwlabNodeCommand } from "./hwlab-node/entry";
|
|
import {
|
|
guardedSentinelDeliveryAction,
|
|
runWebProbeSentinelCommand,
|
|
webProbeSentinelDeliveryAuthorityFromCatalog,
|
|
} from "./hwlab-node-web-sentinel-cicd";
|
|
import { parseNodeScopedDelegatedOptions } from "./hwlab-node/plan";
|
|
import { parseNodeWebProbeSentinelOptions } from "./hwlab-node/web-probe-observe";
|
|
import { nodeRuntimeVisiblePipelineRunDiagnostics, summarizeNodeRuntimeControlPlaneStatus } from "./hwlab-node/render";
|
|
import { renderControlPlaneResult, renderPublishCurrentResult } from "./hwlab-node-web-sentinel-cicd-shared";
|
|
import { resolvePacHistoryConsumerSelection, runPlatformInfraPipelinesAsCodeCommand } from "./platform-infra-pipelines-as-code";
|
|
import { giteaHelp, runPlatformInfraGiteaCommand } from "./platform-infra-gitea";
|
|
import { platformInfraHelp } from "./platform-infra/entry";
|
|
import {
|
|
authorizeSentinelPacInternalPublish,
|
|
readSentinelPacInternalPublishCapability,
|
|
type SentinelPacInternalPublishCapability,
|
|
} from "./sentinel-pac-execution-context";
|
|
import type { BranchFollowerRegistry, FollowerSpec, ParsedOptions } from "./cicd-types";
|
|
|
|
const mutationCommand = /\b(?:apply|closeout|trigger-current|refresh|sync|flush|webhook-test)\b/u;
|
|
const pacEvaluator = createRequire(import.meta.url)("../native/cicd/pac-status-evaluator.cjs") as {
|
|
classifyPacPipelineRun: (consumer: Record<string, unknown>, item: Record<string, unknown>) => Record<string, unknown>;
|
|
};
|
|
|
|
describe("YAML 组合的 CI/CD delivery authority", () => {
|
|
test("组合 PaC 与 Gitea YAML,覆盖所有实际 consumer 且 id 唯一", () => {
|
|
const catalog = readCicdDeliveryAuthorityCatalog();
|
|
expect(catalog.consumers).toHaveLength(8);
|
|
expect(new Set(catalog.consumers.map((item) => item.consumerId)).size).toBe(8);
|
|
for (const consumerId of ["agentrun-nc01-v02", "hwlab-nc01-v03", "sentinel-nc01-v03", "unidesk-host", "platform-infra-gitea-nc01"]) {
|
|
const authority = resolveCicdDeliveryAuthority({ consumerId });
|
|
expect(authority.kind).toBe("pac-pr-merge");
|
|
expect(authority.mutationHintsAllowed).toBe(false);
|
|
}
|
|
});
|
|
|
|
test("repository identity 从通用 Git remote 解析,不依赖仓库特例", () => {
|
|
expect(gitRepositoryIdentity("git@github.com:acme/widgets.git")).toBe("acme/widgets");
|
|
expect(gitRepositoryIdentity("https://example.test/acme/widgets.git")).toBe("acme/widgets");
|
|
expect(gitRepositoryIdentity("not-a-repository")).toBeNull();
|
|
});
|
|
|
|
test("未知、冲突和歧义 authority 均 fail-closed", () => {
|
|
expect(resolveCicdDeliveryAuthority({ node: "NC01", lane: "v03" })).toMatchObject({
|
|
kind: "unknown",
|
|
reason: "authority-ambiguous",
|
|
mutationHintsAllowed: false,
|
|
});
|
|
expect(resolveCicdDeliveryAuthority({ consumerId: "hwlab-nc01-v03", node: "JD01" })).toMatchObject({
|
|
kind: "unknown",
|
|
reason: "authority-identity-mismatch",
|
|
mutationHintsAllowed: false,
|
|
});
|
|
expect(resolveCicdDeliveryAuthority({ declaredSourceAuthorityMode: "giteaSnapshot", declaredConfigRef: "fixture.yaml" })).toMatchObject({
|
|
kind: "unknown",
|
|
reason: "authority-not-declared",
|
|
mutationHintsAllowed: false,
|
|
});
|
|
expect(resolveCicdDeliveryAuthority({
|
|
node: "NC01",
|
|
lane: "nc01-v02",
|
|
sourceRepository: "acme/drifted",
|
|
sourceBranch: "v0.2",
|
|
declaredSourceAuthorityMode: "gitMirrorSnapshot",
|
|
declaredConfigRef: "config/agentrun.yaml",
|
|
})).toMatchObject({
|
|
kind: "unknown",
|
|
reason: "authority-identity-mismatch",
|
|
mutationHintsAllowed: false,
|
|
});
|
|
});
|
|
|
|
test("只有显式 legacy-manual authority 允许旧 mutation 实现", () => {
|
|
const catalog: CicdDeliveryAuthorityCatalog = {
|
|
consumers: [{
|
|
consumerId: "arbitrary-pac",
|
|
node: "NODE-A",
|
|
lane: "lane-a",
|
|
namespace: "ci-a",
|
|
pipelineRunPrefix: "widgets-a-",
|
|
repositoryRef: "repo-a",
|
|
sourceRepository: "acme/widgets",
|
|
sourceBranch: "main",
|
|
sourceSnapshotPrefix: "refs/unidesk/snapshots/widgets/main",
|
|
giteaOwner: "mirrors",
|
|
giteaRepository: "acme-widgets",
|
|
giteaRepositoryUrl: "https://gitea.example.test/mirrors/acme-widgets",
|
|
pacControllerVersion: "v0.48.0",
|
|
configRefs: ["pac.yaml", "gitea.yaml"],
|
|
}],
|
|
configRefs: ["pac.yaml", "gitea.yaml"],
|
|
};
|
|
const legacy = resolveCicdDeliveryAuthorityFromCatalog(catalog, {
|
|
node: "NODE-B",
|
|
lane: "lane-b",
|
|
sourceRepository: "acme/legacy",
|
|
sourceBranch: "release",
|
|
declaredSourceAuthorityMode: "gitMirrorSnapshot",
|
|
declaredConfigRef: "legacy-lanes.yaml",
|
|
});
|
|
expect(legacy).toMatchObject({ kind: "legacy-manual", mutationHintsAllowed: true });
|
|
expect(decideCicdDeliveryMutation(legacy, {
|
|
command: "fixture trigger-current",
|
|
statusCommand: "fixture status",
|
|
action: "trigger-current",
|
|
node: "NODE-B",
|
|
lane: "lane-b",
|
|
})).toMatchObject({ allowed: true });
|
|
});
|
|
|
|
test("只读 Next 只有 status/history 和稳定自动链修复引用", () => {
|
|
const next = pacReadOnlyNext("NC01", "hwlab-nc01-v03");
|
|
expect(Object.keys(next).sort()).toEqual(["fixAutomaticDelivery", "history", "status", "valuesPrinted"]);
|
|
expect(JSON.stringify(next)).not.toMatch(mutationCommand);
|
|
expect(PAC_AUTOMATIC_DELIVERY_REFERENCE).toBe("docs/reference/platform-infra.md#gitea-与-pipelines-as-code-边界");
|
|
expect(PAC_AUTOMATIC_DELIVERY_REFERENCE).not.toContain("issues/");
|
|
});
|
|
});
|
|
|
|
describe("migrated CLI 执行 guard 与提示清理", () => {
|
|
test("AgentRun migrated trigger/refresh/mirror mutation 在远端调用前结构化拒绝", async () => {
|
|
const commands = [
|
|
["control-plane", "trigger-current", "--node", "NC01", "--lane", "nc01-v02", "--confirm"],
|
|
["control-plane", "refresh", "--node", "NC01", "--lane", "nc01-v02", "--confirm"],
|
|
["git-mirror", "sync", "--node", "NC01", "--lane", "nc01-v02", "--confirm", "--wait"],
|
|
["git-mirror", "flush", "--node", "NC01", "--lane", "nc01-v02", "--confirm", "--wait"],
|
|
];
|
|
for (const args of commands) {
|
|
const result = await runAgentRunCommand({} as never, args) as Record<string, unknown>;
|
|
expect(result).toMatchObject({ ok: false, mode: "pac-pr-merge-mutation-blocked", mutation: false });
|
|
expect(JSON.stringify(result.next)).not.toMatch(mutationCommand);
|
|
}
|
|
});
|
|
|
|
test("HWLAB migrated delivery mutation 在异步 job 创建前结构化拒绝", async () => {
|
|
const commands = [
|
|
["control-plane", "trigger-current", "--node", "NC01", "--lane", "v03", "--confirm"],
|
|
["control-plane", "refresh", "--node", "NC01", "--lane", "v03", "--confirm"],
|
|
["control-plane", "sync", "--node", "NC01", "--lane", "v03", "--confirm"],
|
|
["control-plane", "source-workspace", "sync", "--node", "NC01", "--lane", "v03", "--confirm"],
|
|
["git-mirror", "sync", "--node", "NC01", "--lane", "v03", "--confirm", "--wait"],
|
|
["git-mirror", "flush", "--node", "NC01", "--lane", "v03", "--confirm", "--wait"],
|
|
];
|
|
for (const args of commands) {
|
|
const result = await runHwlabNodeCommand({} as never, args) as Record<string, unknown>;
|
|
expect(result).toMatchObject({ ok: false, mode: "pac-pr-merge-mutation-blocked", mutation: false });
|
|
expect(JSON.stringify(result.next)).not.toMatch(mutationCommand);
|
|
}
|
|
});
|
|
|
|
test("Web sentinel migrated delivery mutation 在 state load 和异步 job 前拒绝", () => {
|
|
const spec = parseNodeScopedDelegatedOptions("control-plane", ["status", "--node", "NC01", "--lane", "v03"]).spec;
|
|
const sourceOverride = { sourceCommit: null, sourceStageRef: null, sourceMirrorCommit: null, sourceAuthority: null };
|
|
const commands = [
|
|
{ kind: "image", action: "build", node: "NC01", lane: "v03", sentinelId: null, dryRun: true, confirm: false, wait: false, timeoutSeconds: 30, sourceOverride },
|
|
{ kind: "control-plane", action: "trigger-current", node: "NC01", lane: "v03", sentinelId: null, dryRun: true, confirm: false, wait: false, timeoutSeconds: 30, rerun: false, manualRecovery: false, reason: null, sourceOverride },
|
|
{ kind: "publish", action: "publish-current", node: "NC01", lane: "v03", sentinelId: null, dryRun: true, confirm: false, wait: false, timeoutSeconds: 30, rerun: false, manualRecovery: true, reason: "explicit recovery", sourceOverride },
|
|
];
|
|
for (const options of commands) {
|
|
const result = runWebProbeSentinelCommand(spec, options as never) as unknown as Record<string, unknown>;
|
|
expect(result).toMatchObject({ ok: false, mode: "pac-pr-merge-mutation-blocked", mutation: false });
|
|
expect(Object.keys(result.next as Record<string, unknown>).sort()).toEqual([
|
|
"controlPlaneStatus",
|
|
"fixAutomaticDelivery",
|
|
"history",
|
|
"pacHistory",
|
|
"pacStatus",
|
|
"status",
|
|
]);
|
|
expect(JSON.stringify(result.next)).not.toMatch(mutationCommand);
|
|
}
|
|
});
|
|
|
|
test("Web sentinel 稳定 PaC consumer 漂移或缺失时保持 unknown,不降级为 legacy", () => {
|
|
const spec = parseNodeScopedDelegatedOptions("control-plane", ["status", "--node", "NC01", "--lane", "v03"]).spec;
|
|
const catalog = readCicdDeliveryAuthorityCatalog();
|
|
const consumerId = "sentinel-nc01-v03";
|
|
const drifted: CicdDeliveryAuthorityCatalog = {
|
|
...catalog,
|
|
consumers: catalog.consumers.map((consumer) => consumer.consumerId === consumerId
|
|
? { ...consumer, node: "DRIFTED" }
|
|
: consumer),
|
|
};
|
|
const missing: CicdDeliveryAuthorityCatalog = {
|
|
...catalog,
|
|
consumers: catalog.consumers.filter((consumer) => consumer.consumerId !== consumerId),
|
|
};
|
|
for (const fixture of [drifted, missing]) {
|
|
const authority = webProbeSentinelDeliveryAuthorityFromCatalog(spec, fixture);
|
|
expect(authority).toMatchObject({ kind: "unknown", mutationHintsAllowed: false });
|
|
const decision = decideCicdDeliveryMutation(authority, {
|
|
command: "web-probe sentinel publish-current",
|
|
statusCommand: "web-probe sentinel control-plane status",
|
|
action: "sentinel-publish-current",
|
|
node: spec.nodeId,
|
|
lane: spec.lane,
|
|
});
|
|
expect(decision).toMatchObject({ allowed: false });
|
|
if (!decision.allowed) expect(JSON.stringify(decision.result.next)).not.toMatch(mutationCommand);
|
|
}
|
|
});
|
|
|
|
test("Web sentinel 内部 publish capability 默认关闭,启用后仍因 admission provenance 缺失而拒绝", () => {
|
|
const authority = resolveCicdDeliveryAuthority({ consumerId: "sentinel-nc01-v03" });
|
|
expect(authority.kind).toBe("pac-pr-merge");
|
|
const options = {
|
|
kind: "publish",
|
|
action: "publish-current",
|
|
node: "NC01",
|
|
lane: "v03",
|
|
sentinelId: "nc01-web-probe-sentinel",
|
|
dryRun: false,
|
|
confirm: true,
|
|
wait: true,
|
|
timeoutSeconds: 180,
|
|
rerun: false,
|
|
manualRecovery: false,
|
|
reason: null,
|
|
internalExecution: "pac-controller",
|
|
sourceOverride: { sourceCommit: "a".repeat(40), sourceStageRef: null, sourceMirrorCommit: null, sourceAuthority: "gitea-snapshot" },
|
|
} as const;
|
|
const capability = readSentinelPacInternalPublishCapability();
|
|
expect(capability).toMatchObject({ enabled: false, admissionProvenance: "unavailable" });
|
|
expect(authorizeSentinelPacInternalPublish(options, authority, capability)).toMatchObject({
|
|
allowed: false,
|
|
reason: "capability-disabled",
|
|
});
|
|
const enabled: SentinelPacInternalPublishCapability = { ...capability, enabled: true };
|
|
expect(authorizeSentinelPacInternalPublish(options, authority, enabled)).toMatchObject({
|
|
allowed: false,
|
|
reason: "admission-provenance-unavailable",
|
|
});
|
|
expect(authorizeSentinelPacInternalPublish({ ...options, internalExecution: null }, authority, enabled)).toMatchObject({
|
|
allowed: false,
|
|
reason: "operator-entrypoint",
|
|
});
|
|
});
|
|
|
|
test("Web sentinel 保持旧自动入口,不把 default SA、Pod env 或可写 metadata 当 creator proof", () => {
|
|
const pacYaml = readFileSync("config/platform-infra/pipelines-as-code.yaml", "utf8");
|
|
expect(pacYaml).toContain("sentinelInternalPublish:");
|
|
expect(pacYaml).toContain("enabled: false");
|
|
expect(pacYaml).toContain("issues/1769");
|
|
expect(pacYaml).toContain("service_account: default");
|
|
for (const node of ["jd01", "nc01"] as const) {
|
|
const source = readFileSync(`.tekton/web-probe-sentinel-${node}-pac.yaml`, "utf8");
|
|
expect(source).toContain("serviceAccountName: default");
|
|
expect(source).toContain("web-probe sentinel publish-current \\");
|
|
expect(source).not.toContain("pac-publish-current");
|
|
expect(source).not.toContain("UNIDESK_PAC_INTERNAL_ENTRYPOINT");
|
|
expect(source).not.toContain("UNIDESK_PAC_POD_UID");
|
|
}
|
|
const parsed = parseNodeWebProbeSentinelOptions([
|
|
"pac-publish-current",
|
|
"--node", "NC01",
|
|
"--lane", "v03",
|
|
"--sentinel", "nc01-web-probe-sentinel",
|
|
"--confirm",
|
|
"--wait",
|
|
"--source-commit", "a".repeat(40),
|
|
"--source-stage-ref", `refs/unidesk/snapshots/gitea-actions/unidesk-master-nc01/${"a".repeat(40)}`,
|
|
"--source-authority", "gitea-snapshot",
|
|
]);
|
|
expect(parsed.sentinel).toMatchObject({ kind: "publish", internalExecution: "pac-controller", manualRecovery: false });
|
|
});
|
|
|
|
test("PaC 外层事件与内层 deterministic publish 分开归类,只有外层可驱动 delivery", () => {
|
|
const consumer = { repository: "sentinel-nc01-v03", pipelineRunPrefix: "hwlab-web-probe-sentinel-nc01-", pipeline: "hwlab-web-probe-sentinel-nc01" };
|
|
const outer = {
|
|
metadata: {
|
|
name: "hwlab-web-probe-sentinel-nc01-9h49j",
|
|
labels: {
|
|
"app.kubernetes.io/managed-by": "pipelinesascode.tekton.dev",
|
|
"pipelinesascode.tekton.dev/event-type": "push",
|
|
"pipelinesascode.tekton.dev/repository": "sentinel-nc01-v03",
|
|
},
|
|
annotations: { "pipelinesascode.tekton.dev/git-provider": "gitea" },
|
|
},
|
|
};
|
|
const inner = {
|
|
metadata: {
|
|
name: "hwlab-web-probe-sentinel-nc01-web-probe-sentinel-b125fb358d46",
|
|
labels: {
|
|
"app.kubernetes.io/part-of": "hwlab-web-probe-sentinel",
|
|
"unidesk.ai/ci-system": "tekton",
|
|
},
|
|
annotations: {
|
|
"unidesk.ai/source-authority": "gitea-snapshot",
|
|
"unidesk.ai/publish-gitops": "true",
|
|
},
|
|
},
|
|
};
|
|
expect(pacEvaluator.classifyPacPipelineRun(consumer, outer)).toMatchObject({
|
|
deliveryClass: "outer-pac-event",
|
|
deliveryAuthorityEligible: true,
|
|
admissionProvenanceVerified: false,
|
|
metadataObservationOnly: true,
|
|
});
|
|
expect(pacEvaluator.classifyPacPipelineRun(consumer, inner)).toMatchObject({
|
|
deliveryClass: "inner-deterministic-publish",
|
|
deliveryAuthorityEligible: false,
|
|
parentPipelineRun: null,
|
|
parentRelation: "unproven",
|
|
});
|
|
|
|
const catalog = readCicdDeliveryAuthorityCatalog();
|
|
for (const consumerId of ["agentrun-nc01-v02", "hwlab-nc01-v03", "sentinel-nc01-v03", "unidesk-host"]) {
|
|
const configured = catalog.consumers.find((item) => item.consumerId === consumerId);
|
|
if (configured === undefined) throw new Error(`missing classification fixture ${consumerId}`);
|
|
const classified = pacEvaluator.classifyPacPipelineRun({
|
|
repository: configured.repositoryRef,
|
|
pipelineRunPrefix: configured.pipelineRunPrefix,
|
|
}, {
|
|
metadata: {
|
|
name: `${configured.pipelineRunPrefix}-outer-fixture`,
|
|
labels: {
|
|
"app.kubernetes.io/managed-by": "pipelinesascode.tekton.dev",
|
|
"pipelinesascode.tekton.dev/event-type": "push",
|
|
"pipelinesascode.tekton.dev/repository": configured.repositoryRef,
|
|
},
|
|
annotations: { "pipelinesascode.tekton.dev/git-provider": "gitea" },
|
|
},
|
|
});
|
|
expect(classified).toMatchObject({ deliveryClass: "outer-pac-event", deliveryAuthorityEligible: true });
|
|
}
|
|
});
|
|
|
|
test("Web sentinel 业务观测和仪表盘动作不被 delivery guard 误阻断", () => {
|
|
for (const options of [
|
|
{ kind: "dashboard", action: "trigger" },
|
|
{ kind: "validate", action: "validate" },
|
|
{ kind: "maintenance", action: "status" },
|
|
{ kind: "control-plane", action: "status" },
|
|
{ kind: "image", action: "status" },
|
|
]) {
|
|
expect(guardedSentinelDeliveryAction(options as never)).toBeNull();
|
|
}
|
|
});
|
|
|
|
test("HWLAB degraded summary 不再返回 flush 或 triggerCurrent", () => {
|
|
const scoped = parseNodeScopedDelegatedOptions("control-plane", ["status", "--node", "NC01", "--lane", "v03"]);
|
|
const summary = summarizeNodeRuntimeControlPlaneStatus({ ok: false, degradedReason: "git-mirror-pending-flush" }, scoped);
|
|
expect(summary.nextAction).toBe("bun scripts/cli.ts platform-infra pipelines-as-code status --target NC01 --consumer hwlab-nc01-v03");
|
|
expect(summary.next).not.toHaveProperty("triggerCurrent");
|
|
expect(summary.next).not.toHaveProperty("gitMirrorFlush");
|
|
expect(JSON.stringify(summary.next)).not.toMatch(mutationCommand);
|
|
});
|
|
|
|
test("HWLAB migrated PipelineRun diagnostics 不泄漏 rerun Next 或文本提示", () => {
|
|
const authority = resolveCicdDeliveryAuthority({ consumerId: "hwlab-nc01-v03" });
|
|
const diagnostics = nodeRuntimeVisiblePipelineRunDiagnostics({
|
|
failureSummary: { nextAction: "choose controlled rerun" },
|
|
next: { rerun: "bun scripts/cli.ts hwlab nodes control-plane trigger-current --confirm --rerun" },
|
|
}, authority);
|
|
expect(diagnostics?.next).toBeNull();
|
|
expect(JSON.stringify(diagnostics)).not.toContain("trigger-current");
|
|
expect(JSON.stringify(diagnostics)).not.toContain("controlled rerun");
|
|
expect(JSON.stringify(diagnostics)).toContain("不得人工 rerun");
|
|
});
|
|
|
|
test("默认 help 只暴露观察入口,legacy/platform 写入口只在 scoped help", async () => {
|
|
const agentRunDefault = agentRunHelpText(["control-plane", "--help"]);
|
|
const hwlabDefault = JSON.stringify(hwlabNodeHelp());
|
|
const pacDefault = JSON.stringify(await runPlatformInfraPipelinesAsCodeCommand({} as never, ["help"]));
|
|
const giteaDefault = JSON.stringify(giteaHelp());
|
|
expect(agentRunDefault).not.toMatch(mutationCommand);
|
|
expect(hwlabDefault).not.toMatch(mutationCommand);
|
|
expect(pacDefault).not.toMatch(mutationCommand);
|
|
expect(giteaDefault).not.toMatch(mutationCommand);
|
|
expect(agentRunHelpText(["control-plane", "legacy-cicd", "--help"])).toContain("trigger-current");
|
|
expect(JSON.stringify(hwlabNodeHelp("legacy-cicd"))).toContain("git-mirror flush");
|
|
expect(JSON.stringify(await runPlatformInfraPipelinesAsCodeCommand({} as never, ["help", "platform-bootstrap"]))).toContain("pipelines-as-code apply");
|
|
expect(JSON.stringify(giteaHelp("platform-bootstrap"))).toContain("platform-infra gitea apply");
|
|
});
|
|
|
|
test("所有 PaC consumer 与 AgentRun/HWLAB plan 均只返回 authority-aware 只读 Next", async () => {
|
|
const results = [
|
|
await runAgentRunCommand({} as never, ["control-plane", "plan", "--node", "NC01", "--lane", "nc01-v02", "--raw"]),
|
|
await runHwlabNodeCommand({} as never, ["control-plane", "plan", "--node", "NC01", "--lane", "v03", "--raw"]),
|
|
...await Promise.all(readCicdDeliveryAuthorityCatalog().consumers.map((consumer) => (
|
|
runPlatformInfraPipelinesAsCodeCommand({} as never, ["plan", "--target", consumer.node, "--consumer", consumer.consumerId, "--raw"])
|
|
))),
|
|
] as Record<string, unknown>[];
|
|
for (const result of results) {
|
|
const next = result.next as Record<string, unknown>;
|
|
expect(Object.keys(next).sort()).toEqual(["fixAutomaticDelivery", "history", "status", "valuesPrinted"]);
|
|
expect(JSON.stringify(next)).not.toMatch(mutationCommand);
|
|
}
|
|
});
|
|
|
|
test("Gitea 与 mirror 默认 plan 只返回只读 Next", async () => {
|
|
const plans = [
|
|
await runPlatformInfraGiteaCommand({} as never, ["plan", "--target", "NC01", "--raw"]),
|
|
await runPlatformInfraGiteaCommand({} as never, ["mirror", "plan", "--target", "NC01", "--raw"]),
|
|
] as Record<string, unknown>[];
|
|
for (const plan of plans) {
|
|
expect(plan.mutation).toBe(false);
|
|
expect(JSON.stringify(plan.next)).not.toMatch(mutationCommand);
|
|
expect(JSON.stringify(plan.next)).toContain("status");
|
|
expect(JSON.stringify(plan.next)).toContain(PAC_AUTOMATIC_DELIVERY_REFERENCE);
|
|
}
|
|
});
|
|
|
|
test("PaC history detail 只按唯一 consumer 生成 Next,四类前缀无默认 consumer 回退", () => {
|
|
const ids = ["sentinel-nc01-v03", "unidesk-host", "agentrun-nc01-v02", "hwlab-nc01-v03"];
|
|
const consumers = readCicdDeliveryAuthorityCatalog().consumers.filter((consumer) => ids.includes(consumer.consumerId)).map((consumer) => ({
|
|
id: consumer.consumerId,
|
|
node: consumer.node,
|
|
pipelineRunPrefix: consumer.pipelineRunPrefix,
|
|
}));
|
|
for (const consumer of consumers) {
|
|
const detailId = `${consumer.pipelineRunPrefix}-fixture`;
|
|
expect(resolvePacHistoryConsumerSelection(consumers, "NC01", null, detailId)).toEqual({
|
|
selectedConsumerIds: [consumer.id],
|
|
nextConsumerId: consumer.id,
|
|
});
|
|
}
|
|
expect(() => resolvePacHistoryConsumerSelection(consumers, "NC01", null, "no-matching-pipelinerun")).toThrow("matched 0");
|
|
expect(() => resolvePacHistoryConsumerSelection([
|
|
{ id: "a", node: "NC01", pipelineRunPrefix: "overlap-" },
|
|
{ id: "b", node: "NC01", pipelineRunPrefix: "overlap-run" },
|
|
], "NC01", null, "overlap-run-1")).toThrow("matched 2");
|
|
expect(() => resolvePacHistoryConsumerSelection(consumers, "NC01", "hwlab-nc01-v03", "hwlab-web-probe-sentinel-nc01-fixture")).toThrow("belongs to sentinel-nc01-v03");
|
|
});
|
|
|
|
test("PaC、HWLAB scoped help 的真实 CLI 路由可达且默认入口不泄漏 delivery mutation", () => {
|
|
const pacDefault = runCliJson(["platform-infra", "pipelines-as-code", "--help"]);
|
|
const pacBootstrap = runCliJson(["platform-infra", "pipelines-as-code", "help", "platform-bootstrap"]);
|
|
const pacCompatibility = runCliJson(["platform-infra", "pipelines-as-code", "help", "compatibility-diagnostics"]);
|
|
const hwlabPlatform = runCliJson(["hwlab", "nodes", "control-plane", "platform-maintenance", "--help"]);
|
|
const hwlabLegacy = runCliJson(["hwlab", "nodes", "control-plane", "legacy-cicd", "--help"]);
|
|
expect(JSON.stringify(pacDefault.data)).not.toMatch(mutationCommand);
|
|
expect(JSON.stringify(pacBootstrap.data)).toContain("pipelines-as-code apply");
|
|
expect(JSON.stringify(pacCompatibility.data)).not.toMatch(/webhook-test|trigger-current|\bsync\b|\bflush\b|apply --/u);
|
|
expect((hwlabPlatform.data as Record<string, unknown>).command).toBe("hwlab nodes control-plane platform-maintenance");
|
|
expect((hwlabLegacy.data as Record<string, unknown>).command).toBe("hwlab nodes control-plane legacy-cicd");
|
|
});
|
|
|
|
test("PaC webhook-test 已从公开路由与远端执行器移除", async () => {
|
|
const result = await runPlatformInfraPipelinesAsCodeCommand({} as never, ["webhook-test", "--target", "NC01", "--confirm"]) as Record<string, unknown>;
|
|
expect(result).toMatchObject({ ok: false, error: "unsupported-platform-infra-pipelines-as-code-command" });
|
|
expect(JSON.stringify(result.help)).not.toContain("webhook-test");
|
|
expect(readFileSync("scripts/src/platform-infra-pipelines-as-code-remote.sh", "utf8")).not.toContain("webhook-test");
|
|
});
|
|
|
|
test("Gitea webhook-test 已从公开路由与远端执行器移除", async () => {
|
|
const result = await runPlatformInfraGiteaCommand({} as never, ["mirror", "webhook", "test", "--target", "NC01", "--confirm"]) as Record<string, unknown>;
|
|
expect(result).toMatchObject({ ok: false, error: "unsupported-platform-infra-gitea-mirror-webhook-command" });
|
|
expect(readFileSync("scripts/src/platform-infra-gitea-remote.sh", "utf8")).not.toContain("mirror-webhook-test");
|
|
});
|
|
|
|
test("Sentinel migrated 文本输出只渲染实际只读 status/history/fix,不打印空 mutation 标签", () => {
|
|
const next = {
|
|
status: "bun scripts/cli.ts web-probe sentinel control-plane status --node NC01 --lane v03",
|
|
history: "bun scripts/cli.ts platform-infra pipelines-as-code history --target NC01 --consumer sentinel-nc01-v03",
|
|
fixAutomaticDelivery: PAC_AUTOMATIC_DELIVERY_REFERENCE,
|
|
};
|
|
for (const renderedText of [
|
|
renderPublishCurrentResult({ command: "fixture publish", ok: false, next }),
|
|
renderControlPlaneResult({ command: "fixture status", ok: false, next }),
|
|
]) {
|
|
expect(renderedText).toContain("status:");
|
|
expect(renderedText).toContain("history:");
|
|
expect(renderedText).toContain("fix-automatic-delivery:");
|
|
expect(renderedText).not.toMatch(/pac-closeout:|manual-recovery:|trigger-current:|apply:|sync:|flush:/u);
|
|
}
|
|
});
|
|
|
|
test("retired branch-follower 写操作和默认 cleanup 在任何远端执行前结构化拒绝", async () => {
|
|
const commands = [
|
|
["branch-follower", "apply", "--confirm", "--json"],
|
|
["branch-follower", "run-once", "--follower", "hwlab-jd01-v03", "--confirm", "--wait", "--json"],
|
|
["branch-follower", "job", "--follower", "agentrun-jd01-v02", "--job", "image-build", "--json"],
|
|
["branch-follower", "gate", "--follower", "hwlab-jd01-v03", "--gate", "runtime-closeout", "--confirm", "--json"],
|
|
["branch-follower", "debug-step", "--follower", "web-probe-sentinel-master", "--step", "state-write", "--confirm", "--json"],
|
|
["branch-follower", "cleanup-state", "--all", "--confirm", "--json"],
|
|
];
|
|
for (const args of commands) {
|
|
const rendered = await runBranchFollowerCommand(null, args);
|
|
const payload = JSON.parse(rendered.renderedText) as Record<string, unknown>;
|
|
expect(payload).toMatchObject({ ok: false, mutation: false });
|
|
expect(JSON.stringify(payload.next)).not.toMatch(mutationCommand);
|
|
}
|
|
const forged = await runBranchFollowerCommand(null, ["branch-follower", "legacy-cicd", "apply", "--config", "/tmp/forged-legacy.yaml", "--confirm", "--json"]);
|
|
expect(JSON.parse(forged.renderedText)).toMatchObject({
|
|
ok: false,
|
|
mutation: false,
|
|
mode: "noncanonical-branch-follower-config-mutation-blocked",
|
|
});
|
|
expect(Object.keys((JSON.parse(forged.renderedText) as { next: Record<string, unknown> }).next).sort()).toEqual([
|
|
"fixAutomaticDelivery",
|
|
"status",
|
|
"valuesPrinted",
|
|
]);
|
|
await expect(runBranchFollowerCommand(null, ["branch-follower", "plan", "--json"])).rejects.toThrow("default surface is read-only");
|
|
});
|
|
|
|
test("branch-follower unknown 与多 node scope 不猜测 JD01 PaC 入口", () => {
|
|
const options = { followerId: null } as ParsedOptions;
|
|
const unknownRegistry = {
|
|
followers: [{
|
|
id: "unknown-fixture",
|
|
deliveryAuthority: "unknown",
|
|
source: { repository: "acme/unknown", branch: "main" },
|
|
target: { node: "NODE-X", lane: "lane-x" },
|
|
} as FollowerSpec],
|
|
} as BranchFollowerRegistry;
|
|
const unknownNext = branchFollowerReadOnlyNext(unknownRegistry, options);
|
|
expect(Object.keys(unknownNext).sort()).toEqual(["events", "fixAutomaticDelivery", "logs", "status", "valuesPrinted"]);
|
|
expect(JSON.stringify(unknownNext)).not.toContain("JD01");
|
|
|
|
const catalog = readCicdDeliveryAuthorityCatalog();
|
|
const consumers = ["hwlab-jd01-v03", "hwlab-nc01-v03"].map((id) => {
|
|
const consumer = catalog.consumers.find((item) => item.consumerId === id);
|
|
if (consumer === undefined) throw new Error(`missing fixture consumer ${id}`);
|
|
return {
|
|
id: `${id}-retired-follower`,
|
|
deliveryAuthority: "retired-readonly",
|
|
source: { repository: consumer.sourceRepository, branch: consumer.sourceBranch },
|
|
target: { node: consumer.node, lane: consumer.lane },
|
|
} as FollowerSpec;
|
|
});
|
|
const multiNodeNext = branchFollowerReadOnlyNext({ followers: consumers } as BranchFollowerRegistry, options);
|
|
expect(Object.keys(multiNodeNext).sort()).toEqual(["events", "fixAutomaticDelivery", "logs", "status", "valuesPrinted"]);
|
|
expect(JSON.stringify(multiNodeNext)).not.toContain("--target JD01");
|
|
});
|
|
|
|
test("retired branch-follower 与 Gitea Actions POC 默认帮助只读", async () => {
|
|
expect(JSON.stringify(branchFollowerHelp())).not.toMatch(mutationCommand);
|
|
expect(JSON.stringify(cicdGiteaActionsPocHelp())).not.toMatch(mutationCommand);
|
|
await expect(runGiteaActionsPocCommand(null, ["apply"])).rejects.toThrow("intentionally unavailable");
|
|
});
|
|
|
|
test("platform-infra 默认 delivery 帮助和 AgentRun 失败帮助不暴露人工推进", () => {
|
|
const platformHelp = platformInfraHelp() as { usage: string[] };
|
|
const deliveryUsage = platformHelp.usage.filter((line) => line.includes("gitea") || line.includes("pipelines-as-code"));
|
|
expect(JSON.stringify(deliveryUsage)).not.toMatch(mutationCommand);
|
|
expect(deliveryUsage).toContain("bun scripts/cli.ts platform-infra gitea mirror webhook status --target NC01");
|
|
expect(deliveryUsage).toContain("bun scripts/cli.ts platform-infra pipelines-as-code help platform-bootstrap");
|
|
const agentRunOperations = unsupportedAgentRunCommand(["unexpected"]).renderedText.split("Operations:")[1] ?? "";
|
|
expect(agentRunOperations).not.toMatch(mutationCommand);
|
|
const root = runCliJson(["--help"]);
|
|
expect(JSON.stringify(root)).not.toMatch(/platform-infra gitea apply|platform-infra gitea mirror sync/u);
|
|
});
|
|
});
|
|
|
|
function runCliJson(args: string[]): { ok: boolean; command: string; data: unknown } {
|
|
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
|
|
cwd: process.cwd(),
|
|
encoding: "utf8",
|
|
timeout: 15_000,
|
|
});
|
|
if (result.status !== 0) throw new Error(`CLI ${args.join(" ")} failed: ${result.stderr}`);
|
|
return JSON.parse(result.stdout) as { ok: boolean; command: string; data: unknown };
|
|
}
|