Skip to content

feat: 사용자 프로필 업데이트 기능 구현#22

Merged
JoonKyoLee merged 8 commits intomainfrom
feat/change-user-profile
Nov 19, 2025
Merged

feat: 사용자 프로필 업데이트 기능 구현#22
JoonKyoLee merged 8 commits intomainfrom
feat/change-user-profile

Conversation

@JoonKyoLee
Copy link
Contributor

@JoonKyoLee JoonKyoLee commented Nov 19, 2025

✨ 작업 내용

  • 사용자 프로필 업데이트 로직 구현

📝 적용 범위

  • /user

📌 참고 사항

Summary by CodeRabbit

  • 새로운 기능
    • 사용자 프로필 정보 수정 기능 추가 — 인증된 사용자가 자신의 프로필을 갱신할 수 있습니다.
  • 버그 수정 / 검증
    • 이름 길이 검증(최대 20자) 및 관련 오류 코드·성공 메시지 추가.
  • 테스트
    • 프로필 업데이트에 대한 단위/통합 테스트 추가 — 성공, 사용자 미존재, 유효성 실패 케이스 검증.

@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

인증된 사용자가 자신의 프로필 이름을 수정할 수 있는 PATCH 엔드포인트(/api/v1/users/me)와 관련 DTO, 도메인 메서드, 서비스 로직, 에러/성공 코드, 및 단위 테스트가 추가되었습니다.

Changes

코호트 / 파일(s) 변경 요약
API 메시지 정의
src/main/java/com/almang/inventory/global/api/SuccessMessage.java
UPDATE_USER_PROFILE_SUCCESS("사용자 프로필 정보 수정 성공") 열거형 상수 추가
에러 코드
src/main/java/com/almang/inventory/global/exception/ErrorCode.java
NAME_IS_LONG(HttpStatus.BAD_REQUEST, "이름은 20자를 초과할 수 없습니다") 추가
컨트롤러
src/main/java/com/almang/inventory/user/controller/UserController.java
PATCH /api/v1/users/me 엔드포인트 추가: updateUserProfile(@Valid @RequestBodyUpdateUserProfileRequest,@AuthenticationPrincipal CustomUserPrincipal)
서비스
src/main/java/com/almang/inventory/user/service/UserService.java
@Transactional updateUserProfile(Long userId, UpdateUserProfileRequest) 메서드 추가, User 엔티티 갱신 및 UpdateUserProfileResponse 반환
도메인
src/main/java/com/almang/inventory/user/domain/User.java
public void updateProfile(String name) 메서드 추가: null/길이(>20) 검증 후 이름 변경, 길이 초과 시 BaseException(ErrorCode.NAME_IS_LONG) 발생
DTOs
src/main/java/com/almang/inventory/user/dto/request/UpdateUserProfileRequest.java
src/main/java/com/almang/inventory/user/dto/response/UpdateUserProfileResponse.java
요청: record UpdateUserProfileRequest(@notblank String name) 추가, 응답: record UpdateUserProfileResponse(boolean success) 추가
테스트
src/test/java/com/almang/inventory/user/controller/UserControllerTest.java
src/test/java/com/almang/inventory/user/service/UserServiceTest.java
인증 헬퍼 리팩토링, PATCH 엔드포인트/서비스에 대한 성공·실패(404)·유효성(400, 이름 길이 초과) 테스트 추가 및 기존 테스트에 auth() 적용

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant Ctrl as UserController
    participant Svc as UserService
    participant Repo as UserRepository
    participant U as User Entity

    C->>Ctrl: PATCH /api/v1/users/me\n{name: "새 이름"}
    activate Ctrl
    Ctrl->>Ctrl: `@Valid` 검증
    Ctrl->>Ctrl: 인증정보에서 userId 추출
    Ctrl->>Svc: updateUserProfile(userId, request)
    deactivate Ctrl

    activate Svc
    Svc->>Repo: findById(userId)
    Repo-->>Svc: User (or null)
    alt User 존재
        Svc->>U: updateProfile(name)
        U-->>Svc: name 업데이트 (검증 실패 시 예외)
        Svc->>Repo: (트랜잭션 커밋으로 영속성 반영)
        Svc-->>Ctrl: UpdateUserProfileResponse(success=true)
    else 없음
        Svc-->>Ctrl: BaseException(USER_NOT_FOUND)
    end
    deactivate Svc

    Ctrl-->>C: 200 OK / 4xx 에러 응답
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 검토 포인트 (개선 중심, 추천 참조 포함):
    • UserController: @AuthenticationPrincipal CustomUserPrincipal 사용 방식이 컨텍스트(필요 권한, null 가능성)에 적절한지 확인 — Spring Security 문서: AuthenticationPrincipal 사용 권장 패턴을 참고하세요.
    • User.updateProfile: 길이 검사(20자) 위치가 도메인에 적절하나, 국제문자(유니코드) 길이 측정 방식(바이트 vs 코드포인트)을 명확히 하세요. 필요 시 String.codePointCount 사용을 권장합니다.
    • 트랜잭션 범위: UserService의 @Transactional 설정과 readOnly 분리(읽기/쓰기 명확화)를 재검토하세요. Spring 트랜잭션 가이드도 참고하면 유용합니다.
    • 테스트: 경계조건(정확히 20자), 공백/제어문자, 인증 없을 때 동작(401) 등을 추가 검증하면 안전합니다.

