文件
hyb-oa/server/tests/unit/services/wecomApprovalRuleValidator.test.js
T

179 行
6.7 KiB
JavaScript

const {
evaluateRuleReport,
validateApprovalRuleArchive,
} = require("../../../src/services/integrations/wecomApprovalRuleValidator");
const SENSITIVE_SP_NO = "SP_RULE_VALIDATOR_FULL_SHOULD_NOT_LEAK";
const SENSITIVE_TEMPLATE_ID = "TPL_RULE_VALIDATOR_FULL_SHOULD_NOT_LEAK";
function employee(id, overrides = {}) {
const { roles = ["employee"], ...rest } = overrides;
return {
id,
employee_no: rest.employee_no || `EMP-${id}`,
status: rest.status || "active",
roles: roles.map((role_code) => ({ role_code })),
wecom_identity_binding: rest.bound === false ? null : { external_user_id: `wecom-${id}-secret-userid` },
direct_manager: rest.direct_manager || null,
...rest,
};
}
function archive(overrides = {}) {
const manager = employee("manager", { employee_no: "SMOKE101", roles: ["manager"] });
const applicant = employee("applicant", {
employee_no: "SMOKE002",
direct_manager: manager,
});
return {
id: "archive-1",
sp_no: SENSITIVE_SP_NO,
template_id: SENSITIVE_TEMPLATE_ID,
template_type: "leave",
normalized_status: "approved",
applicant,
participants: [{
role: "approver",
node_index: 1,
employee: manager,
}],
actions: [],
...overrides,
};
}
describe("wecomApprovalRuleValidator", () => {
it("passes when actual approver matches OA direct manager", () => {
const result = validateApprovalRuleArchive(archive(), { manualGates: { leave: "A" } });
expect(result.status).toBe("PASS_RULE_MATCH");
expect(result.ruleMode).toBe("direct_manager");
expect(result.findings).toEqual([]);
});
it("fails when applicant is the actual approver", () => {
const row = archive();
row.participants = [{ role: "approver", node_index: 1, employee: row.applicant }];
const result = validateApprovalRuleArchive(row, { manualGates: { leave: "A" } });
expect(result.status).toBe("FAIL_SELF_APPROVAL");
expect(result.findings.map((item) => item.type)).toContain("FAIL_SELF_APPROVAL");
});
it("fails when manual WAPPR1 gate says applicant can choose approver", () => {
const result = validateApprovalRuleArchive(archive(), { manualGates: { leave: "B" } });
expect(result.status).toBe("FAIL_APPLICANT_CAN_CHOOSE");
expect(result.findings[0]).toMatchObject({
type: "FAIL_APPLICANT_CAN_CHOOSE",
reason: "manual_wappr1_approver_can_be_changed",
});
});
it("fails when WeCom actual approver does not match OA theoretical approver", () => {
const result = validateApprovalRuleArchive(archive({
participants: [{
role: "approver",
node_index: 1,
employee: employee("other", { employee_no: "OTHER001", roles: ["employee"] }),
}],
}), { manualGates: { leave: "A" } });
expect(result.status).toBe("FAIL_APPROVER_MISMATCH");
});
it("allows explainable admin fallback when no manager or approval role candidate exists", () => {
const fallback = employee("admin", { employee_no: "HYD000006", roles: ["admin"] });
const applicant = employee("applicant", {
employee_no: "SMOKE002",
direct_manager: null,
});
const result = validateApprovalRuleArchive(archive({
applicant,
participants: [{ role: "approver", node_index: 1, employee: fallback }],
}), {
manualGates: { leave: "A" },
fallbackEmployee: fallback,
approvalRoleEmployees: [],
});
expect(result.status).toBe("PASS_WITH_ADMIN_FALLBACK");
expect(result.ruleMode).toBe("admin_fallback");
});
it("pauses item status before admin fallback when manual gate is missing", () => {
const fallback = employee("admin", { employee_no: "HYD000006", roles: ["admin"] });
const applicant = employee("applicant", {
employee_no: "SMOKE002",
direct_manager: null,
});
const result = validateApprovalRuleArchive(archive({
applicant,
participants: [{ role: "approver", node_index: 1, employee: fallback }],
}), {
fallbackEmployee: fallback,
approvalRoleEmployees: [],
});
expect(result.status).toBe("PAUSED_MANUAL_CONFIRMATION");
expect(result.findings.map((item) => item.type)).toContain("PASS_WITH_ADMIN_FALLBACK");
});
it("uses approval-role approver when direct manager is unavailable", () => {
const roleApprover = employee("hr", { employee_no: "HR001", roles: ["hr"] });
const applicant = employee("applicant", {
employee_no: "SMOKE002",
direct_manager: employee("manager", { employee_no: "SMOKE101", status: "inactive", roles: ["manager"] }),
});
const result = validateApprovalRuleArchive(archive({
applicant,
participants: [{ role: "approver", node_index: 1, employee: roleApprover }],
}), {
manualGates: { leave: "A" },
approvalRoleEmployees: [roleApprover],
});
expect(result.status).toBe("PASS_RULE_MATCH");
expect(result.ruleMode).toBe("approval_role");
});
it("blocks when no approver candidate or fallback exists", () => {
const applicant = employee("applicant", {
employee_no: "SMOKE002",
direct_manager: null,
});
const result = validateApprovalRuleArchive(archive({
applicant,
participants: [{ role: "approver", node_index: 1, employee: employee("other") }],
}), { manualGates: { leave: "A" } });
expect(result.status).toBe("SKIP/BLOCKED_BY_EXTERNAL_CONFIG");
expect(result.findings.map((item) => item.reason)).toContain("no_valid_direct_manager_role_or_admin_fallback");
});
it("redacts full external identifiers from public result", () => {
const result = validateApprovalRuleArchive(archive(), { manualGates: { leave: "A" } });
const serialized = JSON.stringify(result);
expect(serialized).not.toContain(SENSITIVE_SP_NO);
expect(serialized).not.toContain(SENSITIVE_TEMPLATE_ID);
expect(serialized).not.toContain("wecom-manager-secret-userid");
expect(serialized).not.toContain("SMOKE101");
});
it("prioritizes blocker conclusions in report evaluation", () => {
expect(evaluateRuleReport([{ status: "PASS_RULE_MATCH" }])).toBe("PASS_RULE_MATCH");
expect(evaluateRuleReport([{ status: "PASS_WITH_ADMIN_FALLBACK" }])).toBe("PASS_WITH_ADMIN_FALLBACK");
expect(evaluateRuleReport([{ status: "FAIL_APPROVER_MISMATCH" }, { status: "PASS_RULE_MATCH" }]))
.toBe("FAIL_APPROVER_MISMATCH");
expect(evaluateRuleReport([{ status: "PASS_RULE_MATCH" }], {
manualGateFindings: [{ status: "FAIL_APPLICANT_CAN_CHOOSE" }],
})).toBe("FAIL_APPLICANT_CAN_CHOOSE");
expect(evaluateRuleReport([{ status: "PASS_RULE_MATCH" }], {
templateDataGaps: [{ status: "SKIP/BLOCKED_BY_EXTERNAL_CONFIG" }],
})).toBe("SKIP/BLOCKED_BY_EXTERNAL_CONFIG");
expect(evaluateRuleReport([], { noData: true })).toBe("SKIP/BLOCKED_BY_EXTERNAL_CONFIG");
});
});