Skip to content

feat: 품목 삭제 기능 구현#145

Merged
JoonKyoLee merged 10 commits intomainfrom
feat/delete-product
Nov 30, 2025
Merged

feat: 품목 삭제 기능 구현#145
JoonKyoLee merged 10 commits intomainfrom
feat/delete-product

Conversation

@JoonKyoLee
Copy link
Contributor

@JoonKyoLee JoonKyoLee commented Nov 30, 2025

✨ 작업 내용

  • 품목 삭제 기능 구현

📝 적용 범위

  • /product

📌 참고 사항

Summary by CodeRabbit

릴리즈 노트

  • New Features

    • 제품 삭제 기능이 추가되었습니다. 사용자는 이제 제품을 삭제할 수 있습니다.
    • 삭제된 제품은 논리적으로 제거되어 데이터 무결성이 유지됩니다.
  • Tests

    • 제품 삭제 기능에 대한 테스트 커버리지가 추가되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@JoonKyoLee JoonKyoLee self-assigned this Nov 30, 2025
@JoonKyoLee JoonKyoLee added the enhancement New feature or request label Nov 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 30, 2025

Walkthrough

제품 soft delete 기능을 구현하는 변경입니다. Product 도메인에 soft delete 처리를 추가하고, ProductController에 DELETE 엔드포인트를 신설한 후, ProductService에 삭제 로직을 포함하며, 여러 테스트 클래스에 DiscordErrorNotifier 의존성을 추가합니다.

Changes

코호트 / 파일(들) 변경 요약
제품 삭제 기능 구현
src/main/java/com/almang/inventory/product/controller/ProductController.java, src/main/java/com/almang/inventory/product/service/ProductService.java, src/main/java/com/almang/inventory/product/domain/Product.java
DELETE /api/v1/product/{productId} 엔드포인트 추가. Product 엔도티에 soft delete 처리(@SQLDelete, @Where, deletedAt 필드, delete() 메서드) 구현. ProductService에 deleteProduct 메서드 추가하여 권한 검증 후 제품 삭제 수행.
응답 타입 정의
src/main/java/com/almang/inventory/product/dto/response/DeleteProductResponse.java
DeleteProductResponse 레코드 생성 (success 필드).
성공 메시지 추가
src/main/java/com/almang/inventory/global/api/SuccessMessage.java
DELETE_PRODUCT_SUCCESS 열거형 상수 추가.
제품 삭제 테스트
src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java, src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
성공, 미존재, 권한 거부, 사용자 미존재 시나리오에 대한 테스트 메서드 추가. DiscordErrorNotifier mock 추가.
테스트 클래스 DiscordErrorNotifier 주입
src/test/java/com/almang/inventory/admin/controller/AdminControllerTest.java, src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java, src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java, src/test/java/com/almang/inventory/order/template/controller/OrderTemplateControllerTest.java, src/test/java/com/almang/inventory/receipt/controller/ReceiptControllerTest.java, src/test/java/com/almang/inventory/store/admin/controller/StoreAdminControllerTest.java, src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java, src/test/java/com/almang/inventory/user/auth/controller/AuthControllerTest.java, src/test/java/com/almang/inventory/user/controller/UserControllerTest.java, src/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.java
여러 테스트 클래스에 @MockitoBean DiscordErrorNotifier 필드 추가.

🎯 코드 리뷰 평가

🎯 3 (Moderate) | ⏱️ ~30분

추가 검토 권장 사항

  • Product.delete() 메서드: Soft delete 시맨틱 검증 필요. deletedAt = LocalDateTime.now() 설정이 정말 항상 의도한 동작인지 확인 (예: 타임존, UTC 처리)
  • ProductService.deleteProduct() 접근 제어: userId와 productId의 store 일치성 검증 로직이 정확한지 확인. 교차 테넌트 삭제 방지가 제대로 되었는지 점검
  • findProductById에 삭제 체크 추가: deletedAt이 null이 아니면 PRODUCT_NOT_FOUND 던지는 로직이 기존 쿼리 필터(@Where)와 중복되지 않는지 확인. 단일 책임 원칙 관점에서 일관성 검증
  • 테스트 클래스 DiscordErrorNotifier 추가: 10개 테스트 클래스에 동일한 mock 주입이 반복됨. 베이스 테스트 클래스나 공통 설정으로 추출하여 DRY 원칙 적용 고려
  • DeleteProductResponse: 단순히 success boolean만 반환. 향후 삭제 타임스탐프나 추적 정보 필요 시를 대비하여 확장성 검토

