d18c8a0d76
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
4.1 KiB
TypeScript
133 lines
4.1 KiB
TypeScript
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);
|
||
});
|
||
});
|