Possibly related PRs

Poem

프로필 바꾸는 패치 한 줄,
검증은 단단히, 예외는 슬쩍 탐색.
서비스는 트랜잭션 품고, 엔티티는 이름을 바꾸네.
테스트는 경계까지 챙기니, 배포도 웃음꽃 😊

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 제목이 변경 사항의 핵심을 명확하게 요약합니다: 사용자 프로필 업데이트 기능 구현
Description check ✅ Passed 요약, 적용 범위, 참고 사항 섹션이 모두 작성되어 템플릿을 준수합니다.
Linked Issues check ✅ Passed 코드 변경 사항이 Issue #18의 프로필 수정 로직 구현 목표를 완전히 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 사용자 프로필 업데이트 기능과 직접적으로 연관되어 있습니다.
✨ 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/change-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: 2

🧹 Nitpick comments (1)
src/main/java/com/almang/inventory/user/dto/response/UpdateUserProfileResponse.java (1)

3-5: 응답 필드명 일관성을 검토해보세요.

현재 코드베이스에서 유사한 응답 DTO들의 필드명이 일관되지 않습니다:

  • LogoutResponse: boolean success
  • ChangePasswordResponse: boolean isChanged
  • UpdateUserProfileResponse: boolean success (현재 파일)

LogoutResponse 패턴을 따르고 있지만, 전체적으로 일관된 네이밍 컨벤션을 정하는 것을 고려해보세요. 예를 들어, 모든 변경 작업 응답에 isXXXed 형태를 사용하거나, 모두 success로 통일하는 방식입니다.

참고: 단순 boolean 필드보다는 HTTP 상태 코드로 충분한 경우가 많습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3c0ae6 and 6fa1d90.

📒 Files selected for processing (8)
  • src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1 hunks)
  • src/main/java/com/almang/inventory/user/controller/UserController.java (2 hunks)
  • src/main/java/com/almang/inventory/user/domain/User.java (1 hunks)
  • src/main/java/com/almang/inventory/user/dto/request/UpdateUserProfileRequest.java (1 hunks)
  • src/main/java/com/almang/inventory/user/dto/response/UpdateUserProfileResponse.java (1 hunks)
  • src/main/java/com/almang/inventory/user/service/UserService.java (2 hunks)
  • src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (5 hunks)
  • src/test/java/com/almang/inventory/user/service/UserServiceTest.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
src/main/java/com/almang/inventory/user/dto/response/UpdateUserProfileResponse.java (2)
src/main/java/com/almang/inventory/user/auth/dto/response/ChangePasswordResponse.java (1)
  • ChangePasswordResponse (3-5)
src/main/java/com/almang/inventory/user/auth/dto/response/LogoutResponse.java (1)
  • LogoutResponse (3-5)
src/main/java/com/almang/inventory/user/controller/UserController.java (2)
src/test/java/com/almang/inventory/user/auth/controller/AuthControllerTest.java (1)
  • Test (150-173)