Possibly related PRs

Poem

🗑️ 품목 삭제, 부드럽게 사라지고
데이터는 영원히 남아있고
권한 검사는 튼튼하고
테스트는 촘촘하네.
Soft delete의 우아한 춤! 🩰

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive 삭제 기능과 무관한 변경사항들(DiscordErrorNotifier 주입 등)이 여러 테스트 파일에서 감지되었습니다. 이러한 변경들이 PR의 주요 목표와 연관이 있는지 확인 필요. DiscordErrorNotifier 주입이 필수 변경사항인지, 아니면 별도의 리팩토링 작업인지 명확히 설명해주시기 바랍니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 '품목 삭제 기능'을 명확하고 간결하게 표현하며, 변경 내용과 완벽하게 일치합니다.
Description check ✅ Passed PR 설명이 저장소의 템플릿을 따르고 있으며 작업 내용, 적용 범위, 참고 사항을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed Issue #143의 모든 구현 목표가 충족되었습니다: 품목 삭제 기능 구현 완료, 삭제된 품목을 조회 결과에서 제외하도록 수정 완료.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/delete-product

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (10)
src/main/java/com/almang/inventory/product/domain/Product.java (3)

19-20: Soft delete 전략 일관성 정리가 필요해 보입니다 (@SQLDelete vs delete 메서드).

현재 @SQLDelete(sql = "UPDATE products SET deleted_at = NOW() WHERE product_id = ?")@Where(clause = "deleted_at IS NULL")로 Hibernate 레벨 soft delete를 쓰면서, 동시에 delete() 메서드에서 this.deletedAt = LocalDateTime.now();로 애플리케이션 레벨에서도 값을 세팅하고 있습니다.
이렇게 두 가지 경로가 섞이면 “실제 삭제 시점·주체가 어디인지”를 읽는 사람이 헷갈리기 쉽고, DB 서버 시간(NOW())과 애플리케이션 서버 시간(LocalDateTime.now())이 어긋날 여지도 있습니다.

권장 방향 예시는 아래 둘 중 하나로 정리하는 것입니다.

  1. Hibernate 기반 전략으로 통일

    • @SQLDelete + @Where만 사용하고, 서비스에서는 productRepository.delete(product)만 호출.
    • 이 경우 delete() 메서드는 제거하거나, 단순히 repository.delete(this)를 감싸는 헬퍼 정도로 사용.
    • DB 시간이 진실(source of truth)이므로 deleted_at 컬럼을 DB가 관리하게 하고 싶다면, @Column(insertable = false, updatable = false)도 고려할 수 있습니다.
  2. 엔티티 메서드 기반 soft delete로 통일

    • @SQLDelete를 제거하고, 서비스에서 product.delete();save를 호출하는 방식으로 일관.
    • 이 경우 deletedAt은 항상 애플리케이션에서 관리되며, DB 함수 의존을 줄일 수 있습니다.

또한, @Where("deleted_at IS NULL")는 Hibernate가 생성하는 JPQL에는 자동으로 붙지만, nativeQuery = true인 쿼리에는 적용되지 않습니다. 나중에 Product 관련 native 쿼리를 추가할 경우 deleted_at IS NULL 조건을 수동으로 붙여주는 것을 팀 규칙으로 정해두면 좋겠습니다.

Hibernate soft delete 관련 개념은 공식 문서의 @SQLDelete, @Where 섹션을 함께 참고해 두시면 팀원 교육에도 도움이 됩니다.


67-68: deletedAt 필드 매핑 자체는 적절합니다.

@Column(name = "deleted_at") + LocalDateTime deletedAt 조합은 일반적인 soft delete 컬럼 매핑으로 문제 없어 보입니다.
다만 위에서 언급한 것처럼 soft delete를 DB에서만 관리할지, 엔티티에서 세팅할지 전략을 정한 뒤에 insertable/updatable 여부나 인덱스 추가(deleted_at + store_id 등)를 함께 정리하면, 대량 데이터 환경에서도 조회 성능과 유지보수성이 좋아집니다.


