Skip to content

feat: 사용자 프로필 정보 조회 기능 구현#21

Merged
JoonKyoLee merged 5 commits intomainfrom
feat/get-user-profile
Nov 19, 2025
Merged

feat: 사용자 프로필 정보 조회 기능 구현#21
JoonKyoLee merged 5 commits intomainfrom
feat/get-user-profile

Conversation

@JoonKyoLee
Copy link
Contributor

@JoonKyoLee JoonKyoLee commented Nov 19, 2025

✨ 작업 내용

  • 사용자 정보 조회 기능 구현

📝 적용 범위

  • /user

📌 참고 사항

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 인증된 사용자가 자신의 프로필 정보를 조회할 수 있는 새로운 엔드포인트 추가
  • 테스트

    • 사용자 프로필 조회 기능의 테스트 커버리지 추가 (성공 및 오류 시나리오)

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

coderabbitai bot commented Nov 19, 2025

Walkthrough

사용자 정보 조회 기능을 구현하기 위해 성공 메시지 열거형, REST 컨트롤러, 서비스 계층, 응답 DTO를 추가하고, 성공 및 오류 시나리오를 다루는 단위 및 통합 테스트를 작성했습니다.

Changes

Cohort / File(s) Summary
API 응답 정의
src/main/java/com/almang/inventory/global/api/SuccessMessage.java
사용자 정보 조회 성공 메시지를 위한 GET_USER_PROFILE_SUCCESS 열거형 상수 추가
컨트롤러 레이어
src/main/java/com/almang/inventory/user/controller/UserController.java
새로운 스프링 REST 컨트롤러 클래스 추가; POST /api/v1/users/me 엔드포인트 구현으로 인증된 사용자의 프로필 조회
응답 DTO
src/main/java/com/almang/inventory/user/dto/response/UserProfileResponse.java
사용자 정보(username, name, role, storeName)를 담는 새로운 레코드 추가 및 User/Store로부터 생성하는 정적 팩토리 메서드
비즈니스 로직
src/main/java/com/almang/inventory/user/service/UserService.java
사용자 정보 조회 로직을 담는 새로운 서비스 클래스; getUserProfile(userId) 메서드로 User와 Store 정보를 조회하여 응답 생성
컨트롤러 테스트
src/test/java/com/almang/inventory/user/controller/UserControllerTest.java
@WebMvcTest 기반 단위 테스트로 성공(HTTP 200) 및 사용자 미발견(HTTP 404) 시나리오 검증
서비스 테스트
src/test/java/com/almang/inventory/user/service/UserServiceTest.java
@SpringBootTest 기반 통합 테스트로 정상 조회 및 사용자 없음 예외 처리 검증

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant UserRepo as UserRepository
    participant StoreRepo as StoreRepository
    participant DB

    Client->>Controller: POST /api/v1/users/me<br/>(with auth token)
    activate Controller
    Controller->>Service: getUserProfile(userId)
    deactivate Controller
    
    activate Service
    Service->>UserRepo: findById(userId)
    activate UserRepo
    UserRepo->>DB: SELECT * FROM user
    DB-->>UserRepo: User
    UserRepo-->>Service: User
    deactivate UserRepo
    
    Note over Service: User가 null이면<br/>USER_NOT_FOUND 예외 발생
    
    Service->>Service: user.getStore()
    Service->>StoreRepo: (Store 정보 접근)
    Service-->>Controller: UserProfileResponse
    deactivate Service
    
    Controller-->>Client: ApiResponse<UserProfileResponse><br/>HTTP 200 ✓
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 분

추가 검토 필요 영역:

  • UserController의 에러 처리 전략: 현재 구현에서 getUserProfile() 메서드가 BaseException을 던질 때 전역 예외 핸들러(@ControllerAdvice 등)가 이를 적절히 처리하는지 확인 필요
  • 트랜잭션 경계: UserService.getUserProfile()@Transactional(readOnly = true) 어노테이션이 있는지 확인; Store 정보는 User와의 관계를 통해 지연 로딩되므로 트랜잭션 범위 내에서 접근해야 함
  • 로깅 레벨: 사용자 정보 조회 성공 시 민감한 정보가 로그에 노출되지 않도록 주의 (username 정도만 로깅 권장)

Possibly related PRs

Poem

