Skip to content

[FEATURE] 찜 조회 및 등록 v2 API 구현#455

Open
gdbs1107 wants to merge 5 commits intodevelopfrom
feat/#453/jjym
Open

[FEATURE] 찜 조회 및 등록 v2 API 구현#455
gdbs1107 wants to merge 5 commits intodevelopfrom
feat/#453/jjym

Conversation

@gdbs1107
Copy link
Collaborator

@gdbs1107 gdbs1107 commented Mar 24, 2026

📣 Related Issue

📝 Summary

찜 조회 및 등록 v2 API 구현

🙏 Question & PR point

대부분의 로직은 동일합니다. 참조 대상이 RecommnedFurniture에서 Curation_raw_furniture로 변경되었습니다.
또한 낙관적 락 로직에 대해서 재검토를 진행해볼까 합니다.

public boolean toggleRawProduct(User user, Long rawProductId) {
        int retryCount = 0;
        while (retryCount < MAX_RETRIES) {
            try {
                return jjymService.rawProductJjymToggle(user.getId(), rawProductId);
            } catch (OptimisticLockException | DataIntegrityViolationException e) {
                long backoffTime = (long) Math.pow(2, retryCount) * RETRY_DELAY_MS;
                try {
                    Thread.sleep(backoffTime);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new DataIntegrityViolationException("찜 토글 처리 중 인터럽트가 발생했습니다", ie);
                }
                retryCount++;
            }
        }

        throw new DataIntegrityViolationException("찜 시도가 정해진 횟수를 초과하였습니다");
    }

이게 현재의 낙관적 락 로직 및 fallback 로직인데, 비관적 락과 기능을 좀 더 비교해보는 시간을 가져봐야 할 것 같습니다.
또한 찜한 가구가 많을때의 조회 로직 최적화도 고민을 해보고 성능 최적화를 해보겠습니다.

📬 Postman

찜 가구 토글 API

image

찜 가구 조회 API

image

Summary by CodeRabbit

  • 새로운 기능
    • 큐레이션(원본) 상품 대상 찜 토글(v2) 추가 및 사용자의 원본 상품 찜 목록 조회 기능 추가(이미지, 사이트, 색상, 브랜드, 가격, 찜수 포함).
  • 리팩터
    • API 버전 경로를 각 엔드포인트에 명시해 v1/v2 엔드포인트 구분 명확화.
  • 테스트
    • 새 v2 엔드포인트 및 서비스 흐름을 검증하는 단위/웹 계층 테스트 추가.

@gdbs1107 gdbs1107 requested review from PBEM22 and kbt82883 March 24, 2026 04:12
@gdbs1107 gdbs1107 self-assigned this Mar 24, 2026
@gdbs1107 gdbs1107 added ✨ feat 기능구현 ✊ 재연 재연 labels Mar 24, 2026
@gdbs1107 gdbs1107 linked an issue Mar 24, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

컨트롤러의 클래스 레벨 API 버전핑 제거 및 엔드포인트별 버전 명시, raw product용 v2 찜(toggle/조회) 기능 추가(서비스·레포·페이사드·DTO·테스트 포함).

Changes