118-120: delete() 메서드의 역할을 명확히 정의해 두는 것이 좋겠습니다.

현재 delete()는 단순히 deletedAtLocalDateTime.now()를 세팅만 하고 있어, 호출자가 이 메서드 후에 save를 할지, delete를 할지 코드만 보고는 알기 어렵습니다. 위의 soft delete 전략을 정리한 뒤에:

  • 만약 repository.save(product) 기반 soft delete를 표준으로 삼는다면, 서비스 레이어에서 반드시 delete()를 호출하는 규칙을 문서화하고,
  • 반대로 repository.delete(product)만 쓰는 구조라면, delete()는 제거하거나 테스트용/도메인 이벤트용 등 분명한 용도를 주는 편이 좋습니다.

이렇게 해두면 삭제 플로우를 추적할 때 “삭제 진입점이 어디인지”가 훨씬 명확해집니다.

src/test/java/com/almang/inventory/admin/controller/AdminControllerTest.java (1)

14-14: DiscordErrorNotifier 목킹 추가 방향 적절

DiscordErrorNotifier@MockitoBean으로 주입해서 컨트롤러 테스트에서 실제 Bean 주입 오류를 막는 패턴은 다른 컨트롤러 테스트들과도 일관되고, 현재 테스트 시나리오에 부작용도 없어 보입니다.
추후 Discord 알림 동작 자체를 검증해야 할 때는 이 목 객체에 대한 상호작용 검증을 추가하는 정도만 고민하시면 될 것 같습니다.

Also applies to: 37-37

src/main/java/com/almang/inventory/product/service/ProductService.java (2)

72-84: deleteProduct 트랜잭션 흐름은 깔끔하지만, soft delete 정책을 주석/테스트로 명시해 두면 더 좋겠습니다

  • UserStoreContext 조회 → findProductByIdvalidateStoreAccessproduct.delete() 순서가 명확해서, 삭제 권한 체크와 soft delete 적용 흐름이 잘 드러납니다.
  • @Transactional 컨텍스트에서 엔티티 메서드 product.delete()만 호출하고 save를 호출하지 않는 것은 JPA dirty checking 전제를 따르는 구조라서 문제 없습니다. 다만 delete()가 단순히 deletedAt만 세팅한다는 사실을 유지보수자가 바로 이해하기 어렵기 때문에,
    • Product.delete() 내부에 짧은 Javadoc/주석으로 “soft delete 전용. 영속성 컨텍스트 내에서만 사용” 정도를 남기거나
    • 이 메서드를 검증하는 단위 테스트를 Product 도메인 레벨에 추가해 두면 정책 이해에 도움이 됩니다.

현재 설계상 삭제 실패 케이스는 모두 예외로 처리하고 성공 시에만 success=true를 반환하므로, API 관점에서도 일관성 있습니다.


129-129: deletedAt(null) 명시와 findProductById의 soft delete 가드는 정책상 문제 없지만, 책임 위치를 정리해 둘 여지가 있습니다

  • toEntity에서 .deletedAt(null)을 명시하는 부분은 새로 생성되는 품목에 대해 “항상 삭제되지 않은 상태로 시작한다”는 의도를 드러내는 장점이 있지만, 필드 기본값이 null이라면 기능상으로는 중복입니다. 팀에서 “빌더에 모든 초기 상태를 명시한다”는 합의가 없다면 제거해도 무방합니다.
  • findProductById에서 product.getDeletedAt() != null이면 PRODUCT_NOT_FOUND를 던지는 것은 soft delete 정책을 서비스 계층에서 한 번 더 방어하는 코드입니다. Product 엔티티에 이미 @Where(deleted_at IS NULL)가 붙어 있다면 이 체크는 실질적으로는 도달하지 않겠지만,
    • “삭제된 레코드는 모든 조회에서 항상 숨긴다”는 정책을 명확히 표현한다는 장점이 있고,
    • 반대로 “삭제 필터링 책임을 엔티티/리포지토리에서만 지게 한다”는 팀 규칙이라면 이 부분은 정리 대상이 될 수 있습니다.

