diff --git a/src/features/product/repositories/product.repository.spec.ts b/src/features/product/repositories/product.repository.spec.ts index 89d29aa3..32d00e8f 100644 --- a/src/features/product/repositories/product.repository.spec.ts +++ b/src/features/product/repositories/product.repository.spec.ts @@ -235,18 +235,18 @@ describe('ProductRepository (real DB)', () => { }); }); - describe('findProductById (active만)', () => { - it('is_active: false면 반환 안함', async () => { + describe('findProductByIdIncludingInactive', () => { + it('inactive 상품도 반환', async () => { const store = await createStore(prisma); const inactive = await createProduct(prisma, { store_id: store.id, is_active: false, }); - const result = await repo.findProductById({ + const result = await repo.findProductByIdIncludingInactive({ productId: inactive.id, storeId: store.id, }); - expect(result).toBeNull(); + expect(result?.id).toBe(inactive.id); }); it('store_id 불일치면 null', async () => { @@ -254,7 +254,7 @@ describe('ProductRepository (real DB)', () => { const storeB = await createStore(prisma); const product = await createProduct(prisma, { store_id: storeA.id }); - const result = await repo.findProductById({ + const result = await repo.findProductByIdIncludingInactive({ productId: product.id, storeId: storeB.id, }); @@ -303,7 +303,7 @@ describe('ProductRepository (real DB)', () => { data: { deleted_at: new Date() }, }); - const result = await repo.findProductById({ + const result = await repo.findProductByIdIncludingInactive({ productId: product.id, storeId: store.id, }); @@ -314,21 +314,6 @@ describe('ProductRepository (real DB)', () => { }); }); - describe('findProductByIdIncludingInactive', () => { - it('inactive 상품도 반환', async () => { - const store = await createStore(prisma); - const inactive = await createProduct(prisma, { - store_id: store.id, - is_active: false, - }); - const result = await repo.findProductByIdIncludingInactive({ - productId: inactive.id, - storeId: store.id, - }); - expect(result?.id).toBe(inactive.id); - }); - }); - describe('createProduct / updateProduct / softDeleteProduct', () => { it('createProduct는 store_id를 결합하여 생성', async () => { const store = await createStore(prisma); diff --git a/src/features/product/repositories/product.repository.ts b/src/features/product/repositories/product.repository.ts index 89ad152f..24da236a 100644 --- a/src/features/product/repositories/product.repository.ts +++ b/src/features/product/repositories/product.repository.ts @@ -264,55 +264,6 @@ export class ProductRepository { return Boolean(found); } - async findProductById(args: { productId: bigint; storeId: bigint }) { - return this.prisma.product.findFirst({ - where: { - id: args.productId, - store_id: args.storeId, - is_active: true, - }, - // soft-delete extension은 root만 patch하므로 nested relation에 가드를 명시한다 - include: { - images: { - where: activeWhere, - orderBy: { sort_order: 'asc' }, - }, - product_categories: { - // 링크·대상 카테고리의 soft-delete 가드. is_active는 셀러 화면에서 - // 기존 지정을 계속 보여줘야 하므로 걸지 않는다. - where: { ...activeWhere, category: activeWhere }, - include: { - category: true, - }, - }, - product_tags: { - where: { ...activeWhere, tag: activeWhere }, - include: { - tag: true, - }, - }, - option_groups: { - where: activeWhere, - orderBy: { sort_order: 'asc' }, - include: { - option_items: { - where: activeWhere, - orderBy: { sort_order: 'asc' }, - }, - }, - }, - custom_template: { - include: { - text_tokens: { - where: activeWhere, - orderBy: { sort_order: 'asc' }, - }, - }, - }, - }, - }); - } - async findProductByIdIncludingInactive(args: { productId: bigint; storeId: bigint; diff --git a/src/features/product/services/product-seller-query.service.spec.ts b/src/features/product/services/product-seller-query.service.spec.ts index a7ebc615..45c0a4fb 100644 --- a/src/features/product/services/product-seller-query.service.spec.ts +++ b/src/features/product/services/product-seller-query.service.spec.ts @@ -175,6 +175,27 @@ describe('SellerProductQueryService (real DB)', () => { expect(result.images).toHaveLength(1); }); + it('soft-delete된 상품이면 404', async () => { + const { account, store } = await setupSellerWithStore(prisma); + const deleted = await createSellerProduct(store.id, { + deleted_at: new Date(), + }); + + await expect( + service.sellerProduct(account.id, deleted.id), + ).rejects.toThrowDomain('PRODUCT_NOT_FOUND'); + }); + + it('숨김(is_active=false) 본인 상품도 상세를 반환한다', async () => { + const { account, store } = await setupSellerWithStore(prisma); + const hidden = await createSellerProduct(store.id, { is_active: false }); + + const result = await service.sellerProduct(account.id, hidden.id); + + expect(result.id).toBe(hidden.id.toString()); + expect(result.isActive).toBe(false); + }); + it('soft-delete된 이미지·옵션 그룹·옵션 아이템은 상세에서 제외한다', async () => { const { account, store } = await setupSellerWithStore(prisma); const product = await createSellerProduct(store.id); diff --git a/src/features/product/services/product-seller-query.service.ts b/src/features/product/services/product-seller-query.service.ts index 2d76889e..34216c31 100644 --- a/src/features/product/services/product-seller-query.service.ts +++ b/src/features/product/services/product-seller-query.service.ts @@ -70,7 +70,8 @@ export class SellerProductQueryService extends SellerBaseService { productId: bigint, ): Promise { const ctx = await this.requireSellerContext(accountId); - const row = await this.productRepository.findProductById({ + // 목록이 숨김 상품도 보여주므로 상세도 is_active와 무관하게 연다 + const row = await this.productRepository.findProductByIdIncludingInactive({ productId, storeId: ctx.storeId, });