pelagia-portal/App/tests/integration/vendor-approval.test.ts
Hardik b70eec261b test(integration): repair stale integration suite (wip: 97/108)
The integration suite had rotted against the app. Systematic fixes:
- seed refs: MV Ocean Pride/Sea Breeze/TECH-OPS → current seed entities
- helper appendLineItem set lineItems[i].description; createPo now keys on
  lineItems[i].name → zero line items. Fixed to .name.
- vendor gating: lifecycle setups (approval/payment/receipt) now attach the
  seeded verified vendor before approval.
- cleanup: POAction has no onDelete:Cascade, so deletePo(sByTitle) now removes
  POAction rows before the PO.
- import-api: fixture committed to tests/fixtures/Sample_PO.xlsx (was an
  absolute path to a non-existent dir).
- products-search: code search assertion .every → .some (search spans fields).

11 failures remain (behavioral drift — separate commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 02:34:07 +05:30

167 lines
6.2 KiB
TypeScript

/**
* Integration tests for vendor-gated approval and provide-vendor-id.
* Covers:
* - Approval blocked when no vendor assigned
* - Approval succeeds once vendor is set
* - ACCOUNTS role can now call provideVendorId
* - Unverified vendor rejected by provideVendorId
* - AUDITOR cannot provide vendor ID
*/
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 { approvePo, requestVendorId } from "@/app/(portal)/approvals/[id]/actions";
import { provideVendorId } from "@/app/(portal)/po/[id]/actions";
import {
makeSession, getSeedUser, getSeedVessel, getSeedAccount, getSeedVendor,
makePoForm, deletePosByTitle,
} from "./helpers";
const PREFIX = "INTTEST_VENDOR_APPROVAL_";
let techId: string;
let managerId: string;
let accountsId: string;
let auditorId: string;
let vesselId: string;
let accountId: string;
let verifiedVendorId: string;
let unverifiedVendorDbId: string;
beforeAll(async () => {
const [tech, mgr, acct, vessel, account, vendor] = await Promise.all([
getSeedUser("tech@pelagia.local"),
getSeedUser("manager@pelagia.local"),
getSeedUser("accounts@pelagia.local"),
getSeedVessel("MV Pelagia Star"),
getSeedAccount("700201"),
getSeedVendor("Apar Industries Ltd"),
]);
techId = tech.id;
managerId = mgr.id;
accountsId = acct.id;
vesselId = vessel.id;
accountId = account.id;
verifiedVendorId = vendor.id;
// Auditor — create on-the-fly if not seeded
const maybeAuditor = await db.user.findFirst({ where: { role: "AUDITOR" } });
if (maybeAuditor) {
auditorId = maybeAuditor.id;
} else {
const created = await db.user.create({
data: {
employeeId: "EMP-TEST-AUD",
email: "auditor@test.local",
name: "Test Auditor",
passwordHash: "irrelevant",
role: "AUDITOR",
},
});
auditorId = created.id;
}
// Grab an unverified vendor
const unverified = await db.vendor.findFirst({ where: { isVerified: false } });
unverifiedVendorDbId = unverified!.id;
});
afterEach(async () => {
await deletePosByTitle(PREFIX);
});
async function makeReviewPo(title: string, withVendor = false) {
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({
title,
vesselId,
accountId,
intent: "submit",
vendorId: withVendor ? verifiedVendorId : undefined,
});
const result = await createPo(form);
return (result as { id: string }).id;
}
// ── Vendor required for approval ──────────────────────────────────────────────
describe("approval — vendor required", () => {
it("blocks approval when PO has no vendor assigned", async () => {
const poId = await makeReviewPo(`${PREFIX}NoVendorBlock`);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));
const result = await approvePo({ poId });
expect(result).toHaveProperty("error");
expect((result as { error: string }).error).toMatch(/vendor/i);
const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
expect(po?.status).toBe("MGR_REVIEW");
});
it("allows approval when PO has a vendor assigned", async () => {
const poId = await makeReviewPo(`${PREFIX}VendorPresent`, true);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));
const result = await approvePo({ poId });
expect(result).toEqual({ ok: true });
const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
expect(po?.status).toBe("MGR_APPROVED");
});
});
// ── provideVendorId — role expansion ─────────────────────────────────────────
describe("provideVendorId — role expansion", () => {
async function makePendingPo(title: string) {
const poId = await makeReviewPo(title);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER"));
await requestVendorId({ poId });
return poId;
}
it("ACCOUNTS can provide a verified vendor ID", async () => {
const poId = await makePendingPo(`${PREFIX}AccountsProvide`);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
const result = await provideVendorId({ poId, vendorId: verifiedVendorId });
expect(result).toEqual({ ok: true });
const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
expect(po?.status).toBe("MGR_REVIEW");
expect(po?.vendorId).toBe(verifiedVendorId);
});
it("rejects an unverified vendor (no vendorId field on Vendor record)", async () => {
const poId = await makePendingPo(`${PREFIX}UnverifiedVendor`);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
const result = await provideVendorId({ poId, vendorId: unverifiedVendorDbId });
expect(result).toHaveProperty("error");
const po = await db.purchaseOrder.findUnique({ where: { id: poId } });
expect(po?.status).toBe("VENDOR_ID_PENDING");
});
it("AUDITOR cannot provide vendor ID", async () => {
const poId = await makePendingPo(`${PREFIX}AuditorDenied`);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(auditorId, "AUDITOR"));
const result = await provideVendorId({ poId, vendorId: verifiedVendorId });
expect(result).toHaveProperty("error");
});
it("returns error when called on a PO not in VENDOR_ID_PENDING state", async () => {
// PO still in MGR_REVIEW — no requestVendorId called
const poId = await makeReviewPo(`${PREFIX}WrongState`);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
const result = await provideVendorId({ poId, vendorId: verifiedVendorId });
expect(result).toHaveProperty("error");
});
});