/// import { convexTest } from "convex-test"; import { describe, expect, test } from "vitest"; import { api, internal } from "./_generated/api"; import type { Id } from "./_generated/dataModel"; import schema from "./schema"; const modules = import.meta.glob("./**/*.ts"); delete modules["./savingsChat.test.ts"]; describe("savingsChat.getContext", () => { test("counts and sums every matching transaction before applying prompt limits", async () => { const t = convexTest(schema, modules); const seeded = await t.run(async (ctx) => { const userId = await ctx.db.insert("users", { name: "Test User", email: "test@example.com", }); const giroAccountId = await ctx.db.insert("accounts", { userId, name: "Girokonto", type: "checking", openingBalance: 0, currency: "EUR", isArchived: false, }); const otherAccountId = await ctx.db.insert("accounts", { userId, name: "Tagesgeld", type: "savings", openingBalance: 0, currency: "EUR", isArchived: false, }); const amounts: number[] = []; const months = ["2025-12", "2026-01", "2026-02", "2026-03", "2026-04", "2026-05", "2026-06"]; for (let index = 0; index < 450; index++) { const month = months[index % months.length]; const day = String((index % 27) + 1).padStart(2, "0"); const bookingDate = `${month}-${day}`; const amount = index % 3 === 0 ? 100 : -25; amounts.push(amount); await ctx.db.insert("transactions", { userId, accountId: giroAccountId, bookingDate, valueDate: bookingDate, description: `Giro transaction ${index}`, counterparty: "Counterparty", amount, isPending: false, effectiveMonth: index % 10 === 0 ? undefined : bookingDate.slice(0, 7), }); } for (let index = 0; index < 50; index++) { const bookingDate = `2026-06-${String((index % 27) + 1).padStart(2, "0")}`; await ctx.db.insert("transactions", { userId, accountId: otherAccountId, bookingDate, valueDate: bookingDate, description: `Other account transaction ${index}`, amount: 999, isPending: false, effectiveMonth: bookingDate.slice(0, 7), }); } return { userId, giroAccountId, expectedIncome: amounts.filter((amount) => amount > 0).reduce((sum, amount) => sum + amount, 0), expectedExpenses: amounts.filter((amount) => amount < 0).reduce((sum, amount) => sum + amount, 0), expectedBalance: amounts.reduce((sum, amount) => sum + amount, 0), }; }); const asUser = t.withIdentity({ subject: `${seeded.userId}|test-session`, tokenIdentifier: `test:${seeded.userId}`, }); const context = await asUser.query(api.savingsChat.getContext, { from: "2025-12-01", to: "2026-06-30", accountId: seeded.giroAccountId as Id<"accounts">, basis: "effective", }); expect(context.accountName).toBe("Girokonto"); expect(context.isComplete).toBe(true); expect(context.totals.transactionCount).toBe(450); expect(context.totals.income).toBe(seeded.expectedIncome); expect(context.totals.expenses).toBe(seeded.expectedExpenses); expect(context.totals.balance).toBe(seeded.expectedBalance); }); test("builds complete prompt lines for every matching transaction", async () => { const t = convexTest(schema, modules); const seeded = await t.run(async (ctx) => { const userId = await ctx.db.insert("users", { name: "Prompt User", email: "prompt@example.com", }); const categoryId = await ctx.db.insert("categories", { userId, name: "Lebensmittel", kind: "ausgabe", block: "variabel", color: "#22c55e", sortOrder: 1, isSystem: false, }); const giroAccountId = await ctx.db.insert("accounts", { userId, name: "Girokonto", type: "checking", openingBalance: 0, currency: "EUR", isArchived: false, }); const otherAccountId = await ctx.db.insert("accounts", { userId, name: "Depot", type: "investment", openingBalance: 0, currency: "EUR", isArchived: false, }); await ctx.db.insert("transactions", { userId, accountId: giroAccountId, categoryId, bookingDate: "2026-02-14", valueDate: "2026-02-14", description: "Supermarkt", counterparty: "Markt GmbH", amount: -42.5, isPending: false, }); await ctx.db.insert("transactions", { userId, accountId: giroAccountId, bookingDate: "2026-02-15", valueDate: "2026-02-15", description: "Gehalt", counterparty: "Arbeitgeber", amount: 2500, isPending: false, effectiveMonth: "2026-02", }); await ctx.db.insert("transactions", { userId, accountId: otherAccountId, bookingDate: "2026-02-16", valueDate: "2026-02-16", description: "Other account should not appear", amount: 999, isPending: false, effectiveMonth: "2026-02", }); return { userId, giroAccountId }; }); const asUser = t.withIdentity({ subject: `${seeded.userId}|test-session`, tokenIdentifier: `test:${seeded.userId}`, }); const context = await asUser.query(internal.savingsChat.getPromptContext, { from: "2026-02-01", to: "2026-02-28", accountId: seeded.giroAccountId as Id<"accounts">, basis: "effective", }); expect(context.totals.transactionCount).toBe(2); expect(context.transactionLines).toHaveLength(2); expect(context.transactionLines.join("\n")).toContain( "2026-02-14 | Supermarkt (Markt GmbH) | -42.50€ | Lebensmittel | Girokonto", ); expect(context.transactionLines.join("\n")).toContain( "2026-02-15 | Gehalt (Arbeitgeber) | 2500.00€ | Ohne Kategorie | Girokonto", ); expect(context.transactionLines.join("\n")).not.toContain("Other account should not appear"); }); });