Skip to content

[feat] 기록,투표 수정하기 api 개발#275

Merged
buzz0331 merged 10 commits into
developfrom
feat/#274-update-record-vote
Aug 26, 2025
Merged

[feat] 기록,투표 수정하기 api 개발#275
buzz0331 merged 10 commits into
developfrom
feat/#274-update-record-vote

Conversation

@buzz0331
Copy link
Copy Markdown
Contributor

@buzz0331 buzz0331 commented Aug 23, 2025

#️⃣ 연관된 이슈

closes #274

📝 작업 내용

기록, 투표 수정하기 api를 개발하였습니다.
요구사항에 따라 두 엔티티 모두 content만 수정하게끔 하였습니다. (이때, Record는 500자 제한, Vote는 20자 제한)

추후에 유지보수를 용이하게 하기 위해서 엔드포인트와 서비스 메서드는 분리하였습니다. 다만, RoomPost라는 상위 도메인을 이용하는 것이 좋아보여 RoomPostUpdateService라는 클래스에서 두 메서드를 구현하도록 하였습니다.
기록, 투표 삭제하기 api도 추후에 리팩토링을 할때 이렇게 합치는 것은 어떨지 제안드립니다~

📸 스크린샷

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신기능

    • 방 내 기록/투표를 수정하는 PATCH API(기록/투표별 업데이트)와 관련 요청/응답 형식을 추가했습니다.
    • 업데이트 처리를 담당하는 서비스와 유스케이스 인터페이스를 도입했습니다.
  • 문서

    • 신규 수정 API에 대한 Swagger 응답 설명을 보강했습니다.
  • 리팩터

    • 내부 저장소 메서드 명칭을 일관성 있게 정리했습니다(동작 변경 없음).
  • 테스트

    • 기록/투표 수정 API 통합 테스트를 추가해 성공, 유효성/권한/미존재/교차 방 접근 시나리오를 검증했습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 23, 2025

Walkthrough

기록 및 투표 수정 기능 추가: PATCH 엔드포인트 2개, 요청/응답 DTO·커맨드·유스케이스·서비스·도메인 업데이트 메서드 추가, Swagger 응답 설명에 RECORD_UPDATE/VOTE_UPDATE 추가, 일부 퍼시스턴스 메서드명(saveRecord→save) 정리 및 통합 테스트 추가.

Changes

Cohort / File(s) Summary
Swagger 응답 설명 업데이트
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
업데이트용 상수 추가: RECORD_UPDATE, VOTE_UPDATE (관련 오류 세트 정의).
웹 컨트롤러: PATCH 엔드포인트
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java
PATCH /rooms/{roomId}/records/{recordId}PATCH /rooms/{roomId}/votes/{voteId} 추가; RoomPostUpdateUseCase 주입 및 위임, Swagger 주석 포함.
요청 DTO (Request)
src/main/java/konkuk/thip/roompost/adapter/in/web/request/RecordUpdateRequest.java, .../VoteUpdateRequest.java
content 필드, 검증 어노테이션(NotBlank, Size) 및 toCommand(userId, roomId, id) 매퍼 추가.
응답 DTO (Response)
src/main/java/konkuk/thip/roompost/adapter/in/web/response/RecordUpdateResponse.java, .../VoteUpdateResponse.java
roomId 단일 필드 레코드와 정적 팩토리 of(...).
애플리케이션 포트/커맨드
src/main/java/konkuk/thip/roompost/application/port/in/RoomPostUpdateUseCase.java, .../dto/record/RecordUpdateCommand.java, .../dto/vote/VoteUpdateCommand.java
업데이트 유스케이스 인터페이스 및 커맨드 DTO(roomId, postId, userId, content) 추가.
서비스/도메인 로직
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java, src/main/java/konkuk/thip/roompost/domain/Record.java, src/main/java/konkuk/thip/roompost/domain/Vote.java
서비스 구현(참여자 검증 → 조회 → 도메인 업데이트 → 저장). 도메인에 updateRecord/updateVote 추가(방·작성자 검증 후 content 갱신).
퍼시스턴스/포트 명칭 정리
src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java, src/main/java/konkuk/thip/roompost/application/port/out/RecordCommandPort.java, src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java
메서드명 saveRecord(...)save(...)로 변경(호출부 동기화).
통합 테스트
src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java, .../VoteUpdateApiTest.java
성공/유효성 실패/권한 실패/존재하지 않음/잘못된 roomId 케이스 검증 및 DB 상태 확인.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Controller as RoomPostCommandController
  participant UseCase as RoomPostUpdateUseCase
  participant Validator as RoomParticipantValidator
  participant RecordPort as RecordCommandPort
  participant VotePort as VoteCommandPort

  rect rgba(230,245,255,0.6)
  note over Client,Controller: 기록 수정 (PATCH /rooms/{roomId}/records/{recordId})
  Client->>Controller: RecordUpdateRequest(content)
  Controller->>UseCase: updateRecord(RecordUpdateCommand)
  UseCase->>Validator: validateParticipant(roomId, userId)
  UseCase->>RecordPort: getByIdOrThrow(recordId)
  UseCase->>RecordPort: save(updated Record)
  RecordPort-->>UseCase: persisted
  UseCase-->>Controller: roomId
  Controller-->>Client: 200 OK, RecordUpdateResponse(roomId)
  end

  rect rgba(240,255,240,0.6)
  note over Client,Controller: 투표 수정 (PATCH /rooms/{roomId}/votes/{voteId})
  Client->>Controller: VoteUpdateRequest(content)
  Controller->>UseCase: updateVote(VoteUpdateCommand)
  UseCase->>Validator: validateParticipant(roomId, userId)
  UseCase->>VotePort: getByIdOrThrow(voteId)
  UseCase->>VotePort: save(updated Vote)
  VotePort-->>UseCase: persisted
  UseCase-->>Controller: roomId
  Controller-->>Client: 200 OK, VoteUpdateResponse(roomId)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