Cohort / File(s) Summary
Controller: 경로 버전 명시 및 V2 추가
src/main/java/or/sopt/houme/domain/furniture/presentation/controller/JjymController.java
클래스 레벨 @RequestMapping 제거. 기존 v1 엔드포인트에 /api/v1를 명시하고, raw-product용 v2 엔드포인트(POST /api/v2/curation-raw-products/{rawProductId}/jjym, GET /api/v2/jjyms) 추가.
V2 DTOs
src/main/java/or/sopt/houme/domain/furniture/presentation/dto/response/JjymV2ItemResponse.java, .../JjymV2ListResponse.java
raw product 찜 조회용 DTO 추가: 항목별 메타(이미지/경로/색상/브랜드/가격/찜수 등) 및 리스트 래퍼.
서비스 인터페이스 & 구현
src/main/java/or/sopt/houme/domain/furniture/service/JjymService.java, .../JjymServiceImpl.java
인터페이스에 rawProductJjymToggle, getMyRawProductJjyms 추가. 구현체에 RecommendFurniture 자동 생성, 최신 raw product 선택, 색상 수집, 찜 카운트 집계 등 복합 로직 및 예외 타입 조정 포함.
레포지토리 변경
src/main/java/or/sopt/houme/domain/furniture/repository/CurationRawProductRepository.java
findAllByProductIdIn(List<Long>) 메서드 추가(원본 raw product들의 일괄 조회용).
Optimistic Lock Facade 확장
src/main/java/or/sopt/houme/domain/furniture/service/facade/JjymOptimisticLockFacade.java
raw product용 toggleRawProduct(User, Long) 추가 — 기존 toggle과 동일한 재시도/지수 백오프 로직 적용.
테스트 추가
src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java, src/test/java/or/sopt/houme/domain/furniture/service/JjymServiceImplTest.java
컨트롤러 레이어와 서비스 레이어용 단위/웹 계층 테스트 추가. V2 토글/조회 흐름과 RecommendFurniture 생성·매핑·집계 동작을 검증하는 케이스 포함.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Client
    participant Controller as JjymController\n(v2 endpoints)
    participant Facade as JjymOptimisticLockFacade
    participant Service as JjymServiceImpl
    participant Repo as Repository Layer
    participant DB as Database

    User->>Client: 찜 토글 요청 (rawProduct)
    Client->>Controller: POST /api/v2/curation-raw-products/{id}/jjym
    Controller->>Facade: toggleRawProduct(user, rawProductId)
    loop retry with backoff
        Facade->>Service: rawProductJjymToggle(userId, rawProductId)
        Service->>Repo: find User, find CurationRawProduct
        Repo->>DB: select by id
        DB-->>Repo: entities
        Repo-->>Service: entities
        Service->>Repo: find RecommendFurniture by (RAW, productId)
        Repo->>DB: select recommend_furniture
        DB-->>Repo: entity or empty
        alt recommend missing
            Service->>Repo: save RecommendFurniture
            Repo->>DB: insert
            DB-->>Repo: saved entity
        end
        Service->>Repo: find/delete or save Jjym
        Repo->>DB: transaction
        DB-->>Repo: success or optimistic lock error
    end
    Service-->>Facade: true/false
    Facade-->>Controller: result
    Controller-->>Client: ApiResponse<JjymToggleResponse>

    User->>Client: 찜 목록 조회 요청
    Client->>Controller: GET /api/v2/jjyms
    Controller->>Service: getMyRawProductJjyms(userId)
    Service->>Repo: find Jjyms with RecommendFurniture
    Repo->>DB: join query
    DB-->>Repo: jjym+rf rows
    Repo-->>Service: results
    Service->>Repo: findAllByProductIdIn(productIds)
    Repo->>DB: bulk select raw products (latest per productId)
    DB-->>Repo: raw product rows
    Repo-->>Service: raw products
    Service->>Repo: find colors by rawProductIds
    Repo->>DB: select colors
    DB-->>Repo: color rows
    Repo-->>Service: colors
    Service->>Repo: count jjym by recommendFurnitureIds
    Repo->>DB: group count
    DB-->>Repo: counts
    Repo-->>Service: counts
    Service-->>Controller: JjymV2ListResponse
    Controller-->>Client: ApiResponse<JjymV2ListResponse>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • [FEAT] 찜 기능 구현 #291: 기존 jjym/favorite 기능을 도입한 PR로, 이번 변경이 raw-product v2 확장 관련 코드(컨트롤러/서비스/페이사드)를 이어받음.

Suggested labels

:m: test``

Suggested reviewers

  • PBEM22
  • kbt82883
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경사항을 명확히 반영하고 있습니다. 찜 v2 API 구현이라는 핵심 기능이 제목에 정확히 드러나 있습니다.
Linked Issues check ✅ Passed 구현 내용이 #453 요구사항을 완벽히 충족합니다. 찜 조회 API(GET /api/v2/jjyms)와 토글 API(POST /api/v2/curation-raw-products/{rawProductId}/jjym)가 모두 구현되었고, 반환 필드도 모두 포함됩니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 #453 요구사항 범위 내입니다. v1 엔드포인트의 경로 조정도 API 버전 관리 구조화의 일부로서 필요한 변경입니다.
Description check ✅ Passed PR 설명에서 구현 내용, Postman 스크린샷, 관련 이슈 정보가 상세히 제시되어 있어 변경사항을 파악하기에 충분합니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#453/jjym

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 and usage tips.

@github-actions
Copy link

github-actions bot commented Mar 24, 2026

Test Results

282 tests   282 ✅  16s ⏱️
 67 suites    0 💤
 67 files      0 ❌

Results for commit 2cd4d47.