개인적으로는 메서드명을 findActiveProductById처럼 바꾸어 “삭제되지 않은 품목 전용 조회”라는 의미를 드러내면 의도가 더 명확해질 것 같습니다. (기존 호출부가 모두 활성 품목 조회 용도이므로 시그니처 영향은 크지 않습니다.)

Also applies to: 144-150

src/test/java/com/almang/inventory/receipt/controller/ReceiptControllerTest.java (1)

18-18: Receipt 컨트롤러 테스트에서도 DiscordErrorNotifier 목킹 패턴이 잘 적용되었습니다

전역 예외 처리나 AOP에서 DiscordErrorNotifier를 주입받는 구조라면, 각 WebMvcTest마다 @MockitoBean으로 등록해 두는 지금 방식이 가장 단순하고 안정적인 해결책입니다.
다른 컨트롤러 테스트들과 일관된 구성이어서, 추후 공통 설정(예: 테스트용 설정 클래스)으로 묶고 싶을 때도 마이그레이션이 수월해 보입니다.

Also applies to: 54-54

src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java (1)

32-32: 테스트에서 LocalDateTime.now() 사용은 현재 맥락에서는 안전하지만, 향후에는 고정 Clock 고려도 좋습니다

여러 OrderResponse 스텁에서 4번째 인자를 LocalDateTime.now()로 채우신 부분은, 해당 필드를 별도로 검증하지 않기 때문에 테스트를 불안정하게 만들지는 않습니다. 다만:

  • 시간이 요구사항에 직접적인 의미를 갖는 필드를 나중에 검증해야 할 경우,
  • 또는 시간 기반 정렬/필터 로직을 테스트해야 할 경우에는

Clock을 주입받아서 테스트에서는 Clock.fixed(...)로 고정된 시간을 사용하는 패턴이 유지보수에 유리합니다 (Spring @TestConfiguration 등으로 쉽게 교체 가능).

현 상태에서는 과하지 않은 선택이라 그대로 유지하셔도 무방하되, “시간 의존 로직이 생기면 Clock 주입으로 전환” 정도만 팀 합의로 공유해 두시면 좋겠습니다.

Also applies to: 84-84, 269-269, 337-337, 353-353, 457-457

src/test/java/com/almang/inventory/product/service/ProductServiceTest.java (1)

14-14: 삭제 시나리오 테스트가 soft delete 정책을 잘 커버하고 있습니다

새로 추가된 네 가지 테스트가 각각 다른 관점에서 soft delete 동작을 검증하고 있어서 서비스 변경에 대한 안전망이 잘 갖춰졌습니다.

  • 품목_삭제에_성공한다
    • DeleteProductResponse.success()를 검증하고,
    • 같은 ID로 상세 조회 시 PRODUCT_NOT_FOUND,
    • 목록 조회에서도 totalElements == 0을 확인해서 “삭제된 품목은 모든 조회에서 숨긴다”는 정책을 명확히 보장하고 있습니다.
  • 존재하지_않는_품목_삭제시_예외가_발생한다
    • 존재하지 않는 ID에 대해 PRODUCT_NOT_FOUND 예외를 기대해, 서비스가 조용히 무시하거나 idempotent 삭제로 처리하지 않음을 명시합니다.
  • 다른_상점의_품목을_삭제하려고_하면_예외가_발생한다
    • 교차 상점 삭제 시 STORE_ACCESS_DENIED를 검증해 접근 제어 정책을 잘 커버합니다. 이는 기존 업데이트/조회 관련 테스트 패턴과도 일관됩니다. (Based on learnings, 서비스 레벨에서 접근 검증 후 도메인 메서드를 호출하는 구조를 유지하고 있습니다.)
  • 삭제된_품목은_목록_및_상세에서_조회되지_않는다
    • 앞선 성공/예외 케이스와 약간 중복되긴 하지만, “삭제 → 상세/목록 두 경로 모두에서 숨겨짐”을 한 테스트 안에 통합적으로 검증해 시나리오 가독성이 좋습니다.