기록 수정 API 개발 [#274]
투표 수정 API 개발 [#274]
권한/방참여 검증 반영 [#274]
요청 파라미터 검증 추가 [#274]

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
메서드명 변경: saveRecordsave (src/main/java/konkuk/thip/roompost/application/port/out/RecordCommandPort.java, .../adapter/out/persistence/RecordCommandPersistenceAdapter.java, .../application/service/RecordCreateService.java) 업데이트 API 구현 목적과 직접적 요구사항과 무관한 네이밍 리팩터링으로 보임(기능 변경 없음).

Possibly related PRs

Suggested reviewers

  • seongjunnoh
  • hd0rable

Poem

깡총깡총, 패치하러 왔네 🐇
기록·투표 살짝 고쳐서, 방을 지키네.
한 줄 문구 바꾸니, 방 ID가 반짝,
권한 검사 통과하면, 업데이트 찰칵!
토끼도 코드를 쓱쓱, 축하드려요! 🎉

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#274-update-record-vote

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 23, 2025

Test Results

426 tests   426 ✅  34s ⏱️
128 suites    0 💤
128 files      0 ❌

Results for commit 85211f4.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java (1)

62-68: Record 업데이트 시 콘텐츠 필드 한정 보장 필요

RecordJpaEntity#updateFrom(Record) 구현을 확인한 결과, 해당 메서드는 content 외에도 likeCount, commentCount, page, isOverview를 모두 변경하고 있습니다.
요구사항상 Record 업데이트는 콘텐츠(content) 필드만 수정되어야 하므로, 반드시 아래와 같이 수정이 필요합니다.

수정이 필요한 위치:

  • src/main/java/konkuk/thip/roompost/adapter/out/jpa/RecordJpaEntity.java 39–44번째 줄

제안 방안:

  • RecordJpaEntity에 콘텐츠만 반영하는 전용 메서드 추가
    public RecordJpaEntity updateContent(String content) {
        this.content = content;
        return this;
    }
  • RecordCommandPersistenceAdapter.update(…)에서는 전용 메서드만 호출하도록 변경
    public void update(Record record) {
        RecordJpaEntity entity = recordJpaRepository
            .findByPostIdAndStatus(record.getId(), ACTIVE)
            .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND));
    
    -   recordJpaRepository.save(entity.updateFrom(record));
    +   entity.updateContent(record.getContent());
    +   recordJpaRepository.save(entity);
    }

위와 같이 수정하면 콘텐츠 외 필드가 의도치 않게 변경되는 문제를 제거할 수 있습니다.

🧹 Nitpick comments (21)
src/main/java/konkuk/thip/roompost/domain/Vote.java (1)

98-102: 도메인 불변식 보강: content 길이(≤20)·null 검증 추가 제안

PR 설명에 명시된 “투표 content 20자 제한”이 도메인 메서드에서 보장되지 않습니다. 웹/서비스 계층(@SiZe 등)에서 막더라도 도메인 메서드가 다른 경로에서 호출되면 규칙이 무력화될 수 있어, 도메인 계층에서도 방어적으로 검증하는 편이 안전합니다. 아래와 같이 최소 검증을 추가하는 것을 제안합니다. ErrorCode 매핑이 필요하면 InvalidStateException + 전용 코드로 교체하세요.

 public void updateVote(Long userId, Long roomId, String content) {
     validateRoomId(roomId);
     validateCreator(userId);
-    this.content = content;
+    final String newContent = java.util.Objects.requireNonNull(content, "content must not be null");
+    if (newContent.length() > 20) {
+        // TODO: 프로젝트 규약에 맞게 ErrorCode를 신설/적용하세요 (예: VOTE_INVALID_CONTENT_LENGTH).
+        throw new IllegalArgumentException("투표 내용은 최대 20자까지 가능합니다.");
+    }
+    this.content = newContent;
 }

검증 포인트

  • VoteUpdateRequest에 @SiZe(max=20) 존재 여부 확인
  • DB 컬럼 길이 제약 동기화 (초과 시 RDB 레벨 예외 발생 가능)
  • 테스트 경계값 케이스(20/21자) 존재 여부 확인
src/main/java/konkuk/thip/roompost/adapter/in/web/response/RecordUpdateResponse.java (1)

3-8: 응답 페이로드 재검토: roomId만 반환이 충분한지 확인

업데이트 결과로 roomId만 반환하면 클라이언트가 방 정보를 이미 알고 있을 가능성이 높아 중복 정보가 될 수 있습니다. 다음 중 하나를 고려해 보세요.

  • 204 No Content로 단순화
  • 최소한 postId(레코드 id) 또는 updatedAt/버전 등을 포함해 클라이언트 캐시 무효화/동기화에 도움 제공

현재 형태를 유지한다면, Swagger 스키마에 의도(왜 roomId만 필요한지)를 명시해 혼동을 줄이는 것을 권장합니다.

src/main/java/konkuk/thip/roompost/domain/Record.java (1)

104-108: 도메인 불변식 보강: content 길이(≤500)·null 검증 추가 제안

“기록 content 500자 제한”이 도메인 레벨에서 보장되지 않습니다. 서비스/컨트롤러에서만 검증하면 도메인 직접 호출 경로에서 누락될 수 있습니다. 아래처럼 최소 검증을 권장합니다. ErrorCode 매핑은 프로젝트 규약에 맞게 조정하세요.

 public void updateRecord(Long userId, Long roomId, String content) {
     validateRoomId(roomId);
     validateCreator(userId);
-    this.content = content;
+    final String newContent = java.util.Objects.requireNonNull(content, "content must not be null");
+    if (newContent.length() > 500) {
+        // TODO: 프로젝트 규약에 맞게 ErrorCode를 신설/적용하세요 (예: RECORD_INVALID_CONTENT_LENGTH).
+        throw new IllegalArgumentException("기록 내용은 최대 500자까지 가능합니다.");
+    }
+    this.content = newContent;
 }

검증 포인트

  • RecordUpdateRequest에 @SiZe(max=500) 존재 여부 확인
  • DB 컬럼 길이 제약 동기화
  • 테스트 경계값 케이스(500/501자) 존재 여부 확인
src/main/java/konkuk/thip/roompost/adapter/in/web/response/VoteUpdateResponse.java (1)

3-8: 응답 페이로드 재검토: roomId만 반환이 충분한지 확인

RecordUpdateResponse와 동일한 맥락입니다. 업데이트 후 클라이언트가 실제로 필요한 식별자/메타데이터(postId, updatedAt, version 등)를 포함할지, 아니면 204 No Content로 단순화할지 결정하면 좋겠습니다. 결정 사유는 Swagger에 명시하면 API 소비자가 이해하기 쉽습니다.

src/main/java/konkuk/thip/roompost/application/port/in/dto/vote/VoteUpdateCommand.java (1)

3-9: 필드명 변경 제안: VoteUpdateCommandpostIdvoteId 명확화

VoteUpdateCommand는 투표(vote) 전용 커맨드이므로, 내부 필드명(postId)을 voteId로 변경하면 의도를 더 명확히 전달할 수 있습니다. 이를 위해 최소한 다음 지점들을 함께 리팩터링해야 합니다:

  • DTO 정의
    • src/main/java/konkuk/thip/roompost/application/port/in/dto/vote/VoteUpdateCommand.java
       public record VoteUpdateCommand(
               Long roomId,
  •        Long postId,
    
  •        Long voteId,
           Long userId,
           String content
    
    ) {}
    
    
  • 웹 요청 매핑
    • src/main/java/konkuk/thip/roompost/adapter/in/web/request/VoteUpdateRequest.java
      • toCommand 매개변수명 및 순서 일치화
  • 유스케이스 인터페이스
    • src/main/java/konkuk/thip/roompost/application/port/in/RoomPostUpdateUseCase.java
      • Long updateVote(VoteUpdateCommand command);
  • 서비스 구현
    • src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java
      • command.postId()command.voteId()
      • voteCommandPort.getByIdOrThrow(command.voteId());
  • 응답 및 결과 DTO
    • src/main/java/konkuk/thip/roompost/application/port/in/dto/vote/VoteResult.java
    • src/main/java/konkuk/thip/roompost/adapter/in/web/response/VoteResponse.java
      • 각 클래스의 postId 필드를 voteId로 일치시킬지 검토
  • 테스트
    • src/test/java/konkuk/thip/roompost/adapter/in/web/VoteApiTest.java
      • JSON 경로($.data.postId) 및 vote.getPostId() 검증 로직
  • 검색/매퍼 등 기타 참조
    • RoomPostSearchServicedto.postId()
    • ReactionQueryMapperpostId 매핑부

위 변경은 용어 통일과 의도 명확화 차원에서 권장되는 선택적(refactor) 작업이며, 연쇄적인 사용처 수정이 필요합니다. 변경 후 영향 범위를 다시 한번 확인해 주세요. 추가 도움이 필요하시면 알려주세요.

src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java (1)

46-49: findById(readOnly) 트랜잭션 힌트

어댑터 레이어에서 트랜잭션을 강제할 필요는 없지만, 조회 메서드는 상위 서비스(@transactional(readOnly = true)) 경계에서 실행되도록 구성하면 성능 및 의도 전달에 도움이 됩니다. 현재 서비스에 트랜잭션 경계가 없다면 추가를 권장합니다(아래 서비스 코멘트 참조).

src/main/java/konkuk/thip/roompost/adapter/in/web/request/VoteUpdateRequest.java (1)

14-21: 양끝 공백 정규화 여부 결정

입력 값의 양끝 공백을 허용하지 않는 정책이라면 커맨드로 매핑 시 trim()을 적용해 저장 데이터 일관성을 확보하는 것을 권장합니다. 웹 레이어에서 정규화하지 않을 경우 도메인에서 일관되게 처리하는 대안도 있습니다.

적용 예시(diff):

-                        this.content
+                        this.content.trim()
src/main/java/konkuk/thip/roompost/adapter/in/web/request/RecordUpdateRequest.java (1)

14-21: 양끝 공백 정규화 여부 결정

Vote와 동일하게 공백 정책을 정하고 일관되게 적용하세요. 웹에서 정규화하려면 다음과 같이 변경 가능합니다.

-                        this.content
+                        this.content.trim()
src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (3)

22-36: 서비스 레이어에 트랜잭션 경계 추가 권장

검증(조회) → 엔티티 변경 → 저장의 여러 DB 상호작용을 하나의 원자적 단위로 보장하려면 서비스 레벨에 @Transactional을 두는 것이 바람직합니다. 현재는 리포지토리 단위 트랜잭션에 의존하게 되어, 추후 로직 확장 시 일관성이 흔들릴 수 있습니다.

적용 예시(diff):

 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;

 @Service
 @RequiredArgsConstructor
+@Transactional
 public class RoomPostUpdateService implements RoomPostUpdateUseCase {

필요 시 읽기 전용 쿼리성 유즈케이스에는 @Transactional(readOnly = true)를 별도 부여하세요.


31-35: Record 업데이트 흐름 타당성(소속 검증 → 로드 → 도메인 업데이트 → 영속화)

방 참가자 검증 후 도메인 메서드로 수정, 포트 update(record)로 반영하는 순서가 명확합니다. 다만 반환값으로 command.roomId()를 돌려주는 대신, 불일치 방어를 강화하려면 로드한 엔티티의 record.getRoomId()를 반환하는 방식을 고려할 수 있습니다.

-        return command.roomId();
+        return record.getRoomId();

39-51: 포트 메서드 네이밍 일관성 제안(update vs updateVote)

Record는 update(record)인데 Vote는 updateVote(vote)로 달라 가독성이 다소 떨어집니다. 포트/어댑터 전반에서 update(T entity) 형태로 통일하면 호출부 사고부하가 줄어듭니다. 추후 리팩토링 이슈로 분리 제안합니다.

원하시면 포트/어댑터/서비스/테스트 전반 네이밍 일괄 치환 스크립트를 제공하겠습니다.

src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (2)

128-141: 검증 메시지/명세 용어 불일치: 투표는 20자 제한

비즈니스 규칙은 "투표 content 최대 20자"인데 테스트 설명과 데이터는 500자 기준을 사용하고 있습니다. 동작상 501자도 20자 제한에 걸려 실패하므로 테스트는 통과하지만, 독자에게 오해를 유발합니다. 설명과 입력을 20자 기준으로 맞춰 주세요.

권장 수정(diff):

-    @DisplayName("[실패-검증] content가 500자 초과면 400 Bad Request")
+    @DisplayName("[실패-검증] content가 20자 초과면 400 Bad Request")
 void update_vote_validation_too_long() throws Exception {
-    String tooLong = "가".repeat(501);
+    String tooLong = "가".repeat(21); // 21자 → 제한(20자) 초과

경계값(정확히 20자) 성공 케이스도 추가해 주세요:

@Test
@DisplayName("[성공-검증] content가 정확히 20자면 200 OK")
void update_vote_validation_boundary_20_ok() throws Exception {
    String twenty = "가".repeat(20);
    Map<String, Object> body = Map.of("content", twenty);

    mockMvc.perform(patch("/rooms/{roomId}/votes/{voteId}", room.getRoomId(), vote.getPostId())
                    .requestAttr("userId", author.getUserId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(body)))
            .andExpect(status().isOk());

    assertThat(voteJpaRepository.findById(vote.getPostId()).orElseThrow().getContent())
            .isEqualTo(twenty);
}

114-126: 공백 입력 검증 케이스 보강 제안

"" 케이스 외에 " "(공백만)도 @NotBlank에 의해 실패해야 합니다. 하나 더 추가하면 입력 검증 신뢰도가 높아집니다.

src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java (6)

95-112: 성공 케이스에서 응답 바디 계약도 함께 검증해 주세요

상태코드와 DB 반영만 확인하고 있습니다. 응답 스키마(BaseResponse)의 핵심 필드 존재(예: $.data) 또는 일부 값을 함께 검증하면 계약 회귀를 더 빨리 잡을 수 있습니다.

예시(스키마에 맞춰 조정):

-                .andExpect(status().isOk());
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.data").exists());
+                // 가능하다면 아래도 확인(필드명이 다르면 맞게 수정)
+                // .andExpect(jsonPath("$.data.postId").value(record.getPostId()))
+                // .andExpect(jsonPath("$.data.content").value(newContent));

114-129: 공백 문자열 외에도 whitespace-only 및 필드 누락(null) 케이스를 추가해 주세요

현재 ""만 검증합니다. " " 같은 공백만 있는 경우와 content 키 자체가 없는 경우도 @notblank 기대 동작을 확인해 두면 견고합니다.

예시 추가 테스트:

@Test
@DisplayName("[실패-검증] content가 공백문자만이면 400 Bad Request")
void update_record_validation_whitespace_only() throws Exception {
    Map<String, Object> body = Map.of("content", "   ");
    mockMvc.perform(patch("/rooms/{roomId}/records/{recordId}", room.getRoomId(), record.getPostId())
                    .requestAttr("userId", author.getUserId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(body)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode()))
            .andExpect(jsonPath("$.message").exists());
}

@Test
@DisplayName("[실패-검증] content 필드 누락 시 400 Bad Request")
void update_record_validation_missing_field() throws Exception {
    Map<String, Object> body = Map.of(); // content 없음
    mockMvc.perform(patch("/rooms/{roomId}/records/{recordId}", room.getRoomId(), record.getPostId())
                    .requestAttr("userId", author.getUserId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(body)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode()))
            .andExpect(jsonPath("$.message").exists());
}

130-145: 경계값(정확히 500자) 허용 성공 케이스도 추가 권장

현재는 501자 초과만 실패를 확인합니다. 500자 정확히 허용되는지 성공 테스트를 한 건 추가하면 요구사항을 더 명확히 보장할 수 있습니다.

예시:

@Test
@DisplayName("[성공-검증] content가 정확히 500자면 200 OK")
void update_record_validation_boundary_500() throws Exception {
    String boundary = "가".repeat(500);
    Map<String, Object> body = Map.of("content", boundary);

    mockMvc.perform(patch("/rooms/{roomId}/records/{recordId}", room.getRoomId(), record.getPostId())
                    .requestAttr("userId", author.getUserId())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(body)))
            .andExpect(status().isOk());
}

147-185: 실패 케이스에서 에러 코드(code)도 함께 검증하면 좋습니다

403 Forbidden 케이스들에서 message만 확인하고 있습니다. 에러 코드까지 검증하면 에러 매핑 변경으로 인한 회귀를 조기에 잡을 수 있습니다.

예시:

.andExpect(status().isForbidden())
// .andExpect(jsonPath("$.code").value(API_FORBIDDEN.getCode())) // 프로젝트 공통 ErrorCode에 맞춰 상수 사용
.andExpect(jsonPath("$.message").exists());

187-201: 404 Not Found 케이스도 에러 코드 검증을 추가해 주세요

현재 message만 확인합니다. 공통 ErrorCode에 맞추어 code 필드 검증을 추가하면 계약 안정성이 올라갑니다.


209-215: roomId 불일치 시나리오에서 권한 변수 제거로 테스트 의도를 선명하게

현재 author는 otherRoom의 참가자가 아니기 때문에 403의 원인이 "미참가자"인지 "room-Record 불일치"인지 모호합니다. author를 otherRoom 참가자로 추가해 권한 요인을 배제하면 무결성 검증 의도가 더 명확해집니다.

아래처럼 참가자 추가:

         void setUpOtherRoom() {
             BookJpaEntity book2 = bookJpaRepository.save(TestEntityFactory.createBook());
             otherRoom = roomJpaRepository.save(TestEntityFactory.createRoom(book2, TestEntityFactory.createScienceCategory()));
-            // author는 otherRoom 참가자가 아님(또는 참가자라도 기록은 원래 room 소속)
+            // author를 otherRoom 참가자로 추가하여 권한 변수 제거
+            roomParticipantJpaRepository.save(
+                    RoomParticipantJpaEntity.builder()
+                            .currentPage(0)
+                            .userPercentage(0.0)
+                            .roomParticipantRole(RoomParticipantRole.MEMBER)
+                            .userJpaEntity(author)
+                            .roomJpaEntity(otherRoom)
+                            .build()
+            );
         }
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (2)

136-151: @ExceptionDescription 상수 사용 일관화(정적 임포트 활용) 및 문구 정교화

다른 곳에서는 정적 임포트(RECORD_CREATE 등)를 쓰고 있는데, 여기서는 FQCN을 사용하고 있어 일관성이 깨집니다. 또한 파라미터 설명의 "게시글"을 "기록"으로 구체화하면 가독성이 좋아집니다.

아래 수정 제안:

-import konkuk.thip.common.swagger.SwaggerResponseDescription;
 ...
-    @ExceptionDescription(SwaggerResponseDescription.RECORD_UPDATE)
+    @ExceptionDescription(RECORD_UPDATE)
     public BaseResponse<RecordUpdateResponse> updateRecord(
             @Parameter(hidden = true) @UserId Long userId,
             @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 게시글 ID", example = "1") @PathVariable Long recordId,
+            @Parameter(description = "수정할 기록 ID", example = "1") @PathVariable Long recordId,
             @RequestBody @Valid final RecordUpdateRequest request
     ) {

참고: 위 변경으로 SwaggerResponseDescription 일반 임포트는 불필요해집니다(이미 import static ...SwaggerResponseDescription.*; 존재).


136-168: REST 경로 네이밍 일관성 제안: 단수/복수 혼재 정리

create는 /rooms/{roomId}/record//vote(단수), 수정·삭제는 /rooms/{roomId}/records/{recordId}//votes/{voteId}(복수)로 혼재되어 있습니다. 향후 API 확장성과 직관성을 위해 복수형으로 통일(POST /rooms/{roomId}/records, /rooms/{roomId}/votes)을 고려해 주세요. 현재 변경 분량과 리스크를 감안해 후속 리팩터링 이슈로 분리하는 것을 추천합니다.

원하시면 마이그레이션 전략(기존 경로를 deprecated로 유지 + 새 경로 추가)도 함께 제안드리겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 31decda and 004c666.

📒 Files selected for processing (17)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (3 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/request/RecordUpdateRequest.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/request/VoteUpdateRequest.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/response/RecordUpdateResponse.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/response/VoteUpdateResponse.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/port/in/RoomPostUpdateUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/port/in/dto/record/RecordUpdateCommand.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/port/in/dto/vote/VoteUpdateCommand.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/port/out/RecordCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/domain/Record.java (1 hunks)
  • src/main/java/konkuk/thip/roompost/domain/Vote.java (2 hunks)
  • src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.

Applied to files:

  • src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
🧬 Code graph analysis (2)
src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java (2)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (30-394)
src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (2)
  • Nested (191-219)
  • SpringBootTest (37-220)
src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (2)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (30-394)
src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java (1)
  • SpringBootTest (37-233)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (21)
src/main/java/konkuk/thip/roompost/adapter/in/web/response/RecordUpdateResponse.java (1)

6-8: 정적 팩토리 유지 타당—팀 컨벤션 일관성 확보

record 생성자가 간단하므로 new로도 충분하지만, of(...) 정적 팩토리가 기존 응답 DTO들과 컨벤션을 맞춘 것이라면 유지에 동의합니다.

src/main/java/konkuk/thip/roompost/adapter/in/web/response/VoteUpdateResponse.java (1)

6-8: 정적 팩토리 일관성 OK

RecordUpdateResponse와의 형태적 일관성이 좋아 유지에 동의합니다.

src/main/java/konkuk/thip/roompost/application/port/in/dto/record/RecordUpdateCommand.java (1)

1-9: LGTM! 잘 설계된 immutable command 객체입니다.

Java record를 사용하여 불변성을 보장하고, 업데이트에 필요한 모든 필드(roomId, postId, userId, content)를 명확하게 정의했습니다. 명명 규칙도 일관성 있게 잘 되어 있습니다.

src/main/java/konkuk/thip/roompost/application/port/in/RoomPostUpdateUseCase.java (1)

6-9: LGTM! 깔끔하고 명확한 인터페이스 설계입니다.

업데이트 작업을 위한 use case 인터페이스가 잘 정의되어 있습니다. Record와 Vote 업데이트 메서드가 일관된 패턴으로 구성되어 있고, 반환 타입도 적절합니다.

src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)

153-157: LGTM! 기존 DELETE 패턴과 일관성 있는 에러 코드 구성입니다.

RECORD_UPDATE의 에러 코드들이 RECORD_DELETE와 동일한 패턴(ROOM_ACCESS_FORBIDDEN, RECORD_NOT_FOUND, RECORD_ACCESS_FORBIDDEN)으로 구성되어 있어 일관성이 좋습니다.


180-184: LGTM! Vote 업데이트 에러 코드도 일관성 있게 구성되었습니다.

VOTE_UPDATE의 에러 코드들이 VOTE_DELETE와 동일한 패턴으로 구성되어 있어, Record와 Vote 업데이트 간의 일관성도 유지하고 있습니다.

src/main/java/konkuk/thip/roompost/application/port/out/RecordCommandPort.java (2)

13-13: 적절한 메서드명 일반화입니다.

saveRecord에서 save로 변경한 것은 좋은 개선입니다. 포트 인터페이스에서 메서드명이 더 간결하고 일반적이 되었으며, 다른 엔티티들의 save 패턴과도 일치합니다.


13-25: RecordCommandPort.update 메서드 사용 확인 완료

  • RoomPostUpdateService (src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java)에서 recordCommandPort.update(record) 호출
  • PostHandler (src/main/java/konkuk/thip/post/application/service/handler/PostHandler.java) 내 case RECORD -> recordCommandPort.update((Record) post) 호출

위 호출들을 통해 update 메서드가 새로 추가된 업데이트 흐름에 올바르게 연동되어 있음을 확인했습니다. 더 이상의 수정이나 검토가 필요하지 않습니다.

src/main/java/konkuk/thip/roompost/application/service/RecordCreateService.java (1)

61-61: LGTM! 포트 인터페이스 변경에 맞춰 올바르게 업데이트되었습니다.

saveRecord에서 save로 메서드 호출이 변경되었으며, 반환 타입과 매개변수는 동일하게 유지되어 기능상 변경사항이 없습니다.

src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java (2)

53-59: 소프트 삭제 흐름 타당성 확인

ACTIVE 상태에서만 조회 후 softDelete()save하는 흐름은 무결성 측면에서 적절합니다. 삭제된 레코드에 대한 중복 삭제가 RECORD_NOT_FOUND로 귀결되는 것도 일관적입니다.


31-43: 메서드명 리네임 완료 확인

전체 코드베이스에서 saveRecord 호출을 검색한 결과, 더 이상 해당 메서드명이 존재하지 않음을 확인했습니다. 따라서 save(Record)로의 리네임이 안전하게 적용되었습니다.

src/main/java/konkuk/thip/roompost/adapter/in/web/request/VoteUpdateRequest.java (1)

8-13: 검증 어노테이션 구성 적절(20자 제한, 공백 불가)

@NotBlank + @Size(max = 20)로 요구사항을 정확히 반영했습니다. 스키마 예시 길이도 제한 내입니다.

src/main/java/konkuk/thip/roompost/adapter/in/web/request/RecordUpdateRequest.java (1)

8-13: 검증 어노테이션 구성 적절(500자 제한, 공백 불가)

레코드 콘텐츠 500자 제한이 명확히 반영되어 있습니다. 메시지 포맷도 일관적입니다.

src/main/java/konkuk/thip/roompost/application/service/RoomPostUpdateService.java (1)

27-34: 확인: getByIdOrThrow 기본 구현 제공 완료

서비스에서 사용하는 두 포트 인터페이스에 모두 getByIdOrThrow가 default 메서드로 구현되어 있어, 누락에 따른 컴파일 오류는 발생하지 않습니다.

  • src/main/java/konkuk/thip/roompost/application/port/out/RecordCommandPort.java (19–22행)
  • src/main/java/konkuk/thip/roompost/application/port/out/VoteCommandPort.java (24–27행)

이상으로 해당 검증 사항은 완료되었습니다.

src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (5)

95-112: 성공 케이스 구성 적절

HTTP 200 확인 후 DB 상태까지 검증하는 흐름이 좋습니다. 요청자 식별은 requestAttr("userId", ...)로 일관되게 모킹되어 있습니다.


143-158: 방 참가자 아님 → 403 및 DB 불변 검증 적절

권한 거부 시 기존 내용 유지 확인까지 포함되어 있어 회귀를 잘 잡을 수 있습니다.


160-175: 작성자 아님 → 403 및 DB 불변 검증 적절

작성자 검증 로직의 요구사항을 잘 커버합니다.


177-189: 미존재 리소스 → 404 검증 적절

최대값 ID 사용으로 존재하지 않음을 명확히 표현했습니다.


191-219: roomId 불일치 시 접근 거부 케이스 유용

교차 방 접근 차단 요구사항을 안정적으로 검증합니다.

src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java (1)

37-58: 테스트 구성과 시나리오 커버리지가 전반적으로 훌륭합니다

작성자/타 참가자/외부인, 잘못된 roomId, 존재하지 않는 recordId 등 주요 경로를 모두 커버하고 있고, DB 상태까지 검증하는 점이 좋습니다.

src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (1)

30-31: RoomPostUpdateUseCase 주입 및 분리 👍

생성/삭제와 수정(use case)을 분리해 의존성 방향이 명확해졌고, 유지보수성 측면에서 좋은 구조입니다.

Comment on lines +153 to +168
@Operation(
summary = "투표 수정",
description = "사용자가 방 투표를 수정합니다. (투표 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/votes/{voteId}")
@ExceptionDescription(SwaggerResponseDescription.VOTE_UPDATE)
public BaseResponse<VoteUpdateResponse> updateVote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 게시글 ID", example = "1") @PathVariable Long voteId,
@RequestBody @Valid final VoteUpdateRequest request
) {
return BaseResponse.ok(VoteUpdateResponse.of(
roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

동일하게 Vote 수정 엔드포인트도 정적 임포트와 문구 정리 + 삭제 엔드포인트의 예외 기술 오타 수정 필요

  • 여기서도 FQCN 대신 정적 임포트를 사용해 주세요.
  • "게시글 ID" → "투표 ID"로 명확화.
  • 별도 위치이지만, 삭제 엔드포인트의 @ExceptionDescription(RECORD_DELETE)VOTE_DELETE로 교체해야 Swagger 문서가 정확합니다.

적용 제안:

-    @ExceptionDescription(SwaggerResponseDescription.VOTE_UPDATE)
+    @ExceptionDescription(VOTE_UPDATE)
     public BaseResponse<VoteUpdateResponse> updateVote(
             @Parameter(hidden = true) @UserId Long userId,
             @Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
-            @Parameter(description = "수정할 게시글 ID", example = "1") @PathVariable Long voteId,
+            @Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
             @RequestBody @Valid final VoteUpdateRequest request
     ) {

파일 내 다른 위치(삭제 API)의 오타 수정(선택 블록 밖 변경):

-    @ExceptionDescription(RECORD_DELETE)
+    @ExceptionDescription(VOTE_DELETE)
     @DeleteMapping("/rooms/{roomId}/vote/{voteId}")
     public BaseResponse<VoteDeleteResponse> deleteVote( ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Operation(
summary = "투표 수정",
description = "사용자가 방 투표를 수정합니다. (투표 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/votes/{voteId}")
@ExceptionDescription(SwaggerResponseDescription.VOTE_UPDATE)
public BaseResponse<VoteUpdateResponse> updateVote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 게시글 ID", example = "1") @PathVariable Long voteId,
@RequestBody @Valid final VoteUpdateRequest request
) {
return BaseResponse.ok(VoteUpdateResponse.of(
roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
));
}
@Operation(
summary = "투표 수정",
description = "사용자가 방 투표를 수정합니다. (투표 내용만 수정 가능)"
)
@PatchMapping("/rooms/{roomId}/votes/{voteId}")
@ExceptionDescription(VOTE_UPDATE)
public BaseResponse<VoteUpdateResponse> updateVote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "수정할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "수정할 투표 ID", example = "1") @PathVariable Long voteId,
@RequestBody @Valid final VoteUpdateRequest request
) {
return BaseResponse.ok(VoteUpdateResponse.of(
roomPostUpdateUseCase.updateVote(request.toCommand(userId, roomId, voteId))
));
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java
around lines 153 to 168, replace the fully-qualified reference to
SwaggerResponseDescription.VOTE_UPDATE with a static import of VOTE_UPDATE,
update the @Parameter description for the path variable currently labeled "수정할
게시글 ID" to "수정할 투표 ID", and also fix the separate delete endpoint (elsewhere in
the same file) by replacing @ExceptionDescription(RECORD_DELETE) with
@ExceptionDescription(VOTE_DELETE) so the Swagger docs are accurate.

Comment on lines +130 to +145
@Test
@DisplayName("[실패-검증] content가 500자 초과면 400 Bad Request")
void update_record_validation_too_long() throws Exception {
// given
String tooLong = "가".repeat(501);
Map<String, Object> body = Map.of("content", tooLong);

// when & then
mockMvc.perform(patch("/rooms/{roomId}/records/{recordId}", room.getRoomId(), record.getPostId())
.requestAttr("userId", author.getUserId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode()))
.andExpect(jsonPath("$.message").exists());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

[교차 확인] Vote 업데이트의 글자 수 제한(20자) 규칙과 테스트/검증 일치 여부 점검 필요

PR 요약에 따르면 Vote content는 20자 제한, Record는 500자 제한입니다. 현재 VoteUpdateApiTest에는 500자 초과 검증이 보입니다(관련 스니펫 기준). 실제 DTO/도메인 제약과 테스트가 일치하는지 확인해 주세요.

아래 스크립트로 제한 값을 한 번에 점검할 수 있습니다(리포지토리 루트에서 실행).


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== DTO 제한 확인(Record/Vote Update Request) ==="
rg -nP --type=java -C2 'class\s+RecordUpdateRequest|class\s+VoteUpdateRequest|@Size|@Length|MAX_?LENGTH|limit' || true

echo
echo "=== 도메인(Record/Vote) content 제약 확인 ==="
rg -nP --type=java -C2 'class\s+Record\b|class\s+Vote\b|content|@Size|@Length|MAX_?LENGTH|limit' src/main/java || true

echo
echo "=== 테스트에서 길이 검증 사용하는 곳 확인 ==="
rg -nP --type=java -n 'repeat\(\d+\)|too_long|tooLong|500|20' src/test/java || true

Length of output: 187357


[교차 확인] VoteUpdateApiTest 글자 수 검증 범위 수정 요청

VoteUpdateRequest에 설정된 @Size(max = 20) 제약과 일치하도록, VoteUpdateApiTest에서 테스트 대상 글자 수를 20자 초과로 변경해야 합니다.

  • 파일: src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (라인 129–132)
    • String tooLong = "가".repeat(501);String tooLong = "가".repeat(21);
    • @DisplayName("[실패-검증] content가 500자 초과면 400 Bad Request")@DisplayName("[실패-검증] content가 20자 초과면 400 Bad Request")
    • 검증 메시지도 "투표 내용은 최대 20자 입니다."를 명시적으로 확인하도록 jsonPath("$.message", containsString("투표 내용은 최대 20자 입니다.")) 추가

RecordUpdateApiTest는 @Size(max = 500)과 일치하여 변경 불필요합니다.

🤖 Prompt for AI Agents
In src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java
around lines 130–145: no change required — this test aligns with the
@Size(max=500) constraint so leave it as-is; if the reviewer intended
VoteUpdateApiTest, update
src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java at
lines ~129–132 by changing String tooLong = "가".repeat(501) to "가".repeat(21),
update the @DisplayName to "[실패-검증] content가 20자 초과면 400 Bad Request", and add
an assertion jsonPath("$.message", containsString("투표 내용은 최대 20자 입니다.")) to
explicitly verify the validation message.

seongjunnoh
seongjunnoh previously approved these changes Aug 25, 2025
Copy link
Copy Markdown
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

내용만 수정가능한게 요구사항이었군요! 확인했습니다!

Comment on lines +104 to +108
public void updateRecord(Long userId, Long roomId, String content) {
validateRoomId(roomId);
validateCreator(userId);
this.content = content;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment on lines +98 to +102
public void updateVote(Long userId, Long roomId, String content) {
validateRoomId(roomId);
validateCreator(userId);
this.content = content;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment on lines +9 to +12
@Schema(description = "기록 내용", example = "띱은 최고의 서비스인가?")
@NotBlank(message = "기록 내용은 필수입니다.")
@Size(max = 500, message = "기록 내용은 최대 500자 입니다.")
String content
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment on lines +37 to +42
@SpringBootTest
@ActiveProfiles("test")
@Transactional
@AutoConfigureMockMvc(addFilters = false)
@DisplayName("[통합] 기록 수정 api 통합 테스트")
class RecordUpdateApiTest {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

오 세세한 테스트케이스까지 모두 통합테스트 코드로 작성해주셨군요! 좋습니다

@seongjunnoh
Copy link
Copy Markdown
Collaborator

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)

src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java (1)> 62-68: Record 업데이트 시 콘텐츠 필드 한정 보장 필요

RecordJpaEntity#updateFrom(Record) 구현을 확인한 결과, 해당 메서드는 content 외에도 likeCount, commentCount, page, isOverview를 모두 변경하고 있습니다.
요구사항상 Record 업데이트는 콘텐츠(content) 필드만 수정되어야 하므로, 반드시 아래와 같이 수정이 필요합니다.
수정이 필요한 위치:

  • src/main/java/konkuk/thip/roompost/adapter/out/jpa/RecordJpaEntity.java 39–44번째 줄

제안 방안:

  • RecordJpaEntity에 콘텐츠만 반영하는 전용 메서드 추가
    public RecordJpaEntity updateContent(String content) {
        this.content = content;
        return this;
    }
  • RecordCommandPersistenceAdapter.update(…)에서는 전용 메서드만 호출하도록 변경
    public void update(Record record) {
        RecordJpaEntity entity = recordJpaRepository
            .findByPostIdAndStatus(record.getId(), ACTIVE)
            .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND));
    
    -   recordJpaRepository.save(entity.updateFrom(record));
    +   entity.updateContent(record.getContent());
    +   recordJpaRepository.save(entity);
    }

위와 같이 수정하면 콘텐츠 외 필드가 의도치 않게 변경되는 문제를 제거할 수 있습니다.

🧹 Nitpick comments (21)
📜 Review details
🔇 Additional comments (21)

p3 : 코래 말처럼 content 만을 업데이트하는 메서드를 Record, Vote jpa entity 에 정의하고, 해당 메서드를 사용하는것도 좋아보입니다!!
코래 리뷰처럼 다른 필드가 이상한 값으로 변경되는 상황은 없을 것 같지만(어차피 find한 값을 고대로 다시 매핑하는 것이므로), 여러 필드를 매핑하는 것보다는 content 만 매핑하도록 코드를 수정한다면 더 효율적이지 않나 생각합니다!!

@buzz0331
Copy link
Copy Markdown
Contributor Author

Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)

src/main/java/konkuk/thip/roompost/adapter/out/persistence/RecordCommandPersistenceAdapter.java (1)> 62-68: Record 업데이트 시 콘텐츠 필드 한정 보장 필요

RecordJpaEntity#updateFrom(Record) 구현을 확인한 결과, 해당 메서드는 content 외에도 likeCount, commentCount, page, isOverview를 모두 변경하고 있습니다.
요구사항상 Record 업데이트는 콘텐츠(content) 필드만 수정되어야 하므로, 반드시 아래와 같이 수정이 필요합니다.
수정이 필요한 위치:

  • src/main/java/konkuk/thip/roompost/adapter/out/jpa/RecordJpaEntity.java 39–44번째 줄

제안 방안:

  • RecordJpaEntity에 콘텐츠만 반영하는 전용 메서드 추가
    public RecordJpaEntity updateContent(String content) {
        this.content = content;
        return this;
    }
  • RecordCommandPersistenceAdapter.update(…)에서는 전용 메서드만 호출하도록 변경
    public void update(Record record) {
        RecordJpaEntity entity = recordJpaRepository
            .findByPostIdAndStatus(record.getId(), ACTIVE)
            .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND));
    
    -   recordJpaRepository.save(entity.updateFrom(record));
    +   entity.updateContent(record.getContent());
    +   recordJpaRepository.save(entity);
    }

위와 같이 수정하면 콘텐츠 외 필드가 의도치 않게 변경되는 문제를 제거할 수 있습니다.

🧹 Nitpick comments (21)
📜 Review details
🔇 Additional comments (21)

p3 : 코래 말처럼 content 만을 업데이트하는 메서드를 Record, Vote jpa entity 에 정의하고, 해당 메서드를 사용하는것도 좋아보입니다!! 코래 리뷰처럼 다른 필드가 이상한 값으로 변경되는 상황은 없을 것 같지만(어차피 find한 값을 고대로 다시 매핑하는 것이므로), 여러 필드를 매핑하는 것보다는 content 만 매핑하도록 코드를 수정한다면 더 효율적이지 않나 생각합니다!!

저도 해당 부분을 고려하긴 했습니다. 다만 현재 저희의 업데이트 컨벤션은 updateFrom 메서드 하나로 엔티티 전체 필드를 업데이트하는 방식이고, 추후 요구사항에 따라 수정 대상 필드가 늘어날 가능성도 있기 때문에 굳이 updateContent와 같은 전용 메서드를 추가하여 일관성을 깨는 필요는 크지 않다고 생각했습니다.

즉, 지금 구조에서는 다른 필드가 의도치 않게 변경될 위험은 거의 없고(find한 값을 그대로 다시 매핑하기 때문), 오히려 현재의 일관된 updateFrom 패턴을 유지하는 편이 유지보수 측면에서 더 낫다고 판단했습니다.

Copy link
Copy Markdown
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

확인햇슴다

Copy link
Copy Markdown

@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 (9)
src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (9)

71-89: 중복된 RoomParticipant 픽스처는 Factory 메서드로 단순화하세요

현재 빌더 블록이 2회 반복됩니다. currentPage 값은 테스트 결과에 영향이 없으므로 TestEntityFactory.createRoomParticipant(...)를 사용해 간결하게 유지하는 편이 좋습니다.

아래와 같이 치환을 제안합니다:

-        roomParticipantJpaRepository.save(
-                RoomParticipantJpaEntity.builder()
-                        .currentPage(10)
-                        .userPercentage(80.0)
-                        .roomParticipantRole(RoomParticipantRole.HOST)
-                        .userJpaEntity(author)
-                        .roomJpaEntity(room)
-                        .build()
-        );
-        roomParticipantJpaRepository.save(
-                RoomParticipantJpaEntity.builder()
-                        .currentPage(5)
-                        .userPercentage(50.0)
-                        .roomParticipantRole(RoomParticipantRole.MEMBER)
-                        .userJpaEntity(otherMember)
-                        .roomJpaEntity(room)
-                        .build()
-        );
+        roomParticipantJpaRepository.save(
+                TestEntityFactory.createRoomParticipant(room, author, RoomParticipantRole.HOST, 80.0)
+        );
+        roomParticipantJpaRepository.save(
+                TestEntityFactory.createRoomParticipant(room, otherMember, RoomParticipantRole.MEMBER, 50.0)
+        );

95-112: 성공 케이스에서 응답 페이로드와 비-수정 필드 불변성까지 함께 검증하세요

요구사항상 content만 수정 가능하므로, 응답 본문의 content 필드와 DB에서 다른 필드들이 변하지 않았음을 함께 검증하면 회귀를 막을 수 있습니다.

아래 보강을 제안합니다:

     void update_vote_success() throws Exception {
         // given
         String newContent = "수정된 투표 내용";
         Map<String, Object> body = Map.of("content", newContent);
+        int prevPage = vote.getPage();
+        int prevCommentCount = vote.getCommentCount();
+        int prevLikeCount = vote.getLikeCount();

         // when & then
         mockMvc.perform(patch("/rooms/{roomId}/votes/{voteId}", room.getRoomId(), vote.getPostId())
                         .requestAttr("userId", author.getUserId())
                         .contentType(MediaType.APPLICATION_JSON)
                         .content(objectMapper.writeValueAsString(body)))
-                .andExpect(status().isOk());
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.content").value(newContent));

         // DB 반영 확인
         VoteJpaEntity updated = voteJpaRepository.findById(vote.getPostId()).orElseThrow();
         assertThat(updated.getContent()).isEqualTo(newContent);
+        assertThat(updated.getPage()).isEqualTo(prevPage);
+        assertThat(updated.getCommentCount()).isEqualTo(prevCommentCount);
+        assertThat(updated.getLikeCount()).isEqualTo(prevLikeCount);
     }

114-127: 공백문자만 있는 입력도 ‘무효’로 검증하는 케이스를 추가하세요

빈 문자열("")만 검증 중입니다. " \t\n" 등 공백으로만 구성된 입력도 400을 기대한다면 별도 케이스가 필요합니다(@notblank 보장).

다음 테스트 추가를 제안합니다:

     @Test
     @DisplayName("[실패-검증] content가 공백이면 400 Bad Request")
     void update_vote_validation_blank() throws Exception {
         Map<String, Object> body = Map.of("content", "");
@@
                 .andExpect(jsonPath("$.message").exists());
     }
+
+    @Test
+    @DisplayName("[실패-검증] content가 공백문자만 있으면 400 Bad Request")
+    void update_vote_validation_blank_whitespace() throws Exception {
+        Map<String, Object> body = Map.of("content", "  \t\n ");
+
+        mockMvc.perform(patch("/rooms/{roomId}/votes/{voteId}", room.getRoomId(), vote.getPostId())
+                        .requestAttr("userId", author.getUserId())
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode()))
+                .andExpect(jsonPath("$.message").exists());
+    }

128-141: 길이 제한의 경계값(정확히 20자) 성공 케이스를 추가하세요

현재 21자 실패만 검증합니다. 20자 입력이 정상 처리(200, DB 반영)되는지 보장하면 요구사항을 더 정확히 커버합니다.

경계값 성공 테스트를 제안합니다:

     @Test
     @DisplayName("[실패-검증] content가 20자 초과면 400 Bad Request")
     void update_vote_validation_too_long() throws Exception {
         String tooLong = "가".repeat(21);
         Map<String, Object> body = Map.of("content", tooLong);
@@
                 .andExpect(jsonPath("$.message").exists());
     }
+
+    @Test
+    @DisplayName("[성공] content가 20자(경계값)일 때 200 OK")
+    void update_vote_boundary_max_length_success() throws Exception {
+        String boundary = "가".repeat(20);
+        Map<String, Object> body = Map.of("content", boundary);
+
+        mockMvc.perform(patch("/rooms/{roomId}/votes/{voteId}", room.getRoomId(), vote.getPostId())
+                        .requestAttr("userId", author.getUserId())
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isOk());
+
+        VoteJpaEntity updated = voteJpaRepository.findById(vote.getPostId()).orElseThrow();
+        assertThat(updated.getContent()).isEqualTo(boundary);
+    }

149-155: 에러 응답의 코드 필드도 함께 검증하세요

403에서 message만 검증하면 약합니다. 공통 에러 스키마를 따른다면 $.code 존재(또는 기대값 일치)까지 확인해 신뢰도를 높이세요.

-                .andExpect(status().isForbidden())
-                .andExpect(jsonPath("$.message").exists());
+                .andExpect(status().isForbidden())
+                .andExpect(jsonPath("$.code").exists())
+                .andExpect(jsonPath("$.message").exists());

166-172: 작성자 아님(403) 케이스도 에러 코드 검증을 포함하세요

위와 동일합니다.

-                .andExpect(status().isForbidden())
-                .andExpect(jsonPath("$.message").exists());
+                .andExpect(status().isForbidden())
+                .andExpect(jsonPath("$.code").exists())
+                .andExpect(jsonPath("$.message").exists());

187-189: 404 Not Found 응답에도 에러 코드 존재 여부를 검증하세요

에러 포맷 일관성 확인에 유용합니다.

-                .andExpect(status().isNotFound())
-                .andExpect(jsonPath("$.message").exists());
+                .andExpect(status().isNotFound())
+                .andExpect(jsonPath("$.code").exists())
+                .andExpect(jsonPath("$.message").exists());

209-215: roomId 불일치(403) 응답도 에러 코드까지 검증하세요

다른 실패 케이스와 동일한 기준으로 검증을 맞추면 좋습니다.

-                    .andExpect(status().isForbidden())
-                    .andExpect(jsonPath("$.message").exists());
+                    .andExpect(status().isForbidden())
+                    .andExpect(jsonPath("$.code").exists())
+                    .andExpect(jsonPath("$.message").exists());

1-220: 중복된 MockMvc 호출을 헬퍼로 추출하면 테스트 가독성이 올라갑니다

본문에서 patch 호출/직렬화/공통 헤더 설정이 반복됩니다. private 헬퍼로 추출하면 각 케이스가 “의도”만 드러나 더 읽기 쉬워집니다.

예시(추가 코드):

private ResultActions doPatch(Long userId, Long roomId, Long voteId, String content) throws Exception {
    Map<String, Object> body = Map.of("content", content);
    return mockMvc.perform(
        patch("/rooms/{roomId}/votes/{voteId}", roomId, voteId)
            .requestAttr("userId", userId)
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(body))
    );
}

사용 예:

doPatch(author.getUserId(), room.getRoomId(), vote.getPostId(), newContent)
    .andExpect(status().isOk());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 004c666 and 85211f4.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java (3 hunks)
  • src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/roompost/adapter/in/web/RoomPostCommandController.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (2)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (30-394)
src/test/java/konkuk/thip/roompost/adapter/in/web/RecordUpdateApiTest.java (1)
  • Nested (203-232)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/test/java/konkuk/thip/roompost/adapter/in/web/VoteUpdateApiTest.java (2)

37-43: 통합 테스트 구성 및 설정 전반은 적절합니다

@activeprofiles("test") + @transactional + @AutoConfigureMockMvc(addFilters = false) 조합, 그리고 MockMvc 기반 시나리오 구성이 명확합니다. 클래스/시나리오 네이밍도 읽기 좋습니다.


95-112: (run_scripts 실행 중...)

@buzz0331 buzz0331 merged commit 3153d83 into develop Aug 26, 2025
4 checks passed
@buzz0331 buzz0331 deleted the feat/#274-update-record-vote branch August 26, 2025 14:12
@hd0rable
Copy link
Copy Markdown
Member

hd0rable commented Aug 27, 2025

기록, 투표 삭제하기 api도 추후에 리팩토링을 할때 이렇게 합치는 것은 어떨지 제안드립니다~

수고하셨어요~~ 삭제하기 리펙토링시에 참고해서 수정하겠습니다~~
헛근데 트랜잭션 어노테이션이 누락되어있는거같은데 삭제하기 리펙할때 추가해놓겟씁니당!!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-319] [feat] 기록, 투표 수정 api 개발

3 participants