♻️ This comment has been updated with latest results.

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.

🧹 Nitpick comments (4)
src/main/java/or/sopt/houme/domain/furniture/service/facade/JjymOptimisticLockFacade.java (2)

40-58: DataIntegrityViolationException 대신 도메인 예외 사용 권장

코딩 가이드라인에 따르면 서비스/파사드 레이어에서는 DataIntegrityViolationException 같은 Spring 예외 대신 FurnitureException 같은 도메인 예외로 변환해서 던지는 게 좋아요. 기존 toggle() 메서드도 동일한 패턴인데, 둘 다 리팩토링하면 좋을 것 같아요.

♻️ 도메인 예외로 변환하는 예시
             } catch (InterruptedException ie) {
                 Thread.currentThread().interrupt();
-                throw new DataIntegrityViolationException("찜 토글 처리 중 인터럽트가 발생했습니다", ie);
+                throw new FurnitureException(ErrorCode.JJYM_TOGGLE_INTERRUPTED);
             }
             retryCount++;
         }

-        throw new DataIntegrityViolationException("찜 시도가 정해진 횟수를 초과하였습니다");
+        throw new FurnitureException(ErrorCode.JJYM_TOGGLE_MAX_RETRIES_EXCEEDED);

As per coding guidelines: "Never throw IllegalArgumentException directly from services or entities. Input/domain validation failures must be converted to domain exceptions."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/or/sopt/houme/domain/furniture/service/facade/JjymOptimisticLockFacade.java`
around lines 40 - 58, Replace thrown Spring exceptions in toggleRawProduct with
the domain FurnitureException: catch blocks should still handle
OptimisticLockException and DataIntegrityViolationException from
jjymService.rawProductJjymToggle, but when InterruptedException occurs inside
Thread.sleep wrap and rethrow a new FurnitureException (include the
InterruptedException as cause and use a clear message like "찜 토글 처리 중 인터럽트가
발생했습니다"); likewise, when retries exceed MAX_RETRIES replace the final throw of
DataIntegrityViolationException with a FurnitureException describing that retry
attempts were exhausted. Mirror the same pattern used by the existing toggle()
method for consistent domain-exception handling.

19-58: 중복된 retry 로직 추출 고려

toggle()toggleRawProduct() 메서드의 retry 로직이 거의 동일해요. 나중에 추가 기능이 생기면 공통 유틸리티 메서드로 추출하는 것도 좋을 것 같아요.

♻️ 공통 retry 유틸리티 예시
private <T> T executeWithRetry(Supplier<T> action) {
    int retryCount = 0;
    while (retryCount < MAX_RETRIES) {
        try {
            return action.get();
        } catch (OptimisticLockException | DataIntegrityViolationException e) {
            long backoffTime = (long) Math.pow(2, retryCount) * RETRY_DELAY_MS;
            try {
                Thread.sleep(backoffTime);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new FurnitureException(ErrorCode.JJYM_TOGGLE_INTERRUPTED);
            }
            retryCount++;
        }
    }
    throw new FurnitureException(ErrorCode.JJYM_TOGGLE_MAX_RETRIES_EXCEEDED);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/or/sopt/houme/domain/furniture/service/facade/JjymOptimisticLockFacade.java`
around lines 19 - 58, Both toggle(User, Long) and toggleRawProduct(User, Long)
contain duplicated retry/backoff logic; extract that into a reusable generic
helper (e.g., executeWithRetry or executeWithRetrySupplier) and call it from
both methods. Implement a private generic method that accepts a
Supplier<Boolean> (or a ThrowingSupplier) and encapsulates the retry loop,
backoff calculation, Thread.sleep handling (preserving interrupt behavior by
Thread.currentThread().interrupt()), and consistent exception translation (use
the same exception type/messages as currently thrown in
toggle/toggleRawProduct). Replace the bodies of toggle and toggleRawProduct to
simply invoke this helper with jjymService.jjymToggle(user.getId(),
recommendFurnitureId) and jjymService.rawProductJjymToggle(user.getId(),
rawProductId) respectively so behavior remains identical but logic is
centralized.
src/test/java/or/sopt/houme/domain/furniture/service/JjymServiceImplTest.java (2)

67-72: 예외 케이스 테스트도 고려해보면 좋겠어요 (optional)

