afc6da1606
- 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>
162 lines
5.5 KiB
TypeScript
162 lines
5.5 KiB
TypeScript
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);
|
||
});
|
||
});
|