[feat] 기록,투표 수정하기 api 개발#275
Conversation
Walkthrough기록 및 투표 수정 기능 추가: PATCH 엔드포인트 2개, 요청/응답 DTO·커맨드·유스케이스·서비스·도메인 업데이트 메서드 추가, Swagger 응답 설명에 RECORD_UPDATE/VOTE_UPDATE 추가, 일부 퍼시스턴스 메서드명(saveRecord→save) 정리 및 통합 테스트 추가. Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
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 unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
Test Results426 tests 426 ✅ 34s ⏱️ Results for commit 85211f4. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
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.java39–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: 필드명 변경 제안:VoteUpdateCommand의postId→voteId명확화
VoteUpdateCommand는 투표(vote) 전용 커맨드이므로, 내부 필드명(postId)을voteId로 변경하면 의도를 더 명확히 전달할 수 있습니다. 이를 위해 최소한 다음 지점들을 함께 리팩터링해야 합니다:
- DTO 정의
src/main/java/konkuk/thip/roompost/application/port/in/dto/vote/VoteUpdateCommand.javapublic 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.javasrc/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()검증 로직- 검색/매퍼 등 기타 참조
RoomPostSearchService내dto.postId()ReactionQueryMapper등postId매핑부위 변경은 용어 통일과 의도 명확화 차원에서 권장되는 선택적(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.
📒 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.javasrc/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: 정적 팩토리 일관성 OKRecordUpdateResponse와의 형태적 일관성이 좋아 유지에 동의합니다.
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)을 분리해 의존성 방향이 명확해졌고, 유지보수성 측면에서 좋은 구조입니다.
| @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)) | ||
| )); | ||
| } |
There was a problem hiding this comment.
동일하게 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.
| @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.
| @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()); | ||
| } |
There was a problem hiding this comment.
💡 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 || trueLength 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
left a comment
There was a problem hiding this comment.
내용만 수정가능한게 요구사항이었군요! 확인했습니다!
| public void updateRecord(Long userId, Long roomId, String content) { | ||
| validateRoomId(roomId); | ||
| validateCreator(userId); | ||
| this.content = content; | ||
| } |
| public void updateVote(Long userId, Long roomId, String content) { | ||
| validateRoomId(roomId); | ||
| validateCreator(userId); | ||
| this.content = content; | ||
| } |
| @Schema(description = "기록 내용", example = "띱은 최고의 서비스인가?") | ||
| @NotBlank(message = "기록 내용은 필수입니다.") | ||
| @Size(max = 500, message = "기록 내용은 최대 500자 입니다.") | ||
| String content |
| @SpringBootTest | ||
| @ActiveProfiles("test") | ||
| @Transactional | ||
| @AutoConfigureMockMvc(addFilters = false) | ||
| @DisplayName("[통합] 기록 수정 api 통합 테스트") | ||
| class RecordUpdateApiTest { |
There was a problem hiding this comment.
오 세세한 테스트케이스까지 모두 통합테스트 코드로 작성해주셨군요! 좋습니다
p3 : 코래 말처럼 content 만을 업데이트하는 메서드를 Record, Vote jpa entity 에 정의하고, 해당 메서드를 사용하는것도 좋아보입니다!! |
저도 해당 부분을 고려하긴 했습니다. 다만 현재 저희의 업데이트 컨벤션은 updateFrom 메서드 하나로 엔티티 전체 필드를 업데이트하는 방식이고, 추후 요구사항에 따라 수정 대상 필드가 늘어날 가능성도 있기 때문에 굳이 updateContent와 같은 전용 메서드를 추가하여 일관성을 깨는 필요는 크지 않다고 생각했습니다. 즉, 지금 구조에서는 다른 필드가 의도치 않게 변경될 위험은 거의 없고(find한 값을 그대로 다시 매핑하기 때문), 오히려 현재의 일관된 updateFrom 패턴을 유지하는 편이 유지보수 측면에서 더 낫다고 판단했습니다. |
There was a problem hiding this comment.
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.
📒 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 실행 중...)
수고하셨어요~~ 삭제하기 리펙토링시에 참고해서 수정하겠습니다~~ |
#️⃣ 연관된 이슈
📝 작업 내용
기록, 투표 수정하기 api를 개발하였습니다.
요구사항에 따라 두 엔티티 모두 content만 수정하게끔 하였습니다. (이때, Record는 500자 제한, Vote는 20자 제한)
추후에 유지보수를 용이하게 하기 위해서 엔드포인트와 서비스 메서드는 분리하였습니다. 다만, RoomPost라는 상위 도메인을 이용하는 것이 좋아보여 RoomPostUpdateService라는 클래스에서 두 메서드를 구현하도록 하였습니다.
기록, 투표 삭제하기 api도 추후에 리팩토링을 할때 이렇게 합치는 것은 어떨지 제안드립니다~
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신기능
문서
리팩터
테스트