182 lines
9.2 KiB
TypeScript
182 lines
9.2 KiB
TypeScript
import { gitMirrorFlushJobManifest, gitMirrorSyncJobManifest, gitMirrorV02SyncRequirement, hwlabG14MonitorStateFileName, parseGitMirrorStatusRefs, parsePipelineTaskRunMetrics, rolloutRecordBody, semanticChangelogBullets, v02PipelineServiceIds } from "./src/hwlab-g14";
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
function record(value: unknown): Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
}
|
|
|
|
assertCondition(
|
|
hwlabG14MonitorStateFileName({ once: false, dryRun: false }) === "latest-monitor-job.json",
|
|
"long-running monitor should own latest-monitor-job.json",
|
|
);
|
|
assertCondition(
|
|
hwlabG14MonitorStateFileName({ once: true, dryRun: false }) === "latest-once-job.json",
|
|
"once jobs must not overwrite the long-running monitor pointer",
|
|
);
|
|
assertCondition(
|
|
hwlabG14MonitorStateFileName({ once: false, dryRun: true }) === "latest-dry-run-job.json",
|
|
"dry-run monitor jobs must not overwrite the live monitor pointer",
|
|
);
|
|
assertCondition(
|
|
hwlabG14MonitorStateFileName({ once: true, dryRun: true }) === "latest-once-dry-run-job.json",
|
|
"once dry-runs need a distinct pointer for low-noise diagnostics",
|
|
);
|
|
|
|
const gitMirrorJob = gitMirrorSyncJobManifest("git-mirror-hwlab-sync-manual-test");
|
|
assertCondition(gitMirrorJob.kind === "Job", "git mirror sync must be a manual Job, not a CronJob", gitMirrorJob);
|
|
assertCondition(record(gitMirrorJob.metadata).namespace === "devops-infra", "git mirror sync Job must target devops-infra", gitMirrorJob);
|
|
assertCondition(record(record(gitMirrorJob.metadata).labels)["hwlab.pikastech.local/trigger"] === "manual-cli", "git mirror sync Job must be labeled as manual CLI triggered", gitMirrorJob);
|
|
assertCondition(record(gitMirrorJob.spec).backoffLimit === 0, "git mirror sync Job should fail visibly instead of retrying in the background", gitMirrorJob);
|
|
|
|
const gitMirrorFlushJob = gitMirrorFlushJobManifest("git-mirror-hwlab-flush-manual-test");
|
|
assertCondition(gitMirrorFlushJob.kind === "Job", "git mirror flush must be a manual Job", gitMirrorFlushJob);
|
|
assertCondition(record(record(gitMirrorFlushJob.metadata).labels)["app.kubernetes.io/component"] === "flush-controller", "git mirror flush Job must be labeled separately from sync", gitMirrorFlushJob);
|
|
assertCondition(record(record(gitMirrorFlushJob.metadata).labels)["hwlab.pikastech.local/trigger"] === "manual-cli", "git mirror flush Job must be labeled as manual CLI triggered", gitMirrorFlushJob);
|
|
const flushTemplate = record(record(gitMirrorFlushJob.spec).template);
|
|
const flushPodSpec = record(flushTemplate.spec);
|
|
const flushContainer = record(Array.isArray(flushPodSpec.containers) ? flushPodSpec.containers[0] : null);
|
|
assertCondition(JSON.stringify(flushContainer.command) === JSON.stringify(["/script/flush.sh"]), "git mirror flush Job must run the flush script", gitMirrorFlushJob);
|
|
|
|
const gitMirrorStatusRaw = [
|
|
'lastSync={"ok":true}',
|
|
"lastWrite=",
|
|
"lastFlush=",
|
|
'refs={"refs":{"localV02":"0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc","localG14":"1111111111111111111111111111111111111111","localGitops":"2222222222222222222222222222222222222222","githubGitops":"3333333333333333333333333333333333333333"},"pendingFlush":true}',
|
|
].join("\n");
|
|
const parsedGitMirrorRefs = parseGitMirrorStatusRefs(gitMirrorStatusRaw);
|
|
assertCondition(
|
|
parsedGitMirrorRefs.refs.localV02 === "0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc",
|
|
"git mirror status parser must expose local v0.2 ref for trigger pre-sync",
|
|
parsedGitMirrorRefs,
|
|
);
|
|
assertCondition(parsedGitMirrorRefs.pendingFlush === true, "git mirror status parser must preserve pending flush signal", parsedGitMirrorRefs);
|
|
assertCondition(
|
|
gitMirrorV02SyncRequirement("0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc", gitMirrorStatusRaw).required === false,
|
|
"trigger-current must not sync mirror when local v0.2 already matches source commit",
|
|
);
|
|
assertCondition(
|
|
gitMirrorV02SyncRequirement("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", gitMirrorStatusRaw).required === true,
|
|
"trigger-current must sync mirror before creating PipelineRun when local v0.2 is stale",
|
|
);
|
|
assertCondition(
|
|
!v02PipelineServiceIds().includes("hwlab-cli"),
|
|
"v0.2 PipelineRun service matrix must not build hwlab-cli because cli is short-connection source tool",
|
|
v02PipelineServiceIds(),
|
|
);
|
|
|
|
const prBody = [
|
|
"## 背景",
|
|
"",
|
|
"G14 DEV Cloud Workbench 中,内部 `message-trace-body` 的 `pre` 会在 trace 更新时被替换,导致用户滚到中间后被重置到顶部。",
|
|
"",
|
|
"## 修改",
|
|
"",
|
|
"- trace row patch 从 index/replace 改为 stable `data-trace-row-id` keyed reconciliation。",
|
|
"- 已存在 row 只移动和原地更新 header/body,不替换 `li/pre`。",
|
|
"- `message-trace-body` 内部滚动状态按 `traceUiKey + rowId` 记忆。",
|
|
].join("\n");
|
|
|
|
const semanticBullets = semanticChangelogBullets("fix: preserve inner trace scroll", prBody, {
|
|
keyFiles: [
|
|
"modified web/hwlab-cloud-web/app.mjs",
|
|
"modified web/hwlab-cloud-web/scripts/trace-scroll.test.mjs",
|
|
],
|
|
});
|
|
|
|
assertCondition(
|
|
semanticBullets.some((line) => line.includes("修复目标") && line.includes("滚到中间后被重置到顶部")),
|
|
"semantic changelog should explain the user-visible bug being fixed",
|
|
semanticBullets,
|
|
);
|
|
assertCondition(
|
|
semanticBullets.some((line) => line.includes("data-trace-row-id")),
|
|
"semantic changelog should include PR body change bullets instead of only file stats",
|
|
semanticBullets,
|
|
);
|
|
|
|
const summaryBullets = semanticChangelogBullets("feat: preload gateway tran helper", [
|
|
"## 摘要",
|
|
"- 新增 `/app/tools/hwlab-gateway-tran.mjs`,支持 `cmd`、`ps`、`upload`、`download`。",
|
|
].join("\n"), {});
|
|
|
|
assertCondition(
|
|
summaryBullets.some((line) => line.includes("hwlab-gateway-tran.mjs") && line.includes("upload")),
|
|
"semantic changelog should also extract Chinese summary sections",
|
|
summaryBullets,
|
|
);
|
|
|
|
const rolloutBody = rolloutRecordBody({
|
|
pr: { number: 506, title: "fix: preserve inner trace scroll", url: "https://github.com/pikasTech/HWLAB/pull/506" },
|
|
prData: { json: { title: "fix: preserve inner trace scroll", body: prBody, url: "https://github.com/pikasTech/HWLAB/pull/506" } },
|
|
fileSummary: {
|
|
summary: { files: 2, additions: 268, deletions: 22, commits: 1 },
|
|
keyFiles: [
|
|
"modified web/hwlab-cloud-web/app.mjs",
|
|
"modified web/hwlab-cloud-web/scripts/trace-scroll.test.mjs",
|
|
],
|
|
},
|
|
sourceCommit: "1a3fa9e6fbc987142463ecff2cbcef240a6278f2",
|
|
pipelineRun: "hwlab-g14-ci-poll-1a3fa9e6fbc9",
|
|
gitopsRevision: "21462b78ce4e7dba4ea374398f60db690e290147",
|
|
mergedAt: "2026-05-27T06:52:22Z",
|
|
pipelineSucceededAt: "2026-05-27T06:55:38Z",
|
|
finishedAt: "2026-05-27T06:55:47Z",
|
|
rollout: { pipelineText: "True\nSucceeded", argoText: "21462\nSynced\nHealthy\nSucceeded\n21462", healthOk: true },
|
|
ciMetrics: parsePipelineTaskRunMetrics("hwlab-g14-ci-poll-test", [
|
|
"taskrun-a\tbuild-hwlab-cloud-api\t2026-05-27T06:54:43Z\t2026-05-27T06:54:52Z\tservice-id=hwlab-cloud-api;status=reused;build-backend=reused-catalog;",
|
|
"taskrun-b\tbuild-hwlab-cloud-web\t2026-05-27T06:54:43Z\t2026-05-27T06:55:08Z\tservice-id=hwlab-cloud-web;status=published;build-backend=buildkit;",
|
|
].join("\n")),
|
|
});
|
|
|
|
assertCondition(
|
|
rolloutBody.includes("- 上线 changelog(语义化):"),
|
|
"rollout record must label natural-language changelog separately",
|
|
);
|
|
assertCondition(
|
|
rolloutBody.includes("- 自动 diff 摘要:"),
|
|
"rollout record must keep automatic diff summary separately",
|
|
);
|
|
assertCondition(
|
|
rolloutBody.indexOf("- 上线 changelog(语义化):") < rolloutBody.indexOf("- 自动 diff 摘要:"),
|
|
"semantic changelog should appear before automatic diff summary",
|
|
);
|
|
assertCondition(
|
|
rolloutBody.includes("changed files: 2; +268 / -22; commits: 1"),
|
|
"automatic diff summary should preserve file/stat evidence",
|
|
);
|
|
assertCondition(
|
|
rolloutBody.includes("lazy build reused: 1/2"),
|
|
"rollout record should include lazy-build reused ratio",
|
|
rolloutBody,
|
|
);
|
|
assertCondition(
|
|
rolloutBody.includes("reused services: hwlab-cloud-api") && rolloutBody.includes("rebuild services: hwlab-cloud-web"),
|
|
"rollout record should list reused and rebuild services",
|
|
rolloutBody,
|
|
);
|
|
assertCondition(
|
|
rolloutBody.includes("hwlab-cloud-api: reused, 9s") && rolloutBody.includes("hwlab-cloud-web: published, 25s"),
|
|
"rollout record should include each service duration",
|
|
rolloutBody,
|
|
);
|
|
|
|
console.log(JSON.stringify({
|
|
ok: true,
|
|
checks: [
|
|
"long-running monitor owns latest-monitor-job.json",
|
|
"once jobs do not overwrite long-running monitor state",
|
|
"dry-run jobs do not overwrite live monitor state",
|
|
"once dry-run jobs have a distinct diagnostic state file",
|
|
"git mirror sync is a manual devops-infra Job, not a CronJob",
|
|
"git mirror flush is a manual devops-infra Job, not a CronJob",
|
|
"trigger-current can decide whether v0.2 git mirror pre-sync is required",
|
|
"v0.2 PipelineRun service matrix excludes hwlab-cli",
|
|
"rollout brief includes natural-language changelog before automatic diff summary",
|
|
"semantic changelog extracts Chinese summary sections",
|
|
"rollout brief includes lazy-build reused/rebuild metrics and service durations",
|
|
],
|
|
}, null, 2));
|