文件
hyb-oa/server/tests/unit/scripts/check-process-contract.test.js

174 行
8.0 KiB
JavaScript

const path = require("node:path");
const { pathToFileURL } = require("node:url");
let gate;
function csvEscape(value) {
return `"${String(value).replace(/"/g, "\"\"")}"`;
}
function buildCsv(rows) {
const header = [
"ID",
"Title",
"Description",
"Acceptance",
"Test_Method",
"Tools",
"Dev_Status",
"Review1_Status",
"Regression_Status",
"Files",
"Dependencies",
"Notes",
];
return [
header.join(","),
...rows.map((row) => header.map((column) => csvEscape(row[column] ?? "")).join(",")),
].join("\n");
}
function validFixtureRow(overrides = {}) {
return {
ID: "WAPC2F-0",
Title: "合法企微审批样件合同",
Description: "WAPC2F 自动生成 OA-POC 请假/补卡测试模板;路线:WAPC2F -> WCBK1 -> WACP3 -> WADM1 -> wecom_attendance_approval_mirror_v1。",
Acceptance: "固定 WECOM_AGENT_SECRET;不尝试其他 Secret;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审,不是同一 OA 员工,不是同一企微 userid;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。",
Test_Method: "受影响流程:WAPC2 缺配置 -> 自动生成 POC 样件 -> WAPC2 自动复跑;验证步骤:node scripts/qa/check-process-contract.mjs --issue fixture.csv --profile wecom-approval-fixture --json。",
Tools: "node|vitest",
Dev_Status: "TODO",
Review1_Status: "TODO",
Regression_Status: "TODO",
Files: "scripts/qa/check-wecom-approval-fixtures.js|server/package.json",
Dependencies: "[]",
Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 sp_no、完整 userid 或原始 payload;无 fallback。",
...overrides,
};
}
function validCallbackRow(overrides = {}) {
return {
ID: "WCBK1-0",
Title: "合法企微审批回调只读合同",
Description: "WCBK1 回调只读 POC,路线:WCBK1 -> WACP3 -> WADM1 -> wecom_attendance_approval_mirror_v1;范围只验证企微审批回调。",
Acceptance: "固定 WECOM_APPROVAL_CALLBACK_URL、WECOM_CALLBACK_TOKEN、WECOM_CALLBACK_ENCODING_AES_KEY;GET /api/wecom/approval/callback 做 URL 验证;POST /api/wecom/approval/callback 接收事件;使用 msg_signature 验签和 EncodingAESKey 解密;只读、幂等,不写 OA 业务镜像,不写企微数据;不使用通讯录 Secret,不尝试其他 Secret,不允许运行时猜 Secret;缺配置记录 NEEDS_CONFIG,外部阻断记录 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。",
Test_Method: "受影响流程:WAPC2FLIVE 样件 -> WCBK1 回调只读 POC -> WACP3 补偿拉取;验证步骤:node scripts/qa/check-process-contract.mjs --issue callback.csv --profile wecom-callback --json。",
Tools: "node|vitest",
Dev_Status: "TODO",
Review1_Status: "TODO",
Regression_Status: "TODO",
Files: "server/src/routes/wecomApprovalCallback.js|server/src/services/integrations/wecomCallbackCrypto.js",
Dependencies: "[]",
Notes: "回滚路径:移除回调 URL 或新增路由;报告不得输出原始 XML、密文、完整 sp_no、完整 userid、完整 template_id、Token、EncodingAESKey 或原始 payload。",
...overrides,
};
}
function evaluate(rows, profile = "wecom-approval-fixture") {
return gate.evaluateIssueContract({
csvText: buildCsv(rows),
issuePath: "fixture.csv",
profile,
});
}
function errorCodes(result) {
return result.errors.map((item) => item.code);
}
describe("check-process-contract", () => {
beforeAll(async () => {
const scriptPath = path.resolve(__dirname, "../../../../scripts/qa/check-process-contract.mjs");
gate = await import(pathToFileURL(scriptPath).href);
});
it("passes a valid WAPC2F fixture contract", () => {
const result = evaluate([validFixtureRow()]);
expect(result.status).toBe("PASS");
expect(errorCodes(result)).toEqual([]);
expect(result.checks.some((item) => item.name === "profile.wecom_approval_fixture")).toBe(true);
});
it("passes a valid WCBK1 callback contract", () => {
const result = evaluate([validCallbackRow()], "wecom-callback");
expect(result.status).toBe("PASS");
expect(errorCodes(result)).toEqual([]);
expect(result.checks.some((item) => item.name === "profile.wecom_callback")).toBe(true);
});
it("fails when Dependencies is not a JSON array", () => {
const result = evaluate([validFixtureRow({ Dependencies: "WAPC2F-0" })]);
expect(result.status).toBe("FAIL");
expect(errorCodes(result)).toContain("csv.dependencies");
});
it("fails when Test_Method lacks affected business flow wording", () => {
const result = evaluate([validFixtureRow({ Test_Method: "验证步骤:node scripts/qa/check-process-contract.mjs --baseline。" })]);
expect(result.status).toBe("FAIL");
expect(errorCodes(result)).toContain("csv.test_method");
});
it("fails self-approval defaults", () => {
const result = evaluate([
validFixtureRow({
Acceptance: "默认 wangshunli -> wangshunli;固定 WECOM_AGENT_SECRET;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。",
}),
]);
expect(result.status).toBe("FAIL");
expect(errorCodes(result)).toContain("fixture.self_approval");
});
it("fails when live write switch is missing", () => {
const row = validFixtureRow({
Acceptance: "固定 WECOM_AGENT_SECRET;不尝试其他 Secret;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。",
});
const result = evaluate([row]);
expect(result.status).toBe("FAIL");
expect(errorCodes(result)).toContain("fixture.required_term");
expect(result.errors.some((item) => item.message.includes("WECOM_APPROVAL_FIXTURE_WRITE=1"))).toBe(true);
});
it("fails when Secret fallback is not explicitly forbidden", () => {
const row = validFixtureRow({
Acceptance: "固定 WECOM_AGENT_SECRET;默认 dry-run;live 必须 --live --create-templates --submit-test-approvals 且 WECOM_APPROVAL_FIXTURE_WRITE=1;申请人 SMOKE002 -> 审批人 SMOKE101;禁止自审;缺配置记录 NEEDS_CONFIG 或 SKIP/BLOCKED_BY_EXTERNAL_CONFIG。",
Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 sp_no、完整 userid 或原始 payload。",
});
const result = evaluate([row]);
expect(result.status).toBe("FAIL");
expect(errorCodes(result)).toContain("wecom.secret_source");
});
it("fails when privacy report boundary is incomplete", () => {
const row = validFixtureRow({
Notes: "回滚路径:撤回代码;企微后台 OA-POC 测试模板或测试单如已生成需隐藏、停用或保留审计;报告不得输出手机号、邮箱、token、secret、完整模板 ID、完整 userid 或原始 payload;无 fallback。",
});
const result = evaluate([row]);
expect(result.status).toBe("FAIL");
expect(errorCodes(result)).toContain("fixture.privacy");
});
it("parses CLI options for baseline and issue profiles", () => {
expect(gate.parseArgs(["--baseline", "--json"])).toMatchObject({
baseline: true,
json: true,
});
expect(gate.parseArgs(["--issue", "a.csv", "--profile", "wecom"])).toMatchObject({
issue: "a.csv",
profile: "wecom",
});
expect(gate.parseArgs(["--issue", "a.csv", "--profile", "wecom-callback"])).toMatchObject({
issue: "a.csv",
profile: "wecom-callback",
});
expect(() => gate.parseArgs(["--issue", "a.csv", "--profile", "unknown"])).toThrow(/unsupported profile/);
});
});