Files
butcem/src/features/contacts/service.int.test.ts
T
2026-11-05 12:37:57 +03:00

271 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { listAccounts } from "@/features/accounts/service";
import {
getTransaction,
listTransactions,
updateTransaction,
} from "@/features/transactions/service";
import { currentMonth, getAccountStatement } from "@/features/transactions/statement";
import { AccountModel } from "@/models/account";
import { todayIstanbul } from "@/shared/dates";
import { addDays } from "@/shared/recurrence";
import {
contactEntrySchema,
contactFormSchema,
type ContactEntryInput,
type ContactFormInput,
} from "@/shared/schemas/contact";
import { transactionFilterSchema } from "@/shared/schemas/transaction";
import { setupSeededDb, teardownDb } from "@/test/seeded-db";
import {
countDueContacts,
createContact,
createContactEntry,
getContactStatement,
listContacts,
setContactArchived,
updateContact,
updateContactEntry,
} from "./service";
let ids: Awaited<ReturnType<typeof setupSeededDb>>;
let cakir = "";
let ayse = "";
let emre = "";
let zeynep = "";
let fatma = "";
const today = todayIstanbul();
beforeAll(async () => {
ids = await setupSeededDb();
cakir = ids.household("Çakır Ailesi");
ayse = ids.user("ayse");
emre = ids.user("emre");
zeynep = ids.user("zeynep");
fatma = ids.user("fatma");
});
afterAll(teardownDb);
const acc = (name: string) =>
AccountModel.findOne({ householdId: cakir, name })
.lean()
.then((a) => String(a!._id));
const contactForm = (over: Partial<ContactFormInput> = {}) =>
contactFormSchema.parse({ name: "Test Kişi", openingDate: today, ...over });
const entry = (over: Partial<ContactEntryInput>) =>
contactEntrySchema.parse({ kind: "lend", amount: "1000", date: today, ...over });
async function balance(userId: string, accountId: string) {
return (await listAccounts(userId, cakir))!.accounts.find((a) => a.id === accountId)!
.balanceMinor!;
}
async function contactBalance(userId: string, id: string) {
return (await listContacts(userId, cakir))!.contacts.find((c) => c.id === id)!.balanceMinor;
}
const sums = async () =>
(await listTransactions(ayse, cakir, transactionFilterSchema.parse({})))!.sums;
describe("cari defteri", () => {
let ahmet = "";
let lendId = "";
it("borç verme ve tahsilat: hesap bakiyeleri değişir, gelir/gider toplamlarına girmez", async () => {
const ziraat = await acc("Ziraat Maaş");
const cuzdan = await acc("Cüzdan");
const [zBefore, cBefore, sBefore] = [
await balance(ayse, ziraat),
await balance(ayse, cuzdan),
await sums(),
];
const c = await createContact(
ayse,
cakir,
contactForm({ name: "Kemal Test", relation: "friend" }),
);
expect(c.ok).toBe(true);
ahmet = c.ok ? c.data.id : "";
const l = await createContactEntry(
ayse,
cakir,
ahmet,
entry({ accountId: ziraat, amount: "5.000" }),
);
expect(l.ok).toBe(true);
lendId = l.ok ? l.data.id : "";
await createContactEntry(
emre,
cakir,
ahmet,
entry({ kind: "collect", accountId: cuzdan, amount: "2.000" }),
);
expect(await balance(ayse, ziraat)).toBe(zBefore - 5_000_00);
expect(await balance(ayse, cuzdan)).toBe(cBefore + 2_000_00);
expect(await contactBalance(ayse, ahmet)).toBe(3_000_00);
expect(await sums()).toEqual(sBefore);
const detail = (await getTransaction(ayse, cakir, lendId))!;
expect(detail).toMatchObject({
type: "transfer",
contact: { kind: "lend", name: "Kemal Test" },
});
expect(detail.toAccount).toBeNull();
});
it("borç alma ve geri ödeme; açılış bakiyesi; ekstre yürüyen bakiye", async () => {
const ziraat = await acc("Ziraat Maaş");
const c = await createContact(
ayse,
cakir,
contactForm({ name: "Hasan Amca", relation: "family", openingBalance: "-1.000" }),
);
const hasan = c.ok ? c.data.id : "";
await createContactEntry(
ayse,
cakir,
hasan,
entry({ kind: "borrow", accountId: ziraat, amount: "10.000" }),
);
await createContactEntry(
ayse,
cakir,
hasan,
entry({ kind: "repay", accountId: ziraat, amount: "4.000" }),
);
expect(await contactBalance(ayse, hasan)).toBe(-7_000_00);
const s = (await getContactStatement(ayse, cakir, hasan))!;
expect(s.rows).toHaveLength(2);
expect(s.rows[0]!.balanceMinor).toBe(-7_000_00);
expect(s.rows[1]!.balanceMinor).toBe(-11_000_00);
expect(s.contact.balanceMinor).toBe(-7_000_00);
const list = (await listContacts(ayse, cakir))!;
expect(list.totals.iOweMinor).toBeGreaterThanOrEqual(7_000_00);
expect(list.totals.owesMeMinor).toBeGreaterThanOrEqual(3_000_00);
});
it("hareket düzenleme: tür değişince yön ve bakiyeler değişir; işlem formuyla düzenlenemez", async () => {
const ziraat = await acc("Ziraat Maaş");
const before = await balance(ayse, ziraat);
const u = await updateContactEntry(
ayse,
cakir,
lendId,
entry({ kind: "borrow", accountId: ziraat, amount: "5.000" }),
);
expect(u.ok).toBe(true);
expect(await balance(ayse, ziraat)).toBe(before + 10_000_00);
expect(await contactBalance(ayse, ahmet)).toBe(-7_000_00);
const d = (await getTransaction(ayse, cakir, lendId))!;
expect(d.account).toBeNull();
expect(d.toAccount?.id).toBe(ziraat);
const viaForm = await updateTransaction(ayse, cakir, lendId, {
type: "transfer",
amount: 1,
date: today,
accountId: ziraat,
toAccountId: await acc("Cüzdan"),
categoryId: null,
splits: [],
payee: "",
description: "",
tags: [],
installments: 1,
attachmentIds: [],
});
expect(viaForm.ok).toBe(false);
});
it("vade uyarısı: yaklaşan, geçmiş, kapalı bakiye", async () => {
const c = await createContact(
ayse,
cakir,
contactForm({
name: "Vade Kişi",
openingBalance: "500",
dueDate: addDays(today, 2),
reminderDays: 3,
}),
);
const id = c.ok ? c.data.id : "";
const before = await countDueContacts(ayse, cakir);
let v = (await listContacts(ayse, cakir))!.contacts.find((x) => x.id === id)!;
expect(v.due).toBe("upcoming");
await updateContact(
ayse,
cakir,
id,
contactForm({ name: "Vade Kişi", openingBalance: "500", dueDate: addDays(today, -1) }),
);
v = (await listContacts(ayse, cakir))!.contacts.find((x) => x.id === id)!;
expect(v.due).toBe("overdue");
await updateContact(
ayse,
cakir,
id,
contactForm({ name: "Vade Kişi", openingBalance: "0", dueDate: addDays(today, -1) }),
);
v = (await listContacts(ayse, cakir))!.contacts.find((x) => x.id === id)!;
expect(v.due).toBe("none");
expect(await countDueContacts(ayse, cakir)).toBe(before - 1);
});
it("aynı ad engellenir; arşivli cariye hareket eklenmez", async () => {
expect((await createContact(ayse, cakir, contactForm({ name: "kemal TEST" }))).ok).toBe(false);
expect((await setContactArchived(ayse, cakir, ahmet, true)).ok).toBe(true);
expect(
(await createContactEntry(ayse, cakir, ahmet, entry({ accountId: await acc("Cüzdan") }))).ok,
).toBe(false);
expect((await setContactArchived(ayse, cakir, ahmet, false)).ok).toBe(true);
});
});
describe("yetki ve gizlilik", () => {
it("izleyici yazamaz; hane dışı göremez; başka hanenin hesabı kullanılamaz", async () => {
expect((await createContact(zeynep, cakir, contactForm({ name: "X" }))).ok).toBe(false);
expect(await listContacts(fatma, cakir)).toBeNull();
const c = await createContact(ayse, cakir, contactForm({ name: "Hesap Testi" }));
const id = c.ok ? c.data.id : "";
const otherHousehold = String(
(await AccountModel.findOne({ name: "Vakıfbank Emekli" }).lean())!._id,
);
expect(
(await createContactEntry(ayse, cakir, id, entry({ accountId: otherHousehold }))).ok,
).toBe(false);
});
it("yalnızca ben cari: başkası listede, ekstrede ve işlemlerde görmez; hesap ekstresinde ad gizlenir", async () => {
const ziraat = await acc("Ziraat Maaş");
const c = await createContact(
ayse,
cakir,
contactForm({ name: "Gizli Kişi", isPrivate: true }),
);
const id = c.ok ? c.data.id : "";
const e = await createContactEntry(
ayse,
cakir,
id,
entry({ accountId: ziraat, amount: "750" }),
);
const txId = e.ok ? e.data.id : "";
expect((await listContacts(emre, cakir))!.contacts.some((x) => x.id === id)).toBe(false);
expect(await getContactStatement(emre, cakir, id)).toBeNull();
expect(await getTransaction(emre, cakir, txId)).toBeNull();
expect((await createContactEntry(emre, cakir, id, entry({ accountId: ziraat }))).ok).toBe(
false,
);
const st = (await getAccountStatement(emre, cakir, ziraat, currentMonth()))!;
const row = st.rows.find((r) => r.item.id === txId)!;
expect(row.restricted).toBe(true);
expect(row.item.contact!.name).not.toBe("Gizli Kişi");
// Ortak hesabın bakiyesi herkes için aynı.
expect(await balance(emre, ziraat)).toBe(await balance(ayse, ziraat));
});
});