import mongoose, { Types } from "mongoose"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { getDb, getMongoClient } from "@/lib/db/mongo"; import { AuditLogModel } from "@/models/audit-log"; import { HouseholdModel } from "@/models/household"; import { HouseholdMemberModel } from "@/models/household-member"; import { runSeed } from "../../../scripts/seed/run"; import { adminCreateHousehold, adminSetMemberRole, getHouseholdDetail, listMemberships, removeAllMembershipsOfUser, removeMember, resolveMembership, setMemberRole, updateHouseholdSettings, } from "./service"; /** * VERİ İZOLASYONU (Bölüm 5): başka hanenin verisine erişim imkânsız olmalı. * Seed verisi: Çakır Ailesi (admin+ayse owner, emre member, zeynep viewer, ozan member), * Annemler (fatma owner, mehmet member, ayse viewer), burak = hanesi olmayan admin. */ const ids = new Map(); let cakir = ""; let annemler = ""; const settings = { name: "Çakır Ailesi", budgetMonthStartDay: 15, installmentBudgetMode: "purchase_month" as const, dailyPlanMode: "even" as const, includePrivateAccountsInTotals: false, extraStoreSavingThresholdMinor: 25_000, }; beforeAll(async () => { expect(getDb().databaseName).toBe("butcem_test"); await getDb().dropDatabase(); await runSeed({ log: () => {} }); for (const u of await getDb().collection("user").find().toArray()) ids.set(String(u.username), String(u._id)); cakir = String((await HouseholdModel.findOne({ name: "Çakır Ailesi" }))!._id); annemler = String((await HouseholdModel.findOne({ name: "Annemler" }))!._id); }); afterAll(async () => { await getDb().dropDatabase(); await mongoose.disconnect(); await getMongoClient().close(); }); const id = (username: string) => ids.get(username)!; describe("üyelik çözümleme (izolasyon çekirdeği)", () => { it("üye olmayan kullanıcı başka hanenin üyeliğini çözemez", async () => { expect(await resolveMembership(id("fatma"), cakir)).toBeNull(); expect(await resolveMembership(id("emre"), annemler)).toBeNull(); }); it("rol yetmezse null döner", async () => { expect((await resolveMembership(id("ayse"), annemler, "viewer"))?.role).toBe("viewer"); expect(await resolveMembership(id("ayse"), annemler, "member")).toBeNull(); expect(await resolveMembership(id("zeynep"), cakir, "member")).toBeNull(); expect((await resolveMembership(id("emre"), cakir, "member"))?.role).toBe("member"); }); it("hanesi olmayan admin hiçbir haneyi göremez (admin ayrıcalığı yok)", async () => { expect(await listMemberships(id("burak"))).toEqual([]); expect(await resolveMembership(id("burak"), cakir)).toBeNull(); expect(await getHouseholdDetail(id("burak"), cakir)).toBeNull(); }); it("geçersiz ve uydurma kimlikler reddedilir", async () => { const forged = new Types.ObjectId().toHexString(); expect(await resolveMembership(id("ayse"), forged)).toBeNull(); expect(await resolveMembership(id("ayse"), "not-an-id")).toBeNull(); expect(await resolveMembership(id("ayse"), { $ne: null } as unknown as string)).toBeNull(); expect(await resolveMembership("", cakir)).toBeNull(); }); it("kullanıcı yalnızca kendi hanelerini listeler", async () => { expect((await listMemberships(id("ayse"))).map((m) => [m.name, m.role])).toEqual([ ["Annemler", "viewer"], ["Çakır Ailesi", "owner"], ]); expect((await listMemberships(id("fatma"))).map((m) => m.name)).toEqual(["Annemler"]); }); }); describe("hane ayrıntısı ve ayarlar", () => { it("başka hanenin ayrıntısı okunamaz", async () => { expect(await getHouseholdDetail(id("fatma"), cakir)).toBeNull(); const own = await getHouseholdDetail(id("emre"), cakir); expect(own?.members.map((m) => m.username).sort()).toEqual([ "admin", "ayse", "emre", "ozan", "zeynep", ]); }); it("başka hanenin sahibi, üye ve izleyici ayarları değiştiremez", async () => { for (const u of ["fatma", "emre", "zeynep", "burak"]) { const res = await updateHouseholdSettings(id(u), cakir, settings); expect(res.ok, u).toBe(false); } const h = await HouseholdModel.findById(cakir).lean(); expect(h?.settings?.budgetMonthStartDay).toBe(1); }); it("sahip ayarları değiştirir ve denetim kaydı yazılır", async () => { const res = await updateHouseholdSettings(id("ayse"), cakir, settings); expect(res.ok).toBe(true); const h = await HouseholdModel.findById(cakir).lean(); expect(h?.settings?.budgetMonthStartDay).toBe(15); const audit = await AuditLogModel.findOne({ action: "household.settings.update", householdId: cakir, }).lean(); expect(audit?.actorId.toString()).toBe(id("ayse")); expect((audit?.diff as Record).budgetMonthStartDay).toEqual({ from: 1, to: 15, }); }); }); describe("üyelik değişiklikleri", () => { it("üye ve izleyici rol değiştiremez, üye çıkaramaz", async () => { expect((await setMemberRole(id("emre"), cakir, id("zeynep"), "owner")).ok).toBe(false); expect((await removeMember(id("zeynep"), cakir, id("emre"))).ok).toBe(false); }); it("bir hanenin sahibi başka hanedeki üyeliği etkileyemez", async () => { // ayse Çakır'ın sahibi; fatma Çakır üyesi değil → işlem bulunamaz, Annemler'deki rolü değişmez. expect((await setMemberRole(id("ayse"), cakir, id("fatma"), "viewer")).ok).toBe(false); expect((await removeMember(id("ayse"), cakir, id("mehmet"))).ok).toBe(false); // fatma Annemler'in sahibi; Çakır üzerinde yetkisi yok. expect((await setMemberRole(id("fatma"), cakir, id("emre"), "viewer")).ok).toBe(false); const fatma = await HouseholdMemberModel.findOne({ householdId: annemler, userId: id("fatma"), }).lean(); const mehmet = await HouseholdMemberModel.findOne({ householdId: annemler, userId: id("mehmet"), }).lean(); const emre = await HouseholdMemberModel.findOne({ householdId: cakir, userId: id("emre"), }).lean(); expect([fatma?.role, mehmet?.role, emre?.role]).toEqual(["owner", "member", "member"]); }); it("son sahip düşürülemez; birden çok sahip varken düşürülebilir", async () => { expect((await adminSetMemberRole(id("admin"), annemler, id("fatma"), "member")).ok).toBe(false); expect((await setMemberRole(id("fatma"), annemler, id("fatma"), "member")).ok).toBe(false); // Çakır'da iki sahip var (admin + ayse). expect((await setMemberRole(id("ayse"), cakir, id("admin"), "member")).ok).toBe(true); // Artık ayse son sahip. expect((await setMemberRole(id("ayse"), cakir, id("ayse"), "member")).ok).toBe(false); expect((await setMemberRole(id("ayse"), cakir, id("admin"), "owner")).ok).toBe(true); expect( await AuditLogModel.countDocuments({ action: "household.member.role", householdId: cakir }), ).toBe(2); }); it("sahip üyeyi haneden çıkarır; kullanıcı artık haneyi göremez", async () => { expect((await removeMember(id("ayse"), cakir, id("ozan"))).ok).toBe(true); expect(await resolveMembership(id("ozan"), cakir)).toBeNull(); expect(await listMemberships(id("ozan"))).toEqual([]); }); }); describe("admin işlemleri", () => { it("hane + ilk sahip + denetim kaydı tek transaction'da oluşur", async () => { const res = await adminCreateHousehold(id("admin"), { name: "Yazlık", ownerUserId: id("burak"), }); expect(res.ok).toBe(true); const hid = res.ok ? res.data.id : ""; expect((await resolveMembership(id("burak"), hid, "owner"))?.name).toBe("Yazlık"); expect( await AuditLogModel.countDocuments({ action: "household.create", householdId: hid }), ).toBe(1); }); it("var olmayan kullanıcıyla hane oluşturulmaz", async () => { const before = await HouseholdModel.countDocuments(); const res = await adminCreateHousehold(id("admin"), { name: "Hayalet", ownerUserId: new Types.ObjectId().toHexString(), }); expect(res.ok).toBe(false); expect(await HouseholdModel.countDocuments()).toBe(before); }); it("kullanıcı silinince tüm üyelikleri temizlenir", async () => { expect(await removeAllMembershipsOfUser(id("mehmet"))).toBe(1); expect(await listMemberships(id("mehmet"))).toEqual([]); }); });