/// import { convexTest } from "convex-test"; import { makeFunctionReference } from "convex/server"; import { describe, expect, test } from "vitest"; import type { Id } from "./_generated/dataModel"; import schema from "./schema"; const modules = import.meta.glob("./**/*.ts"); delete modules["./balances.test.ts"]; const listLatest = makeFunctionReference<"query">("bank/balances:listLatest"); const upsertAccountBalance = makeFunctionReference<"mutation">( "bank/internal:upsertAccountBalance", ); type LatestBalanceRow = { accountId: Id<"accounts">; accountName: string; balance: number | null; status: string; errorMessage?: string; }; async function seedBalanceFixture() { const t = convexTest(schema, modules); const seeded = await t.run(async (ctx) => { const userId = await ctx.db.insert("users", { name: "Balance User", email: "balance@example.com", }); const otherUserId = await ctx.db.insert("users", { name: "Other User", email: "other-balance@example.com", }); const accountId = await ctx.db.insert("accounts", { userId, name: "Girokonto", type: "giro", iban: "DE89370400440532013000", openingBalance: 125, currency: "EUR", isArchived: false, externalId: "giro-1", }); const archivedAccountId = await ctx.db.insert("accounts", { userId, name: "Altes Konto", type: "giro", openingBalance: 50, currency: "EUR", isArchived: true, externalId: "old-1", }); const otherAccountId = await ctx.db.insert("accounts", { userId: otherUserId, name: "Hidden Konto", type: "giro", openingBalance: 999, currency: "EUR", isArchived: false, externalId: "hidden-1", }); return { userId, accountId, archivedAccountId, otherUserId, otherAccountId }; }); return { t, seeded, asUser: t.withIdentity({ subject: `${seeded.userId}|test-session`, tokenIdentifier: `test:${seeded.userId}`, }), }; } describe("bank/balances.listLatest", () => { test("returns a missing state for active accounts without live balance snapshots", async () => { const { asUser, seeded } = await seedBalanceFixture(); const result = await asUser.query(listLatest, {}); expect(result).toEqual([ { accountId: seeded.accountId, accountName: "Girokonto", accountType: "giro", iban: "DE89370400440532013000", externalId: "giro-1", balance: null, currency: "EUR", provider: null, fetchedAt: null, status: "missing", }, ]); }); test("returns the newest live balance without mutating openingBalance", async () => { const { t, asUser, seeded } = await seedBalanceFixture(); await t.mutation(upsertAccountBalance, { userId: seeded.userId, accountId: seeded.accountId as Id<"accounts">, externalId: "giro-1", provider: "fints", balance: 900, currency: "EUR", fetchedAt: 1_780_000_000_000, status: "fresh", }); await t.mutation(upsertAccountBalance, { userId: seeded.userId, accountId: seeded.accountId as Id<"accounts">, externalId: "giro-1", provider: "comdirect", balance: 1234.56, currency: "EUR", asOf: "2026-06-23", fetchedAt: 1_780_000_060_000, status: "fresh", }); const result = await asUser.query(listLatest, {}); const account = await t.run((ctx) => ctx.db.get(seeded.accountId)); expect(result).toMatchObject([ { accountId: seeded.accountId, accountName: "Girokonto", balance: 1234.56, currency: "EUR", provider: "comdirect", fetchedAt: 1_780_000_060_000, asOf: "2026-06-23", status: "fresh", }, ]); expect(account?.openingBalance).toBe(125); }); test("can include archived accounts for settings and preserves error snapshots", async () => { const { t, asUser, seeded } = await seedBalanceFixture(); await t.mutation(upsertAccountBalance, { userId: seeded.userId, accountId: seeded.archivedAccountId as Id<"accounts">, externalId: "old-1", provider: "fints", balance: 50, currency: "EUR", fetchedAt: 1_780_000_060_000, status: "error", errorMessage: "HKSAL wird nicht unterstuetzt", }); const defaultResult = await asUser.query(listLatest, {}); const settingsResult = await asUser.query(listLatest, { includeArchived: true }); expect(defaultResult.map((row: LatestBalanceRow) => row.accountId)).toEqual([ seeded.accountId, ]); expect(settingsResult).toHaveLength(2); expect( settingsResult.find( (row: LatestBalanceRow) => row.accountId === seeded.archivedAccountId, ), ) .toMatchObject({ accountName: "Altes Konto", balance: 50, status: "error", errorMessage: "HKSAL wird nicht unterstuetzt", }); }); });