현재 happy path 위주로 테스트가 작성되어 있어요. 시간 여유 있을 때 아래 케이스들 추가하면 더 견고해져요:

  • 존재하지 않는 userId로 호출 시 예외 처리
  • 존재하지 않는 rawProductId로 호출 시 예외 처리
  • 찜 목록이 비어있는 경우
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/or/sopt/houme/domain/furniture/service/JjymServiceImplTest.java`
around lines 67 - 72, The test currently covers only the happy path; add
negative tests in JjymServiceImplTest that mock userRepository.findById,
curationRawProductRepository.findById, and
jjymRepository.findByUserIdAndRecommendFurnitureId to return Optional.empty() to
assert the service method under test (e.g., createJjym / the method being
exercised in this test) throws the expected exceptions for: missing userId,
missing rawProductId, and an empty jjym list scenario; use
given(...).willReturn(Optional.empty()) for each repository and assertThrows (or
the project's equivalent) to verify the correct exception type/message.

46-79: 찜 해제(toggle-off) 케이스 테스트 추가하면 좋겠어요

현재 테스트는 찜이 없을 때 새로 생성하는 케이스만 커버하고 있어요. Toggle 기능 특성상 이미 찜이 존재할 때 삭제하는 시나리오도 테스트해두면 안전해요.

💡 추가 테스트 케이스 예시
`@Test`
`@DisplayName`("raw product 기준 찜 토글 시 이미 찜이 존재하면 삭제한다")
void rawProductJjymToggle_deletesExistingJjym() {
    User user = User.builder().id(1L).build();
    CurationRawProduct rawProduct = CurationRawProduct.builder()
            .id(10L)
            .productId(1000L)
            // ... 필요한 필드들
            .build();
    RecommendFurniture recommendFurniture = RecommendFurniture.builder()
            .id(20L)
            .furnitureProductId(1000L)
            .source(CurationSource.RAW)
            .build();
    Jjym existingJjym = Jjym.builder()
            .id(30L)
            .user(user)
            .recommendFurniture(recommendFurniture)
            .build();

    given(userRepository.findById(1L)).willReturn(Optional.of(user));
    given(curationRawProductRepository.findById(10L)).willReturn(Optional.of(rawProduct));
    given(recommendFurnitureRepository.findBySourceAndFurnitureProductId(CurationSource.RAW, 1000L))
            .willReturn(Optional.of(recommendFurniture));
    given(jjymRepository.findByUserIdAndRecommendFurnitureId(1L, 20L))
            .willReturn(Optional.of(existingJjym));

    boolean result = jjymService.rawProductJjymToggle(1L, 10L);

    assertThat(result).isFalse();
    then(jjymRepository).should().delete(existingJjym);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/or/sopt/houme/domain/furniture/service/JjymServiceImplTest.java`
