import { rm } from "node:fs/promises"; import sharp from "sharp"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { readAttachmentForUser, uploadAttachment } from "@/features/attachments/service"; import { env } from "@/lib/env"; import { AttachmentModel } from "@/models/attachment"; import { ProductTypeModel } from "@/models/product-type"; import { productFormSchema, type ProductFormInput } from "@/shared/schemas/product"; import { setupSeededDb, teardownDb } from "@/test/seeded-db"; import { createProduct, listProducts, listProductTypeOptions, setProductArchived, updateProduct, } from "./service"; let ids: Awaited>; let cakir = ""; let ayse = ""; let emre = ""; let zeynep = ""; let fatma = ""; beforeAll(async () => { await rm(env().UPLOAD_DIR, { recursive: true, force: true }); 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(async () => { await teardownDb(); await rm(env().UPLOAD_DIR, { recursive: true, force: true }); }); const typeId = (ref: string) => ProductTypeModel.findOne({ seedRef: ref }) .lean() .then((x) => String(x!._id)); const png = () => sharp({ create: { width: 40, height: 30, channels: 3, background: "#cc6633" } }) .png() .toBuffer(); async function form(over: Partial = {}) { return productFormSchema.parse({ productTypeId: await typeId("toz-deterjan"), brand: "Persil", name: "Matik", barcode: "", packageQuantity: "4,5", packageUnit: "kg", typeQuantity: "30", note: "", imageIds: [], ...over, }); } describe("ürünler (marka/paket)", () => { it("seed: dönüşümler ve paket açıklaması; izolasyon", async () => { const all = (await listProducts(ayse, cakir))!; expect(all.products).toHaveLength(22); const ariel = all.products.find((p) => p.brand === "Ariel")!; expect(ariel).toMatchObject({ productTypeName: "Toz Deterjan", typeUnit: "yıkama", inTypeUnit: 33_000, packageLabel: "5 kg · 33 yıkama", conversionMissing: false, }); const icim = all.products.find((p) => p.brand === "İçim")!; expect(icim).toMatchObject({ typeQuantity: null, inTypeUnit: 500 }); const peynir = (await listProducts(ayse, cakir, { productTypeId: await typeId("beyaz-peynir"), }))!; expect(peynir.products.map((p) => p.packageLabel)).toEqual(["500 g · 0,5 kg"]); expect(await listProducts(fatma, cakir)).toBeNull(); expect((await listProductTypeOptions(ayse, cakir))!.length).toBe(19); }); it("dönüşüm zorunlu; kendiliğinden çevrilebilende yok sayılır; barkod tek", async () => { const missing = await createProduct(ayse, cakir, await form({ typeQuantity: "" })); expect(missing).toMatchObject({ ok: false, fieldErrors: { typeQuantity: expect.any(String) } }); const ok = await createProduct(ayse, cakir, await form({ barcode: "8690 0000 0099 9" })); expect(ok.ok).toBe(true); const sut = await createProduct( ayse, cakir, await form({ productTypeId: await typeId("sut"), brand: "Torku", packageQuantity: "1000", packageUnit: "ml", typeQuantity: "7", }), ); expect(sut.ok).toBe(true); const list = (await listProducts(ayse, cakir))!.products; expect(list.find((p) => p.brand === "Persil")).toMatchObject({ barcode: "8690000000999", inTypeUnit: 30_000, }); expect(list.find((p) => p.brand === "Torku")).toMatchObject({ typeQuantity: null, inTypeUnit: 1_000, }); const dup = await createProduct( ayse, cakir, await form({ brand: "Başka", barcode: "8690000000999" }), ); expect(dup).toMatchObject({ ok: false, fieldErrors: { barcode: expect.any(String) } }); expect(productFormSchema.safeParse({ ...(await form()), barcode: "12a" }).success).toBe(false); expect((await createProduct(zeynep, cakir, await form({ brand: "İzleyici" }))).ok).toBe(false); }); it("görseller: bağlanır, sıralanır, çıkarılır; okuma hane üyeleriyle sınırlı", async () => { const a1 = await uploadAttachment(ayse, cakir, { name: "on.png", bytes: await png() }); const a2 = await uploadAttachment(ayse, cakir, { name: "arka.png", bytes: await png() }); const id1 = a1.ok ? a1.data.id : ""; const id2 = a2.ok ? a2.data.id : ""; const r = await createProduct( ayse, cakir, await form({ brand: "Peros", imageIds: [id2, id1] }), ); expect(r.ok).toBe(true); const pid = r.ok ? r.data.id : ""; let p = (await listProducts(ayse, cakir))!.products.find((x) => x.id === pid)!; expect(p.images.map((i) => i.id)).toEqual([id2, id1]); // Aynı hanenin başka üyesi görür; başka hane görmez. expect(await readAttachmentForUser(emre, id1, "thumb")).not.toBeNull(); expect(await readAttachmentForUser(fatma, id1, "full")).toBeNull(); // Başkasının yüklediği bekleyen görsel bağlanamaz. const foreign = await uploadAttachment(emre, cakir, { name: "x.png", bytes: await png() }); const bad = await updateProduct( ayse, cakir, pid, await form({ brand: "Peros", imageIds: [id1, foreign.ok ? foreign.data.id : ""] }), ); expect(bad.ok).toBe(false); p = (await listProducts(ayse, cakir))!.products.find((x) => x.id === pid)!; expect(p.images).toHaveLength(2); expect( (await updateProduct(ayse, cakir, pid, await form({ brand: "Peros", imageIds: [id1] }))).ok, ).toBe(true); expect(await AttachmentModel.exists({ _id: id2, deletedAt: { $ne: null } })).toBeTruthy(); p = (await listProducts(ayse, cakir))!.products.find((x) => x.id === pid)!; expect(p.images.map((i) => i.id)).toEqual([id1]); }); it("arşiv: listeden çıkar, düzenlenemez, barkod çakışırsa geri alınamaz", async () => { const r = await createProduct(ayse, cakir, await form({ brand: "Arşiv", barcode: "12345678" })); const id = r.ok ? r.data.id : ""; expect((await setProductArchived(ayse, cakir, id, true)).ok).toBe(true); expect((await listProducts(ayse, cakir))!.products.some((p) => p.id === id)).toBe(false); expect( (await listProducts(ayse, cakir, { includeArchived: true }))!.products.find( (p) => p.id === id, )!.archived, ).toBe(true); expect((await updateProduct(ayse, cakir, id, await form({ brand: "Arşiv" }))).ok).toBe(false); expect( (await createProduct(ayse, cakir, await form({ brand: "Yeni", barcode: "12345678" }))).ok, ).toBe(true); expect((await setProductArchived(ayse, cakir, id, false)).ok).toBe(false); }); });