mmday-firebase/tests/services/rewardCatalogService.test.ts
윤정민 9a66d3fb12 Add shipment, refund reason, and option availability to orders
- shipped 전환 시 운송사·송장번호(shipment) 필수 검증 및 shippedAt/deliveredAt 기록, POST /admin/order/shipment로 배송정보 정정(배송 시작 이력에 감사 정보 동기화)
- refunded 전환 시 관리자 환불 사유(2~500자) 필수 — 주문 문서·상태 이력·포인트 원장(adminReason/adminActor)에 기록하고 DTO로 노출
- 상품 옵션에 active 필드 추가(구 문서는 활성 간주) — 비활성 옵션 주문은 OPTION_UNAVAILABLE 거부, 활성·교환 가능 상품은 활성 옵션 1개 이상 필요
- OrderDto/OrderStatusHistoryEntryDto/ProductOptionDto 확장 및 관련 테스트 보강
- 수정 파일 줄바꿈 LF 정규화 및 eslint --fix 적용
2026-07-22 17:46:45 +09:00

105 lines
3.3 KiB
TypeScript

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");
});
});