Skip to content

chore: store-detail 구매자 조회 API 릴리즈#161

Merged
chanwoo7 merged 17 commits into
mainfrom
develop
Jun 24, 2026
Merged

chore: store-detail 구매자 조회 API 릴리즈#161
chanwoo7 merged 17 commits into
mainfrom
develop

Conversation

@chanwoo7

@chanwoo7 chanwoo7 commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

store-detail 구매자(유저) 조회 API를 운영(main)에 릴리즈한다. 매장 상세 헤더·상품 탭·후기 탭 조회와 StoreImage 스키마를 포함한다.

Scope

develop에 누적된 store-detail 도메인 전체:

진행 상황

  • 전부 develop에서 CI 통과 + Codex 리뷰 완료 후 머지됨
  • 신규 마이그레이션 1건: store_image 테이블 + store.access_guide_text/regular_closure_text 컬럼 (둘 다 nullable / 신규 테이블 → 기존 데이터 무중단)

Impact

  • 신규 구매자 조회 API 4종 추가. 기존 계약/동작 변경 없음
  • 배포 시 prod 마이그레이션 적용 필요(store_image 테이블 + store 컬럼 2개)
  • 매장 이미지·안내문·휴무 텍스트의 seller 입력 API는 차기(현재는 조회 + 시드)

Test plan

  • 각 PR yarn validate 통과(누적 테스트 1300+)
  • 릴리즈 CI(check / coverage-report / CodeQL / pr-title) green 확인
  • Codex + CodeRabbit 두 봇 리뷰 완료 확인

Summary by CodeRabbit

  • New Features

    • 매장 상세 화면에서 이미지, 지도 정보, 영업 안내, 정기 휴무, 웹사이트 등을 볼 수 있게 되었습니다.
    • 매장 공개 리뷰를 커서 기반으로 조회하고, 좋아요 수와 내가 좋아요한 상태를 확인할 수 있게 되었습니다.
    • 매장별 상품 목록과 카테고리 목록을 제공해 더 쉽게 탐색할 수 있게 되었습니다.
  • Bug Fixes

    • 매장/리뷰 조회에서 비활성 또는 삭제된 데이터가 노출되지 않도록 개선했습니다.
    • 시드 재실행 시 매장 이미지 데이터 정리 순서 문제를 보완했습니다.

