WFDASH-WEBAPP/src/lib/server/collection-return-guard.test.ts

115 lines
5 KiB
TypeScript
Raw Normal View History

// Run the real PB hook in a JS sandbox; no live records are changed.
import { readFileSync } from "node:fs";
import { runInNewContext } from "node:vm";
import { describe, expect, it, vi } from "vitest";
function setup(stored: Record<string, unknown> = {}, collection = "order_items") {
let hook!: (event: unknown) => void;
let capability!: (event: unknown) => void;
let createHook!: (event: unknown) => void;
const next = vi.fn();
const body = { _collection_return_guard: { updated: "v1", parent_updated: "p1" } };
const parent = { id: "po1", updated: "p1", status: "received", booked_at: "" };
const app = {
findRecordById: vi.fn((name: string) => name === "stock_purchases" ? parent : original),
findRecordsByFilter: vi.fn((): unknown[] => []),
runInTransaction: (fn: (tx: unknown) => void) => fn(app),
};
const info = { body, context: "batch" };
const original = { id: "row1", updated: "v1", item_status: "collected", status: "collected", purchase: "po1", ...stored };
const e = {
requestInfo: () => info, auth: { isSuperuser: () => true }, app, next,
record: { original: () => original, collection: () => ({ name: collection }), getString: (field: string) => String((original as Record<string, unknown>)[field] ?? "") },
};
const source = readFileSync("pocketbase/pb_hooks/collection_return.pb.js", "utf8");
runInNewContext(source, {
onRecordUpdateRequest: (callback: typeof hook) => { hook = callback; },
onRecordCreateRequest: (callback: typeof hook) => { createHook = callback; },
routerAdd: (_method: string, _path: string, callback: typeof capability) => { capability = callback; },
$apis: { requireSuperuserAuth: () => ({}) },
BadRequestError: Error, ForbiddenError: Error,
});
return { call: () => hook(e), create: () => createHook(e), e, next, app, info, parent, capability, original };
}
describe("PB collection return transaction guard", () => {
it("allows a matching collected revision inside a batch", () => {
const test = setup();
test.call();
expect(test.next).toHaveBeenCalledOnce();
});
it("leaves ordinary updates alone", () => {
const test = setup();
test.info.body = {} as typeof test.info.body;
test.call();
expect(test.next).toHaveBeenCalledOnce();
});
it.each([
{ updated: "v2" }, { item_status: "ready_invoice" }, { qty_delivered: 1 },
{ order_item_details: { invoice: { number: "INV1", qbInvoiceId: "qb1" } } },
{ order_item_details: JSON.stringify({ invoice: { number: "INV1", billingCompany: "CNL" } }) },
])("refuses a concurrent advance: %j", stored => {
const test = setup(stored);
expect(test.call).toThrow();
expect(test.next).not.toHaveBeenCalled();
});
it("refuses non-batch resets", () => {
const test = setup();
test.info.context = "default";
expect(test.call).toThrow();
});
it("refuses non-superusers", () => {
const test = setup();
test.e.auth.isSuperuser = () => false;
expect(test.call).toThrow();
});
it("accepts a supplier-invoice blob on a still-collected customer item", () => {
const test = setup({ order_item_details: { invoice: { number: "SUP1" } } });
test.call();
expect(test.next).toHaveBeenCalledOnce();
});
it("refuses a stock parent changed after the app's check", () => {
const test = setup({}, "stock_purchase_items");
test.parent.updated = "p2";
expect(test.call).toThrow();
});
it("refuses a stock booking that started after the app's check", () => {
const test = setup({}, "stock_purchase_items");
test.app.findRecordsByFilter.mockReturnValue([{ id: "new-receipt" }]);
expect(test.call).toThrow();
});
it("allows an unbooked received header to reopen", () => {
const test = setup({ status: "received" }, "stock_purchases");
test.call();
expect(test.next).toHaveBeenCalledOnce();
});
it("refuses a stock header booked between checking and saving", () => {
const test = setup({ status: "received", booked_at: "today" }, "stock_purchases");
expect(test.call).toThrow();
});
it("refuses stale confirmation after a reset has committed", () => {
const test = setup({ item_status: "collection_pool" });
test.info.body = { item_status: "ready_invoice" } as unknown as typeof test.info.body;
expect(test.call).toThrow();
expect(test.next).not.toHaveBeenCalled();
});
it("allows normal collection confirmation", () => {
const test = setup();
test.info.body = { item_status: "ready_invoice" } as unknown as typeof test.info.body;
test.call();
expect(test.next).toHaveBeenCalledOnce();
});
it("refuses a stale stock booking ledger insert after a reset reopens the PO", () => {
const test = setup({ reference_type: "stock_purchase", reference_id: "po1" });
test.parent.status = "sent";
expect(test.create).toThrow();
expect(test.next).not.toHaveBeenCalled();
});
it("allows a stock booking ledger insert while the PO is received", () => {
const test = setup({ reference_type: "stock_purchase", reference_id: "po1" });
test.create();
expect(test.next).toHaveBeenCalledOnce();
});
});