📋 프로필 조회, 이제 실현하다니
컨트롤러와 서비스 춤을 추고
테스트까지 짜임새 있게 다갈래 🎭
계층을 넘나드는 데이터의 여행
400줄이 나를 위해 춤을 추네! 💫

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 핵심을 명확히 반영합니다. '사용자 프로필 정보 조회 기능 구현'은 추가된 UserController 엔드포인트와 관련 서비스의 주요 목적을 정확히 나타냅니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 작업 내용, 적용 범위, 참고 사항을 모두 포함하고 있습니다. 다만 구체적인 변경 파일 목록이 있으면 더 도움이 될 것입니다.
Linked Issues check ✅ Passed PR의 모든 코드 변경이 Issue #17의 '사용자 정보 반환' 요구사항을 충족합니다. UserController, UserService, UserProfileResponse가 일관되게 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 사용자 프로필 조회 기능 구현 범위 내에 있습니다. SuccessMessage에 새로운 상수 추가, 테스트 코드도 모두 관련 기능을 지원합니다.
✨ 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/get-user-profile

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 (6)
src/main/java/com/almang/inventory/user/dto/response/UserProfileResponse.java (1)

1-21: DTO 구조와 매핑은 명확하지만, 팩토리 메서드 시그니처를 단순화할 여지가 있습니다.

  • 현재 from(User user, Store store)에서 하는 일은 user.getStore()를 호출하는 것과 거의 동일해서, 서비스 단에서 굳이 Store를 분리해 넘길 필요는 없어 보입니다.
    public static UserProfileResponse from(User user) 형태로 단순화하고, 내부에서 Store store = user.getStore();를 사용하면 호출부가 더 깔끔해집니다.
  • 한 가지 확인 포인트는 UserStore 연관관계입니다. 도메인에서 storeoptional = false로 보장되지 않는다면, store.getName()에서 NPE 가능성이 생깁니다. 연관관계에서 필수 여부를 강제하거나, 여기서 방어 로직(예: store == null ? null : store.getName())을 두는지 검토해 보시면 좋겠습니다.

전반적인 필드 구성과 응답 구조는 요구사항과 잘 맞습니다. 딱 필요한 정보만 뽑아서 주는 점이 좋네요.

src/test/java/com/almang/inventory/user/service/UserServiceTest.java (2)

22-68: 통합 테스트 구성과 픽스처 설계가 깔끔합니다.

  • @SpringBootTest + @Transactional + @ActiveProfiles("test") 조합으로 실제 빈/DB 환경에서 조회 로직을 검증하는 구조가 잘 잡혀 있습니다.
  • newStore(), newUser(Store store) 헬퍼로 픽스처 생성을 캡슐화한 것도 테스트 가독성과 재사용성 측면에서 좋은 선택입니다.
  • 성공 케이스에서 문자열 리터럴 대신 savedUser / store 객체의 값을 그대로 검증해도(예: assertThat(response.username()).isEqualTo(savedUser.getUsername())) 변경에 조금 더 강한 테스트가 됩니다. 선택 사항이지만, 추후 도메인 변경 시 테스트 유지보수성이 올라갑니다.

잘 구성된 통합 테스트라 “서비스 단의 스펙”을 읽기에도 좋습니다.


70-79: 예외 검증 시 메시지 문자열 의존도를 줄이면 테스트가 더 견고해집니다.

현재는 아래와 같이 예외 메시지 텍스트를 부분 문자열로 검증하고 있습니다.

assertThatThrownBy(() -> userService.getUserProfile(notExistUserId))
        .isInstanceOf(BaseException.class)
        .hasMessageContaining(ErrorCode.USER_NOT_FOUND.getMessage());
  • 메시지는 번역/표현 변경에 의해 쉽게 바뀔 수 있어, 도메인 규칙보다는 “문구”에 테스트가 종속되는 형태입니다.
  • BaseExceptiongetErrorCode()와 같은 접근자를 제공한다면, extracting("errorCode").isEqualTo(ErrorCode.USER_NOT_FOUND) 식으로 에러 코드 중심으로 검증하는 방식을 추천드립니다. 도메인 에러 정책에 더 직접적으로 매핑됩니다.

참고로 AssertJ 공식 문서의 extracting, satisfies 등 기능을 활용하면 도메인 예외 검증을 더 풍부하게 표현할 수 있습니다.

src/main/java/com/almang/inventory/user/service/UserService.java (1)

20-37: 조회 로직은 명확하지만, Store 널 처리와 로깅 레벨/위치에 대해 한 번 점검해 보면 좋겠습니다.

  • getUserProfile에서 User user = findUserById(userId);Store store = user.getStore();를 곧바로 사용하고 있습니다. 도메인에서 User.store가 항상 존재한다는 제약이 없다면, 여기서 NPE가 발생할 수 있습니다.
    • 연관관계 매핑에서 optional = false / nullable = false로 강제하거나,
    • 이 메서드에서 store == null인 경우에 대한 별도 처리(예: 다른 에러 코드, 기본값)를 두는 방식을 검토해 주세요.
  • 로그가 컨트롤러(UserController)와 서비스 양쪽에서 모두 INFO 레벨로 남고 있어, 트래픽이 많은 경우 로그 노이즈가 될 수 있습니다.
    • “요청/응답 추적”을 컨트롤러 레이어에 집중시키고, 서비스는 도메인적으로 의미 있는 이벤트에만 로그를 남기거나,
    • 서비스 로그를 DEBUG 레벨로 내리는 것도 한 가지 선택지입니다.