around lines 46 - 79, Add a complementary "toggle-off" unit test in
JjymServiceImplTest that exercises rawProductJjymToggle when a Jjym already
exists: set up User, CurationRawProduct, RecommendFurniture and an existing
Jjym, mock userRepository.findById(...),
curationRawProductRepository.findById(...),
recommendFurnitureRepository.findBySourceAndFurnitureProductId(...) to return
the recommendFurniture, and
jjymRepository.findByUserIdAndRecommendFurnitureId(...) to return
Optional.of(existingJjym); call jjymService.rawProductJjymToggle(...) and assert
the result is false and that jjymRepository.delete(existingJjym) was called (and
no new recommendFurniture or jjym save occurred).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/java/or/sopt/houme/domain/furniture/service/facade/JjymOptimisticLockFacade.java`:
- Around line 40-58: Replace thrown Spring exceptions in toggleRawProduct with
the domain FurnitureException: catch blocks should still handle
OptimisticLockException and DataIntegrityViolationException from
jjymService.rawProductJjymToggle, but when InterruptedException occurs inside
Thread.sleep wrap and rethrow a new FurnitureException (include the
InterruptedException as cause and use a clear message like "찜 토글 처리 중 인터럽트가
발생했습니다"); likewise, when retries exceed MAX_RETRIES replace the final throw of
DataIntegrityViolationException with a FurnitureException describing that retry
attempts were exhausted. Mirror the same pattern used by the existing toggle()
method for consistent domain-exception handling.
- Around line 19-58: Both toggle(User, Long) and toggleRawProduct(User, Long)
contain duplicated retry/backoff logic; extract that into a reusable generic
helper (e.g., executeWithRetry or executeWithRetrySupplier) and call it from
both methods. Implement a private generic method that accepts a
Supplier<Boolean> (or a ThrowingSupplier) and encapsulates the retry loop,
backoff calculation, Thread.sleep handling (preserving interrupt behavior by
Thread.currentThread().interrupt()), and consistent exception translation (use
the same exception type/messages as currently thrown in
toggle/toggleRawProduct). Replace the bodies of toggle and toggleRawProduct to
simply invoke this helper with jjymService.jjymToggle(user.getId(),
recommendFurnitureId) and jjymService.rawProductJjymToggle(user.getId(),
rawProductId) respectively so behavior remains identical but logic is
centralized.

In
`@src/test/java/or/sopt/houme/domain/furniture/service/JjymServiceImplTest.java`:
- Around line 67-72: The test currently covers only the happy path; add negative
tests in JjymServiceImplTest that mock userRepository.findById,
curationRawProductRepository.findById, and
jjymRepository.findByUserIdAndRecommendFurnitureId to return Optional.empty() to
assert the service method under test (e.g., createJjym / the method being
exercised in this test) throws the expected exceptions for: missing userId,
missing rawProductId, and an empty jjym list scenario; use
given(...).willReturn(Optional.empty()) for each repository and assertThrows (or
the project's equivalent) to verify the correct exception type/message.
- Around line 46-79: Add a complementary "toggle-off" unit test in
JjymServiceImplTest that exercises rawProductJjymToggle when a Jjym already
exists: set up User, CurationRawProduct, RecommendFurniture and an existing
Jjym, mock userRepository.findById(...),
curationRawProductRepository.findById(...),
recommendFurnitureRepository.findBySourceAndFurnitureProductId(...) to return
the recommendFurniture, and
jjymRepository.findByUserIdAndRecommendFurnitureId(...) to return
Optional.of(existingJjym); call jjymService.rawProductJjymToggle(...) and assert
the result is false and that jjymRepository.delete(existingJjym) was called (and
no new recommendFurniture or jjym save occurred).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e0cac057-1df9-4ff2-a5e4-ceaa413ae208

📥 Commits

Reviewing files that changed from the base of the PR and between 64bfb8a and e2f6042.

📒 Files selected for processing (9)
  • src/main/java/or/sopt/houme/domain/furniture/presentation/controller/JjymController.java
  • src/main/java/or/sopt/houme/domain/furniture/presentation/dto/response/JjymV2ItemResponse.java
  • src/main/java/or/sopt/houme/domain/furniture/presentation/dto/response/JjymV2ListResponse.java
  • src/main/java/or/sopt/houme/domain/furniture/repository/CurationRawProductRepository.java
  • src/main/java/or/sopt/houme/domain/furniture/service/JjymService.java
  • src/main/java/or/sopt/houme/domain/furniture/service/JjymServiceImpl.java
  • src/main/java/or/sopt/houme/domain/furniture/service/facade/JjymOptimisticLockFacade.java
  • src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java
  • src/test/java/or/sopt/houme/domain/furniture/service/JjymServiceImplTest.java

@PBEM22
Copy link
Collaborator

PBEM22 commented Mar 24, 2026

📝 Code Coverage

Overall Project 52.05% -0.66%
Files changed 71.2% 🍏

File Coverage
JjymV2ListResponse.java 100% 🍏
JjymV2ItemResponse.java 100% 🍏
JjymServiceImpl.java 63.72% -23.5% 🍏
JjymController.java 57.63% 🍏
JjymOptimisticLockFacade.java 6.98% -46.51%

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java`:
- Around line 83-118: The test getMyRawProductJjyms_success in
JjymV2ControllerTest currently instantiates JjymController and calls
controller.getMyRawProductJjyms(...) directly which bypasses Spring MVC routing,
`@GetMapping` and `@AuthenticationPrincipal` binding; change the test to perform an
HTTP GET against the real endpoint using mockMvc: call
mockMvc.perform(get("/api/v2/jjyms").with(authentication(authToken()))) (or set
up MockMvc.standaloneSetup(new JjymController(...)) if needed), configure the
jjymService mock as before, and replace direct ResponseEntity assertions with
mockMvc.andExpect assertions for status and response JSON (e.g.,
status().isOk(), jsonPath(...) checks for items[0].rawProductId,
items[0].isJjym, brandName, jjymCount) so the HTTP layer, routing and security
binding are exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 393f94a1-585d-41cd-b790-d0817db305a1

📥 Commits