chanwoo7 added 15 commits June 24, 2026 03:06
- storeDetail 쿼리 신설: 매장 헤더(이미지·평점·리뷰수·찜)·전화·주소·좌표·영업/휴무/안내문 노출. OptionalJwtAuthGuard로 비로그인 조회 + 로그인 시 isWishlisted
- StoreImage 테이블 + Store.access_guide_text·regular_closure_text 컬럼 추가 (구매자 조회용, seller 입력 API는 차기)
- 시드/스토어 팩토리 보강, 단위(mapper)·통합(service/resolver) 테스트 18건
store_image FK가 ON DELETE RESTRICT라 store 삭제 전 store_image를 정리하지 않으면 2회차 yarn prisma:seed가 FK 위반으로 실패한다. resetSeedScope에 storeImage.deleteMany를 추가해 멱등성을 보장한다. (Codex P2 반영)
feat(store): 구매자 매장 상세 조회(storeDetail)와 StoreImage 스키마 추가
- storeProducts(input): 활성 상품(+활성 매장)만, 카테고리/매장내 검색(상품명·태그)/커서 페이지네이션. 대표 이미지 thumbnail + 할인율 계산
- storeProductCategories(storeId): 매장 보유 활성 상품의 카테고리만(빈 카테고리 제외) + productCount + sort_order 정렬
- product feature에 구매자 조회 레이어 신설(resolver/service/mapper/dto/types/constants), ProductRepository에 listActiveProductsByStore·listStoreProductCategories 추가
- 개인화 없는 public 쿼리. 단위(mapper)·통합(service/resolver) 테스트 19건
비활성/삭제 매장이 활성 상품을 보유하면 storeProductCategories가 카테고리·productCount를 노출하던 불일치를 수정한다. groupBy 필터에 store active/deleted 술어를 추가해 storeProducts와 가시성을 일치시킨다. (Codex P2 반영)
- storeReviews(input): 매장 공개 리뷰 목록(최신순·커서), totalCount(후기 탭 카운트), media·작성자 닉네임·연결 상품명(주문 스냅샷)
- 리뷰 좋아요 수 집계 + isLiked(OptionalJwtAuthGuard, 비로그인 false)
- 매장 공개 리뷰 조회를 store feature가 소유(StoreReviewRepository 신설). user의 ReviewRepository(본인 리뷰 작성/관리)와 책임 분리, cross-feature 경계 준수
- 단위(mapper)·통합(service/resolver) 테스트 11건
비활성/삭제 매장의 store_id를 알면 storeReviews로 리뷰 내용·미디어·totalCount가 노출되던 문제를 수정한다. listStoreReviews·countStoreReviews where에 store active/deleted 술어를 추가해 storeDetail의 가드와 일치시킨다. (Codex P2 반영)
storeProductCategories는 카테고리를 is_active/deleted_at로 거르는데, storeProducts의 categoryIds와 categoryId 필터는 비활성/삭제 카테고리를 포함해 사이드바와 불일치했다. listActiveProductsByStore의 categoryId 필터와 product_categories select에 category active/deleted 술어를 추가한다. (Codex P2 :778 반영)
탈퇴(soft-delete) 시 nickname이 deleted_<id>로 덮어써지는데, soft-delete extension은 nested user_profile에 deleted_at을 주입하지 않아 storeReviews가 deleted_<id>를 그대로 노출했다. user_profile.deleted_at을 함께 select하고 매퍼에서 탈퇴 프로필은 authorNickname=null로 익명화한다. (Codex P2 :55 반영)
search가 태그로 매칭될 때 tag.deleted_at을 확인하지 않아 삭제된 태그명으로도 상품이 검색되던 문제를 수정한다. product_tags.some.tag 조건에 deleted_at: null을 추가한다. (Codex P2 :759 반영)
feat(store): 구매자 매장 후기 목록 조회(storeReviews)와 좋아요 집계
parseId("0")=0n이 args.cursor/args.categoryId의 truthiness 체크에서 falsy로 떨어져, 잘못된 categoryId가 전체 목록을 반환하고 zero cursor가 페이지를 리셋하던 문제를 수정한다. 0n도 유효 인자로 다루도록 !== undefined로 분기한다. (Codex P2 :740 반영)
storeProducts(#158)와 동일하게 parseId("0")=0n이 args.cursor truthiness 체크에서 falsy로 떨어져 zero cursor가 페이지를 리셋하던 문제를 수정한다. listStoreReviews의 cursor 분기를 !== undefined로 바꾼다.
feat(product): 구매자 매장 상품 목록·카테고리 조회 API
fix(store): storeReviews의 0 값 cursor 보존
@github-actions

Copy link
Copy Markdown

🧹 knip — dead-code 리포트

요약 항목 없음
전체 리포트
(knip 출력 없음 — 이슈 0이거나 실행 실패)

청소 후보(오탐 가능) · 기준 docs/guide/architecture-conventions.md

@github-actions

Copy link
Copy Markdown

🩺 NestJS Doctor — 89/100 (Good)

진단 259건 (error 0).

Category error warning info
architecture 0 0 13
correctness 0 109 0
performance 0 24 16
schema 0 0 84
security 0 13 0
architecture / security 상위 항목
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'IAuditLogRepository'.
  • warning security/security/no-exposed-env-vars: Direct 'process.env.NODE_ENV' access in 'AuthController'. Use ConfigService instead.
  • warning security/security/require-guards-on-endpoints: Endpoint 'start' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'callback' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'refresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'logout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogin' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerRefresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'devIssueToken' has no @UseGuards() at class or method level.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/conversation/repositories/conversation.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'ConversationRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/order/repositories/order.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'OrderRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/product/repositories/product.repository'.

오탐 포함 가능 · 기준 docs/guide/architecture-conventions.md

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.64865% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...eatures/product/repositories/product.repository.ts 91.66% 0 Missing and 1 partial ⚠️
...ures/store/repositories/store-review.repository.ts 92.30% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@chanwoo7, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 9 minutes and 49 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa973b00-70ca-45f1-b2fe-ba3301d5dc0a

📥 Commits

Reviewing files that changed from the base of the PR and between 93882a4 and 64ea223.

📒 Files selected for processing (8)
  • src/features/product/dto/inputs/store-products.input.ts
  • src/features/product/services/product-storefront-mappers.helper.spec.ts
  • src/features/product/services/product-storefront-mappers.helper.ts
  • src/features/product/services/product-storefront.service.spec.ts
  • src/features/store/dto/inputs/store-reviews.input.ts
  • src/features/store/services/store-detail.service.ts
  • src/features/store/services/store-review.service.ts
  • src/prisma/soft-delete.middleware.ts
📝 Walkthrough

Walkthrough

store_image 테이블 마이그레이션과 Prisma 스키마 변경을 기반으로 매장 상세(storeDetail), 공개 리뷰(storeReviews), 상품 목록(storeProducts/storeProductCategories) 세 개의 GraphQL 스토어프론트 쿼리가 추가됐다. 각 기능은 SDL, 출력 타입, 리포지토리, 서비스, 리졸버, 통합 테스트 전 계층을 포함한다.

Changes

스토어프론트 API 구현

Layer / File(s) Summary
DB 스키마: store_image 테이블 및 store 텍스트 컬럼
prisma/migrations/.../migration.sql, prisma/schema.prisma, prisma/seed/idempotent.ts, prisma/seed/stores.ts
store_image 신규 테이블(FK ON DELETE RESTRICT, 복합 인덱스, soft-delete)과 store 텍스트 컬럼 2개를 마이그레이션·스키마에 추가하고, 시드 삭제 순서를 FK 제약에 맞게 보정하며 storeA 시드 데이터에 이미지와 신규 필드를 채운다.
매장 상세 API: SDL, 타입, 리포지토리
src/features/store/store-detail.graphql, src/features/store/types/store-detail-output.type.ts, src/features/store/constants/store-detail-error-messages.ts, src/features/store/repositories/store.repository.ts, src/features/store/services/store-mappers.helper.ts, src/test/factories/store.factory.ts
storeDetail 쿼리 SDL, StoreDetail/StoreMapProvider 타입, StoreDetailRow 인터페이스, findStoreDetailById 메서드, STORE_DETAIL_ERRORS 상수, buildRegionLabel 시그니처 변경, 테스트 팩토리에 신규 필드 추가.
매장 상세 API: 매퍼, 서비스, 리졸버, 모듈 등록
src/features/store/services/store-detail-mappers.helper.ts, src/features/store/services/store-detail-mappers.helper.spec.ts, src/features/store/services/store-detail.service.ts, src/features/store/services/store-detail.service.spec.ts, src/features/store/resolvers/store-detail-query.resolver.ts, src/features/store/resolvers/store-detail-query.resolver.spec.ts, src/features/store/store.module.ts
toStoreDetail 매퍼(좌표·리뷰 통계·이미지 URL·regionLabel 변환), StoreDetailService(parallel 조회, NotFoundException, isWishlisted), OptionalJwtAuthGuard 적용 리졸버, 모듈 provider 확장, 각 계층 실 DB 통합 테스트.
매장 리뷰 API: SDL, 타입, DTO, 리포지토리
src/features/store/store-reviews.graphql, src/features/store/types/store-review-output.type.ts, src/features/store/constants/store-review.constants.ts, src/features/store/dto/inputs/store-reviews.input.ts, src/features/store/repositories/store-review.repository.ts
storeReviews 커서 기반 쿼리 SDL, StoreReview/StoreReviewMedia/StoreReviewConnection 타입, StoreReviewsInput DTO, StoreReviewRepository(listStoreReviews·countStoreReviews·aggregateLikeCounts·findLikedReviewIds).
매장 리뷰 API: 매퍼, 서비스, 리졸버
src/features/store/services/store-review-mappers.helper.ts, src/features/store/services/store-review-mappers.helper.spec.ts, src/features/store/services/store-review.service.ts, src/features/store/services/store-review.service.spec.ts, src/features/store/resolvers/store-review-query.resolver.ts, src/features/store/resolvers/store-review-query.resolver.spec.ts
toStoreReview 매퍼(soft-delete 익명화, media 재구성), StoreReviewService(커서 페이징, 좋아요 집계, parallel), 리졸버, 실 DB 통합 테스트(9개 시나리오).
매장 상품 스토어프론트: SDL, 타입, DTO, 리포지토리
src/features/product/product-storefront.graphql, src/features/product/types/product-storefront-output.type.ts, src/features/product/constants/product-storefront.constants.ts, src/features/product/dto/inputs/store-products.input.ts, src/features/product/repositories/product.repository.ts
storeProducts/storeProductCategories 쿼리 SDL, StoreProduct/StoreProductConnection/StoreProductCategory 타입, StoreProductsInput DTO, listActiveProductsByStore(커서·카테고리·검색 필터)·listStoreProductCategories(groupBy 집계).
매장 상품 스토어프론트: 매퍼, 서비스, 리졸버, 모듈 등록
src/features/product/services/product-storefront-mappers.helper.ts, src/features/product/services/product-storefront-mappers.helper.spec.ts, src/features/product/services/product-storefront.service.ts, src/features/product/services/product-storefront.service.spec.ts, src/features/product/resolvers/product-storefront-query.resolver.ts, src/features/product/resolvers/product-storefront-query.resolver.spec.ts, src/features/product/product.module.ts
calcDiscountRate/toStoreProduct/toStoreProductCategory 매퍼, ProductStorefrontService(limit 정규화, 커서 페이징), ProductStorefrontQueryResolver, ProductModule provider 확장, 실 DB 통합 테스트.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Resolver as StoreDetailQueryResolver
  participant Service as StoreDetailService
  participant StoreRepo as StoreRepository
  participant WishlistRepo as StoreWishlistRepository
  rect rgba(59, 130, 246, 0.5)
    note over Client,WishlistRepo: storeDetail 쿼리 (OptionalJwtAuth)
    Client->>Resolver: storeDetail(storeId, JwtUser?)
    Resolver->>Service: storeDetail(storeId, accountId?)
    Service->>StoreRepo: findStoreDetailById(storeId)
    StoreRepo-->>Service: StoreDetailRow (store_images 포함)
    Service->>Service: NotFoundException if null
    par 병렬 조회
      Service->>StoreRepo: aggregateReviewStats(storeId)
      Service->>WishlistRepo: findWishlistedStoreIds(accountId?)
    end
    Service-->>Resolver: toStoreDetail(row, reviewStat, isWishlisted)
    Resolver-->>Client: StoreDetail
  end
Loading
sequenceDiagram
  participant Client
  participant Resolver as StoreReviewQueryResolver
  participant Service as StoreReviewService
  participant Repo as StoreReviewRepository
  rect rgba(16, 185, 129, 0.5)
    note over Client,Repo: storeReviews 쿼리 (OptionalJwtAuth, 커서 기반)
    Client->>Resolver: storeReviews(input, JwtUser?)
    Resolver->>Service: storeReviews(input, accountId?)
    par 병렬 조회
      Service->>Repo: listStoreReviews(storeId, limit+1, cursor?)
      Service->>Repo: countStoreReviews(storeId)
    end
    Service->>Repo: aggregateLikeCounts(reviewIds)
    Service->>Repo: findLikedReviewIds(reviewIds, accountId) if 로그인
    Service-->>Resolver: StoreReviewConnection (items, totalCount, hasMore, nextCursor)
    Resolver-->>Client: StoreReviewConnection
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~100 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 store-detail 구매자 조회 API 릴리즈라는 변경 범위를 간결하게 잘 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 93882a4f88

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread prisma/schema.prisma

created_at DateTime @default(now()) @db.DateTime(3)
updated_at DateTime @updatedAt @db.DateTime(3)
deleted_at DateTime? @db.DateTime(3)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register StoreImage with soft-delete filtering

This new soft-deletable model is not added to SOFT_DELETE_MODELS in src/prisma/soft-delete.middleware.ts, so any direct prisma.storeImage.findMany/findFirst/count read will not get the repository-wide deleted_at: null filter that other soft-deletable tables rely on. The current detail query filters nested images manually, but the first direct StoreImage read can return or count deleted carousel images unless StoreImage is registered there.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (5)
src/features/store/resolvers/store-review-query.resolver.spec.ts (1)

56-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

잘못된 accountId 예외 경로도 한 케이스는 잡아두는 게 좋습니다.

이번 resolver는 src/global/auth/parse-account-id.ts:5-23를 직접 호출하므로, 빈 문자열·음수·비숫자 accountId에서 BadRequestException이 나는지 검증하는 테스트가 있어야 주요 분기가 닫힙니다.

As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지, 정상 흐름뿐 아니라 주요 예외/분기 케이스가 포함되는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/store/resolvers/store-review-query.resolver.spec.ts` around
lines 56 - 84, Add a test case in store-review-query.resolver.spec.ts that
covers the invalid accountId path used by storeReviews, since it calls
parseAccountId directly. Verify that empty string, negative, or non-numeric
accountId inputs trigger BadRequestException, alongside the existing
login/unlogin happy-path tests. Use the resolver.storeReviews and parseAccountId
flow to locate the branch and keep the test deterministic with mocked/stubbed
inputs.

Source: Path instructions

src/features/store/services/store-review.service.spec.ts (1)

173-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

삭제 매장 분기는 아직 검증되지 않습니다.

테스트 이름은 비활성/삭제 매장인데 현재는 is_active: false만 확인합니다. deleted_at 필터는 별도 조건이라서, 삭제된 매장만 세팅한 케이스를 하나 더 추가해야 이 분기 회귀를 잡을 수 있습니다.

As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지, 정상 흐름뿐 아니라 주요 예외/분기 케이스가 포함되는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/store/services/store-review.service.spec.ts` around lines 173 -
181, 현재 `storeReviews` 스펙의 “비활성/삭제 매장” 테스트는 `is_active`만 검증하고 있어 `deleted_at`
분기를 놓치고 있습니다. `storeReviews`와 `createStore`/`makeReview`를 사용하는 같은 테스트 파일에 삭제된
매장만 대상으로 하는 케이스를 추가해, `deleted_at`이 설정된 매장에서도 `items`가 비고 `totalCount`가 0인지
확인하세요. 기존 비활성 매장 케이스와 분리해 `is_active`/`deleted_at` 각각의 필터 분기를 모두 커버하도록 유지하세요.

Source: Path instructions

src/features/product/resolvers/product-storefront-query.resolver.spec.ts (1)

19-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

이 spec은 real DB 통합 대신 resolver 단위로 고립하는 편이 맞습니다.

현재는 createTestingModuleWithRealDb와 직접 Prisma write로 DB 의존성을 그대로 끌고 오면서 happy path만 확인하고 있습니다. ProductStorefrontQueryResolver가 얇은 위임 계층인 만큼, 이 파일은 ProductStorefrontService를 mock/stub 해서 인자 전달과 서비스 예외 전파를 검증하고, real DB 경로는 별도 integration/e2e 성격의 테스트로 분리하는 편이 더 안정적입니다. As per path instructions, src/**/*.spec.ts: "테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지, 정상 흐름뿐 아니라 주요 예외/분기 케이스가 포함되는지 확인하세요."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/product/resolvers/product-storefront-query.resolver.spec.ts`
around lines 19 - 69, This spec is doing real DB integration work instead of
isolating the resolver; refactor ProductStorefrontQueryResolver tests to mock or
stub ProductStorefrontService and keep ProductRepository/Prisma out of this unit
spec. Update the resolver setup in beforeAll to use a testing module with a
mocked ProductStorefrontService, then verify storeProducts and
storeProductCategories only pass the correct arguments and return the mocked
result. Add at least one case for service error propagation so the resolver’s
thin delegation behavior is covered without relying on
createTestingModuleWithRealDb, createStore, or direct prisma writes.

Source: Path instructions

src/features/store/services/store-detail.service.spec.ts (1)

22-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

*.spec.ts에서 실DB 의존성을 분리해 주세요.

Line 23-36은 createTestingModuleWithRealDb, PrismaClient, truncateAll에 직접 기대고 있어서 이 스펙이 DB 상태와 실행 환경에 묶입니다. 이 경로는 Repository를 mock/stub한 단위 스펙으로 두고, 실DB 검증은 별도 통합/e2e 테스트로 분리하는 편이 현재 규칙에 맞습니다. As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/store/services/store-detail.service.spec.ts` around lines 22 -
37, This spec is coupled to the real database through
createTestingModuleWithRealDb, PrismaClient, and truncateAll; refactor
StoreDetailService.spec.ts into a pure unit test by mocking/stubbing
StoreRepository and StoreWishlistRepository in the testing module setup and
removing any real DB lifecycle calls from beforeAll/beforeEach/afterAll. Keep
StoreDetailService as the system under test, and move any database-backed
verification into a separate integration/e2e test file so this *.spec.ts no
longer depends on external state or environment.

Source: Path instructions

src/features/store/resolvers/store-detail-query.resolver.spec.ts (1)

25-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

리졸버 스펙도 실DB 대신 mock/stub으로 고정하는 편이 좋겠습니다.

Line 26-35의 real DB 부트스트랩 때문에 이 테스트가 리졸버 계약 검증보다 DB 통합 상태에 더 크게 의존합니다. 이 파일은 Service를 stub해서 리졸버의 인자 전달과 인증 분기만 검증하고, DB 경로는 별도 통합 테스트로 옮기는 구성이 규칙에 더 잘 맞습니다. As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/store/resolvers/store-detail-query.resolver.spec.ts` around
lines 25 - 45, The resolver spec is bootstrapping a real database via
createTestingModuleWithRealDb, which makes StoreDetailQueryResolver tests depend
on DB state instead of just resolver behavior. Replace the real DB setup with a
mocked/stubbed testing module by stubbing StoreDetailService and any repository
dependencies used by the resolver, and keep the test focused on argument
forwarding and auth branching in StoreDetailQueryResolver. Move any DB
integration coverage for StoreRepository or StoreWishlistRepository into
separate integration tests.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/product/dto/inputs/store-products.input.ts`:
- Around line 4-17: `StoreProductsInput`의 ID 관련 필드 검증이 빈 문자열("")을 허용하고 있으므로,
`storeId`, `categoryId`, `cursor`가 DTO 단계에서 non-empty만 통과하도록 보강하세요.
`@IsString()`만 쓰는 대신 `IsNotEmpty` 같은 검증을 `StoreProductsInput`에 추가해
`categoryId`/`cursor`가 빈 값으로 들어와 서비스에서 누락값처럼 처리되지 않게 하고, `storeId`도 동일하게 빈 문자열을
차단하도록 수정하세요.

In `@src/features/product/services/product-storefront-mappers.helper.ts`:
- Around line 15-18: The discountRate calculation in the product storefront
mapper can return values above 100 when salePrice is negative, so clamp the
result to the 0–100 range in the mapping logic. Update the discount computation
in product-storefront-mappers.helper.ts so the function that derives
discountRate from salePrice and regularPrice treats invalid or negative
salePrice values safely (for example by normalizing them before calculation or
clamping the final percentage), while keeping the existing zero fallback for
null, non-positive, or inverted prices.

In `@src/features/product/services/product-storefront.service.spec.ts`:
- Around line 33-48: This spec is relying on a real database via
createTestingModuleWithRealDb, truncateAll, and direct Prisma cleanup, but it
should be a unit test controlled with stubs/mocks. Replace the real-db setup in
product-storefront.service.spec.ts by mocking ProductRepository (and any
Prisma-backed behavior) when creating the module for ProductStorefrontService,
and remove the truncate/disconnect lifecycle tied to the actual DB. Keep real
database verification in separate integration/e2e coverage.
- Around line 337-354: 테스트명이 “비활성/삭제 매장” 분기를 모두 검증하는 것처럼 보이지만, 현재
`storeProductCategories` 테스트는 `is_active: false`만 확인하고 `deleted_at` 조건은 전혀 검증하지
않습니다. `storeProductCategories`가 의존하는 `store: { is_active: true, deleted_at: null
}` 분기를 정확히 커버하도록 이 케이스를 둘로 분리하고, 하나는 `is_active: false` 매장, 다른 하나는 `deleted_at`이
설정된 매장으로 만들어 각각 `service.storeProductCategories` 결과가 빈 배열인지 확인하세요.

In `@src/features/store/resolvers/store-review-query.resolver.spec.ts`:
- Around line 25-44: This resolver spec is still tied to the real database
through createTestingModuleWithRealDb and truncate/connection cleanup, so
refactor it to a mock-based unit test. Update StoreReviewQueryResolver spec
setup to use a normal testing module with mocked
StoreReviewService/StoreReviewRepository (and any guard/user dependencies if
present), remove truncateAll/closeTruncateConnection/disconnectTestPrismaClient
usage, and keep DB-backed coverage in a separate integration/e2e test suite.

In `@src/features/store/services/store-review.service.spec.ts`:
- Around line 20-35: The spec is tied to a real database through
createTestingModuleWithRealDb, PrismaClient, and truncateAll(), so refactor
StoreReviewService tests to use mocked/stubbed dependencies instead of real DB
access. Update the setup around createTestingModuleWithRealDb,
StoreReviewRepository, and service initialization so the service is tested in
isolation with deterministic repository behavior, and move any true persistence
coverage into a separate integration/e2e suite. Also make sure the revised
StoreReviewService.spec covers the main success path plus key error/branch cases
without relying on DB state.

In `@src/features/store/services/store-review.service.ts`:
- Around line 38-42: The `storeReviewService` like-status lookup is using a
truthy check on `accountId`, which incorrectly skips `findLikedReviewIds` when
the account ID is `0n`. Update the conditional in `StoreReviewService` to branch
explicitly on `accountId !== undefined` so `0n` is treated as a valid value, and
keep the fallback `new Set<string>()` only for the undefined case.

---

Nitpick comments:
In `@src/features/product/resolvers/product-storefront-query.resolver.spec.ts`:
- Around line 19-69: This spec is doing real DB integration work instead of
isolating the resolver; refactor ProductStorefrontQueryResolver tests to mock or
stub ProductStorefrontService and keep ProductRepository/Prisma out of this unit
spec. Update the resolver setup in beforeAll to use a testing module with a
mocked ProductStorefrontService, then verify storeProducts and
storeProductCategories only pass the correct arguments and return the mocked
result. Add at least one case for service error propagation so the resolver’s
thin delegation behavior is covered without relying on
createTestingModuleWithRealDb, createStore, or direct prisma writes.

In `@src/features/store/resolvers/store-detail-query.resolver.spec.ts`:
- Around line 25-45: The resolver spec is bootstrapping a real database via
createTestingModuleWithRealDb, which makes StoreDetailQueryResolver tests depend
on DB state instead of just resolver behavior. Replace the real DB setup with a
mocked/stubbed testing module by stubbing StoreDetailService and any repository
dependencies used by the resolver, and keep the test focused on argument
forwarding and auth branching in StoreDetailQueryResolver. Move any DB
integration coverage for StoreRepository or StoreWishlistRepository into
separate integration tests.

In `@src/features/store/resolvers/store-review-query.resolver.spec.ts`:
- Around line 56-84: Add a test case in store-review-query.resolver.spec.ts that
covers the invalid accountId path used by storeReviews, since it calls
parseAccountId directly. Verify that empty string, negative, or non-numeric
accountId inputs trigger BadRequestException, alongside the existing
login/unlogin happy-path tests. Use the resolver.storeReviews and parseAccountId
flow to locate the branch and keep the test deterministic with mocked/stubbed
inputs.

In `@src/features/store/services/store-detail.service.spec.ts`:
- Around line 22-37: This spec is coupled to the real database through
createTestingModuleWithRealDb, PrismaClient, and truncateAll; refactor
StoreDetailService.spec.ts into a pure unit test by mocking/stubbing
StoreRepository and StoreWishlistRepository in the testing module setup and
removing any real DB lifecycle calls from beforeAll/beforeEach/afterAll. Keep
StoreDetailService as the system under test, and move any database-backed
verification into a separate integration/e2e test file so this *.spec.ts no
longer depends on external state or environment.

In `@src/features/store/services/store-review.service.spec.ts`:
- Around line 173-181: 현재 `storeReviews` 스펙의 “비활성/삭제 매장” 테스트는 `is_active`만 검증하고
있어 `deleted_at` 분기를 놓치고 있습니다. `storeReviews`와 `createStore`/`makeReview`를 사용하는
같은 테스트 파일에 삭제된 매장만 대상으로 하는 케이스를 추가해, `deleted_at`이 설정된 매장에서도 `items`가 비고
`totalCount`가 0인지 확인하세요. 기존 비활성 매장 케이스와 분리해 `is_active`/`deleted_at` 각각의 필터 분기를
모두 커버하도록 유지하세요.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4790ee45-d522-42cf-84bd-b9553e25c1a2

📥 Commits

Reviewing files that changed from the base of the PR and between 7360cf6 and 93882a4.

📒 Files selected for processing (40)
  • prisma/migrations/20260623175315_add_store_image_and_detail_columns/migration.sql
  • prisma/schema.prisma
  • prisma/seed/idempotent.ts
  • prisma/seed/stores.ts
  • src/features/product/constants/product-storefront.constants.ts
  • src/features/product/dto/inputs/store-products.input.ts
  • src/features/product/product-storefront.graphql
  • src/features/product/product.module.ts
  • src/features/product/repositories/product.repository.ts
  • src/features/product/resolvers/product-storefront-query.resolver.spec.ts
  • src/features/product/resolvers/product-storefront-query.resolver.ts
  • src/features/product/services/product-storefront-mappers.helper.spec.ts
  • src/features/product/services/product-storefront-mappers.helper.ts
  • src/features/product/services/product-storefront.service.spec.ts
  • src/features/product/services/product-storefront.service.ts
  • src/features/product/types/product-storefront-output.type.ts
  • src/features/store/constants/store-detail-error-messages.ts
  • src/features/store/constants/store-review.constants.ts
  • src/features/store/dto/inputs/store-reviews.input.ts
  • src/features/store/repositories/store-review.repository.ts
  • src/features/store/repositories/store.repository.ts
  • src/features/store/resolvers/store-detail-query.resolver.spec.ts
  • src/features/store/resolvers/store-detail-query.resolver.ts
  • src/features/store/resolvers/store-review-query.resolver.spec.ts
  • src/features/store/resolvers/store-review-query.resolver.ts
  • src/features/store/services/store-detail-mappers.helper.spec.ts
  • src/features/store/services/store-detail-mappers.helper.ts
  • src/features/store/services/store-detail.service.spec.ts
  • src/features/store/services/store-detail.service.ts
  • src/features/store/services/store-mappers.helper.ts
  • src/features/store/services/store-review-mappers.helper.spec.ts
  • src/features/store/services/store-review-mappers.helper.ts
  • src/features/store/services/store-review.service.spec.ts
  • src/features/store/services/store-review.service.ts
  • src/features/store/store-detail.graphql
  • src/features/store/store-reviews.graphql
  • src/features/store/store.module.ts
  • src/features/store/types/store-detail-output.type.ts
  • src/features/store/types/store-review-output.type.ts
  • src/test/factories/store.factory.ts

Comment thread src/features/product/dto/inputs/store-products.input.ts
Comment thread src/features/product/services/product-storefront-mappers.helper.ts Outdated
Comment on lines +33 to +48
beforeAll(async () => {
const { module, prisma: p } = await createTestingModuleWithRealDb({
providers: [ProductStorefrontService, ProductRepository],
});
service = module.get(ProductStorefrontService);
prisma = p;
});

afterAll(async () => {
await closeTruncateConnection();
await disconnectTestPrismaClient();
});

beforeEach(async () => {
await truncateAll();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

*.spec.ts는 실DB 대신 stub/mock으로 통제하는 편이 좋겠습니다.

createTestingModuleWithRealDb, truncateAll, 직접 Prisma 조작까지 모두 실제 DB에 기대고 있어서 이 스펙이 느리고 환경 의존적으로 바뀝니다. 서비스 단위 테스트라면 ProductRepository를 stub/mock으로 대체하고, 실DB 검증은 별도 integration/e2e 테스트로 분리하는 편이 맞습니다.

As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/product/services/product-storefront.service.spec.ts` around
lines 33 - 48, This spec is relying on a real database via
createTestingModuleWithRealDb, truncateAll, and direct Prisma cleanup, but it
should be a unit test controlled with stubs/mocks. Replace the real-db setup in
product-storefront.service.spec.ts by mocking ProductRepository (and any
Prisma-backed behavior) when creating the module for ProductStorefrontService,
and remove the truncate/disconnect lifecycle tied to the actual DB. Keep real
database verification in separate integration/e2e coverage.

Source: Path instructions

Comment thread src/features/product/services/product-storefront.service.spec.ts Outdated
Comment on lines +25 to +44
beforeAll(async () => {
const { module, prisma: p } = await createTestingModuleWithRealDb({
providers: [
StoreReviewQueryResolver,
StoreReviewService,
StoreReviewRepository,
],
});
resolver = module.get(StoreReviewQueryResolver);
prisma = p;
});

afterAll(async () => {
await closeTruncateConnection();
await disconnectTestPrismaClient();
});

beforeEach(async () => {
await truncateAll();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

resolver spec도 실DB 의존으로 고정돼 있습니다.

이 파일도 createTestingModuleWithRealDb와 truncate 흐름에 묶여 있어서 src/**/*.spec.ts 규칙과 어긋납니다. resolver spec은 guard/user/service 상호작용만 검증하도록 mock 기반으로 두고, DB 관통 검증은 별도 integration/e2e로 분리하는 게 안전합니다.

As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지, 정상 흐름뿐 아니라 주요 예외/분기 케이스가 포함되는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/store/resolvers/store-review-query.resolver.spec.ts` around
lines 25 - 44, This resolver spec is still tied to the real database through
createTestingModuleWithRealDb and truncate/connection cleanup, so refactor it to
a mock-based unit test. Update StoreReviewQueryResolver spec setup to use a
normal testing module with mocked StoreReviewService/StoreReviewRepository (and
any guard/user dependencies if present), remove
truncateAll/closeTruncateConnection/disconnectTestPrismaClient usage, and keep
DB-backed coverage in a separate integration/e2e test suite.

Source: Path instructions

Comment on lines +20 to +35
beforeAll(async () => {
const { module, prisma: p } = await createTestingModuleWithRealDb({
providers: [StoreReviewService, StoreReviewRepository],
});
service = module.get(StoreReviewService);
prisma = p;
});

afterAll(async () => {
await closeTruncateConnection();
await disconnectTestPrismaClient();
});

beforeEach(async () => {
await truncateAll();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

이 spec은 DB 의존성을 실DB에 묶고 있습니다.

createTestingModuleWithRealDb, PrismaClient, truncateAll()에 의존하면 테스트 안정성과 실행 속도가 DB 상태에 좌우됩니다. 이 경로는 repository mock/stub 기반의 서비스 단위 테스트로 두고, 실DB 검증이 꼭 필요하면 별도 integration/e2e 스위트로 분리하는 편이 맞습니다.

As per path instructions, src/**/*.spec.ts: 테스트는 시간/uuid/네트워크/DB 의존성을 mock 또는 stub으로 통제하는지, 정상 흐름뿐 아니라 주요 예외/분기 케이스가 포함되는지 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/store/services/store-review.service.spec.ts` around lines 20 -
35, The spec is tied to a real database through createTestingModuleWithRealDb,
PrismaClient, and truncateAll(), so refactor StoreReviewService tests to use
mocked/stubbed dependencies instead of real DB access. Update the setup around
createTestingModuleWithRealDb, StoreReviewRepository, and service initialization
so the service is tested in isolation with deterministic repository behavior,
and move any true persistence coverage into a separate integration/e2e suite.
Also make sure the revised StoreReviewService.spec covers the main success path
plus key error/branch cases without relying on DB state.

Source: Path instructions

Comment thread src/features/store/services/store-review.service.ts
…amp)

- StoreImage를 SOFT_DELETE_MODELS에 등록해 직접 조회 시 deleted_at 자동 필터 적용 (Codex P2)
- storeProducts/storeReviews input의 ID(storeId·categoryId·cursor)에 @isnotempty 추가 (빈 문자열 차단)
- discountRate를 0~100으로 clamp (음수 salePrice 방어)
- accountId 0n falsy 분기를 !== undefined로 (storeReviews·storeDetail — account id 0의 좋아요/찜 누락 방지)
- product-storefront service spec: 비활성/삭제 매장 케이스 분리 + soft-delete 검증 추가

CodeRabbit Functional + Codex P2 반영. CodeRabbit Major(spec을 mock으로 전환)는 testcontainers realDB 통합 컨벤션 유지로 미반영.
fix: 릴리즈 리뷰 반영 (StoreImage soft-delete·ID 빈값/0n 방어·discountRate clamp)
@github-actions

Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.49% 4041/4145
🟢 Branches 93.3% 1268/1359
🟢 Functions 94.9% 763/804
🟢 Lines 97.9% 3685/3764

Test suite run success

1403 tests passing in 168 suites.

Report generated by 🧪jest coverage report action from 64ea223

@chanwoo7

Copy link
Copy Markdown
Member Author

CodeRabbit Major 3건(*.service.spec.ts / *.resolver.spec.ts를 realDB → mock 전환) 미반영합니다.

이 레포는 testcontainers 기반 realDB 통합 테스트가 모든 기존 spec에 확립된 컨벤션입니다(store-listing.service.spec.ts 등 전부 createTestingModuleWithRealDb 사용). 서비스/리졸버 단위 테스트도 동일 패턴을 유지하며, 일반론적 mock 분리는 프로젝트 표준과 어긋나 적용하지 않습니다.

Functional 지적(빈 문자열 ID 차단 · discountRate clamp · accountId 0n 분기)과 Codex P2(StoreImage soft-delete 등록)는 #162로 반영해 develop에 머지했습니다.

@chanwoo7 chanwoo7 merged commit c73071e into main Jun 24, 2026
15 checks passed
@chanwoo7 chanwoo7 deleted the develop branch June 24, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant