pelagia-portal/App/pelagia-portal/tests/integration/create-po.test.ts
Hardik e07ce9bd02 test: unit, integration and E2E test suite (110 unit tests passing)
Unit (Vitest + jsdom):
  po-state-machine.test.ts   21 tests — all transitions and helpers
  permissions.test.ts        11 tests — all 7 roles
  utils.test.ts              17 tests — formatCurrency INR, formatDate, status labels
  validations.test.ts        24 tests — createPoSchema, lineItemSchema, TC defaults
  po-status-badge.test.tsx   17 tests — all 10 statuses
  po-line-items-editor.test.tsx 20 tests — add/remove, GST calc, read-only, diff mode

Integration (Vitest + real DB, mocked auth/notifier):
  create-po.test.ts          — S-01 create, S-02 draft, S-03 submit
  approval-actions.test.ts   — M-02 approve, M-03 reject, M-04 edits/vendor-id, S-06/S-07
  payment-actions.test.ts    — A-01 queue, A-02 mark paid with reference

E2E (Playwright):
  auth.spec.ts               — login, role nav, sign out
  submitter-journey.spec.ts  — S-01 to S-08
  manager-approvals.spec.ts  — M-01 to M-04
  accounts-payment.spec.ts   — A-01, A-02
  po-export.spec.ts          — export buttons, endpoint responses, PDF content
2026-05-06 00:16:10 +05:30

