144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@/lib/db", () => {
|
|
const monthlyInsight = {
|
|
upsert: vi.fn(async ({ where, update, create }: { where: { month: string }; update: Record<string, unknown>; create: Record<string, unknown> }) => ({
|
|
id: "insight-1",
|
|
month: where.month,
|
|
year: (update.year ?? create.year) as number,
|
|
summary: (update.summary ?? create.summary) as string,
|
|
recommendations: (update.recommendations ?? create.recommendations) as string,
|
|
inputSnapshot: (update.inputSnapshot ?? create.inputSnapshot) as string,
|
|
generatedAt: new Date("2026-03-23T12:00:00.000Z"),
|
|
})),
|
|
findUnique: vi.fn(),
|
|
};
|
|
|
|
return {
|
|
db: {
|
|
expense: { findMany: vi.fn() },
|
|
paycheck: { findMany: vi.fn() },
|
|
recurringExpense: { findMany: vi.fn().mockResolvedValue([]) },
|
|
paySchedule: { findFirst: vi.fn().mockResolvedValue(null) },
|
|
monthlyInsight,
|
|
},
|
|
};
|
|
});
|
|
|
|
describe("generateMonthlyInsight", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("stores a fallback for sparse months", async () => {
|
|
const { db } = await import("@/lib/db");
|
|
const { generateMonthlyInsight } = await import("@/lib/insights");
|
|
|
|
vi.mocked(db.expense.findMany).mockResolvedValue([]);
|
|
vi.mocked(db.paycheck.findMany).mockResolvedValue([]);
|
|
vi.mocked(db.recurringExpense.findMany).mockResolvedValue([]);
|
|
|
|
const result = await generateMonthlyInsight("2026-03");
|
|
|
|
expect(result.source).toBe("fallback");
|
|
expect(result.insight.summary).toContain("not enough activity");
|
|
});
|
|
|
|
it("stores model output when Ollama responds", async () => {
|
|
const { db } = await import("@/lib/db");
|
|
const { generateMonthlyInsight } = await import("@/lib/insights");
|
|
|
|
vi.mocked(db.expense.findMany).mockResolvedValue([
|
|
{
|
|
id: "expense-1",
|
|
title: "Groceries",
|
|
date: "2026-03-23",
|
|
amountCents: 3200,
|
|
category: "FOOD",
|
|
createdAt: new Date("2026-03-23T10:00:00.000Z"),
|
|
},
|
|
{
|
|
id: "expense-2",
|
|
title: "Rent",
|
|
date: "2026-03-02",
|
|
amountCents: 120000,
|
|
category: "RENT",
|
|
createdAt: new Date("2026-03-02T10:00:00.000Z"),
|
|
},
|
|
]);
|
|
vi.mocked(db.paycheck.findMany).mockResolvedValue([
|
|
{
|
|
id: "paycheck-1",
|
|
payDate: "2026-03-01",
|
|
amountCents: 180000,
|
|
createdAt: new Date("2026-03-01T10:00:00.000Z"),
|
|
},
|
|
]);
|
|
vi.mocked(db.recurringExpense.findMany).mockResolvedValue([]);
|
|
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
response: JSON.stringify({
|
|
summary: "Spending is stable.",
|
|
recommendations: "Keep food spending under watch.",
|
|
}),
|
|
}),
|
|
} as Response);
|
|
|
|
const result = await generateMonthlyInsight("2026-03");
|
|
|
|
expect(result.source).toBe("model");
|
|
expect(result.insight.summary).toBe("Spending is stable.");
|
|
expect(result.insight.recommendations).toBe('["Keep food spending under watch."]');
|
|
});
|
|
|
|
it("coerces array recommendations from the local model", async () => {
|
|
const { db } = await import("@/lib/db");
|
|
const { generateMonthlyInsight } = await import("@/lib/insights");
|
|
|
|
vi.mocked(db.expense.findMany).mockResolvedValue([
|
|
{
|
|
id: "expense-1",
|
|
title: "Groceries",
|
|
date: "2026-03-23",
|
|
amountCents: 3200,
|
|
category: "FOOD",
|
|
createdAt: new Date("2026-03-23T10:00:00.000Z"),
|
|
},
|
|
{
|
|
id: "expense-2",
|
|
title: "Rent",
|
|
date: "2026-03-02",
|
|
amountCents: 120000,
|
|
category: "RENT",
|
|
createdAt: new Date("2026-03-02T10:00:00.000Z"),
|
|
},
|
|
]);
|
|
vi.mocked(db.paycheck.findMany).mockResolvedValue([
|
|
{
|
|
id: "paycheck-1",
|
|
payDate: "2026-03-01",
|
|
amountCents: 180000,
|
|
createdAt: new Date("2026-03-01T10:00:00.000Z"),
|
|
},
|
|
]);
|
|
vi.mocked(db.recurringExpense.findMany).mockResolvedValue([]);
|
|
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({
|
|
response: JSON.stringify({
|
|
summary: "Spending remains manageable.",
|
|
recommendations: ["Keep groceries planned.", "Move surplus to savings."],
|
|
}),
|
|
}),
|
|
} as Response);
|
|
|
|
const result = await generateMonthlyInsight("2026-03");
|
|
|
|
expect(result.insight.recommendations).toContain("Keep groceries planned.");
|
|
expect(result.insight.recommendations).toContain("Move surplus to savings.");
|
|
});
|
|
});
|