import { Timestamp } from "firebase-admin/firestore"; import { beforeEach, describe, expect, it } from "vitest"; import { firestore } from "../../src/firebase"; import { getCatalog, getCatalogProduct, invalidateRewardCatalog, } from "../../src/services/rewardCatalogService"; const id = "catalog-images-test"; const mainImages = [ "https://cdn.example.com/product/cover.jpg", "https://cdn.example.com/product/gallery-2.jpg", ]; const detailImages = [ "https://cdn.example.com/product/detail-1.jpg", "https://cdn.example.com/product/detail-2.jpg", ]; describe("rewardCatalogService image contract", () => { beforeEach(async () => { invalidateRewardCatalog(id); await firestore.doc(`products/${id}`).delete(); const now = Timestamp.now(); await firestore.doc(`products/${id}`).set({ name: "이미지 계약 상품", pointPrice: 1000, active: true, redeemable: true, displayOrder: 999, mainImages, detailImages, createdAt: now, updatedAt: now, }); }); it("목록에는 순서가 보존된 mainImages만 노출한다", async () => { const product = (await getCatalog()).find((item) => item.id === id); expect(product).toMatchObject({ id, mainImages }); expect(product).not.toHaveProperty("detailImages"); expect(product).not.toHaveProperty("totalStock"); expect(product).not.toHaveProperty("reservedStock"); expect(product).not.toHaveProperty("safetyStock"); expect(product).not.toHaveProperty("options"); }); it("상세에는 mainImages와 detailImages를 모두 순서대로 노출한다", async () => { expect(await getCatalogProduct(id)).toMatchObject({ id, mainImages, detailImages, }); }); it("옵션이 없는 상품은 상세에 optionLabel/options 키가 없다", async () => { const product = await getCatalogProduct(id); expect(product).not.toHaveProperty("optionLabel"); expect(product).not.toHaveProperty("options"); }); }); describe("rewardCatalogService option contract", () => { const optionId = "catalog-options-test"; beforeEach(async () => { invalidateRewardCatalog(optionId); await firestore.doc(`products/${optionId}`).delete(); const now = Timestamp.now(); await firestore.doc(`products/${optionId}`).set({ name: "옵션 계약 상품", pointPrice: 2600, active: true, redeemable: true, displayOrder: 998, mainImages, detailImages, optionLabel: "색상", options: [ { id: "blue", name: "블루" }, { id: "red", name: "레드", active: false }, ], createdAt: now, updatedAt: now, }); }); it("상세에는 비활성 상태를 포함한 옵션을 문서 순서대로 노출한다", async () => { expect(await getCatalogProduct(optionId)).toMatchObject({ id: optionId, optionLabel: "색상", options: [ { id: "blue", name: "블루", active: true }, { id: "red", name: "레드", active: false }, ], }); }); it("목록에는 옵션을 노출하지 않는다", async () => { const product = (await getCatalog()).find((item) => item.id === optionId); expect(product).not.toHaveProperty("optionLabel"); expect(product).not.toHaveProperty("options"); }); });