추가로, 만약 나중에 “삭제된 품목을 특정 관리자 화면에서만 조회” 같은 요구가 생긴다면, 지금 테스트들을 기준으로 별도 메서드(예: getDeletedProductList)를 설계할 수 있어 방향성이 분명한 상태입니다. 지금 구조 자체는 아주 탄탄합니다.

Also applies to: 1092-1226

src/main/java/com/almang/inventory/product/controller/ProductController.java (1)

9-9: DELETE 엔드포인트 설계가 기존 컨트롤러 패턴과 잘 정렬되어 있습니다

  • @DeleteMapping("/{productId}") + ResponseEntity<ApiResponse<DeleteProductResponse>> 조합이 CREATE/UPDATE/GET과 동일한 ApiResponse 래핑 패턴을 그대로 따르고 있어, 클라이언트 입장에서 일관된 응답 포맷을 기대할 수 있습니다.
  • CustomUserPrincipal에서 userId를 꺼내 로그에 남긴 뒤 productService.deleteProduct(productId, userId)를 호출하는 흐름도 다른 메서드들과 동일한 컨벤션입니다.
  • SuccessMessage.DELETE_PRODUCT_SUCCESS를 사용해 메시지 상수 관리도 잘 되어 있어, i18n/문구 변경 시 한 곳에서 관리하기 좋습니다.

추가로, Swagger 문서에 삭제 후 응답 예시(DeleteProductResponse)를 @ApiResponse로 명시해 두면 프론트/외부 연동팀이 스펙을 이해하기 더 수월할 것 같습니다.

Also applies to: 19-19, 84-97

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 596f07d and 34d361d.

