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:
parent
b70eec261b
commit
4c53aeecb0
4 changed files with 20 additions and 12 deletions
|
|
@ -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 });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue