164 lines
5.8 KiB
TypeScript
164 lines
5.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
canPerformAction,
|
|
getAvailableActions,
|
|
getTransition,
|
|
requiresNote,
|
|
} from "@/lib/po-state-machine";
|
|
|
|
describe("PO State Machine", () => {
|
|
describe("canPerformAction", () => {
|
|
it("allows TECHNICAL to submit a DRAFT", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "TECHNICAL")).toBe(true);
|
|
});
|
|
|
|
it("allows MANNING to submit a DRAFT", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "MANNING")).toBe(true);
|
|
});
|
|
|
|
it("disallows ACCOUNTS from submitting a DRAFT", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "ACCOUNTS")).toBe(false);
|
|
});
|
|
|
|
it("allows MANAGER to approve in MGR_REVIEW", () => {
|
|
expect(canPerformAction("MGR_REVIEW", "approve", "MANAGER")).toBe(true);
|
|
});
|
|
|
|
it("allows SUPERUSER to approve in MGR_REVIEW", () => {
|
|
expect(canPerformAction("MGR_REVIEW", "approve", "SUPERUSER")).toBe(true);
|
|
});
|
|
|
|
it("disallows TECHNICAL from approving", () => {
|
|
expect(canPerformAction("MGR_REVIEW", "approve", "TECHNICAL")).toBe(false);
|
|
});
|
|
|
|
it("allows ACCOUNTS to process payment on MGR_APPROVED", () => {
|
|
expect(canPerformAction("MGR_APPROVED", "process_payment", "ACCOUNTS")).toBe(true);
|
|
});
|
|
|
|
it("allows TECHNICAL to confirm receipt on PAID_DELIVERED", () => {
|
|
expect(canPerformAction("PAID_DELIVERED", "confirm_receipt", "TECHNICAL")).toBe(true);
|
|
});
|
|
|
|
it("disallows action on wrong status", () => {
|
|
expect(canPerformAction("CLOSED", "approve", "MANAGER")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getTransition", () => {
|
|
it("returns correct target state for submit from DRAFT", () => {
|
|
const t = getTransition("DRAFT", "submit");
|
|
expect(t?.to).toBe("SUBMITTED");
|
|
});
|
|
|
|
it("returns correct target state for approve from MGR_REVIEW", () => {
|
|
const t = getTransition("MGR_REVIEW", "approve");
|
|
expect(t?.to).toBe("MGR_APPROVED");
|
|
});
|
|
|
|
it("returns correct target state for reject from MGR_REVIEW", () => {
|
|
const t = getTransition("MGR_REVIEW", "reject");
|
|
expect(t?.to).toBe("REJECTED");
|
|
});
|
|
|
|
it("returns null for unknown action on status", () => {
|
|
expect(getTransition("CLOSED", "approve")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("requiresNote", () => {
|
|
it("reject requires a note", () => {
|
|
expect(requiresNote("MGR_REVIEW", "reject")).toBe(true);
|
|
});
|
|
|
|
it("approve does not require a note", () => {
|
|
expect(requiresNote("MGR_REVIEW", "approve")).toBe(false);
|
|
});
|
|
|
|
it("approve_with_note requires a note", () => {
|
|
expect(requiresNote("MGR_REVIEW", "approve_with_note")).toBe(true);
|
|
});
|
|
|
|
it("request_edits requires a note", () => {
|
|
expect(requiresNote("MGR_REVIEW", "request_edits")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("getAvailableActions", () => {
|
|
it("returns submit for TECHNICAL on DRAFT", () => {
|
|
const actions = getAvailableActions("DRAFT", "TECHNICAL");
|
|
expect(actions).toContain("submit");
|
|
});
|
|
|
|
it("returns all 5 actions for MANAGER on MGR_REVIEW", () => {
|
|
const actions = getAvailableActions("MGR_REVIEW", "MANAGER");
|
|
expect(actions).toContain("approve");
|
|
expect(actions).toContain("approve_with_note");
|
|
expect(actions).toContain("reject");
|
|
expect(actions).toContain("request_edits");
|
|
expect(actions).toContain("request_vendor_id");
|
|
});
|
|
|
|
it("returns empty for ACCOUNTS on DRAFT", () => {
|
|
const actions = getAvailableActions("DRAFT", "ACCOUNTS");
|
|
expect(actions).toHaveLength(0);
|
|
});
|
|
|
|
it("returns empty for closed PO", () => {
|
|
const actions = getAvailableActions("CLOSED", "MANAGER");
|
|
expect(actions).toHaveLength(0);
|
|
});
|
|
|
|
// Role expansions added in feat: manager PO creation
|
|
it("returns submit for MANAGER on DRAFT", () => {
|
|
expect(getAvailableActions("DRAFT", "MANAGER")).toContain("submit");
|
|
});
|
|
|
|
it("returns submit for MANAGER on EDITS_REQUESTED", () => {
|
|
expect(getAvailableActions("EDITS_REQUESTED", "MANAGER")).toContain("submit");
|
|
});
|
|
|
|
it("returns provide_vendor_id for ACCOUNTS on VENDOR_ID_PENDING", () => {
|
|
expect(getAvailableActions("VENDOR_ID_PENDING", "ACCOUNTS")).toContain("provide_vendor_id");
|
|
});
|
|
|
|
it("still returns no submit for ACCOUNTS on DRAFT", () => {
|
|
expect(getAvailableActions("DRAFT", "ACCOUNTS")).not.toContain("submit");
|
|
});
|
|
|
|
it("AUDITOR has no available actions on any status", () => {
|
|
for (const status of ["DRAFT", "MGR_REVIEW", "VENDOR_ID_PENDING", "MGR_APPROVED"] as const) {
|
|
expect(getAvailableActions(status, "AUDITOR")).toHaveLength(0);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("canPerformAction — new role expansions", () => {
|
|
it("MANAGER can submit from DRAFT", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "MANAGER")).toBe(true);
|
|
});
|
|
|
|
it("MANAGER can submit from EDITS_REQUESTED", () => {
|
|
expect(canPerformAction("EDITS_REQUESTED", "submit", "MANAGER")).toBe(true);
|
|
});
|
|
|
|
it("ACCOUNTS can provide_vendor_id from VENDOR_ID_PENDING", () => {
|
|
expect(canPerformAction("VENDOR_ID_PENDING", "provide_vendor_id", "ACCOUNTS")).toBe(true);
|
|
});
|
|
|
|
it("ACCOUNTS still cannot submit from DRAFT", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "ACCOUNTS")).toBe(false);
|
|
});
|
|
|
|
it("AUDITOR cannot perform any action", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "AUDITOR")).toBe(false);
|
|
expect(canPerformAction("MGR_REVIEW", "approve", "AUDITOR")).toBe(false);
|
|
});
|
|
|
|
it("ADMIN cannot perform any PO state transitions", () => {
|
|
expect(canPerformAction("DRAFT", "submit", "ADMIN")).toBe(false);
|
|
expect(canPerformAction("MGR_REVIEW", "approve", "ADMIN")).toBe(false);
|
|
expect(canPerformAction("MGR_APPROVED", "process_payment", "ADMIN")).toBe(false);
|
|
});
|
|
});
|
|
});
|