📒 Files selected for processing (17)
  • src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1 hunks)
  • src/main/java/com/almang/inventory/product/controller/ProductController.java (3 hunks)
  • src/main/java/com/almang/inventory/product/domain/Product.java (3 hunks)
  • src/main/java/com/almang/inventory/product/dto/response/DeleteProductResponse.java (1 hunks)
  • src/main/java/com/almang/inventory/product/service/ProductService.java (4 hunks)
  • src/test/java/com/almang/inventory/admin/controller/AdminControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java (8 hunks)
  • src/test/java/com/almang/inventory/order/template/controller/OrderTemplateControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java (4 hunks)
  • src/test/java/com/almang/inventory/product/service/ProductServiceTest.java (2 hunks)
  • src/test/java/com/almang/inventory/receipt/controller/ReceiptControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/store/admin/controller/StoreAdminControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/user/auth/controller/AuthControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (2 hunks)
  • src/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.java (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-20T10:43:47.502Z
Learnt from: JoonKyoLee
Repo: almang2/inventory-server PR: 33
File: src/main/java/com/almang/inventory/product/domain/Product.java:62-66
Timestamp: 2025-11-20T10:43:47.502Z
Learning: In the almang2/inventory-server repository, Product entity update methods (e.g., updateVendor in src/main/java/com/almang/inventory/product/domain/Product.java) do not require null checks on vendor parameters because ProductService validates vendor existence via findVendorByIdAndValidateAccess before calling update methods, and the vendor field has nullable=false constraint ensuring this.vendor is never null for persisted entities.

Applied to files:

  • src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java
  • src/main/java/com/almang/inventory/product/service/ProductService.java
  • src/main/java/com/almang/inventory/product/controller/ProductController.java
  • src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
  • src/main/java/com/almang/inventory/product/domain/Product.java
📚 Learning: 2025-11-22T18:12:13.172Z
Learnt from: JoonKyoLee
Repo: almang2/inventory-server PR: 66
File: src/main/java/com/almang/inventory/order/domain/OrderItem.java:41-53
Timestamp: 2025-11-22T18:12:13.172Z
Learning: In the almang2/inventory-server repository, OrderItem entity update methods (updateQuantity, updatePrice in src/main/java/com/almang/inventory/order/domain/OrderItem.java) do not require null checks because OrderService will validate parameters before calling these update methods, following the same pattern as Product entity updates.
<!--

Applied to files:

  • src/main/java/com/almang/inventory/product/service/ProductService.java
  • src/test/java/com/almang/inventory/product/service/ProductServiceTest.java
  • src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java
  • src/main/java/com/almang/inventory/product/domain/Product.java
🧬 Code graph analysis (1)
src/main/java/com/almang/inventory/product/domain/Product.java (1)
src/main/java/com/almang/inventory/vendor/domain/Vendor.java (1)
  • Entity (8-60)
🔇 Additional comments (13)
src/test/java/com/almang/inventory/store/admin/controller/StoreAdminControllerTest.java (1)

11-12: DiscordErrorNotifier를 WebMvcTest 컨텍스트에 주입한 선택이 적절합니다.

@WebMvcTest(StoreAdminController.class)에서 실제 DiscordErrorNotifier 빈 의존성을 만족시키기 위해 @MockitoBean private DiscordErrorNotifier discordErrorNotifier;를 추가한 흐름이 자연스럽습니다.
현재 테스트 로직에서 직접 스텁이 필요하지 않기 때문에 별도 when(...) 없이 mock만 주입된 상태도 괜찮습니다. 동일 패턴을 다른 컨트롤러 테스트에도 일관되게 적용하신 점도 좋네요. 😄

Also applies to: 35-36

src/test/java/com/almang/inventory/store/controller/StoreControllerTest.java (1)

15-16: StoreControllerTest에서도 모니터링 의존성 주입 패턴이 잘 맞습니다.

DiscordErrorNotifier@MockitoBean으로 주입해서 @WebMvcTest(StoreController.class) 슬라이스가 필요한 빈을 모두 갖추도록 한 구성은 적절합니다.
추후 알림 전송 여부를 검증하고 싶다면 이 mock에 대해 verify(discordErrorNotifier)...를 추가하는 식으로 확장도 가능하니, 지금 구조를 유지하되 필요 시 테스트 범위만 넓히면 될 것 같습니다.

Also applies to: 46-47

src/test/java/com/almang/inventory/order/template/controller/OrderTemplateControllerTest.java (1)

15-16: OrderTemplateControllerTest의 DiscordErrorNotifier mock 추가도 일관적입니다.

다른 컨트롤러 테스트와 동일하게 DiscordErrorNotifier@MockitoBean으로 등록해 WebMvcTest 컨텍스트를 맞춰준 점이 좋습니다.
현재 테스트 시나리오가 에러 알림 자체를 검증하지는 않으므로, 별도의 stubbing 없이 단순 의존성 해소용 mock으로 두는 지금 구성이 충분해 보입니다.

Also applies to: 43-44

src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (1)

15-16: UserControllerTest에서도 모니터링 의존성 Mock 주입이 잘 반영되었습니다.

UserController에 새로 추가된 DiscordErrorNotifier 의존성을 테스트에서 @MockitoBean으로 해결한 방식이 깔끔합니다.
테스트 코드에서 알림 전송까지 검증할 계획이 없다면, 지금처럼 “컨텍스트 부트스트랩용 mock” 수준으로 두는 것이 과하지도 부족하지도 않은 적절한 선택입니다.

Also applies to: 49-50

src/test/java/com/almang/inventory/user/auth/controller/AuthControllerTest.java (1)

14-15: AuthControllerTest의 DiscordErrorNotifier mock 추가도 패턴에 잘 맞습니다.

인증/인가 관련 컨트롤러에서도 DiscordErrorNotifier가 새 의존성으로 들어왔을 때, @WebMvcTest 환경을 깨지 않도록 @MockitoBean으로 mock을 등록한 점이 좋습니다.
이 mock은 현재 테스트 시나리오에 부수 효과를 주지 않으므로, 불필요한 설정 없이도 컨텍스트 안정성을 확보하는 실용적인 방식입니다.

Also applies to: 52-53

src/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.java (1)

18-19: VendorControllerTest에서도 DiscordErrorNotifier mock 주입으로 일관성을 유지했습니다.

VendorController 테스트에 @MockitoBean private DiscordErrorNotifier discordErrorNotifier;를 추가해, 모니터링 의존성을 가진 컨트롤러 테스트가 모두 동일한 패턴을 따르도록 한 점이 좋습니다.
추후 에러 상황에서 디스코드 알림 트리거를 검증해야 할 경우 이 mock을 활용해 verify만 추가하면 되므로, 현재 설계는 확장 여지가 충분합니다.

Also applies to: 50-51

src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1)

