const workflowUtils = require("../../../src/utils/workflow"); const { resolveActionableStep } = workflowUtils; const OWNER_ID = "11111111-1111-4111-8111-111111111111"; function buildSteps() { return [ { id: "approval_1_manager", name: "主管审批", type: "approval", assignee_rule: { type: "department_manager" }, }, { id: "approval_2_finance", name: "财务复核", type: "approval", assignee_rule: { type: "role", value: "finance" }, }, ]; } describe("workflow assignee resolution", () => { describe("parseWorkflowSteps", () => { it("throws error for invalid steps array", () => { expect(() => workflowUtils.parseWorkflowSteps({})).toThrow(/参数校验失败/); expect(() => workflowUtils.parseWorkflowSteps({ steps: [] })).toThrow(/参数校验失败/); }); it("parses valid steps array", () => { expect(workflowUtils.parseWorkflowSteps({ steps: [{ id: 1 }] })).toEqual([{ id: 1 }]); }); }); describe("buildWorkflowStepsFromApprovalRule", () => { it("builds steps correctly for valid rule", () => { const result = workflowUtils.buildWorkflowStepsFromApprovalRule({ level1_role: "manager", level2_role: "finance", }); expect(result.length).toBe(2); expect(result[0].assignee_rule.type).toBe("department_manager"); expect(result[1].assignee_rule.type).toBe("role"); expect(result[1].assignee_rule.value).toBe("finance"); }); it("throws if rule is empty or missing roles", () => { expect(() => workflowUtils.buildWorkflowStepsFromApprovalRule({})).toThrow(/参数校验失败/); }); }); describe("resolveAssigneeFromRule", () => { it("resolves role type", async () => { const result = await workflowUtils.resolveAssigneeFromRule({ type: "role", value: "hr" }); expect(result.assigneeRole).toBe("hr"); expect(result.assigneeId).toBe(null); }); it("throws for invalid rule", async () => { await expect(workflowUtils.resolveAssigneeFromRule(null)).rejects.toThrow(/参数校验失败/); await expect(workflowUtils.resolveAssigneeFromRule({ type: "unknown" })).rejects.toThrow(/参数校验失败/); }); }); describe("toMoneyString", () => { it("converts values to string correctly", () => { expect(workflowUtils.toMoneyString(null)).toBe(null); expect(workflowUtils.toMoneyString("10.0")).toBe("10.0"); expect(workflowUtils.toMoneyString(10)).toBe("10"); }); }); it("blocks submit-time workflow resolution when the direct manager is missing", async () => { const tx = { employee: { findUnique: vi.fn(async () => ({ id: OWNER_ID, direct_manager_id: null, })), }, }; await expect( resolveActionableStep({ tx, steps: buildSteps(), startIndex: 0, ownerId: OWNER_ID, blockOnUnresolvedAssignee: true, }), ).rejects.toMatchObject({ details: { workflow: "no_actionable_approver", reason: "no_manager_found", stepId: "approval_1_manager", }, }); }); it("keeps legacy skip behavior when strict submit resolution is not requested", async () => { const tx = { employee: { findUnique: vi.fn(async () => ({ id: OWNER_ID, direct_manager_id: null, })), }, }; const result = await resolveActionableStep({ tx, steps: buildSteps(), startIndex: 0, ownerId: OWNER_ID, }); expect(result.actionable).toEqual( expect.objectContaining({ step: expect.objectContaining({ id: "approval_2_finance" }), assigneeRole: "finance", }), ); expect(result.skipped).toEqual([ expect.objectContaining({ reason: "no_manager_found", }), ]); }); });