import { describe, expect, test } from "bun:test"; import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { createRequire } from "node:module"; import { sentinelPublishImageBuildShell } from "./hwlab-node-web-sentinel-cicd-jobs"; import type { SentinelCicdState } from "./hwlab-node-web-sentinel-cicd-shared"; import { compactHistoryJson, compactStatusSummary, renderDebugStep, renderHistory, renderStatus } from "./platform-infra-pipelines-as-code"; const require = createRequire(import.meta.url); const { parsePacLogRecords } = require("../native/cicd/pac-status-evaluator.cjs") as { parsePacLogRecords(value: string): Array>; }; describe("PaC 失败证据合同", () => { test("真实 sentinel TaskRun 日志保留 typed 首断点", () => { const fixture = resolve(import.meta.dir, "fixtures/pac/hwlab-web-probe-sentinel-nc01-r5ww2.log"); const record = parsePacLogRecords(readFileSync(fixture, "utf8")).find((item) => item.ok === false); expect(record).toMatchObject({ traceId: "6e6e899d3e812891140aedfd974d524e", firstBreak: { code: "child-process-exit-nonzero", phase: "image-build" }, error: { type: "ChildProcessError", code: "EXIT_NONZERO", phase: "image-build", exitCode: 1 }, }); }); test("history、debug-step、status 默认显示 typed 失败字段", () => { const fixture = resolve(import.meta.dir, "fixtures/pac/hwlab-web-probe-sentinel-nc01-r5ww2.log"); const failure = parsePacLogRecords(readFileSync(fixture, "utf8")).find((item) => item.ok === false) ?? {}; const row = { id: "hwlab-web-probe-sentinel-nc01-r5ww2", consumer: "sentinel-nc01-v03", deliveryClass: "outer-pac-event", status: "False", traceId: failure.traceId, firstBreak: failure.firstBreak, error: failure.error, }; const history = renderHistory({ rows: [row], config: {}, historyErrors: [], next: {} }).renderedText; const debug = renderDebugStep({ realRun: { found: true, pipelineRun: row, firstBreak: row.firstBreak, error: row.error, traceId: row.traceId }, checks: [], target: {}, next: {} }).renderedText; const status = renderStatus({ summary: { latestPipelineRun: row, artifact: row, diagnostics: {} }, consumer: {}, deliveryAuthority: {}, config: {}, coverage: [], target: {}, next: {} }).renderedText; for (const output of [history, debug, status]) { expect(output).toContain("6e6e899d3e8128"); expect(output).toContain("child-process-exit-nonzero"); expect(output).toContain("ChildProcessError"); expect(output).toContain("image-build"); expect(output).toContain("EXIT_NONZERO"); expect(output).toContain("1"); } const compactHistory = compactHistoryJson({ rows: [{ ...row, collector: { matchingTaskCount: 1 } }], detailId: row.id }); const compactStatus = compactStatusSummary({ latestPipelineRun: row, artifact: row }); expect(compactHistory).toMatchObject({ rows: [{ traceId: row.traceId, firstBreak: row.firstBreak, error: row.error, collector: { matchingTaskCount: 1 } }] }); expect(compactStatus).toMatchObject({ artifact: { traceId: row.traceId, firstBreak: row.firstBreak, error: row.error } }); }); test("collector 遍历全部 matching TaskRun,terminal record 仍限定合同任务", () => { const remote = readFileSync(resolve(import.meta.dir, "platform-infra-pipelines-as-code-remote.sh"), "utf8"); expect(remote).toContain("for (const item of matching)"); expect(remote).toContain("const contractTaskRuns = matching.filter((item) => taskTerminalRecord(item) !== null)"); expect(remote).toContain("Object.keys(record(row.firstBreak)).length > 0"); expect(remote).toContain("traceId: row.traceId || null"); }); test("sentinel image build 显式等待 socket 与 worker readiness 并清理 daemon", () => { const shell = sentinelPublishImageBuildShell({ spec: { nodeId: "NC01", lane: "v03", buildkit: { sidecarImage: "moby/buildkit:rootless" }, networkProfile: { imageBuildProxy: { http: "", https: "", all: "", noProxy: [] } }, }, image: { ref: "registry.example.test/web-probe-sentinel:test", monitorWeb: { imageBuildPackageMode: "frozen-lockfile", imageBuildNetworkMode: "host", imageBuildProxySource: "test", }, }, } as unknown as SentinelCicdState, "sentinel-test"); expect(shell).toContain("mkdir -p \"$(dirname \"$buildkit_socket\")\""); expect(shell).toContain("rootlesskit buildkitd --addr"); expect(shell).not.toMatch(/(^|\n)buildkitd --addr/); expect(shell).toContain("debug workers"); expect(shell).toContain("failure_error_type=BuildKitReadinessError"); expect(shell).toContain("failure_error_code=BUILDKIT_NOT_READY"); expect(shell).toContain("cleanup_buildkitd"); expect(shell).not.toContain("buildctl-daemonless.sh build"); expect(spawnSync("sh", ["-n"], { input: shell, encoding: "utf8" })).toMatchObject({ status: 0, stderr: "" }); }); });