feat(shopping): mağazalar, zahmet skoru ve ürünler (marka/paket, dönüşüm, görseller)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user