pelagia-portal/App/tests/integration/create-po.test.ts
Hardik 938ff6df89
All checks were successful
PR checks / checks (pull_request) Successful in 30s
test+ci: green the test baseline and make type-check + unit tests hard gates
Green-lights the test suite so the PR checks can enforce it:
- Fix the NextAuth v5 auth() mock typing across all integration tests (cast to a
  simple async fn so mockResolvedValue accepts the session) — clears ~86 errors.
- Fix stale test values: intent 'resubmit'->'submit' / 'save'->'draft'; ParsedImportLine
  .description -> .name; approvepo -> approvePo; add missing beforeEach/beforeAll imports.
- permissions: MANAGER *can* process_payment (intentional since e1340b9) — update the
  stale assertion.
- po-import-parser: skip the Sample_PO.xlsx fixture tests when the file is absent (it
  lives outside the repo); synthetic-workbook tests still cover the parser.

type-check is now 0 errors and unit tests pass (167 passed, 13 skipped). pr-checks.yml
flips type-check (whole project) and unit tests to HARD gates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:03:54 +05:30

192 lines
7.9 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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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 as unknown as () => Promise<unknown>).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" })
);
});
});