30-31: PRODUCT 섹션에 DELETE_PRODUCT_SUCCESS 추가가 잘 정리되어 있습니다.

DELETE_PRODUCT_SUCCESS("품목 삭제 성공") 상수를 PRODUCT 블록 내부에 추가한 구조가 기존 네이밍/정렬 규칙과 잘 맞습니다.
컨트롤러에서 응답 메시지를 하드코딩하지 않고 이 enum을 재사용하면, 이후 문구 변경이나 다국어 지원 시에도 한 곳에서 관리하기 수월하니 지금 방향을 그대로 유지하시면 좋겠습니다.

src/main/java/com/almang/inventory/product/dto/response/DeleteProductResponse.java (1)

1-5: 삭제 응답 DTO 설계가 기존 패턴과 잘 맞습니다

record DeleteProductResponse(boolean success) 형태는 DeleteOrderResponse, DeleteReceiptResponse와 동일한 미니멀 패턴으로, 삭제 성공 여부만 필요할 때 과하지 않은 설계입니다.
추후 삭제 시 부가 정보를 내려야 할 때(예: 삭제 일시, soft delete 여부 플래그 등) 필드를 확장하기도 쉬운 구조입니다.

src/test/java/com/almang/inventory/order/controller/OrderControllerTest.java (1)

18-18: Order 컨트롤러 테스트의 DiscordErrorNotifier 목킹도 구성 일관성이 좋습니다

OrderControllerTest에서도 DiscordErrorNotifier@MockitoBean으로 등록해, 실제 Bean 의존성으로 인한 테스트 실패를 방지하는 패턴이 잘 적용되어 있습니다.
여타 컨트롤러 테스트들과 동일한 형태이므로, 나중에 Discord 알림 로직을 공통 필터/Advice로 옮기더라도 테스트 구조는 그대로 재사용 가능해 보입니다.

Also applies to: 56-56

src/test/java/com/almang/inventory/inventory/controller/InventoryControllerTest.java (1)

18-18: Inventory 컨트롤러 테스트에서도 에러 노티파이어 목킹이 잘 정비되었습니다

InventoryControllerTestDiscordErrorNotifier@MockitoBean으로 추가해, 실제 모니터링 인프라와 분리된 순수 컨트롤러 단위 테스트로 유지하신 점이 좋습니다.
전역 예외 핸들러/필터에서 이 Bean을 사용하더라도, 현재처럼 목 객체만 주입해 두면 테스트가 인프라 변경에 영향을 덜 받게 됩니다.

Also applies to: 52-52

src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java (3)

7-7: 새로운 import 구문들이 적절하게 추가되었습니다! 👍

삭제 기능 테스트에 필요한 의존성들이 정확하게 import되었습니다.

Also applies to: 18-18, 23-23


563-634: 품목 삭제 테스트가 체계적으로 작성되었습니다! 🎯

네 가지 시나리오를 모두 커버하고 있으며, 기존 테스트 패턴을 일관되게 따르고 있습니다:

  • ✅ 정상 삭제 케이스
  • ✅ 품목 미존재 케이스 (404)
  • ✅ 접근 권한 검증 케이스 (403)
  • ✅ 사용자 미존재 케이스 (404)

테스트 구조도 명확한 given-when-then 패턴을 따르고 있어 가독성이 우수합니다.

참고: Soft delete가 구현된 경우, "이미 삭제된 품목을 다시 삭제"하는 케이스도 고려해볼 수 있습니다. 다만 이는 서비스 레이어에서 처리될 수 있으므로, 필요 시 통합 테스트나 서비스 테스트에서 검증하는 것을 권장합니다.


50-50: DiscordErrorNotifier mock bean is necessary and correctly added.

This dependency is required because GlobalExceptionHandler (a @RestControllerAdvice component) declares DiscordErrorNotifier as a dependency. Since @WebMvcTest loads the entire web layer including global exception handlers, the application context cannot be created without this mock bean.

@JoonKyoLee JoonKyoLee merged commit d138579 into main Nov 30, 2025
1 check passed
@JoonKyoLee JoonKyoLee deleted the feat/delete-product branch November 30, 2025 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 품목 삭제 기능 구현

1 participant