192 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Integration tests for PO creation server action.
* Covers: S-01 (create with line items), S-02 (save as draft), S-03 (submit for approval).
*/
import { vi, describe, it, expect, beforeAll, afterEach } from "vitest";
vi.mock("@/auth", () => ({ auth: vi.fn() }));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
vi.mock("@/lib/notifier", () => ({ notify: vi.fn() }));
import { auth } from "@/auth";
import { db } from "@/lib/db";
import { createPo } from "@/app/(portal)/po/new/actions";
import {
makeSession, getSeedUser, getSeedVessel, getSeedAccount, getSeedVendor,
makePoForm, deletePosByTitle,
} from "./helpers";
const PREFIX = "INTTEST_CREATE_";
let techId: string;
let vesselId: string;
let accountId: string;
let vendorId: string;
beforeAll(async () => {
const [tech, vessel, account, vendor] = await Promise.all([
getSeedUser("tech@pelagia.local"),
getSeedVessel("MV Ocean Pride"),
getSeedAccount("700201"),
getSeedVendor("Apar Industries Ltd"),
]);
techId = tech.id;
vesselId = vessel.id;
accountId = account.id;
vendorId = vendor.id;
});
afterEach(async () => {
await deletePosByTitle(PREFIX);
});
// ── S-02: Save as draft ──────────────────────────────────────────────────────
describe("S-02 — save as draft", () => {
it("creates a PO in DRAFT status", async () => {
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({
title: `${PREFIX}Draft`,
vesselId, accountId, intent: "draft",
});
const result = await createPo(form);
expect(result).not.toHaveProperty("error");
const id = (result as { id: string }).id;
const po = await db.purchaseOrder.findUnique({ where: { id } });
expect(po?.status).toBe("DRAFT");
expect(po?.submittedAt).toBeNull();
});
it("returns error for unauthenticated request", async () => {
vi.mocked(auth).mockResolvedValue(null);
const form = makePoForm({ title: `${PREFIX}Unauth`, vesselId, accountId });
const result = await createPo(form);
expect(result).toEqual({ error: "Unauthorized" });
});
it("returns error when ACCOUNTS role tries to create a PO", async () => {
const acct = await getSeedUser("accounts@pelagia.local");
vi.mocked(auth).mockResolvedValue(makeSession(acct.id, "ACCOUNTS"));
const form = makePoForm({ title: `${PREFIX}ForbiddenAccts`, vesselId, accountId });
const result = await createPo(form);
expect(result).toHaveProperty("error");
});
it("returns error when a required field (vesselId) is missing", async () => {
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = new FormData();
form.set("title", `${PREFIX}NoVessel`);
form.set("accountId", accountId);
form.set("intent", "draft");
form.set("lineItems[0].description", "Item");
form.set("lineItems[0].quantity", "1");
form.set("lineItems[0].unit", "pc");
form.set("lineItems[0].unitPrice", "50");
form.set("lineItems[0].gstRate", "0.18");
const result = await createPo(form);
expect(result).toHaveProperty("error");
});
});
// ── S-01: Create with line items ─────────────────────────────────────────────
describe("S-01 — create PO with line items", () => {
it("stores line items with correct quantity, unit price, and GST rate", async () => {
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({
title: `${PREFIX}LineItems`,
vesselId, accountId, vendorId, intent: "draft",
lineItems: [
{ description: "Gear Oil 80W90", quantity: 50, unit: "L", unitPrice: 182, gstRate: 0.18 },
{ description: "Engine Filter", quantity: 4, unit: "pc", unitPrice: 250, gstRate: 0.12 },
],
});
const result = await createPo(form);
const id = (result as { id: string }).id;
const po = await db.purchaseOrder.findUnique({
where: { id },
include: { lineItems: { orderBy: { sortOrder: "asc" } } },
});
expect(po?.lineItems).toHaveLength(2);
expect(Number(po!.lineItems[0].quantity)).toBe(50);
expect(Number(po!.lineItems[0].unitPrice)).toBe(182);
expect(Number(po!.lineItems[0].gstRate)).toBeCloseTo(0.18);
expect(Number(po!.lineItems[1].unitPrice)).toBe(250);
expect(Number(po!.lineItems[1].gstRate)).toBeCloseTo(0.12);
});
it("sets totalAmount to grand total including GST", async () => {
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
// 10 × 100 × 1.18 = 1180
const form = makePoForm({
title: `${PREFIX}GrandTotal`,
vesselId, accountId, intent: "draft",
lineItems: [{ description: "Item", quantity: 10, unit: "pc", unitPrice: 100, gstRate: 0.18 }],
});
const result = await createPo(form);
const id = (result as { id: string }).id;
const po = await db.purchaseOrder.findUnique({ where: { id } });
expect(Number(po!.totalAmount)).toBeCloseTo(1180, 1);
});
it("stores optional fields (PI quotation no, place of delivery, TC fields)", async () => {
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({ title: `${PREFIX}Optional`, vesselId, accountId, intent: "draft" });
form.set("piQuotationNo", "Verbal");
form.set("placeOfDelivery", "CBD Belapur, Navi Mumbai");
form.set("tcDelivery", "Within 7 days");
form.set("tcPaymentTerms", "Net 45");
const result = await createPo(form);
const id = (result as { id: string }).id;
const po = await db.purchaseOrder.findUnique({ where: { id } });
expect((po as any).piQuotationNo).toBe("Verbal");
expect((po as any).placeOfDelivery).toBe("CBD Belapur, Navi Mumbai");
expect((po as any).tcDelivery).toBe("Within 7 days");
expect((po as any).tcPaymentTerms).toBe("Net 45");
});
it("allows MANNING role to create a PO", async () => {
const manning = await getSeedUser("manning@pelagia.local");
vi.mocked(auth).mockResolvedValue(makeSession(manning.id, "MANNING"));
const form = makePoForm({ title: `${PREFIX}Manning`, vesselId, accountId });
const result = await createPo(form);
expect(result).not.toHaveProperty("error");
});
});
// ── S-03: Submit for approval ─────────────────────────────────────────────────
describe("S-03 — submit for approval", () => {
it("creates PO with status MGR_REVIEW and sets submittedAt", async () => {
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({ title: `${PREFIX}Submit`, vesselId, accountId, intent: "submit" });
const result = await createPo(form);
expect(result).not.toHaveProperty("error");
const id = (result as { id: string }).id;
const po = await db.purchaseOrder.findUnique({ where: { id } });
expect(po?.status).toBe("MGR_REVIEW");
expect(po?.submittedAt).not.toBeNull();
});
it("sends notification to managers on submit", async () => {
const { notify } = await import("@/lib/notifier");
vi.mocked(notify).mockClear();
vi.mocked(auth).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({ title: `${PREFIX}Notify`, vesselId, accountId, intent: "submit" });
await createPo(form);
expect(vi.mocked(notify)).toHaveBeenCalledWith(
expect.objectContaining({ event: "PO_SUBMITTED" })
);
});
});