// tests/allocator.test.ts import { describe, it, expect, beforeEach, afterAll } from "vitest"; import { prisma, resetUser, ensureUser, U, cid, pid, closePrisma } from "./helpers"; import { allocateIncome } from "../src/allocator"; describe("allocator — core behaviors", () => { beforeEach(async () => { await resetUser(U); await ensureUser(U); }); afterAll(async () => { await closePrisma(); }); it("distributes remainder to variables by largest remainder with savings-first tie", async () => { const c1 = cid("c1"); const c2 = cid("c2"); // make this savings to test the tiebreaker await prisma.variableCategory.createMany({ data: [ { id: c1, userId: U, name: "Groceries", percent: 60, priority: 2, isSavings: false, balanceCents: 0n }, { id: c2, userId: U, name: "Saver", percent: 40, priority: 1, isSavings: true, balanceCents: 0n }, ], }); const p1 = pid("rent"); await prisma.fixedPlan.create({ data: { id: p1, userId: U, name: "Rent", cycleStart: new Date().toISOString(), dueOn: new Date(Date.now() + 7 * 864e5).toISOString(), totalCents: 10000n, fundedCents: 0n, priority: 1, fundingMode: "auto-on-deposit", }, }); // $100 income const result = await allocateIncome(prisma as any, U, 10000, new Date().toISOString(), "inc1"); expect(result).toBeDefined(); // rent should be funded first up to need const fixed = result.fixedAllocations ?? []; const variable = result.variableAllocations ?? []; // sanity expect(Array.isArray(fixed)).toBe(true); expect(Array.isArray(variable)).toBe(true); }); it("handles zeros and single bucket", async () => { const cOnly = cid("only"); await prisma.variableCategory.create({ data: { id: cOnly, userId: U, name: "Only", percent: 100, priority: 1, isSavings: false, balanceCents: 0n }, }); const result = await allocateIncome(prisma as any, U, 0, new Date().toISOString(), "inc2"); expect(result).toBeDefined(); const variable = result.variableAllocations ?? []; const sum = variable.reduce((s, a: any) => s + (a.amountCents ?? 0), 0); expect(sum).toBe(0); }); });