트랜잭션을 readOnly = true로 분리한 점과, 조회 + 예외 처리를 findUserById로 캡슐화한 구조는 서비스 레이어 설계 측면에서 좋습니다.

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

41-71: 성공 케이스 테스트가 잘 짜여 있지만, 서비스 호출 인자를 검증하면 버그 탐지력이 더 좋아집니다.

  • CustomUserPrincipal에서 userId = 1L을 세팅하고 있지만, when(userService.getUserProfile(anyLong()))로 스텁을 설정해서 컨트롤러가 잘못된 ID를 넘겨도 테스트가 통과합니다.
  • 아래와 같이 verify를 추가하면, 컨트롤러가 인증 주체의 ID를 정확히 넘기는지까지 보장할 수 있습니다.
// given
when(userService.getUserProfile(userId)).thenReturn(response);

// when & then 이후 검증
verify(userService).getUserProfile(userId);

전반적으로 JSON 구조와 메시지까지 꼼꼼히 검증하고 있어서, API 계약을 문서처럼 보여주는 좋은 테스트입니다.

src/main/java/com/almang/inventory/user/controller/UserController.java (1)

18-39: 엔드포인트 설계는 명확하지만, HTTP 메서드 선택과 인증 주체 널 처리에 대해 한 번 더 고민해 볼 만합니다.

  • 현재 @PostMapping("/me")로 “조회”를 처리하고 있는데, REST 관점에서는 @GetMapping("/me")가 더 자연스럽습니다.
    • 캐싱, 프록시, 클라이언트 라이브러리(예: OpenAPI 코드 생성기) 입장에서도 조회성 요청은 GET으로 노출하는 것이 일반적인 베스트 프랙티스입니다.
    • 만약 기존 클라이언트 제약 등으로 인해 POST를 유지해야 한다면, Swagger 문서 쪽 설명에 그 이유를 명시해 두는 것도 좋겠습니다.
  • @AuthenticationPrincipal CustomUserPrincipal userPrincipal이 항상 non-null이라는 전제가 Spring Security 설정으로 보장되는지 한 번 확인해 주세요.
    • 만약 익명 접근 가능성이나 잘못된 설정이 있다면, userPrincipal == null일 때 별도 예외를 던지는 방어 코드도 고려할 수 있습니다.

ApiResponse.successSuccessMessage.GET_USER_PROFILE_SUCCESS를 사용해 응답 포맷과 메시지를 한 곳에서 관리하는 구조는 깔끔합니다. 딱 보기 좋은 표준 컨트롤러입니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93b0216 and e9b4751.

📒 Files selected for processing (6)
  • src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1 hunks)
  • src/main/java/com/almang/inventory/user/controller/UserController.java (1 hunks)
  • src/main/java/com/almang/inventory/user/dto/response/UserProfileResponse.java (1 hunks)
  • src/main/java/com/almang/inventory/user/service/UserService.java (1 hunks)
  • src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (1 hunks)
  • src/test/java/com/almang/inventory/user/service/UserServiceTest.java (1 hunks)
🔇 Additional comments (2)
src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1)

8-18: GET_USER_PROFILE_SUCCESS 추가 방향 적절합니다.

기존 성공 메시지 패턴(한글 설명, 과거 시제/완료형)과 잘 맞고, 컨트롤러에서 재사용하기에도 명확한 이름입니다. 추가적인 수정 포인트는 없어 보입니다.

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

73-93: 에러 케이스 테스트 구성 좋습니다.

  • UserService.getUserProfile에서 BaseException(USER_NOT_FOUND)가 발생했을 때, HTTP 404 상태, 에러 메시지, data 필드 부재까지 모두 검증하고 있어 에러 응답 스펙이 명확하게 고정됩니다.
  • 성공/실패 모두 같은 엔드포인트(/api/v1/users/me)를 타게 해서, 보안 컨텍스트·응답 래핑(ApiResponse)을 실제와 동일하게 검증하고 있는 점도 좋습니다.

이 수준의 컨트롤러 테스트면 회귀 버그를 잡는 데 꽤 든든하겠습니다.

@JoonKyoLee JoonKyoLee merged commit c3c0ae6 into main Nov 19, 2025
1 check passed
@JoonKyoLee JoonKyoLee deleted the feat/get-user-profile branch November 20, 2025 05:17
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