test(integration): green the last 3 behavioural-drift tests (108/108)

- resubmit: updatePo distinguishes intent "resubmit" (from EDITS_REQUESTED)
  from "submit" (from DRAFT); test now sends "resubmit" (makePoForm widened).
- payment: MANAGER now holds process_payment, so the "wrong permission"
  negative test uses TECHNICAL (which lacks it).
- vendor: provideVendorId rejects on a missing vendorId *code*; seeded
  unverified vendors carry codes, so create a genuinely code-less vendor.

Full integration suite: 108/108 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hardik 2026-06-21 02:39:41 +05:30
parent b70eec261b
commit 4c53aeecb0
4 changed files with 20 additions and 12 deletions

View file

@ -344,7 +344,7 @@ describe("S-07 — edit and resubmit after edits requested", () => {
await requestEdits({ poId, note: "Update line items" }); await requestEdits({ poId, note: "Update line items" });
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL")); vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({ title: `${PREFIX}Resubmit`, vesselId, accountId, intent: "submit" }); const form = makePoForm({ title: `${PREFIX}Resubmit`, vesselId, accountId, intent: "resubmit" });
const result = await updatePo(poId, form); const result = await updatePo(poId, form);
expect(result).toEqual({ id: poId }); expect(result).toEqual({ id: poId });

View file

@ -58,7 +58,7 @@ export function makePoForm(overrides: {
vesselId: string; vesselId: string;
accountId: string; accountId: string;
vendorId?: string; vendorId?: string;
intent?: "draft" | "submit"; intent?: "draft" | "submit" | "resubmit";
lineItems?: Array<{ description: string; quantity: number; unit: string; unitPrice: number; gstRate?: number }>; lineItems?: Array<{ description: string; quantity: number; unit: string; unitPrice: number; gstRate?: number }>;
}): FormData { }): FormData {
const form = new FormData(); const form = new FormData();
@ -76,11 +76,12 @@ export function makePoForm(overrides: {
// ── Cleanup helpers ────────────────────────────────────────────────────────── // ── Cleanup helpers ──────────────────────────────────────────────────────────
// POAction has no onDelete: Cascade, so its rows must be removed before the PO. // POAction and Receipt have no onDelete: Cascade, so their rows must be removed
// (POLineItem / PODocument / Receipt cascade automatically.) // before the PO. (POLineItem / PODocument cascade automatically.)
async function deletePosByIds(ids: string[]) { async function deletePosByIds(ids: string[]) {
if (ids.length === 0) return; if (ids.length === 0) return;
await db.pOAction.deleteMany({ where: { poId: { in: ids } } }); await db.pOAction.deleteMany({ where: { poId: { in: ids } } });
await db.receipt.deleteMany({ where: { poId: { in: ids } } });
await db.purchaseOrder.deleteMany({ where: { id: { in: ids } } }); await db.purchaseOrder.deleteMany({ where: { id: { in: ids } } });
} }

View file

@ -151,14 +151,14 @@ describe("A-02 — mark PO as paid with reference number", () => {
expect(calls).toContain("PAYMENT_SENT"); expect(calls).toContain("PAYMENT_SENT");
}); });
it("MANAGER role cannot mark as paid (wrong permission)", async () => { it("TECHNICAL role cannot mark as paid (no process_payment permission)", async () => {
const poId = await createApprovedPo(`${PREFIX}PaidMgrForbidden`); const poId = await createApprovedPo(`${PREFIX}PaidTechForbidden`);
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS")); vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
await processPayment({ poId }); await processPayment({ poId });
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(managerId, "MANAGER")); vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const result = await markPaid({ poId, paymentRef: "MGR-REF", paymentDate: TODAY }); const result = await markPaid({ poId, paymentRef: "TECH-REF", paymentDate: TODAY });
expect(result).toHaveProperty("error"); expect(result).toHaveProperty("error");
}); });
}); });

View file

@ -7,7 +7,7 @@
* - Unverified vendor rejected by provideVendorId * - Unverified vendor rejected by provideVendorId
* - AUDITOR cannot provide vendor ID * - AUDITOR cannot provide vendor ID
*/ */
import { vi, describe, it, expect, beforeAll, afterEach } from "vitest"; import { vi, describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
vi.mock("@/auth", () => ({ auth: vi.fn() })); vi.mock("@/auth", () => ({ auth: vi.fn() }));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
@ -66,15 +66,22 @@ beforeAll(async () => {
auditorId = created.id; auditorId = created.id;
} }
// Grab an unverified vendor // A vendor with no formal vendorId code — provideVendorId must reject it.
const unverified = await db.vendor.findFirst({ where: { isVerified: false } }); // (Seeded "unverified" vendors can still carry a code, so create a code-less one.)
unverifiedVendorDbId = unverified!.id; const noCode = await db.vendor.create({
data: { name: `${PREFIX}NoCodeVendor`, isVerified: false, vendorId: null },
});
unverifiedVendorDbId = noCode.id;
}); });
afterEach(async () => { afterEach(async () => {
await deletePosByTitle(PREFIX); await deletePosByTitle(PREFIX);
}); });
afterAll(async () => {
await db.vendor.deleteMany({ where: { name: { startsWith: PREFIX } } });
});
async function makeReviewPo(title: string, withVendor = false) { async function makeReviewPo(title: string, withVendor = false) {
vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL")); vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
const form = makePoForm({ const form = makePoForm({