import { describe, it, expect } from "vitest"; import { ORDER_STATUSES, NEXT_STATUS, isKnownOrderStatus, nextOrderStatus, resolveAdvanceAction, buildOrderInboxItem, } from "@/lib/order-flow"; describe("order-flow — server-renderad advance-resolution (ADR-0001)", () => { it("flödets fem steg har nästa status", () => { expect(NEXT_STATUS.draft).toBe("quoted"); expect(NEXT_STATUS.accepted).toBe("delivered"); }); it("aktivt steg → available", () => { expect(resolveAdvanceAction("draft")).toMatchObject({ state: "available" }); expect(resolveAdvanceAction("review")).toMatchObject({ state: "available" }); }); it("terminalt steg → read_only/read_only_resource (inte fake available)", () => { expect(resolveAdvanceAction("delivered")).toMatchObject({ state: "read_only", reason: "read_only_resource" }); expect(resolveAdvanceAction("cancelled")).toMatchObject({ state: "read_only", reason: "read_only_resource" }); }); }); describe("order-flow — kanonisk statusmaskin (ADR-0002, C3)", () => { const TERMINAL: ReadonlySet = new Set(["delivered", "cancelled"]); const EXPECTED_NEXT: Record = { draft: "quoted", quoted: "in_production", in_production: "review", review: "accepted", accepted: "delivered", delivered: null, cancelled: null, }; it("ORDER_STATUSES är utömmande: varje känd status → korrekt nästa/read_only", () => { expect(ORDER_STATUSES).toHaveLength(7); for (const status of ORDER_STATUSES) { expect(isKnownOrderStatus(status), `känd status: ${status}`).toBe(true); if (TERMINAL.has(status)) { expect(nextOrderStatus(status), `${status} är terminal`).toBeNull(); expect(resolveAdvanceAction(status)).toMatchObject({ state: "read_only", reason: "read_only_resource" }); expect(buildOrderInboxItem({ order_id: "abcd1234-5678", status, flow: "custom" })).toBeNull(); } else { expect(nextOrderStatus(status)).toBe(EXPECTED_NEXT[status]); expect(resolveAdvanceAction(status)).toMatchObject({ state: "available" }); } } }); it("flödet är en linjedraft→…→delivered-kedja utan genvägar", () => { let cursor: string | null = "draft"; const walked: string[] = []; while (cursor) { walked.push(cursor); cursor = nextOrderStatus(cursor); } expect(walked).toEqual(["draft", "quoted", "in_production", "review", "accepted", "delivered"]); }); it("okänd status (fri text ur DB) → read_only, ingen nästa, ingen inbox-post (guardad)", () => { for (const status of ["klar", "Klar", "done", "", "in prod", "cancelled "]) { expect(isKnownOrderStatus(status)).toBe(false); expect(nextOrderStatus(status)).toBeNull(); expect(resolveAdvanceAction(status)).toMatchObject({ state: "read_only", reason: "read_only_resource" }); expect(buildOrderInboxItem({ order_id: "abcd1234-5678", status, flow: "custom" })).toBeNull(); } }); it("cancelled ingår i unionen (används som terminal status i tests/produkten)", () => { expect(ORDER_STATUSES).toContain("cancelled"); }); }); describe("order-flow — inbox-projektion (våg 3.3)", () => { const order = (over: Partial<{ order_id: string; status: string; flow: string; total_ore: number }> = {}) => ({ order_id: "abcd1234-5678", status: "review", flow: "custom", total_ore: 500000, ...over, }); it("review-order → approval-post med hög prioritet och advance-action", () => { const item = buildOrderInboxItem(order()); expect(item).toMatchObject({ kind: "approval", priority: "high", state: "open" }); expect(item!.actions?.[0].label).toContain("accepted"); expect(item!.entity).toMatchObject({ type: "order", id: "abcd1234-5678" }); }); it("levererad order → ingen post (inkorgen är öppna arbetsobjekt)", () => { expect(buildOrderInboxItem(order({ status: "delivered" }))).toBeNull(); expect(buildOrderInboxItem(order({ status: "cancelled" }))).toBeNull(); }); it("draft-order → task med normal prioritet", () => { const item = buildOrderInboxItem(order({ status: "draft" })); expect(item).toMatchObject({ kind: "task", priority: "normal" }); expect(item!.subject).toContain("draft"); }); });