diff --git a/src/lib/server/orders-service.test.ts b/src/lib/server/orders-service.test.ts index ae8ffe6..94b289f 100644 --- a/src/lib/server/orders-service.test.ts +++ b/src/lib/server/orders-service.test.ts @@ -2904,6 +2904,9 @@ describe("createOrder quote auto-detection", () => { }); it("never fails order creation when the matcher throws", async () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); const { suggestQuoteForOrder } = await import("./order-quote-link-service"); vi.mocked(suggestQuoteForOrder).mockClear(); vi.mocked(suggestQuoteForOrder).mockRejectedValueOnce(new Error("PB down")); @@ -2918,5 +2921,50 @@ describe("createOrder quote auto-detection", () => { expect(result.order.id).toBeDefined(); expect(result.items_created.length).toBe(1); + // Verifies the inner catch swallowed the matcher failure and + // logged it — without this assertion the test passes purely + // because the `after` mock detaches the promise regardless of + // what production code does. + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining("[createOrder] quote auto-detection failed for order"), + expect.stringContaining("PB down"), + ); + consoleErrorSpy.mockRestore(); + }); + + it("falls back to an inline match when after() is unavailable", async () => { + const consoleErrorSpy = vi + .spyOn(console, "error") + .mockImplementation(() => {}); + const { after } = await import("next/server"); + const { suggestQuoteForOrder } = await import("./order-quote-link-service"); + vi.mocked(suggestQuoteForOrder).mockClear(); + vi.mocked(after).mockClear(); + vi.mocked(after).mockImplementationOnce(() => { + throw new Error("`after` was called outside a request scope."); + }); + await pbMock.collection("customers").create({ id: "cust-x", name: "Acme" }); + await pbMock.collection("companies").create({ id: "comp-x", name: "Acme (Pty) Ltd" }); + + const r = await createOrder({ + customer_id: "cust-x", + company_id: "comp-x", + items: [{ description: "M16 Bolt", quantity: 50 }], + }); + + expect(r.order.id).toBeDefined(); + // Proves the catch fired AND ran the inline fallback — without + // the call assertion the test would pass on the spy alone even + // if `void runQuoteMatch()` was deleted. + expect(suggestQuoteForOrder).toHaveBeenCalledWith(r.order.id); + // The fallback logs its engagement so a silent switch to + // fire-and-forget is impossible to miss in production logs. + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining( + "[createOrder] after() unavailable, running quote match inline", + ), + expect.anything(), + ); + consoleErrorSpy.mockRestore(); }); }); diff --git a/src/lib/server/orders-service.ts b/src/lib/server/orders-service.ts index 955319d..4cb1ffa 100644 --- a/src/lib/server/orders-service.ts +++ b/src/lib/server/orders-service.ts @@ -2235,16 +2235,25 @@ export async function createOrder(input: CreateOrderInput): Promise