diff --git a/src/lib/server/stock-pricing-service.test.ts b/src/lib/server/stock-pricing-service.test.ts index b49dfae..dc24f7b 100644 --- a/src/lib/server/stock-pricing-service.test.ts +++ b/src/lib/server/stock-pricing-service.test.ts @@ -186,3 +186,95 @@ describe("updateStockItemsPricing — stock_ledger audit on cost change", () => ]); }); }); + +describe("updateStockItemsPricing — input validation", () => { + it("rejects negative numbers", async () => { + const result = await updateStockItemsPricing({ + pb: { collection: () => ({ getOne: vi.fn(), update: vi.fn(), create: vi.fn() }) } as never, + updates: [{ id: "abcdefghijklmno", costPrice: -1 }], + actor: { id: "useropid1234567", name: "Gary" }, + }); + expect(result.results[0]).toMatchObject({ + ok: false, + reason: "validation_error", + }); + }); + + it("rejects forbidden fields", async () => { + const result = await updateStockItemsPricing({ + pb: { collection: () => ({ getOne: vi.fn(), update: vi.fn(), create: vi.fn() }) } as never, + updates: [ + { id: "abcdefghijklmno", costPrice: 1, maliciousFlag: true } as never, + ], + actor: { id: "useropid1234567", name: "Gary" }, + }); + expect(result.results[0]).toMatchObject({ + ok: false, + reason: "forbidden_field", + }); + }); +}); + +describe("updateStockItemsPricing — not_found and per-row independence", () => { + it("returns not_found when the row is gone", async () => { + const pb = { + collection: (name: string) => { + if (name === "stock_items") { + return { + getOne: vi.fn().mockRejectedValue(new Error("404 not found")), + update: vi.fn(), + create: vi.fn(), + }; + } + if (name === "stock_ledger") { + return { create: vi.fn() }; + } + throw new Error(`unexpected ${name}`); + }, + }; + const result = await updateStockItemsPricing({ + pb: pb as never, + updates: [{ id: "missing00000000x", costPrice: 1 }], + actor: { id: "useropid1234567", name: "Gary" }, + }); + expect(result.results[0]).toMatchObject({ + ok: false, + reason: "not_found", + }); + }); + + it("does not abort sibling rows when one is not_found", async () => { + const updated = { id: "abcdefghijklmno", cost_price: 5, selling_price: 0, markup: 0 }; + let nextIsMissing = true; + const pb = { + collection: (name: string) => { + if (name === "stock_items") { + return { + getOne: vi.fn().mockImplementation(async () => { + if (nextIsMissing) { + nextIsMissing = false; + throw new Error("404 not found"); + } + return updated; + }), + update: vi.fn().mockResolvedValue(updated), + create: vi.fn(), + }; + } + if (name === "stock_ledger") return { create: vi.fn().mockResolvedValue({ id: "l1" }) }; + throw new Error(`unexpected ${name}`); + }, + }; + const result = await updateStockItemsPricing({ + pb: pb as never, + updates: [ + { id: "missing00000000x", costPrice: 1 }, + { id: "abcdefghijklmno", costPrice: 5 }, + ], + actor: { id: "useropid1234567", name: "Gary" }, + }); + expect(result.summary).toEqual({ total: 2, succeeded: 1, failed: 1 }); + expect(result.results[0]).toMatchObject({ ok: false, reason: "not_found" }); + expect(result.results[1]).toMatchObject({ ok: true }); + }); +});