f0b570284e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
180 lines
6.7 KiB
TypeScript
180 lines
6.7 KiB
TypeScript
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<ReturnType<typeof setupSeededDb>>;
|
||
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<ProductFormInput> = {}) {
|
||
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);
|
||
});
|
||
});
|