import { describe, expect, it } from "vitest"; import { toCsv, csvMoney } from "./csv"; import { transactionFormSchema } from "./schemas/transaction"; import { normalizeTags, transactionEffects } from "./transactions"; const A = "a".repeat(24); const B = "b".repeat(24); const C1 = "c".repeat(24); const C2 = "d".repeat(24); describe("transactionEffects", () => { it("gelir +, gider −, transfer kaynaktan − hedefe +", () => { expect( transactionEffects({ type: "income", status: "posted", amountMinor: 500, accountId: A }), ).toEqual([{ accountId: A, deltaMinor: 500 }]); expect( transactionEffects({ type: "expense", status: "posted", amountMinor: 500, accountId: A }), ).toEqual([{ accountId: A, deltaMinor: -500 }]); expect( transactionEffects({ type: "transfer", status: "posted", amountMinor: 500, accountId: A, toAccountId: B, }), ).toEqual([ { accountId: A, deltaMinor: -500 }, { accountId: B, deltaMinor: 500 }, ]); }); it("planlı, iptal ve silinmiş işlemler etkisizdir", () => { for (const x of [ { status: "planned" as const }, { status: "void" as const }, { status: "posted" as const, deleted: true }, ]) { expect(transactionEffects({ type: "expense", amountMinor: 1, accountId: A, ...x })).toEqual( [], ); } }); }); describe("normalizeTags", () => { it("küçük harf, kırpma, tekrar yok, en fazla 10", () => { expect(normalizeTags(" Market, İNDİRİM ,market,,#tatil")).toEqual([ "market", "indirim", "tatil", ]); expect(normalizeTags(Array.from({ length: 15 }, (_, i) => `e${i}`))).toHaveLength(10); }); }); describe("transactionFormSchema", () => { const base = { type: "expense", amount: "1.200,00", date: "2026-11-05", accountId: A }; it("tek kategorili gideri kabul eder ve tutarı kuruşa çevirir", () => { const r = transactionFormSchema.parse({ ...base, categoryId: C1, tags: "ev, Ev" }); expect(r.amount).toBe(120000); expect(r.categoryId).toBe(C1); expect(r.tags).toEqual(["ev"]); expect(r.splits).toEqual([]); }); it("kategorisiz ve bölmesiz gideri reddeder", () => { expect(transactionFormSchema.safeParse(base).success).toBe(false); }); it("bölünmüş işlemde kalem toplamı tutara eşit olmalı", () => { const ok = transactionFormSchema.safeParse({ ...base, splits: [ { categoryId: C1, amount: "800" }, { categoryId: C2, amount: "250" }, { categoryId: C1, amount: "150,00" }, ], }); expect(ok.success).toBe(true); const bad = transactionFormSchema.safeParse({ ...base, splits: [ { categoryId: C1, amount: "800" }, { categoryId: C2, amount: "250" }, ], }); expect(bad.success).toBe(false); expect(bad.error?.issues[0]?.path).toEqual(["splits"]); const single = transactionFormSchema.safeParse({ ...base, splits: [{ categoryId: C1, amount: "1200" }], }); expect(single.success).toBe(false); }); it("transfer: hedef zorunlu, kaynaktan farklı", () => { expect(transactionFormSchema.safeParse({ ...base, type: "transfer" }).success).toBe(false); expect( transactionFormSchema.safeParse({ ...base, type: "transfer", toAccountId: A }).success, ).toBe(false); expect( transactionFormSchema.safeParse({ ...base, type: "transfer", toAccountId: B }).success, ).toBe(true); }); it("sıfır, negatif ve geçersiz tutarı reddeder", () => { for (const amount of ["0", "-5", "abc", ""]) { expect( transactionFormSchema.safeParse({ ...base, categoryId: C1, amount }).success, amount, ).toBe(false); } }); }); describe("CSV", () => { it("BOM, noktalı virgül, kaçış ve formül enjeksiyonu koruması", () => { const csv = toCsv( ["A", "B"], [ ["x;y", '"tırnak"'], ["=HYPERLINK(1)", csvMoney(-125050)], ], ); expect(csv.startsWith("")).toBe(true); expect(csv).toContain('"x;y";"""tırnak"""'); expect(csv).toContain("'=HYPERLINK(1);-1250,50"); expect(csv.endsWith("\r\n")).toBe(true); }); });