const { summarizeBudgetLedgerByPool, reserveBudgetForEntity, confirmBudgetForEntity, releaseReservedBudgetIfAny, } = require("../../../src/services/purchaseBudget"); describe("purchaseBudget service", () => { it("summarizes reserve confirm release by pool", () => { expect( summarizeBudgetLedgerByPool([ { budget_pool_id: "pool-1", kind: "reserve", amount: "120.00" }, { budget_pool_id: "pool-1", kind: "confirm", amount: "120.00" }, { budget_pool_id: "pool-1", kind: "release", amount: "20.00" }, { budget_pool_id: "pool-2", kind: "reserve", amount: "80.00" }, ]), ).toEqual([ { budgetPoolId: "pool-1", reserve: 120, confirm: 120, release: 20, outstanding: 100, confirmPending: 0, }, { budgetPoolId: "pool-2", reserve: 80, confirm: 0, release: 0, outstanding: 80, confirmPending: 80, }, ]); }); function fixture(entries = [], pool = { id: "pool-1", used_amount: "20.00" }) { return { $queryRaw: vi.fn().mockResolvedValue([]), budgetLedgerEntry: { findMany: vi.fn().mockResolvedValue(entries), create: vi.fn().mockResolvedValue({}) }, budgetPool: { findUnique: vi.fn().mockResolvedValue(pool), update: vi.fn().mockResolvedValue({}) }, }; } const input = { budgetPoolId: "pool-1", entityType: "purchase_request", entityId: "synthetic-request", amount: "12.34", actorId: "synthetic-actor", note: "synthetic" }; it("explains duplicate reserve before writes", async () => { const tx = fixture([{ budget_pool_id: "pool-1", kind: "reserve", amount: "10.00" }]); await expect(reserveBudgetForEntity(tx, input)).rejects.toMatchObject({ code: "VALID_001", statusCode: 409, details: { budget: "reserve_already_exists", reasonCode: "purchase_budget.reserve_already_exists" } }); expect(tx.budgetLedgerEntry.create).not.toHaveBeenCalled(); expect(tx.budgetPool.findUnique).not.toHaveBeenCalled(); expect(tx.budgetPool.update).not.toHaveBeenCalled(); }); it("preserves attempted ledger before missing-pool rejection", async () => { const tx = fixture([], null); await expect(reserveBudgetForEntity(tx, input)).rejects.toMatchObject({ code: "VALID_001", statusCode: 409, details: { budgetPoolId: "not_found", reasonCode: "purchase_budget.pool_not_found" } }); expect(tx.budgetLedgerEntry.create).toHaveBeenCalledWith({ data: { budget_pool_id: "pool-1", entity_type: "purchase_request", entity_id: "synthetic-request", kind: "reserve", amount: "12.34", created_by: "synthetic-actor", note: "synthetic" } }); expect(tx.budgetLedgerEntry.create.mock.invocationCallOrder[0]).toBeLessThan(tx.budgetPool.findUnique.mock.invocationCallOrder[0]); expect(tx.budgetPool.update).not.toHaveBeenCalled(); }); it("retains successful reserve precision and pool accumulation", async () => { const tx = fixture(); await expect(reserveBudgetForEntity(tx, input)).resolves.toEqual({ applied: true, reservedAmount: "12.34" }); expect(tx.budgetPool.update).toHaveBeenCalledWith({ where: { id: "pool-1" }, data: { used_amount: "32.34", updated_by: "synthetic-actor" } }); }); it.each([{ budgetPoolId: null }, { amount: "0" }, { amount: "-1" }, { amount: "invalid" }])("retains no-op reserve %s", async (override) => { const tx = fixture(); await expect(reserveBudgetForEntity(tx, { ...input, ...override })).resolves.toEqual({ applied: false, reservedAmount: "0.00" }); expect(tx.$queryRaw).not.toHaveBeenCalled(); for (const model of [tx.budgetLedgerEntry, tx.budgetPool]) for (const fn of Object.values(model)) expect(fn).not.toHaveBeenCalled(); }); it("confirms remaining amount once without touching pool", async () => { const tx = fixture([{ budget_pool_id: "pool-1", kind: "reserve", amount: "12.34" }, { budget_pool_id: "pool-1", kind: "confirm", amount: "2.34" }]); await expect(confirmBudgetForEntity(tx, input)).resolves.toEqual({ applied: true, items: [{ budgetPoolId: "pool-1", amount: "10.00" }] }); expect(tx.budgetLedgerEntry.create).toHaveBeenCalledWith({ data: expect.objectContaining({ kind: "confirm", amount: "10.00" }) }); expect(tx.budgetPool.findUnique).not.toHaveBeenCalled(); expect(tx.budgetPool.update).not.toHaveBeenCalled(); }); it("retains missing-pool release success with ledger and no pool update", async () => { const tx = fixture([{ budget_pool_id: "pool-1", kind: "reserve", amount: "12.34" }], null); await expect(releaseReservedBudgetIfAny(tx, input)).resolves.toEqual({ applied: true, items: [{ budgetPoolId: "pool-1", amount: "12.34" }] }); expect(tx.budgetLedgerEntry.create).toHaveBeenCalledWith({ data: expect.objectContaining({ kind: "release", amount: "12.34" }) }); expect(tx.budgetPool.update).not.toHaveBeenCalled(); }); it("retains release clamping and no-op fully released ledger", async () => { const tx = fixture([{ budget_pool_id: "pool-1", kind: "reserve", amount: "30.00" }]); await releaseReservedBudgetIfAny(tx, input); expect(tx.budgetPool.update).toHaveBeenCalledWith({ where: { id: "pool-1" }, data: { used_amount: "0.00", updated_by: "synthetic-actor" } }); const completed = fixture([{ budget_pool_id: "pool-1", kind: "reserve", amount: "30.00" }, { budget_pool_id: "pool-1", kind: "release", amount: "30.00" }]); await expect(releaseReservedBudgetIfAny(completed, input)).resolves.toEqual({ applied: false, items: [] }); await expect(confirmBudgetForEntity(completed, input)).resolves.toEqual({ applied: false, items: [] }); expect(completed.budgetLedgerEntry.create).not.toHaveBeenCalled(); expect(completed.budgetPool.findUnique).not.toHaveBeenCalled(); expect(completed.budgetPool.update).not.toHaveBeenCalled(); }); });