91 lines
5.0 KiB
TypeScript
91 lines
5.0 KiB
TypeScript
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<Record<string, unknown>>;
|
||
};
|
||
|
||
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: "" });
|
||
});
|
||
});
|