Conversation
Walkthrough투표 기능이 전체적으로 도입되었습니다. 투표 API, 서비스, 포트, 어댑터, 도메인, 예외 코드가 추가 및 확장되었고, 투표 관련 엔티티 및 레포지토리도 보강되었습니다. 기존 팔로우 예외 코드가 일부 변경되었으며, 테스트 코드가 대폭 추가되어 투표 로직의 정상 및 예외 흐름을 검증합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant VoteCommandController
participant VoteUseCase (VoteService)
participant RoomParticipantValidator
participant VoteCommandPort
participant DB
Client->>VoteCommandController: POST /rooms/{roomId}/vote/{voteId} (VoteRequest)
VoteCommandController->>VoteUseCase: vote(VoteCommand)
VoteUseCase->>RoomParticipantValidator: validateUserIsRoomMember(userId, roomId)
RoomParticipantValidator-->>VoteUseCase: (검증 성공/실패)
VoteUseCase->>VoteCommandPort: findVoteParticipantByUserIdAndVoteId(userId, voteId)
alt 최초 투표
VoteCommandPort-->>VoteUseCase: Optional.empty
VoteUseCase->>VoteCommandPort: saveVoteParticipant
VoteUseCase->>VoteCommandPort: updateVoteItem (increaseCount)
else 이미 투표함
VoteCommandPort-->>VoteUseCase: Optional<VoteParticipant>
alt 다른 항목에 투표
VoteUseCase->>VoteCommandPort: updateVoteParticipant
VoteUseCase->>VoteCommandPort: updateVoteItem (increase/decrease Count)
else 같은 항목에 투표
VoteUseCase-->>VoteCommandController: 예외 발생 (중복 투표)
end
end
VoteUseCase-->>VoteCommandController: VoteResult
VoteCommandController-->>Client: BaseResponse<VoteResponse>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
⏰ 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)
✨ 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. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
src/main/java/konkuk/thip/vote/application/service/dto/VoteResult.java (2)
6-6:type필드의 의미가 불분명합니다.Boolean 타입의
type필드가 무엇을 나타내는지 명확하지 않습니다. 투표/취소를 나타낸다면 enum을 사용하거나 더 명확한 필드명을 고려해보세요.- Boolean type + VoteType type // enum VoteType { VOTE, CANCEL }또는
- Boolean type + Boolean isVoted // 더 명확한 필드명
8-10: static 팩토리 메서드가 불필요합니다.Record는 기본적으로 생성자를 제공하므로 단순히 필드를 전달하는 static 팩토리 메서드는 중복입니다.
이 메서드를 제거하고 직접 생성자를 사용하는 것을 고려해보세요:
- public static VoteResult of(Long voteItemId, Long roomId, Boolean type) { - return new VoteResult(voteItemId, roomId, type); - }src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java (1)
5-9: 계층 간 DTO 분리가 잘 되어 있습니다.서비스 계층의
VoteResult와 웹 계층의VoteResponse를 분리한 것이 좋습니다. 다만VoteResult와 동일하게type필드의 의미가 불분명합니다.src/main/java/konkuk/thip/vote/domain/VoteParticipant.java (1)
27-34: 투표 항목 변경 로직이 올바르게 구현되었습니다.동일한 항목에 대한 중복 투표를 방지하는 검증 로직이 PR 목표에 맞게 잘 구현되었습니다.
null 안전성을 위해 다음과 같은 개선을 고려해보세요:
public void changeVoteItem(Long voteItemId) { + if (voteItemId == null) { + throw new IllegalArgumentException("voteItemId cannot be null"); + } // 같은 항목을 투표하려고 하는 경우 예외처리 - if(this.voteItemId.equals(voteItemId)) { + if(Objects.equals(this.voteItemId, voteItemId)) { throw new InvalidStateException(ErrorCode.VOTE_ITEM_ALREADY_VOTED); } // 투표 항목 변경 this.voteItemId = voteItemId; }src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java (1)
12-13: JPQL 쿼리 대신 메서드 명명 규칙 사용 검토Spring Data JPA의 메서드 명명 규칙을 사용하면 @query 어노테이션 없이도 동일한 기능을 구현할 수 있습니다.
- @Query("SELECT vp FROM VoteParticipantJpaEntity vp WHERE vp.userJpaEntity.userId = :userId AND vp.voteItemJpaEntity.voteItemId = :voteItemId") - Optional<VoteParticipantJpaEntity> findVoteParticipantByUserIdAndVoteItemId(Long userId, Long voteItemId); + Optional<VoteParticipantJpaEntity> findByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(Long userId, Long voteItemId);다만 현재 JPQL 쿼리도 명확하고 성능상 차이가 없으므로, 팀의 선호도에 따라 결정하시면 됩니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java(2 hunks)src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java(2 hunks)src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java(1 hunks)src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java(1 hunks)src/main/java/konkuk/thip/user/domain/User.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java(2 hunks)src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteItemJpaEntity.java(2 hunks)src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteParticipantJpaEntity.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java(3 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepository.java(1 hunks)src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.java(1 hunks)src/main/java/konkuk/thip/vote/application/port/in/VoteUseCase.java(1 hunks)src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java(2 hunks)src/main/java/konkuk/thip/vote/application/service/VoteService.java(1 hunks)src/main/java/konkuk/thip/vote/application/service/dto/VoteCommand.java(1 hunks)src/main/java/konkuk/thip/vote/application/service/dto/VoteResult.java(1 hunks)src/main/java/konkuk/thip/vote/domain/VoteItem.java(2 hunks)src/main/java/konkuk/thip/vote/domain/VoteParticipant.java(2 hunks)src/test/java/konkuk/thip/common/util/TestEntityFactory.java(2 hunks)src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java(1 hunks)src/test/java/konkuk/thip/vote/application/service/VoteServiceTest.java(1 hunks)src/test/java/konkuk/thip/vote/domain/VoteParticipantTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
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의 참가자인지 검증이 필요하다.
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#75
File: src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java:50-83
Timestamp: 2025-07-14T14:19:38.796Z
Learning: Vote와 VoteItem 엔티티는 자주 함께 사용되므로, N+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 API 등에서도 함께 사용될 가능성이 높습니다.
📚 Learning: vote와 voteitem 엔티티는 자주 함께 사용되므로, n+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 api 등에서...
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#75
File: src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteQueryRepositoryImpl.java:50-83
Timestamp: 2025-07-14T14:19:38.796Z
Learning: Vote와 VoteItem 엔티티는 자주 함께 사용되므로, N+1 문제를 방지하기 위해 양방향 매핑과 fetch join을 고려하는 것이 좋습니다. 특히 기록장 조회 API 등에서도 함께 사용될 가능성이 높습니다.
Applied to files:
src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteParticipantJpaEntity.javasrc/main/java/konkuk/thip/vote/adapter/out/jpa/VoteItemJpaEntity.javasrc/test/java/konkuk/thip/common/util/TestEntityFactory.javasrc/test/java/konkuk/thip/vote/domain/VoteParticipantTest.javasrc/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.javasrc/main/java/konkuk/thip/vote/application/service/dto/VoteResult.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepository.javasrc/main/java/konkuk/thip/vote/application/service/VoteService.javasrc/main/java/konkuk/thip/vote/domain/VoteParticipant.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.javasrc/main/java/konkuk/thip/vote/domain/VoteItem.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.javasrc/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.javasrc/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.javasrc/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.javasrc/main/java/konkuk/thip/common/exception/code/ErrorCode.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
📚 Learning: thip 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 roomparticipantpolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 ...
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 프로젝트에서는 "사용자가 방에 속하는지 검증" 로직을 RoomParticipantPolicy 도메인 서비스로 캡슐화하여 재사용성을 높이고 비즈니스 로직의 중복을 방지하는 방식을 선호한다.
Applied to files:
src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.javasrc/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java
📚 Learning: spring data jpa에서 findby{fieldname} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, optional 반환 타입을 사용하는 것이 nu...
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional<Entity> 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.
Applied to files:
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantJpaRepository.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.javasrc/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java
📚 Learning: thip 프로젝트에서 record와 vote는 room에 속하지만 feed는 room에 속하지 않는 구조이며, 댓글 작성 시 record/vote에 대해서만 사용자가 해당 room...
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/common/swagger/SwaggerResponseDescription.javasrc/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.javasrc/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java
📚 Learning: commentcontrollertest는 댓글 생성 api의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(commentcreateapi...
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#101
File: src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java:118-265
Timestamp: 2025-07-23T17:41:55.507Z
Learning: CommentControllerTest는 댓글 생성 API의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(CommentCreateAPITest)에서 다룬다.
Applied to files:
src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java
📚 Learning: thip 프로젝트에서는 cqrs port 분리 시 다음 컨벤션을 따름: commandport에는 findbyxxx를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, querypo...
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.
Applied to files:
src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java (1)
src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteCreateRequest.java (1)
VoteCreateRequest(12-57)
⏰ 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 (36)
src/main/java/konkuk/thip/user/domain/User.java (1)
47-47: 에러 코드명 개선이 적절합니다.
FOLLOW_COUNT_IS_ZERO에서FOLLOW_COUNT_CANNOT_BE_NEGATIVE로 변경하여 실제 검증 목적(음수 방지)을 더 명확하게 표현합니다.src/main/java/konkuk/thip/vote/application/port/in/VoteUseCase.java (1)
6-8: 클린 아키텍처 원칙을 잘 따른 인터페이스입니다.단일 책임 원칙을 따르며, 명확한 메서드 시그니처를 가지고 있습니다.
src/main/java/konkuk/thip/room/application/service/validator/RoomParticipantValidator.java (1)
17-17: 매개변수 순서 수정이 올바릅니다.
existByUserIdAndRoomId호출 시 매개변수 순서를 올바르게 수정했습니다. 이는 사용자 방 참여 검증 로직의 핵심적인 버그 수정입니다.src/test/java/konkuk/thip/common/util/TestEntityFactory.java (2)
24-26: 필요한 import 추가가 적절합니다.투표 참가자 관련 엔티티 import가 새로운 팩토리 메서드를 위해 올바르게 추가되었습니다.
298-303: 팩토리 메서드 구현이 일관성 있고 올바릅니다.
createVoteParticipant메서드가 기존 팩토리 패턴과 일치하게 구현되었으며, 테스트에서 투표 참가자 엔티티 생성을 위한 유틸리티로 적절합니다.src/main/java/konkuk/thip/vote/domain/VoteItem.java (3)
4-5: 도메인 예외 처리를 위한 적절한 import 추가도메인 비즈니스 규칙 위반 시 예외 처리를 위한 import가 올바르게 추가되었습니다.
80-82: 투표 수 증가 로직이 올바릅니다.투표 항목의 카운트를 증가시키는 간단하고 명확한 구현입니다.
84-89: 투표 수 감소 시 유효성 검증이 적절합니다.음수 카운트를 방지하는 검증 로직이 포함되어 있어 도메인 무결성을 보장합니다. 예외 처리도 적절하게 구현되었습니다.
src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepository.java (1)
7-10: 리포지토리 인터페이스 설계가 적절합니다.
Optional을 사용한 쿼리 메서드 시그니처가 적절하며, 메서드명이 명확하고 Spring Data 패턴을 잘 따르고 있습니다.src/test/java/konkuk/thip/vote/domain/VoteParticipantTest.java (2)
14-26: 동일한 투표 항목 변경 시 예외 처리 테스트가 적절합니다.같은
voteItemId로 변경 시 예외가 발생하는 비즈니스 규칙을 올바르게 검증하고 있습니다. 테스트 구조와 assertion이 명확합니다.
28-42: 다른 투표 항목으로 변경 성공 테스트가 올바릅니다.정상적인 투표 항목 변경 시나리오를 적절히 검증하고 있으며, given-when-then 구조가 명확합니다.
src/main/java/konkuk/thip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java (1)
13-17: JPQL 전환이 잘 되었습니다.네이티브 SQL에서 JPQL로의 전환이 적절하게 수행되었습니다. 엔티티 필드 참조 사용으로 타입 안전성과 리팩토링 지원이 개선되었습니다.
src/main/java/konkuk/thip/vote/adapter/in/web/response/VoteResponse.java (1)
10-12: 적절한 팩토리 메서드입니다.계층 간 변환을 위한 static 팩토리 메서드가 적절하게 구현되었습니다.
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)
46-46: 에러 코드 이름 변경이 적절합니다.
FOLLOW_COUNT_IS_ZERO에서FOLLOW_COUNT_CANNOT_BE_NEGATIVE로의 변경이 더 명확하고 정확한 의미를 전달합니다.
135-141: 투표 관련 에러 코드가 포괄적으로 정의되었습니다.투표 기능의 주요 에러 시나리오들이 잘 정의되어 있습니다:
- 방 접근 권한 검증
- 투표 항목 존재 여부
- 중복 투표 방지
- 투표 취소 시 검증
- 카운트 음수 방지
src/main/java/konkuk/thip/vote/domain/VoteParticipant.java (1)
19-25: 도메인 팩토리 메서드가 잘 구현되었습니다.ID 없이 인스턴스를 생성하는 팩토리 메서드가 도메인 주도 설계 원칙에 맞게 구현되었습니다.
src/main/java/konkuk/thip/vote/adapter/out/persistence/repository/VoteParticipantQueryRepositoryImpl.java (1)
25-42: 구현이 잘 되었습니다!N+1 문제를 방지하기 위한 fetch join 사용과 ACTIVE 상태 필터링이 적절합니다. 사용자의 투표 참여 정보를 효율적으로 조회할 수 있는 쿼리입니다.
src/test/java/konkuk/thip/vote/application/service/VoteServiceTest.java (1)
35-125: 테스트 구현이 우수합니다!PR에서 요청한 복잡한 상태 검증 및 업데이트 로직이 모두 적절히 테스트되고 있습니다. 첫 투표, 투표 변경, 중복 투표, 투표 취소 등 모든 주요 시나리오가 커버되어 있으며, 예외 상황도 잘 검증하고 있습니다.
src/main/java/konkuk/thip/vote/adapter/in/web/request/VoteRequest.java (1)
10-27: 요청 DTO 구현이 적절합니다!투표/취소를 Boolean type으로 구분하는 방식이 명확하고, 필수 필드 검증과 Swagger 문서화가 잘 되어 있습니다.
src/main/java/konkuk/thip/vote/adapter/in/web/VoteCommandController.java (1)
47-61: 투표 API 엔드포인트 구현이 적절합니다.RESTful한 경로 설계와 기존 컨트롤러 패턴을 잘 따르고 있습니다. 사용자 인증, 검증, 예외 처리 설명이 모두 적절히 구성되어 있습니다.
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (2)
53-53: 에러 코드 네이밍 개선이 적절합니다.
FOLLOW_COUNT_IS_ZERO에서FOLLOW_COUNT_CANNOT_BE_NEGATIVE로 변경하여 의미가 더 명확해졌습니다.
107-115: 투표 관련 에러 코드가 체계적으로 추가되었습니다.새로운 에러 코드들이 기존 패턴을 잘 따르고 있으며, HTTP 상태 코드와 메시지가 적절합니다. 에러 코드 번호 체계(120000대, 125000대)도 일관성을 유지하고 있습니다.
src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java (1)
35-45: VoteParticipant 관련 메서드들이 잘 설계되었습니다.CQRS 패턴을 잘 따르고 있으며, 사용자-투표 및 사용자-투표항목 조합으로 조회하는 메서드들이 비즈니스 요구사항을 적절히 반영하고 있습니다.
src/test/java/konkuk/thip/vote/adapter/in/web/VoteApiTest.java (7)
66-75: 테스트 설정이 체계적으로 구성되었습니다.필요한 모든 엔티티(사용자, 방, 투표 등)를 적절히 설정하고 있으며,
TestEntityFactory를 활용한 테스트 데이터 생성이 일관성 있게 처리되었습니다.
77-102: 첫 투표 시나리오 테스트가 완벽합니다.API 응답 검증과 함께 데이터베이스 상태 변경(VoteParticipant 생성, VoteItem count 증가)을 모두 확인하여 통합 테스트로서 적절한 검증 범위를 제공합니다.
104-132: 투표 변경 시나리오 테스트가 비즈니스 로직을 정확히 검증합니다.이전 투표 항목의 count 감소와 새로운 투표 항목으로의 변경을 모두 확인하여 복잡한 비즈니스 로직이 올바르게 동작함을 검증하고 있습니다.
134-151: 중복 투표 방지 테스트가 적절합니다.동일한 항목에 재투표 시 적절한 에러 코드(
VOTE_ITEM_ALREADY_VOTED)로 응답하는지 확인하여 비즈니스 규칙을 잘 검증하고 있습니다.
153-173: 투표 취소 시나리오 테스트가 완벽합니다.VoteParticipant 삭제와 VoteItem count 감소를 모두 확인하여 투표 취소 로직이 정확히 구현되었음을 검증합니다.
175-190: 투표 취소 실패 시나리오 테스트가 적절합니다.투표 기록이 없는 상태에서 취소 시도 시 적절한 에러 코드(
VOTE_ITEM_NOT_VOTED_CANNOT_CANCEL)로 응답하는지 확인합니다.
192-208: 권한 검증 테스트가 보안 요구사항을 잘 반영합니다.방에 속하지 않은 사용자의 투표 시도를 차단하고 적절한 에러 코드(
ROOM_ACCESS_FORBIDDEN)로 응답하는지 확인하여 보안성을 검증합니다.src/main/java/konkuk/thip/vote/adapter/out/persistence/VoteCommandPersistenceAdapter.java (6)
78-82: VoteItem 조회 메서드가 깔끔하게 구현되었습니다.기존 패턴을 잘 따르고 있으며, Optional을 활용한 null 안전성도 적절히 처리되었습니다.
84-94: VoteParticipant 조회 메서드들이 비즈니스 요구사항을 잘 반영합니다.사용자-투표 조합과 사용자-투표항목 조합으로 조회하는 두 가지 메서드가 투표 비즈니스 로직의 복잡성을 적절히 지원합니다.
96-107: VoteParticipant 업데이트 로직이 안전하게 구현되었습니다.엔티티 존재 여부 검증과 함께 투표 항목 변경 로직이 적절히 처리되어 있습니다. 에러 처리도 명확합니다.
109-121: VoteParticipant 저장 로직이 관련 엔티티를 충분히 검증합니다.사용자와 투표 항목의 존재 여부를 모두 확인한 후 저장하여 데이터 무결성을 보장합니다.
123-127: VoteParticipant 삭제 로직이 효율적입니다.주석에서 명시한 대로 앞선 검증을 통해 존재 여부가 확인된 상태이므로 ID로 직접 삭제하는 것이 효율적입니다.
129-136: VoteItem 업데이트 로직이 적절합니다.엔티티 존재 여부 검증과 함께
updateFrom메서드를 활용한 업데이트 패턴이 기존 코드와 일관성을 유지합니다.
...ip/room/adapter/out/persistence/repository/roomparticipant/RoomParticipantJpaRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteParticipantJpaEntity.java
Show resolved
Hide resolved
src/main/java/konkuk/thip/vote/application/port/out/VoteCommandPort.java
Show resolved
Hide resolved
src/main/java/konkuk/thip/vote/application/service/VoteService.java
Outdated
Show resolved
Hide resolved
seongjunnoh
left a comment
There was a problem hiding this comment.
LGTM 고생하셨습니다! 코드 깔끔하네요!!
| public VoteParticipantJpaEntity updateVoteItem(VoteItemJpaEntity voteItemJpaEntity) { | ||
| this.voteItemJpaEntity = voteItemJpaEntity; | ||
| return this; | ||
| } |
| public void increaseCount() { | ||
| this.count++; | ||
| } | ||
|
|
||
| public void decreaseCount() { | ||
| if(this.count == 0) { | ||
| throw new InvalidStateException(ErrorCode.VOTE_ITEM_COUNT_CANNOT_BE_NEGATIVE); | ||
| } | ||
| this.count--; | ||
| } |
| @Service | ||
| @RequiredArgsConstructor | ||
| public class VoteService implements VoteUseCase { |
| "https://thip-git-develop-thips-projects.vercel.app" | ||
| )); // 배포 시 도메인 명시 | ||
| webUrl | ||
| )); |
There was a problem hiding this comment.
굳굳 FE 배포 url을 명시하는 것을 고민했었는데, yml에 숨기는것도 좋은거같습니다
| @Query("SELECT rp FROM RoomParticipantJpaEntity rp " + | ||
| "WHERE rp.userJpaEntity.userId = :userId " + | ||
| "AND rp.roomJpaEntity.roomId = :roomId " + | ||
| "AND rp.status = 'ACTIVE'") |
| Optional<VoteParticipant> findVoteParticipantByUserIdAndVoteId(Long userId, Long voteId); | ||
|
|
||
| Optional<VoteParticipant> findVoteParticipantByUserIdAndVoteItemId(Long userId, Long voteItemId); |
There was a problem hiding this comment.
굳굳 확실히 Optional을 반환하고, service에서 ifPresentOrElse 로 분기처리하니 가독성이 훨씬 올라가는 것 같습니다
| public void changeVoteItem(Long voteItemId) { | ||
| // 같은 항목을 투표하려고 하는 경우 예외처리 | ||
| if(this.voteItemId.equals(voteItemId)) { | ||
| throw new InvalidStateException(ErrorCode.VOTE_ITEM_ALREADY_VOTED); | ||
| } | ||
| // 투표 항목 변경 | ||
| this.voteItemId = voteItemId; | ||
| } |
There was a problem hiding this comment.
같은 항목을 투표하는 요청이 올 경우 404 error 를 던지는게 맞나? 싶어서 생각 좀 해봤는데, 이 경우 굳이 이후의
유저가 이전에 투표한 기록 삭제 -> 이전에 투표한 아이템의 count-- -> 유저가 새로 투표한 기록(= 사실 같은 투표 아이템) 저장 -> 유저가 새로 투표한 아이템(= 사실 같은 투표 아이템)의 count++
의 과정을 진행할 필요없이 404 error를 던지고, FE 단에서 분기처리를 하는게 훨씬 효율적일 거 같네요!! 좋습니다!!
관련해서 FE 분들에게 언급하면 좋을 것 같습니다!
| @Test | ||
| @DisplayName("같은 항목으로 다시 투표 시 예외") |
| @SpringBootTest | ||
| @ActiveProfiles("test") | ||
| @AutoConfigureMockMvc(addFilters = false) | ||
| @Transactional | ||
| @DisplayName("[통합] 투표하기 API 통합 테스트") | ||
| class VoteApiTest { |
There was a problem hiding this comment.
통합 테스트 케이스 좋네요!! 가독성 굳굳
| // when | ||
| when(voteCommandPort.findVoteParticipantByUserIdAndVoteId(1L, 1L)) | ||
| .thenReturn(Optional.empty()); | ||
| when(voteCommandPort.getVoteItemByIdOrThrow(100L)).thenReturn(voteItem); | ||
| VoteResult result = voteService.vote(command); |
There was a problem hiding this comment.
LGTM 모킹을 활용해 서비스 단위테스트 좋습니다
#️⃣ 연관된 이슈
📝 작업 내용
api 흐름은 다음과 같습니다.
📸 스크린샷
💬 리뷰 요구사항
로직이 복잡하니 제가 제대로 상태 유효성 검증 또는 변경을 수행했는지 검토해주시면 감사할게요 😅
추가적으로 RoomParticipantValidator에 existByUserIdAndRoomId 쿼리에 파라미터의 순서를 바꿔서 전달하고 있는 오류를 수정했습니다. 지금까지 테스트가 어떻게 통과되고 있었는지 의문이네요 하핳,,
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit
신규 기능
버그 수정
문서화
테스트
리팩터링/정리