建立每日自动异机备份基线(07-27-automatic-offhost-backup)

这个提交包含在:
2026-07-27 10:50:01 +08:00
父节点 325c592aba
当前提交 b78f2a11ce
共修改 19 个文件,包含 2070 行新增和 10 行删除
@@ -5,6 +5,7 @@ const {
evaluateAlertDecision,
executeOpsAlert,
parseArgs,
readSummary,
shouldSendForSummary,
} = require("../../../src/scripts/send-wecom-ops-alert");
@@ -75,6 +76,72 @@ describe("send-wecom-ops-alert", () => {
expect(message.body).not.toContain("手机号");
});
it("reads stdin summary without preserving an injected absolute report path", () => {
const summary = readSummary("-", JSON.stringify({
issueId: "AUTO_BACKUP",
status: "FAIL_AUTOMATIC_BACKUP",
findingCount: 1,
reportDir: `/private/${SENSITIVE_TOKEN}/state`,
}));
expect(summary.issueId).toBe("AUTO_BACKUP");
expect(summary.reportDir).toBe("stdin-summary");
expect(JSON.stringify(summary)).not.toContain(SENSITIVE_TOKEN);
expect(JSON.stringify(summary)).not.toContain("/private/");
});
it("keeps AUTO_BACKUP issue id and uses backup-specific failure guidance", async () => {
const summary = baseSummary({
issueId: "AUTO_BACKUP",
status: "FAIL_AUTOMATIC_BACKUP",
reportDir: "host-backup-state",
});
const message = buildAlertMessage(summary);
const report = await executeOpsAlert(baseOptions(), {
summary,
recipientRows: recipientRows(),
sendMessageImpl: vi.fn(),
env: {},
config: {},
});
expect(message.title).toContain("自动备份");
expect(message.body).toContain("异机传输链路");
expect(report.issueId).toBe("AUTO_BACKUP");
expect(report.summary.issueId).toBe("AUTO_BACKUP");
});
it("uses manual ARM shadow-restore guidance for quarterly reminder", () => {
const summary = baseSummary({
issueId: "AUTO_BACKUP",
findings: [{ detail: "quarterly_shadow_restore_due_2026-Q3" }],
reportDir: "host-backup-state",
});
const message = buildAlertMessage(summary);
const firstDecision = evaluateAlertDecision(summary, baseOptions(), null);
const repeatedDecision = evaluateAlertDecision(summary, baseOptions(), buildAlertState(summary));
expect(message.body).toContain("ARM 隔离目标执行人工影子恢复");
expect(firstDecision.shouldSend).toBe(true);
expect(firstDecision.reason).toBe("quarterly_restore_reminder");
expect(repeatedDecision.shouldSend).toBe(false);
});
it("sends the first automatic-backup failure and suppresses an identical repeat", () => {
const summary = baseSummary({
issueId: "AUTO_BACKUP",
status: "FAIL_AUTOMATIC_BACKUP",
findings: [{ source: "automatic_backup", status: "FAIL_AUTOMATIC_BACKUP", detail: "phase_offhost_transfer_failed" }],
});
const firstDecision = evaluateAlertDecision(summary, baseOptions(), null);
const repeatedDecision = evaluateAlertDecision(summary, baseOptions(), buildAlertState(summary));
expect(firstDecision.shouldSend).toBe(true);
expect(firstDecision.reason).toBe("blocked_or_failed");
expect(repeatedDecision.shouldSend).toBe(false);
expect(repeatedDecision.reason).toBe("repeated_automatic_backup_failure");
});
it("keeps PASS without findings as no-send unless forced", () => {
const summary = baseSummary({ status: "PASS_ARCHIVE_OPS_READY", findingCount: 0 });
expect(shouldSendForSummary(summary, baseOptions())).toBe(false);