Reviewing files that changed from the base of the PR and between e2f6042 and 2cd4d47.

📒 Files selected for processing (1)
  • src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java

Comment on lines +83 to +118
@Test
@DisplayName("GET /api/v2/jjyms 요청 시 원천 상품 찜 목록을 반환한다")
void getMyRawProductJjyms_success() {
given(jjymService.getMyRawProductJjyms(1L)).willReturn(
JjymV2ListResponse.of(List.of(
JjymV2ItemResponse.of(
10L,
true,
"https://image",
"https://site",
List.of("화이트"),
"브랜드A",
"소파",
100000L,
20,
80000L,
5L
)
))
);

JjymController controller = new JjymController(jjymService, jjymOptimisticLockFacade);

ResponseEntity<or.sopt.houme.global.api.ApiResponse<JjymV2ListResponse>> response =
controller.getMyRawProductJjyms((CustomUserDetails) authToken().getPrincipal());

assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().code()).isEqualTo(200);
assertThat(response.getBody().data()).isNotNull();
assertThat(response.getBody().data().items()).hasSize(1);
assertThat(response.getBody().data().items().getFirst().rawProductId()).isEqualTo(10L);
assertThat(response.getBody().data().items().getFirst().isJjym()).isTrue();
assertThat(response.getBody().data().items().getFirst().brandName()).isEqualTo("브랜드A");
assertThat(response.getBody().data().items().getFirst().jjymCount()).isEqualTo(5L);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
# `@WebMvcTest` 내에서 직접 컨트롤러 생성/호출 패턴이 있는지 확인
rg -nP --type=java '@WebMvcTest|new\s+JjymController\s*\(|mockMvc\.perform\s*\(\s*get\("/api/v2/jjyms"'

Repository: TEAM-HOUME/HOUME-SERVER

Length of output: 1053


🏁 Script executed:

#!/bin/bash
# Check the full test class setup for JjymV2ControllerTest.java
head -n 120 src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java

Repository: TEAM-HOUME/HOUME-SERVER

Length of output: 5543


🏁 Script executed:

#!/bin/bash
# Check if mockMvc is declared in the test class
rg -A 5 -B 5 'mockMvc' src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java

Repository: TEAM-HOUME/HOUME-SERVER

Length of output: 841


🏁 Script executed:

#!/bin/bash
# Look at other `@WebMvcTest` examples to see the correct pattern
grep -l '@WebMvcTest' src/test/java/or/sopt/houme/domain/*/presentation/controller/*Test.java | head -2 | xargs -I {} sh -c 'echo "=== {} ===" && head -n 120 {}'

Repository: TEAM-HOUME/HOUME-SERVER

Length of output: 8587


mockMvc.perform()으로 실제 엔드포인트 테스트하기

Line 104에서 new JjymController(...)로 직접 호출하면 @GetMapping 라우팅, @AuthenticationPrincipal 바인딩, JSON 직렬화를 검증하지 못해요. 같은 파일의 POST 테스트(line 76)는 mockMvc.perform(post(...))으로 제대로 하는데, GET도 일관성 있게 맞춰주세요.

mockMvc.perform(get("/api/v2/jjyms").with(authentication(authToken()))).andExpect(...) 패턴으로 바꾸면 HTTP 레이어도 함께 검증되니까 라우팅 이슈나 매핑 문제를 놓치지 않을 수 있어요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/or/sopt/houme/domain/furniture/presentation/controller/JjymV2ControllerTest.java`
around lines 83 - 118, The test getMyRawProductJjyms_success in
JjymV2ControllerTest currently instantiates JjymController and calls
controller.getMyRawProductJjyms(...) directly which bypasses Spring MVC routing,
`@GetMapping` and `@AuthenticationPrincipal` binding; change the test to perform an
HTTP GET against the real endpoint using mockMvc: call
mockMvc.perform(get("/api/v2/jjyms").with(authentication(authToken()))) (or set
up MockMvc.standaloneSetup(new JjymController(...)) if needed), configure the
jjymService mock as before, and replace direct ResponseEntity assertions with
mockMvc.andExpect assertions for status and response JSON (e.g.,
status().isOk(), jsonPath(...) checks for items[0].rawProductId,
items[0].isJjym, brandName, jjymCount) so the HTTP layer, routing and security
binding are exercised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✊ 재연 재연 ✨ feat 기능구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 가구 찜 등록 및 조회 API 구현

2 participants