src/main/java/com/almang/inventory/user/auth/controller/AuthController.java (1)
  • PostMapping (67-80)
src/main/java/com/almang/inventory/user/dto/request/UpdateUserProfileRequest.java (2)
src/main/java/com/almang/inventory/user/auth/dto/request/ChangePasswordRequest.java (1)
  • ChangePasswordRequest (5-7)
src/main/java/com/almang/inventory/user/auth/dto/request/LoginRequest.java (1)
  • LoginRequest (5-8)
src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (1)
src/test/java/com/almang/inventory/user/auth/controller/AuthControllerTest.java (6)
  • Test (150-173)
  • WebMvcTest (38-261)
  • Test (50-67)
  • Test (87-103)
  • Test (120-134)
  • Test (136-148)
src/test/java/com/almang/inventory/user/service/UserServiceTest.java (1)
src/test/java/com/almang/inventory/user/auth/service/AuthServiceTest.java (2)
  • Test (135-148)
  • Test (69-82)
src/main/java/com/almang/inventory/user/service/UserService.java (2)
src/main/java/com/almang/inventory/user/repository/UserRepository.java (1)
  • UserRepository (7-12)
src/main/java/com/almang/inventory/user/auth/service/AuthService.java (2)
  • Transactional (41-51)
  • Transactional (29-39)
🔇 Additional comments (8)
src/test/java/com/almang/inventory/user/service/UserServiceTest.java (2)

83-100: 테스트 검증이 잘 작성되었습니다! 🎯

DB에서 실제 변경사항을 재조회하여 확인하는 부분(97-99번 줄)이 특히 좋습니다. 영속성 검증까지 포함된 탄탄한 테스트네요.

참고로, 95번 줄에서 assertThat(response).isNotNull() 대신 assertThat(response.success()).isTrue()로 명확하게 성공 여부를 검증하는 것도 고려해보세요.


102-112: 예외 처리 테스트가 적절합니다.

기존 테스트 패턴(73-81번 줄)과 동일한 구조로 일관성이 유지되고 있습니다. 에러 케이스 커버리지도 충분합니다.

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

16-16: 성공 메시지 추가가 적절합니다.

기존 패턴을 잘 따르고 있으며, 메시지 내용도 명확합니다.

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

45-51: 좋은 리팩토링입니다! 👍

중복된 인증 객체 생성 로직을 헬퍼 메서드로 추출하여 테스트 코드의 가독성이 향상되었습니다.


100-119: 컨트롤러 테스트가 꼼꼼하게 작성되었습니다.

응답의 모든 필드를 검증하고 있으며, 인증 처리도 적절합니다. 기존 패턴을 잘 따르고 있네요.


140-154: 입력값 검증 테스트가 적절합니다.

빈 문자열에 대한 @NotBlank 검증을 테스트하고 있습니다.

참고: User.java 리뷰에서 언급한 최대 길이 검증(@Size(max = 20))을 DTO에 추가하면, 여기에도 길이 초과 케이스를 테스트하는 것을 권장합니다.

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

46-59: 컨트롤러 구현이 깔끔합니다! ✨

기존 코드 패턴(AuthController의 changePassword)을 잘 따르고 있으며, 다음 사항들이 모두 적절하게 처리되었습니다:

  • @Valid를 통한 요청 검증
  • @AuthenticationPrincipal을 통한 인증 사용자 정보 추출
  • 적절한 로깅
  • 일관된 응답 구조

단, UpdateUserProfileRequest에 최대 길이 검증(@Size(max = 20))이 추가되면 여기서 자동으로 검증이 수행됩니다.

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

34-43: 서비스 로직이 탄탄하게 구현되었습니다! 💪

다음 사항들이 모두 적절합니다:

  • @Transactional 설정 (readOnly가 아님)
  • findUserById를 통한 예외 처리
  • 적절한 로깅 (요청 시작/성공)
  • 기존 AuthService.changePassword 패턴과의 일관성

트랜잭션 컨텍스트에서 엔티티 변경이 자동으로 영속화되는 JPA의 더티 체킹을 잘 활용하고 있습니다.

