WPROC2 增加开发流程自动化门禁
这个提交包含在:
@@ -0,0 +1,143 @@
|
||||
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 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("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", "unknown"])).toThrow(/unsupported profile/);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户