Files
butcem/src/features/categories/service.int.test.ts
T
suphicakir afc6da1606 feat(kakeibo): dört harcama grubu, banka maliyeti ve geri alınabilir migration
- shared/kakeibo: grup devralma, banka maliyeti, kayıt bazında grup, grup özeti (saf, testli)
- kategori ve işlem/kalem modellerine kakeiboGroup ve bankCost; form şemaları
- migration 016 varsayılanları doldurur; npm run migrate -- --down <ad> ile geri alma
- Kakeibo rapor servisi: tutarlar bütçeyle aynı kaynaktan, grup değiştirmeler taşınır

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 04:51:35 +03:00

162 lines
5.5 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 { Types } from "mongoose";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { CategoryModel } from "@/models/category";
import { setupSeededDb, teardownDb } from "@/test/seeded-db";
import {
createCategory,
installDefaultCategories,
listCategories,
setCategoryArchived,
updateCategory,
} from "./service";
let ids: Awaited<ReturnType<typeof setupSeededDb>>;
let cakir = "";
let annemler = "";
beforeAll(async () => {
ids = await setupSeededDb();
cakir = ids.household("Çakır Ailesi");
annemler = ids.household("Annemler");
});
afterAll(teardownDb);
const byKey = (key: string, householdId = cakir) =>
CategoryModel.findOne({ householdId, defaultKey: key })
.lean()
.then((c) => String(c!._id));
const form = (over: Partial<Parameters<typeof createCategory>[2]> = {}) => ({
kind: "expense" as const,
name: "Test",
parentId: null,
icon: "tag" as const,
color: "blue" as const,
kakeiboGroup: null,
bankCost: false,
...over,
});
describe("varsayılan kategoriler", () => {
it("tekrar yüklemede kopya oluşmaz", async () => {
const before = await CategoryModel.countDocuments({ householdId: cakir });
const added = await installDefaultCategories(
new Types.ObjectId(cakir),
new Types.ObjectId(ids.user("admin")),
);
expect(added).toBe(0);
expect(await CategoryModel.countDocuments({ householdId: cakir })).toBe(before);
});
it("alt kategoriler doğru ancestors zinciriyle yüklenir", async () => {
const elektrik = await CategoryModel.findOne({
householdId: cakir,
defaultKey: "faturalar.elektrik",
}).lean();
const faturalar = await byKey("faturalar");
expect(elektrik?.ancestors.map(String)).toEqual([faturalar]);
});
});
describe("izolasyon ve roller", () => {
it("başka hanenin kategorilerini okuyamaz ve değiştiremez", async () => {
expect(await listCategories(ids.user("fatma"), cakir)).toBeNull();
const kira = await byKey("kira");
// fatma kendi hanesinin kimliğiyle, Çakır'ın kategorisini hedefler → bulunamaz.
const r = await updateCategory(
ids.user("fatma"),
annemler,
kira,
form({ name: "Ele geçirildi" }),
);
expect(r.ok).toBe(false);
expect((await CategoryModel.findById(kira).lean())?.name).toBe("Kira");
const r2 = await setCategoryArchived(ids.user("fatma"), annemler, kira, true);
expect(r2.ok).toBe(false);
});
it("izleyici ekleyemez, üye ekler", async () => {
expect((await createCategory(ids.user("zeynep"), cakir, form({ name: "Harçlık" }))).ok).toBe(
false,
);
expect((await createCategory(ids.user("emre"), cakir, form({ name: "Harçlık" }))).ok).toBe(
true,
);
});
it("başka hanenin kategorisini üst kategori yapamaz", async () => {
const annemlerMarket = await byKey("market", annemler);
const r = await createCategory(
ids.user("ayse"),
cakir,
form({ name: "Sızma", parentId: annemlerMarket }),
);
expect(r.ok).toBe(false);
});
});
describe("ağaç kuralları", () => {
it("aynı düzeyde aynı ad (Türkçe normalize) reddedilir", async () => {
const r = await createCategory(ids.user("ayse"), cakir, form({ name: " KİRA " }));
expect(r.ok).toBe(false);
// farklı düzeyde aynı ad serbest
const market = await byKey("market");
expect(
(await createCategory(ids.user("ayse"), cakir, form({ name: "Kira", parentId: market }))).ok,
).toBe(true);
});
it("taşıma alt ağacın ancestors alanlarını günceller", async () => {
const ulasim = await byKey("ulasim");
const yakit = await byKey("ulasim.yakit");
const created = await createCategory(
ids.user("ayse"),
cakir,
form({ name: "LPG", parentId: yakit }),
);
const lpg = created.ok ? created.data.id : "";
const ev = await byKey("ev");
// Yakıt'ı (alt kategorisi LPG ile) "Ev ve mobilya" altına taşı.
const r = await updateCategory(
ids.user("ayse"),
cakir,
yakit,
form({ name: "Yakıt", parentId: ev, icon: "fuel" }),
);
expect(r.ok).toBe(true);
expect((await CategoryModel.findById(yakit).lean())?.ancestors.map(String)).toEqual([ev]);
expect((await CategoryModel.findById(lpg).lean())?.ancestors.map(String)).toEqual([ev, yakit]);
expect((await CategoryModel.findById(yakit).lean())?.parentId?.toString()).not.toBe(ulasim);
});
it("döngü ve tür uyuşmazlığı reddedilir", async () => {
const market = await byKey("market");
const gida = await byKey("market.gida");
expect(
(
await updateCategory(
ids.user("ayse"),
cakir,
market,
form({ name: "Market", parentId: gida }),
)
).ok,
).toBe(false);
const maas = await byKey("maas");
expect(
(await updateCategory(ids.user("ayse"), cakir, gida, form({ name: "Gıda", parentId: maas })))
.ok,
).toBe(false);
});
it("arşivleme alt ağaçla birlikte yapılır; üstü arşivliyken alt kategori geri alınamaz", async () => {
const saglik = await byKey("saglik");
const ilac = await byKey("saglik.ilac");
expect((await setCategoryArchived(ids.user("emre"), cakir, saglik, true)).ok).toBe(true);
const active = (await listCategories(ids.user("emre"), cakir))!.map((c) => c.id);
expect(active).not.toContain(saglik);
expect(active).not.toContain(ilac);
expect((await setCategoryArchived(ids.user("emre"), cakir, ilac, false)).ok).toBe(false);
expect((await setCategoryArchived(ids.user("emre"), cakir, saglik, false)).ok).toBe(true);
});
});