Comment on lines +5 to +7
public record UpdateUserProfileRequest(
@NotBlank String name
) {}
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

최대 길이 검증이 누락되었습니다.

User 엔티티의 name 컬럼이 length = 20으로 제한되어 있지만(src/main/java/com/almang/inventory/user/domain/User.java:46), 요청 DTO에 최대 길이 검증이 없습니다.

20자를 초과하는 이름이 입력되면 데이터베이스 제약 조건 위반이 발생합니다.

해결 방법:

 public record UpdateUserProfileRequest(
-        @NotBlank String name
+        @NotBlank
+        @Size(max = 20, message = "이름은 20자를 초과할 수 없습니다")
+        String name
 ) {}

필요한 import:

import jakarta.validation.constraints.Size;

참고 자료:

  • Bean Validation @Size 문서
  • 기존 프로젝트의 LoginRequestChangePasswordRequest도 유사한 검증 패턴을 참고할 수 있습니다.
🤖 Prompt for AI Agents
In
src/main/java/com/almang/inventory/user/dto/request/UpdateUserProfileRequest.java
around lines 5 to 7, the DTO is missing a max-length validation for name which
can exceed the User entity column length (20) and cause DB constraint
violations; add jakarta.validation.constraints.Size(max = 20) to the name
parameter and add the import statement import
jakarta.validation.constraints.Size; so the record parameter becomes annotated
with @NotBlank and @Size(max = 20) to enforce the same limit at the request
level.

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 (2)
src/main/java/com/almang/inventory/global/exception/ErrorCode.java (1)

24-24: 에러 메시지 일관성 개선을 고려하세요.

에러 메시지 끝에 마침표가 있는 것(10-11번 줄)과 없는 것(17-23번 줄)이 혼재되어 있습니다. 전체 에러 코드에서 일관된 스타일을 유지하는 것이 좋습니다.

권장사항: 프로젝트의 메시지 스타일 가이드를 정하고 모든 에러 메시지에 적용하세요.

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

100-173: 프로필 수정 테스트 커버리지가 잘 구성되어 있습니다.

성공 케이스, 사용자 미존재, 빈 값 검증, 길이 초과 등 주요 시나리오를 모두 테스트하고 있습니다.

추가 개선 제안:

경계값 테스트를 추가하면 더욱 견고해집니다:

  • 정확히 20자인 경우 (성공해야 함)
  • 19자인 경우 (성공해야 함)
@Test
void 사용자_프로필_수정_이름이_정확히_20자일_때_성공한다() throws Exception {
    // given
    UpdateUserProfileRequest request = new UpdateUserProfileRequest("12345678901234567890"); // 정확히 20자
    UpdateUserProfileResponse response = new UpdateUserProfileResponse(true);

    when(userService.updateUserProfile(anyLong(), any(UpdateUserProfileRequest.class)))
            .thenReturn(response);

    // when & then
    mockMvc.perform(patch("/api/v1/users/me")
                    .with(authentication(auth()))
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.data.success").value(true));
}

참고: 경계값 테스트는 off-by-one 에러를 잡아내는 데 효과적입니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa1d90 and f53bc85.

📒 Files selected for processing (4)
  • src/main/java/com/almang/inventory/global/exception/ErrorCode.java (1 hunks)
  • src/main/java/com/almang/inventory/user/domain/User.java (2 hunks)
  • src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (5 hunks)
  • src/test/java/com/almang/inventory/user/service/UserServiceTest.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/com/almang/inventory/user/service/UserServiceTest.java
🔇 Additional comments (1)
src/test/java/com/almang/inventory/user/controller/UserControllerTest.java (1)

45-51: 훌륭한 리팩토링입니다! 👍

중복 코드를 제거하고 테스트 가독성을 높이는 헬퍼 메서드를 추가했습니다. 인증 객체 생성 로직이 한 곳에 집중되어 유지보수가 쉬워졌네요.

@JoonKyoLee JoonKyoLee merged commit 935d90f into main Nov 19, 2025
1 check passed
@JoonKyoLee JoonKyoLee deleted the feat/change-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