Skip to content

[FEAT] 투표 개최자 API 구현#203

Merged
coli-geonwoo merged 29 commits intodevelopfrom
feat/#199
Jul 26, 2025
Merged

[FEAT] 투표 개최자 API 구현#203
coli-geonwoo merged 29 commits intodevelopfrom
feat/#199

Conversation

@coli-geonwoo
Copy link
Contributor

@coli-geonwoo coli-geonwoo commented Jul 23, 2025

🚩 연관 이슈

closed #199

🗣️ 리뷰 요구사항 (선택)

관련 API 스펙은 다음 노션 링크를 참고하세요

Summary by CodeRabbit

  • 신규 기능

    • 투표(폴) 생성, 조회, 완료 처리 REST API가 추가되었습니다.
    • 투표 참여자 이름 및 코드에 대한 유효성 검사가 도입되었습니다.
    • 투표 현황(찬성/반대/총 투표 수) 조회가 가능합니다.
  • 버그 수정

    • 없음
  • 문서화

    • 투표 API에 대한 테스트 및 API 문서가 추가되었습니다.
  • 테스트

    • 투표 관련 서비스, 컨트롤러, 도메인, 리포지토리 테스트가 새로 작성되었습니다.
  • 기타

    • 데이터베이스에 투표 테이블의 memberId 컬럼이 추가되었습니다.
    • 투표 관련 에러 코드가 확장되었습니다.

@coli-geonwoo coli-geonwoo linked an issue Jul 23, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Jul 23, 2025

"""

Walkthrough

이 변경사항은 투표(Poll) 기능의 도메인, 서비스, 컨트롤러, 저장소 계층을 신규로 도입하고, 이에 대한 테스트 및 DB 마이그레이션, 예외 코드 확장, 테스트 픽스처 생성기를 추가합니다. Poll 생성, 조회, 완료 처리 API와 관련 도메인 객체, JPA 엔티티, 리포지토리, 서비스 레이어, REST 컨트롤러, 그리고 통합/단위 테스트가 포함됩니다.

Changes

파일/경로 그룹 변경 요약
src/main/java/com/debatetimer/controller/poll/PollController.java Poll 관련 REST 컨트롤러 신설: 생성, 조회, 완료 처리 엔드포인트 추가
src/main/java/com/debatetimer/domain/poll/ParticipantName.java,
ParticipateCode.java,
Poll.java,
Vote.java,
VoteInfo.java
Poll, Vote, 참가자명, 참가코드 등 Poll 도메인 객체 및 값 객체 신설
src/main/java/com/debatetimer/domainrepository/poll/CustomizeTableDomainRepository.java,
PollDomainRepository.java,
VoteDomainRepository.java
Poll, Vote, CustomizeTable 도메인 리포지토리 신설 및 관련 메서드 구현
src/main/java/com/debatetimer/dto/poll/response/PollCreateResponse.java,
PollInfoResponse.java
Poll 생성/조회 응답 DTO 신설
src/main/java/com/debatetimer/entity/poll/PollEntity.java,
VoteEntity.java
Poll/Vote 엔티티에 도메인 변환, 상태 변경 메서드, Lombok 어노테이션 등 추가
src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java Poll 관련 예외 코드 3종 추가
src/main/java/com/debatetimer/repository/poll/PollRepository.java,
VoteRepository.java
Poll, Vote JPA 리포지토리 인터페이스 신설 및 커스텀 쿼리 메서드 추가
src/main/java/com/debatetimer/service/poll/PollService.java Poll 생성, 조회, 완료 처리 서비스 신설
src/main/resources/db/migration/V11__add_memberId_into_poll.sql Poll 테이블에 member_id 컬럼 추가하는 DB 마이그레이션 스크립트 추가
src/test/java/com/debatetimer/controller/BaseControllerTest.java,
BaseDocumentTest.java,
Tag.java
Poll/Vote 테스트 픽스처, 서비스 목 추가, Tag enum에 POLL_API 추가
src/test/java/com/debatetimer/controller/poll/PollControllerTest.java,
PollDocumentTest.java
Poll API 컨트롤러 및 문서화 테스트 신설 (생성, 조회, 완료 처리)
src/test/java/com/debatetimer/domain/poll/ParticipantNameTest.java,
ParticipateCodeTest.java
ParticipantName, ParticipateCode 값 객체 검증 단위 테스트 신설
src/test/java/com/debatetimer/domainrepository/BaseDomainRepositoryTest.java,
poll/PollDomainRepositoryTest.java,
poll/VoteDomainRepositoryTest.java
Poll/Vote 도메인 리포지토리 통합 테스트 신설 및 베이스 클래스 추가
src/test/java/com/debatetimer/fixture/PollGenerator.java,
VoteGenerator.java
Poll/Vote 테스트 엔티티 생성기 컴포넌트 신설
src/test/java/com/debatetimer/service/BaseServiceTest.java,
poll/PollServiceTest.java
Poll/Vote 관련 서비스 테스트, 픽스처 및 통합 테스트 신설
src/test/java/com/debatetimer/repository/BaseRepositoryTest.java @import 배열에 trailing comma 추가 (비기능성 변경)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PollController
    participant PollService
    participant PollDomainRepository
    participant VoteDomainRepository
    participant CustomizeTableDomainRepository

    Client->>PollController: POST /api/polls/{tableId}
    PollController->>PollService: create(tableId, member)
    PollService->>CustomizeTableDomainRepository: getByIdAndMember(tableId, member)
    PollService->>PollDomainRepository: create(Poll)
    PollService-->>PollController: PollCreateResponse
    PollController-->>Client: 201 Created

    Client->>PollController: GET /api/polls/{pollId}
    PollController->>PollService: readPollInfo(pollId, member)
    PollService->>PollDomainRepository: getByIdAndMemberId(pollId, member.id)
    PollService->>VoteDomainRepository: findVoteInfoByPollId(pollId)
    PollService-->>PollController: PollInfoResponse
    PollController-->>Client: 200 OK

    Client->>PollController: PATCH /api/polls/{pollId}
    PollController->>PollService: updateToDone(pollId, member)
    PollService->>PollDomainRepository: updateToDone(pollId, member.id)
    PollService->>VoteDomainRepository: findVoteInfoByPollId(pollId)
    PollService-->>PollController: PollInfoResponse
    PollController-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40분

Suggested labels

feat

Suggested reviewers

  • unifolio0
    """

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 538be99 and 486abf1.

📒 Files selected for processing (1)
  • src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java
⏰ 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-and-push
✨ 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/#199

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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.

Documentation and Community

  • 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.

@unifolio0 unifolio0 self-requested a review July 23, 2025 20:00
@unifolio0 unifolio0 added the feat 기능 추가 label Jul 23, 2025
@unifolio0 unifolio0 requested a review from leegwichan July 23, 2025 20:00
@github-actions
Copy link

github-actions bot commented Jul 23, 2025

Test Results

105 files  105 suites   13s ⏱️
246 tests 246 ✅ 0 💤 0 ❌
258 runs  258 ✅ 0 💤 0 ❌

Results for commit 486abf1.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Jul 23, 2025

📝 Test Coverage Report

Overall Project 86.68% -1.52% 🍏
Files changed 85.68% 🍏

File Coverage
PollController.java 100% 🍏
PollEntity.java 100% 🍏
VoteDomainRepository.java 100% 🍏
ClientErrorCode.java 100% 🍏
Poll.java 100% 🍏
VoteInfo.java 100% 🍏
PollService.java 100% 🍏
PollDomainRepository.java 88.1% -11.9% 🍏
CustomizeTableDomainRepository.java 66.67% -33.33%
ParticipantName.java 60% -40%
ParticipateCode.java 60% -40%
VoteEntity.java 0%
Vote.java 0%

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (10)
src/main/java/com/debatetimer/domain/poll/ParticipateCode.java (1)

17-21: 메서드 명명 개선 제안

검증 로직은 적절하지만 validateName 메서드명이 실제로 참여 코드를 검증하는 것과 맞지 않습니다. validateCode 또는 validateParticipateCode로 변경하는 것이 더 명확합니다.

-    private void validateName(String value) {
+    private void validateCode(String value) {
         if (value == null || value.isBlank()) {
             throw new DTClientErrorException(ClientErrorCode.INVALID_POLL_PARTICIPANT_CODE);
         }
     }

그리고 생성자에서 호출하는 부분도 함께 수정:

     public ParticipateCode(String value) {
-        validateName(value);
+        validateCode(value);
         this.value = value;
     }
src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java (1)

24-30: null 안전성 개선을 고려해보세요.

현재 voteEntities가 null일 경우 NPE가 발생할 수 있습니다. 방어적 프로그래밍을 위해 null 체크를 추가하는 것을 고려해보세요.

private VoteInfo resolveVoteInfo(long pollId, List<VoteEntity> voteEntities) {
+   if (voteEntities == null || voteEntities.isEmpty()) {
+       return new VoteInfo(pollId, 0L, 0L);
+   }
    Map<VoteTeam, Long> teamCount = voteEntities.stream()
            .collect(Collectors.groupingBy(VoteEntity::getTeam, Collectors.counting()));
    long prosCount = teamCount.getOrDefault(VoteTeam.PROS, 0L);
    long consCount = teamCount.getOrDefault(VoteTeam.CONS, 0L);
    return new VoteInfo(pollId, prosCount, consCount);
}
src/test/java/com/debatetimer/domainrepository/poll/VoteDomainRepositoryTest.java (1)

25-42: 추가 테스트 케이스 고려해보세요.

현재는 기본적인 성공 케이스만 테스트하고 있습니다. 다음과 같은 엣지 케이스들도 테스트하면 더 견고한 코드가 될 것 같습니다:

  • 투표가 없는 경우
  • 한 팀만 투표한 경우
  • 존재하지 않는 pollId로 조회하는 경우

추가 테스트 메서드 예시:

@Test
void 투표가_없는_경우_0을_반환한다() {
    Member member = memberGenerator.generate("email@email.com");
    CustomizeTableEntity table = customizeTableGenerator.generate(member);
    PollEntity pollEntity = pollGenerator.generate(table, PollStatus.PROGRESS);

    VoteInfo voteInfo = voteDomainRepository.findVoteInfoByPollId(pollEntity.getId());

    assertAll(
            () -> assertThat(voteInfo.getPollId()).isEqualTo(pollEntity.getId()),
            () -> assertThat(voteInfo.getTotalCount()).isEqualTo(0L),
            () -> assertThat(voteInfo.getProsCount()).isEqualTo(0L),
            () -> assertThat(voteInfo.getConsCount()).isEqualTo(0L)
    );
}
src/test/java/com/debatetimer/fixture/PollGenerator.java (2)

20-29: 하드코딩된 값들의 상수화를 고려해보세요.

"찬성", "반대", "주제" 같은 하드코딩된 값들이 여러 테스트에서 일관되게 사용되려면 상수로 관리하는 것이 좋겠습니다.

@Component
public class PollGenerator {

+   private static final String DEFAULT_PROS_TEAM_NAME = "찬성";
+   private static final String DEFAULT_CONS_TEAM_NAME = "반대";
+   private static final String DEFAULT_AGENDA = "주제";

    private final PollRepository pollRepository;

    public PollGenerator(final PollRepository pollRepository) {
        this.pollRepository = pollRepository;
    }

    public PollEntity generate(CustomizeTableEntity customizeTableEntity, PollStatus status) {
        Poll poll = new Poll(
                null,
                customizeTableEntity.getId(),
                customizeTableEntity.getMember().getId(),
                status,
-               "찬성",
-               "반대",
-               "주제"
+               DEFAULT_PROS_TEAM_NAME,
+               DEFAULT_CONS_TEAM_NAME,
+               DEFAULT_AGENDA
        );
        return pollRepository.save(new PollEntity(poll));
    }
}

19-30: 메서드 오버로딩으로 유연성을 높여보세요.

테스트에서 다양한 팀명이나 주제가 필요한 경우를 대비해 추가 메서드를 제공하는 것을 고려해보세요.

public PollEntity generate(CustomizeTableEntity customizeTableEntity, PollStatus status, 
                          String prosTeamName, String consTeamName, String agenda) {
    Poll poll = new Poll(
            null,
            customizeTableEntity.getId(),
            customizeTableEntity.getMember().getId(),
            status,
            prosTeamName,
            consTeamName,
            agenda
    );
    return pollRepository.save(new PollEntity(poll));
}
src/test/java/com/debatetimer/domainrepository/BaseDomainRepositoryTest.java (1)

17-30: BaseServiceTest와의 코드 중복을 고려해보세요.

BaseServiceTest에도 동일한 fixture generator들과 repository들이 주입되어 있습니다. 공통 부모 클래스를 만들거나 컴포지션 패턴을 활용하여 중복을 줄이는 것을 고려해볼 수 있습니다.

예를 들어, 공통 테스트 의존성을 관리하는 클래스를 만들어볼 수 있습니다:

@TestComponent
public class TestFixtures {
    @Autowired
    protected MemberGenerator memberGenerator;
    
    @Autowired  
    protected CustomizeTableGenerator customizeTableGenerator;
    
    @Autowired
    protected PollGenerator pollGenerator;
    
    @Autowired
    protected VoteGenerator voteGenerator;
    
    // 기타 공통 fixture들...
}

그리고 각 기본 테스트 클래스에서 이를 주입받아 사용하는 방식으로 중복을 줄일 수 있습니다.

src/test/java/com/debatetimer/service/poll/PollServiceTest.java (1)

24-37: Poll 생성 테스트

선거 생성 테스트가 기본적인 동작을 검증합니다. 하지만 생성된 Poll의 세부 속성(상태, 팀명 등)까지 검증하면 더 완전한 테스트가 될 것입니다.

다음과 같이 생성된 Poll의 세부 속성까지 검증하는 것을 고려해보세요:

 PollCreateResponse createdPoll = pollService.create(table.getId(), member);

 Optional<PollEntity> foundPoll = pollRepository.findById(createdPoll.id());
-assertThat(foundPoll).isPresent();
+assertAll(
+    () -> assertThat(foundPoll).isPresent(),
+    () -> assertThat(foundPoll.get().getStatus()).isEqualTo(PollStatus.PROGRESS),
+    () -> assertThat(foundPoll.get().getMemberId()).isEqualTo(member.getId())
+);
src/test/java/com/debatetimer/controller/poll/PollControllerTest.java (1)

21-37: Poll 생성 API 테스트

선거 생성 API 테스트가 기본적인 성공 시나리오를 다룹니다. 하지만 응답 본문의 내용도 검증하면 더 완전한 테스트가 될 것입니다.

다음과 같이 응답 본문도 검증하는 것을 고려해보세요:

-            given()
-                    .contentType(ContentType.JSON)
-                    .headers(headers)
-                    .pathParam("tableId", table.getId())
-                    .when().post("/api/polls/{tableId}")
-                    .then().statusCode(HttpStatus.CREATED.value());
+            PollCreateResponse response = given()
+                    .contentType(ContentType.JSON)
+                    .headers(headers)
+                    .pathParam("tableId", table.getId())
+                    .when().post("/api/polls/{tableId}")
+                    .then().statusCode(HttpStatus.CREATED.value())
+                    .extract().as(PollCreateResponse.class);
+            
+            assertThat(response.id()).isNotNull();
src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java (1)

37-53: 투표 조회 테스트의 검증 로직을 개선해보세요.

assertAll을 사용한 다중 검증이 좋습니다. 다만 도메인 객체와 엔티티 간의 변환 로직을 더 명확하게 검증할 수 있도록 개선을 제안합니다.

 assertAll(
         () -> assertThat(foundPoll.getId()).isEqualTo(pollEntity.getId()),
+        () -> assertThat(foundPoll.getTableId()).isEqualTo(pollEntity.getTableId()),
         () -> assertThat(foundPoll.getAgenda().getValue()).isEqualTo(pollEntity.getAgenda()),
         () -> assertThat(foundPoll.getStatus()).isEqualTo(pollEntity.getStatus()),
         () -> assertThat(foundPoll.getMemberId()).isEqualTo(pollEntity.getMemberId()),
         () -> assertThat(foundPoll.getProsTeamName().getValue()).isEqualTo(pollEntity.getProsTeamName()),
         () -> assertThat(foundPoll.getConsTeamName().getValue()).isEqualTo(pollEntity.getConsTeamName())
 );
src/main/java/com/debatetimer/service/poll/PollService.java (1)

41-46: 상태 업데이트 메서드의 일관성을 개선할 수 있습니다.

updateToDone 메서드에서 두 번의 데이터베이스 호출이 발생합니다:

  1. pollDomainRepository.updateToDone() - 상태 업데이트
  2. voteDomainRepository.findVoteInfoByPollId() - 투표 정보 조회

성능 최적화를 위해 다음을 고려해보세요:

  • 업데이트된 Poll 객체에서 필요한 정보를 직접 사용하거나
  • 단일 트랜잭션에서 두 작업을 효율적으로 처리하는 방법

또한 readPollInfo 메서드와 코드 중복이 있어 리팩토링을 고려할 수 있습니다:

@Transactional
public PollInfoResponse updateToDone(long pollId, Member member) {
    Poll poll = pollDomainRepository.updateToDone(pollId, member.getId());
    return buildPollInfoResponse(poll.getId(), poll);
}

private PollInfoResponse buildPollInfoResponse(long pollId, Poll poll) {
    VoteInfo voteInfo = voteDomainRepository.findVoteInfoByPollId(pollId);
    return new PollInfoResponse(poll, voteInfo);
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbdcbd3 and 22c2302.

📒 Files selected for processing (33)
  • src/main/java/com/debatetimer/controller/poll/PollController.java (1 hunks)
  • src/main/java/com/debatetimer/domain/poll/ParticipantName.java (1 hunks)
  • src/main/java/com/debatetimer/domain/poll/ParticipateCode.java (1 hunks)
  • src/main/java/com/debatetimer/domain/poll/Poll.java (1 hunks)
  • src/main/java/com/debatetimer/domain/poll/Vote.java (1 hunks)
  • src/main/java/com/debatetimer/domain/poll/VoteInfo.java (1 hunks)
  • src/main/java/com/debatetimer/domainrepository/poll/CustomizeTableDomainRepository.java (1 hunks)
  • src/main/java/com/debatetimer/domainrepository/poll/PollDomainRepository.java (1 hunks)
  • src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java (1 hunks)
  • src/main/java/com/debatetimer/dto/poll/response/PollCreateResponse.java (1 hunks)
  • src/main/java/com/debatetimer/dto/poll/response/PollInfoResponse.java (1 hunks)
  • src/main/java/com/debatetimer/entity/poll/PollEntity.java (4 hunks)
  • src/main/java/com/debatetimer/entity/poll/VoteEntity.java (3 hunks)
  • src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java (1 hunks)
  • src/main/java/com/debatetimer/repository/poll/PollRepository.java (1 hunks)
  • src/main/java/com/debatetimer/repository/poll/VoteRepository.java (1 hunks)
  • src/main/java/com/debatetimer/service/poll/PollService.java (1 hunks)
  • src/main/resources/db/migration/V11__add_memberId_into_poll.sql (1 hunks)
  • src/test/java/com/debatetimer/controller/BaseControllerTest.java (2 hunks)
  • src/test/java/com/debatetimer/controller/BaseDocumentTest.java (2 hunks)
  • src/test/java/com/debatetimer/controller/Tag.java (1 hunks)
  • src/test/java/com/debatetimer/controller/poll/PollControllerTest.java (1 hunks)
  • src/test/java/com/debatetimer/controller/poll/PollDocumentTest.java (1 hunks)
  • src/test/java/com/debatetimer/domain/poll/ParticipantNameTest.java (1 hunks)
  • src/test/java/com/debatetimer/domain/poll/ParticipateCodeTest.java (1 hunks)
  • src/test/java/com/debatetimer/domainrepository/BaseDomainRepositoryTest.java (1 hunks)
  • src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java (1 hunks)
  • src/test/java/com/debatetimer/domainrepository/poll/VoteDomainRepositoryTest.java (1 hunks)
  • src/test/java/com/debatetimer/fixture/PollGenerator.java (1 hunks)
  • src/test/java/com/debatetimer/fixture/VoteGenerator.java (1 hunks)
  • src/test/java/com/debatetimer/repository/BaseRepositoryTest.java (1 hunks)
  • src/test/java/com/debatetimer/service/BaseServiceTest.java (3 hunks)
  • src/test/java/com/debatetimer/service/poll/PollServiceTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/main/java/com/debatetimer/entity/poll/VoteEntity.java (1)

Learnt from: coli-geonwoo
PR: #196
File: src/main/java/com/debatetimer/entity/poll/VoteEntity.java:19-0
Timestamp: 2025-07-10T01:01:08.967Z
Learning: VoteEntity와 같은 JPA 엔티티에서 getter/setter 메서드는 필요할 때 점진적으로 추가하는 방식을 선호함. 초기 엔티티 설정 단계에서는 최소한의 구조만 유지하고 기능을 점진적으로 확장하는 개발 방식을 사용함.

🧬 Code Graph Analysis (13)
src/main/java/com/debatetimer/domain/poll/Vote.java (2)
src/main/java/com/debatetimer/domain/poll/ParticipantName.java (1)
  • Getter (7-22)
src/main/java/com/debatetimer/domain/poll/ParticipateCode.java (1)
  • Getter (7-22)
src/test/java/com/debatetimer/fixture/VoteGenerator.java (1)
src/test/java/com/debatetimer/fixture/PollGenerator.java (1)
  • Component (10-31)
src/test/java/com/debatetimer/domainrepository/BaseDomainRepositoryTest.java (2)
src/test/java/com/debatetimer/DataBaseCleaner.java (1)
  • DataBaseCleaner (11-47)
src/test/java/com/debatetimer/service/BaseServiceTest.java (1)
  • ExtendWith (21-68)
src/main/java/com/debatetimer/domainrepository/poll/PollDomainRepository.java (3)
src/main/java/com/debatetimer/exception/custom/DTClientErrorException.java (1)
  • DTClientErrorException (5-10)
src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java (1)
  • Repository (13-31)
src/main/java/com/debatetimer/domainrepository/poll/CustomizeTableDomainRepository.java (1)
  • Repository (12-24)
src/main/java/com/debatetimer/domain/poll/ParticipantName.java (3)
src/main/java/com/debatetimer/exception/custom/DTClientErrorException.java (1)
  • DTClientErrorException (5-10)
src/main/java/com/debatetimer/domain/poll/ParticipateCode.java (1)
  • Getter (7-22)
src/main/java/com/debatetimer/domain/poll/Vote.java (1)
  • Getter (6-19)
src/main/java/com/debatetimer/domain/poll/VoteInfo.java (1)
src/main/java/com/debatetimer/domain/poll/Poll.java (1)
  • Getter (8-24)
src/main/java/com/debatetimer/domain/poll/Poll.java (1)
src/main/java/com/debatetimer/domain/poll/VoteInfo.java (1)
  • Getter (5-19)
src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java (2)
src/test/java/com/debatetimer/domainrepository/poll/VoteDomainRepositoryTest.java (1)
  • Nested (22-43)
src/test/java/com/debatetimer/service/poll/PollServiceTest.java (3)
  • Nested (24-37)
  • Nested (39-63)
  • Nested (65-78)
src/test/java/com/debatetimer/domain/poll/ParticipantNameTest.java (2)
src/main/java/com/debatetimer/exception/custom/DTClientErrorException.java (1)
  • DTClientErrorException (5-10)
src/test/java/com/debatetimer/domain/poll/ParticipateCodeTest.java (1)
  • Nested (14-25)
src/test/java/com/debatetimer/domainrepository/poll/VoteDomainRepositoryTest.java (1)
src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java (1)
  • Nested (22-65)
src/test/java/com/debatetimer/fixture/PollGenerator.java (1)
src/test/java/com/debatetimer/fixture/VoteGenerator.java (1)
  • Component (10-23)
src/test/java/com/debatetimer/domain/poll/ParticipateCodeTest.java (2)
src/main/java/com/debatetimer/exception/custom/DTClientErrorException.java (1)
  • DTClientErrorException (5-10)
src/test/java/com/debatetimer/domain/poll/ParticipantNameTest.java (1)
  • Nested (14-25)
src/main/java/com/debatetimer/entity/poll/PollEntity.java (3)
src/main/java/com/debatetimer/domain/poll/Poll.java (1)
  • Getter (8-24)
src/main/java/com/debatetimer/domain/poll/ParticipantName.java (1)
  • Getter (7-22)
src/main/java/com/debatetimer/entity/poll/VoteEntity.java (1)
  • Entity (22-51)
🔇 Additional comments (60)
src/main/java/com/debatetimer/domain/poll/Poll.java (2)

20-23: 생성자 위임 패턴이 올바르게 구현되었습니다.

문자열 파라미터를 받는 편의 생성자가 도메인 객체 생성자로 적절히 위임하고 있어, 코드 중복을 방지하고 일관성을 유지합니다.


8-18: 도메인 모델 설계가 잘 되어 있습니다.

불변 객체로 설계되어 있고, 값 객체(TeamName, Agenda)를 활용하여 도메인 로직을 캡슐화했습니다. 필드명도 명확하고 적절합니다.

src/main/java/com/debatetimer/domainrepository/poll/CustomizeTableDomainRepository.java (2)

18-23: 도메인 저장소 구현이 올바른 패턴을 따르고 있습니다.

읽기 전용 트랜잭션 어노테이션, 적절한 예외 처리, 엔티티-도메인 변환 등 모든 부분이 잘 구현되어 있습니다.


12-16: 생성자 주입과 Spring 어노테이션이 적절히 사용되었습니다.

@Repository@RequiredArgsConstructor를 사용한 의존성 주입이 Spring Boot 모범 사례를 따르고 있습니다.

src/test/java/com/debatetimer/repository/BaseRepositoryTest.java (1)

17-17: 후행 쉼표 추가로 코드 일관성이 개선되었습니다.

배열의 마지막 요소 뒤에 쉼표를 추가하는 것은 향후 요소 추가 시 diff를 깔끔하게 유지하는 좋은 관례입니다.

src/test/java/com/debatetimer/controller/Tag.java (1)

9-9: 새로운 Poll API 태그가 일관된 패턴으로 추가되었습니다.

기존 API 태그들과 동일한 명명 규칙을 따르며, 새로운 투표 기능을 위한 적절한 태그입니다.

src/test/java/com/debatetimer/controller/BaseDocumentTest.java (2)

15-15: PollService import가 올바르게 추가되었습니다.

새로운 투표 기능을 위한 서비스 임포트가 적절히 추가되었습니다.


66-67: PollService 목 빈이 일관된 패턴으로 추가되었습니다.

기존 서비스들과 동일한 방식으로 @MockitoBean 어노테이션을 사용하여 테스트 인프라에 적절히 통합되었습니다.

src/test/java/com/debatetimer/controller/BaseControllerTest.java (1)

15-17: 새로운 픽스처 제너레이터 추가가 적절합니다

투표 관련 기능 테스트를 위한 PollGeneratorVoteGenerator 추가가 기존 패턴과 일치하며 적절합니다. 모든 컨트롤러 테스트에서 재사용 가능하도록 베이스 클래스에 추가된 것이 좋은 설계입니다.

Also applies to: 50-54

src/main/java/com/debatetimer/repository/poll/VoteRepository.java (1)

7-10: 깔끔한 리포지토리 인터페이스 구현

Spring Data JPA 규칙을 잘 따르고 있으며, 메서드 명명 규칙도 적절합니다. findAllByPollId 메서드는 자동으로 구현되어 특정 투표에 속한 모든 투표 엔티티를 조회할 수 있습니다.

src/main/java/com/debatetimer/repository/poll/PollRepository.java (1)

7-10: 보안을 고려한 우수한 리포지토리 설계

findByIdAndMemberId 메서드는 투표 ID와 회원 ID를 함께 검증하여 접근 제어를 강화하는 좋은 설계입니다. Optional 반환 타입 사용으로 null 안전성도 확보했습니다.

src/main/java/com/debatetimer/domain/poll/ParticipateCode.java (1)

7-15: 우수한 값 객체 설계

불변성을 보장하고 생성자에서 유효성 검증을 수행하는 도메인 모델링 모범 사례를 잘 따르고 있습니다. 적절한 예외 처리와 함께 깔끔한 구현입니다.

src/main/java/com/debatetimer/dto/poll/response/PollCreateResponse.java (1)

5-10: 깔끔한 DTO 설계입니다.

Record 타입을 사용한 불변 DTO와 도메인 객체로부터 편리하게 생성할 수 있는 생성자가 잘 구현되어 있습니다. API 응답용 DTO로서 적절한 설계입니다.

src/main/java/com/debatetimer/domain/poll/ParticipantName.java (1)

7-22: 적절한 값 객체(Value Object) 구현입니다.

참가자 이름의 유효성 검증을 생성자에서 수행하고, 불변성을 보장하는 설계가 잘 되어 있습니다. ParticipateCode와 동일한 패턴을 따라 일관성도 좋습니다.

src/main/java/com/debatetimer/domain/poll/VoteInfo.java (1)

5-19: 투표 집계 정보를 잘 표현한 도메인 객체입니다.

불변 설계와 생성자에서 총 투표 수를 계산하는 로직이 명확하고 적절합니다. 투표 통계 정보를 캡슐화하는 용도로 잘 설계되었습니다.

src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java (1)

51-57: 투표 기능을 위한 적절한 에러 코드 추가입니다.

새로 추가된 에러 코드들이 적절한 HTTP 상태 코드와 명확한 한국어 오류 메시지를 가지고 있으며, 기존 패턴과 일관성을 유지하고 있습니다. 투표 참가자 검증과 투표 조회 실패에 대한 적절한 에러 처리를 지원합니다.

src/test/java/com/debatetimer/service/BaseServiceTest.java (1)

8-9: 테스트 인프라 확장이 적절합니다.

투표 기능 테스트를 위한 PollRepository, PollGenerator, VoteGenerator가 기존 패턴과 일관성 있게 추가되었습니다. 새로운 투표 관련 기능 테스트를 위한 필수 의존성들이 잘 구성되어 있습니다.

Also applies to: 14-14, 37-39, 52-57

src/main/java/com/debatetimer/entity/poll/VoteEntity.java (2)

23-26: Lombok 어노테이션 추가가 적절합니다.

@Getter@AllArgsConstructor 추가로 코드 간소화가 잘 이루어졌습니다. 점진적 개발 방식에 맞게 필요한 기능만 추가한 점이 좋습니다.


48-50: 도메인 변환 메서드 구현이 올바릅니다.

toDomain() 메서드가 엔티티를 도메인 객체로 올바르게 변환하고 있습니다. poll.getId()를 통해 연관된 Poll의 ID를 추출하는 방식도 적절합니다.

src/test/java/com/debatetimer/domain/poll/ParticipantNameTest.java (1)

14-25: 검증 테스트가 포괄적이고 적절합니다.

매개변수화된 테스트를 사용하여 null, 빈 문자열, 공백 문자열에 대한 검증을 모두 다루고 있습니다. 예외 타입과 메시지 검증도 정확합니다.

src/test/java/com/debatetimer/domain/poll/ParticipateCodeTest.java (1)

14-25: 일관된 테스트 패턴으로 잘 구현되었습니다.

ParticipantNameTest와 동일한 구조로 일관성이 있고, INVALID_POLL_PARTICIPANT_CODE 에러 코드를 올바르게 사용하고 있습니다.

src/main/java/com/debatetimer/domain/poll/Vote.java (2)

6-14: 불변 도메인 객체 설계가 우수합니다.

모든 필드를 final로 선언하여 불변성을 보장하고, ParticipantNameParticipateCode 값 객체를 사용하여 도메인 규칙이 잘 캡슐화되어 있습니다.


16-18: 편의 생성자가 올바르게 구현되었습니다.

String 매개변수를 받아 값 객체로 변환하는 편의 생성자가 주 생성자에 올바르게 위임하고 있습니다. 이는 엔티티에서 도메인 객체로 변환할 때 유용합니다.

src/test/java/com/debatetimer/fixture/VoteGenerator.java (2)

10-17: 테스트 픽스처 설계가 적절합니다.

Spring 컴포넌트로 구현하여 의존성 주입을 활용하고, 생성자 주입 방식을 사용한 점이 좋습니다.


19-22: UUID를 참여 코드로 사용하는 것이 적절합니다.

UUID.randomUUID().toString()을 사용하여 고유한 참여 코드를 생성하는 방식이 테스트 환경에서 충돌을 방지하는 좋은 접근법입니다.

src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java (1)

1-31: 코드 구조와 집계 로직이 잘 구현되었습니다.

도메인 저장소 패턴을 적절히 활용하여 투표 정보 집계 기능을 깔끔하게 구현했습니다. 스트림 API를 사용한 집계 로직도 효율적입니다.

src/test/java/com/debatetimer/domainrepository/poll/VoteDomainRepositoryTest.java (1)

17-44: 테스트 구조가 잘 정리되어 있습니다.

중첩 클래스를 활용한 테스트 그룹화와 fixture generator를 사용한 테스트 데이터 생성이 적절합니다. assertAll을 사용한 다중 검증도 효과적입니다.

src/main/java/com/debatetimer/dto/poll/response/PollInfoResponse.java (2)

7-28: Record를 활용한 깔끔한 DTO 설계입니다.

불변 데이터 클래스로 적절하게 설계되었고, 도메인 객체에서 DTO로의 변환 로직도 명확합니다. 편의 생성자를 통한 객체 생성 방식도 효율적입니다.


17-27: PollInfoResponse에 agenda 필드 누락 확인 필요

  • com.debatetimer.domain.poll.Poll 클래스에
    · private final Agenda agenda;
    · @Getter로 getAgenda() 자동 생성됨
  • com.debatetimer.dto.poll.response.PollInfoResponse record 선언부에 agenda 관련 필드·생성자 파라미터가 없음
  • agenda 값을 포함하려면
    1. record 정의에 String agenda 추가
    2. 레코드 보조 생성자에서 poll.getAgenda().getValue() 전달
    3. PollService, PollController, JSON 매핑 스펙 업데이트
    4. PollServiceTest, PollControllerTest, PollDocumentTest 등 테스트 코드 수정 필요
  • agenda 제외가 의도된 설계인지 확인 부탁드립니다.
src/test/java/com/debatetimer/fixture/PollGenerator.java (1)

10-31: 테스트 픽스처로서 적절한 구현입니다.

VoteGenerator와 일관된 패턴으로 구현되었고, 테스트 데이터 생성 목적에 부합합니다. Spring 컴포넌트로 등록하여 재사용성도 확보했습니다.

src/test/java/com/debatetimer/domainrepository/BaseDomainRepositoryTest.java (1)

13-31: 도메인 저장소 테스트를 위한 잘 구성된 기본 클래스입니다.

BaseServiceTest와 일관된 패턴으로 구현되었고, 웹 환경을 제외하여 테스트 성능을 최적화했습니다. DataBaseCleaner를 통한 테스트 격리도 적절합니다.

src/main/java/com/debatetimer/entity/poll/PollEntity.java (6)

3-3: 도메인 객체 import 추가 확인

Poll 도메인 객체를 import하여 엔티티-도메인 간 변환을 위한 준비가 완료되었습니다.


6-6: JPA 어노테이션과 Lombok 설정 확인

@column 어노테이션과 @Getter 어노테이션이 적절히 추가되어 필드 매핑과 접근성이 개선되었습니다.

Also applies to: 17-17, 21-21


30-34: 데이터베이스 컬럼명 명시적 매핑 개선

tableId와 memberId 필드에 @column 어노테이션으로 데이터베이스 컬럼명을 명시적으로 매핑한 것이 좋습니다. 이는 코드의 가독성과 유지보수성을 향상시킵니다.


48-56: 도메인 객체로부터 엔티티 생성 생성자 구현

Poll 도메인 객체로부터 PollEntity를 생성하는 생성자가 잘 구현되었습니다. 도메인의 값 객체(TeamName, Agenda)에서 실제 값을 추출하여 엔티티 필드에 할당하는 방식이 적절합니다.


58-60: 상태 변경 메서드 캡슐화

updateToDone() 메서드를 통해 Poll 상태를 DONE으로 변경하는 로직이 엔티티 내부에 캡슐화되어 있어 좋습니다. 이는 도메인 로직의 응집성을 높입니다.


62-64: 엔티티에서 도메인 객체로의 변환 메서드

toDomain() 메서드가 엔티티를 도메인 객체로 변환하는 역할을 적절히 수행합니다. Poll 도메인 객체의 생성자를 활용하여 문자열 값들을 값 객체로 자동 변환하는 방식이 효율적입니다.

src/main/java/com/debatetimer/domainrepository/poll/PollDomainRepository.java (5)

1-16: 도메인 리포지토리 클래스 구조 설정

패키지 구조와 필요한 의존성 import가 적절히 구성되었습니다. @repository@requiredargsconstructor 어노테이션을 통한 Spring 빈 등록과 의존성 주입 설정이 올바릅니다.


18-23: Poll 생성 메서드 구현

create 메서드가 도메인 객체를 엔티티로 변환하여 저장 후 다시 도메인 객체로 반환하는 패턴이 일관되게 적용되었습니다. @transactional 어노테이션으로 트랜잭션 관리도 적절합니다.


25-29: Poll 조회 메서드 구현

getByIdAndMemberId 메서드에 @transactional(readOnly = true)를 사용하여 읽기 전용 트랜잭션으로 최적화한 것이 좋습니다. findPoll 헬퍼 메서드를 활용하여 코드 중복을 방지한 점도 훌륭합니다.


31-36: Poll 상태 업데이트 메서드 구현

updateToDone 메서드가 엔티티의 updateToDone() 메서드를 호출하여 상태를 변경하는 방식이 도메인 로직 캡슐화 원칙에 부합합니다. 변경된 엔티티를 도메인 객체로 반환하는 것도 적절합니다.


38-41: 예외 처리를 위한 헬퍼 메서드

findPoll 헬퍼 메서드가 코드 중복을 줄이고 일관된 예외 처리를 제공합니다. POLL_NOT_FOUND 에러 코드를 사용한 예외 처리가 명확합니다.

src/test/java/com/debatetimer/service/poll/PollServiceTest.java (3)

1-22: 테스트 클래스 구조 설정

필요한 import와 테스트 클래스 구조가 적절히 설정되었습니다. BaseServiceTest를 상속받아 공통 테스트 설정을 활용하고, @Autowired로 테스트 대상 서비스를 주입받는 방식이 올바릅니다.


39-63: Poll 정보 조회 테스트

선거 정보 조회 테스트가 매우 포괄적으로 구현되었습니다. 투표 데이터 설정부터 응답의 모든 필드 검증까지 완전히 다루고 있어 훌륭합니다. assertAll을 사용하여 여러 assertion을 그룹화한 것도 좋습니다.


65-78: Poll 상태 업데이트 테스트

선거 완료 상태 변경 테스트가 핵심 동작을 적절히 검증합니다. 상태 변경 전후를 명확히 구분하여 테스트하는 것이 좋습니다.

src/main/java/com/debatetimer/controller/poll/PollController.java (4)

1-21: 컨트롤러 클래스 구조 설정

필요한 import와 어노테이션이 적절히 구성되었습니다. @RestController@requiredargsconstructor를 통한 설정이 Spring Boot 모범 사례를 따르고 있습니다.


23-30: Poll 생성 엔드포인트

POST /api/polls/{tableId} 엔드포인트가 RESTful 설계 원칙에 부합합니다. HTTP 201 Created 상태 코드 사용이 적절하고, @AuthMember를 통한 인증 처리도 올바릅니다.


32-39: Poll 정보 조회 엔드포인트

GET /api/polls/{pollId} 엔드포인트가 리소스 조회에 적합한 설계입니다. HTTP 200 OK 상태 코드 사용이 적절합니다.


41-48: Poll 상태 업데이트 엔드포인트

PATCH /api/polls/{pollId} 엔드포인트가 부분 업데이트에 적합한 HTTP 메서드를 사용했습니다. 상태 변경이라는 특정 작업에 맞는 의미론적 설계가 잘 되어있습니다.

src/test/java/com/debatetimer/controller/poll/PollControllerTest.java (3)

1-19: 컨트롤러 테스트 클래스 구조

필요한 import와 BaseControllerTest 상속이 적절히 설정되었습니다. RestAssured와 AssertJ를 활용한 테스트 환경 구성이 좋습니다.


39-70: Poll 정보 조회 API 테스트

선거 정보 조회 API 테스트가 매우 포괄적으로 구현되었습니다. 투표 데이터 설정, HTTP 요청, 응답 검증까지 모든 단계가 완벽하게 다뤄졌습니다. assertAll을 사용한 다중 assertion도 훌륭합니다.


72-93: Poll 상태 업데이트 API 테스트

선거 완료 상태 변경 API 테스트가 핵심 기능을 적절히 검증합니다. PATCH 요청과 응답 상태 확인이 올바르게 구현되었습니다.

src/test/java/com/debatetimer/domainrepository/poll/PollDomainRepositoryTest.java (2)

25-35: 투표 생성 테스트가 잘 구현되었습니다.

도메인 객체 생성 후 영속성 확인이 적절하게 이루어지고 있습니다. 테스트 로직이 명확하고 검증도 충분합니다.


55-64: 투표 상태 업데이트 테스트가 적절합니다.

상태 변경 로직이 올바르게 테스트되고 있으며, 반환된 도메인 객체의 상태를 적절히 검증하고 있습니다.

src/test/java/com/debatetimer/controller/poll/PollDocumentTest.java (2)

45-61: 투표 생성 API 문서화 테스트가 잘 구성되어 있습니다.

요청 헤더, 경로 파라미터, 응답 필드에 대한 문서화가 적절하고, 테스트 시나리오도 명확합니다.


88-112: 투표 정보 조회 API 문서화가 포괄적입니다.

모든 응답 필드에 대한 설명이 잘 되어 있고, 투표 상태에 대한 설명도 명확합니다.

src/main/java/com/debatetimer/service/poll/PollService.java (4)

1-16: 패키지 구조와 임포트가 잘 정리되어 있습니다.

임포트가 논리적으로 그룹화되어 있고 불필요한 임포트가 없어 보입니다.


17-24: 서비스 클래스 구조가 적절합니다.

Spring 어노테이션과 의존성 주입이 올바르게 구성되어 있습니다.


34-39: 읽기 전용 메서드가 적절하게 구현되었습니다.

@Transactional(readOnly = true) 어노테이션이 올바르게 적용되어 있고, 비즈니스 로직이 간결합니다.


25-32: Poll 생성 시 null ID 전달은 의도된 동작입니다.

도메인 Poll 클래스의 id 필드는 Long 타입의 최종 필드로, 영속화 레이어(JPA 등)에서 식별자를 생성하도록 설계되어 있습니다. 따라서 서비스 계층에서 새 Poll 객체를 생성할 때 null을 넘겨 초기화하는 것은 정상적인 흐름입니다.

  • 추가 개선(선택 사항): null 전달을 숨기고 가독성을 높이기 위해 Poll.createNew(…) 같은 정적 팩토리 메서드를 도입할 수 있습니다.

Comment on lines +1 to +2
ALTER TABLE poll
ADD COLUMN member_id BIGINT NOT NULL
Copy link

@coderabbitai coderabbitai bot Jul 23, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

기존 데이터가 있는 테이블에 NOT NULL 컬럼 추가 시 주의 필요

기존 poll 테이블에 데이터가 있다면 NOT NULL 제약조건이 있는 member_id 컬럼을 추가할 때 마이그레이션이 실패할 수 있습니다. 기존 데이터 처리를 위한 전략이 필요합니다.

다음 중 하나의 방법을 고려해보세요:

  1. 기본값을 설정한 후 별도 마이그레이션에서 실제 값으로 업데이트
  2. 기존 데이터가 없음을 확신할 수 있는 경우 현재 방식 유지

기존 poll 테이블에 데이터가 있는지 확인하는 스크립트를 실행해보겠습니다:


🏁 Script executed:

#!/bin/bash
# Description: Check if there are existing records in poll table by looking at previous migrations
# Expected: Find references to poll table creation and any existing data

# Search for poll table creation and any INSERT statements
rg -A 5 -B 5 "CREATE TABLE.*poll|INSERT INTO.*poll" --type sql

Length of output: 675


기존 데이터가 있는 테이블에 NOT NULL 컬럼 추가 시 데이터 백필 전략 필요

V9 마이그레이션(V9__create_poll_and_vote_table.sql)에서는 poll 테이블만 생성되고 INSERT 구문은 없습니다. 운영 중인 DB에 기존 레코드가 있을 경우, member_id BIGINT NOT NULL 컬럼 추가 시 마이그레이션이 실패할 수 있습니다.

다음 중 하나를 적용해 주세요:

  • 컬럼을 NULL 허용으로 추가
    ALTER TABLE poll ADD COLUMN member_id BIGINT;
    기존 레코드에 적절한 값 배정
    UPDATE poll SET member_id = /* 기본 member ID */;
    NOT NULL 제약조건 추가
    ALTER TABLE poll ALTER COLUMN member_id SET NOT NULL;
  • NOT NULL DEFAULT 옵션으로 추가 후 기본값으로 백필, 이후 DEFAULT 제거
    ALTER TABLE poll ADD COLUMN member_id BIGINT NOT NULL DEFAULT /*기본값*/;
    ALTER TABLE poll ALTER COLUMN member_id DROP DEFAULT;
  • 운영 DB에 레코드가 전혀 없음을 확신할 경우 현 방식 유지

파일: src/main/resources/db/migration/V11__add_memberId_into_poll.sql

🤖 Prompt for AI Agents
In src/main/resources/db/migration/V11__add_memberId_into_poll.sql at lines 1-2,
adding a NOT NULL column member_id directly to the poll table can cause
migration failures if existing data is present. To fix this, either add the
column as nullable first, update existing rows with appropriate member_id
values, then set the column to NOT NULL; or add the column with NOT NULL and a
DEFAULT value to backfill existing rows, then drop the DEFAULT constraint;
alternatively, keep the current approach only if you are certain the table has
no existing records.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

운영중인 DB에 아직 포함안되어서 괜찮아

Copy link

Choose a reason for hiding this comment

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

@coli-geonwoo 알겠습니다! 운영 DB에 아직 poll 테이블 데이터가 없다면 현재 마이그레이션 방식이 안전합니다. 감사합니다.

  (\   /)
 ( ._. )
o_("_")("_")

understanding nod


✏️ Learnings added
Learnt from: coli-geonwoo
PR: debate-timer/debate-timer-be#203
File: src/main/resources/db/migration/V11__add_memberId_into_poll.sql:1-2
Timestamp: 2025-07-24T03:12:25.193Z
Learning: debate-timer 프로젝트에서 poll 관련 테이블들은 아직 운영 환경에 배포되지 않았으므로, NOT NULL 컬럼 추가 시 기존 데이터 백필 전략을 고려할 필요가 없음.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Member

@leegwichan leegwichan left a comment

Choose a reason for hiding this comment

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

/noti @coli-geonwoo
코멘트 많이 달긴 달았는데, 구조적인 문제보다는 저와 다른 사소한 네이밍 이슈가 많은 것 같아요! 반영은 금방 끝날테니 코멘트 많이 달았다고 뭐라하지 마십쇼.
콜리 고생했고~ 추가적인 의견 있으면 더 남겨주세요!

Comment on lines 31 to 36
@Transactional
public Poll updateToDone(long pollId, long memberId) {
PollEntity pollEntity = findPoll(pollId, memberId);
pollEntity.updateToDone();
return pollEntity.toDomain();
}
Copy link
Member

Choose a reason for hiding this comment

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

updateToDone()은 너무 내부 구현 노출하는 것 같은데, finishPoll() 정도는 어떨까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✅ 반영완료

사실 그 네이밍이 더 좋기는 해요. 저도 추상화 준위 신경을 쓰는 편이라.

그런데 궁금한점은 예전에 debate api 만들 때 커찬이 updateXXX를 썼던 기억이 있어서요. 그때 아무래도 내부 변경에 대한 트래킹을 쉽게 하기 위해 해당 네이밍을 썼다고 한걸로 기억하는데, 이번 케이스는 다른 기준을 제시하는 이유가 있는지 궁금해요.

Copy link
Member

Choose a reason for hiding this comment

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

만약에 특정 필드 값등을 바꾸는 것이라면 updateXXX() 라는 용어를 사용할 것 같은데, 도메인 로직이라면 그렇게 사용하지 않을 것 같아요.
해당 업데이트 로직은 Entity에 있는 것은 맞지만 도메인 로직이라고 생각해요. 그래서 finishPoll()을 말씀드렸습니다.

만약에 도메인 로직으로 변경하고 싶다면 아래와 같은 방법도 있겠죠.

// PollService

public finishPoll(long pollId) {
    Poll poll = pollDomainRepository.getById(long pollId);
    poll.finishPoll();
    pollDomainRepository.update(poll); // pollDomainRepository에서 Entity를 조회해서 값을 바꿔 놓기
}

Comment on lines +24 to +36
@Test
void 선거를_생성할_수_있다() {
Member member = memberGenerator.generate("email@email.com");
CustomizeTableEntity table = customizeTableGenerator.generate(member);
Headers headers = headerGenerator.generateAccessTokenHeader(member);

given()
.contentType(ContentType.JSON)
.headers(headers)
.pathParam("tableId", table.getId())
.when().post("/api/polls/{tableId}")
.then().statusCode(HttpStatus.CREATED.value());
}
Copy link
Member

Choose a reason for hiding this comment

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

  • 아래와 같이 안한 이유가 있나요? (아래 코드가 마음에 든다면, 다른 PollControllerTest, PollDocumentTest 에 반영 부탁드립니다)
Suggested change
@Test
void 선거를_생성할_수_있다() {
Member member = memberGenerator.generate("email@email.com");
CustomizeTableEntity table = customizeTableGenerator.generate(member);
Headers headers = headerGenerator.generateAccessTokenHeader(member);
given()
.contentType(ContentType.JSON)
.headers(headers)
.pathParam("tableId", table.getId())
.when().post("/api/polls/{tableId}")
.then().statusCode(HttpStatus.CREATED.value());
}
@Test
void 선거를_생성할_수_있다() {
Member member = memberGenerator.generate("email@email.com");
CustomizeTableEntity table = customizeTableGenerator.generate(member);
given()
.contentType(ContentType.JSON)
.header(HttpHeaders.AUTHORIZATION, accessToken())
.pathParam("tableId", table.getId())
.when().post("/api/polls/{tableId}")
.then().statusCode(HttpStatus.CREATED.value());
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[의견]

이건 일단 보류, CustomizeControllerTest에서 작성한 방식으로 맞춘거라 만약 필요하다면 전부 개선할 필요가 있음

  1. 헤더에 넣는 값을 명확히 한다는 점에서 좋으나,
  2. 반복되는 코드를 감추려고 headerGenerator를 쓰는 거고 의미상으로 안읽히진 않아서 지금도 나쁘지 않다고 생각

Comment on lines 22 to 24
@Nested
class CRUDTest {

Copy link
Member

Choose a reason for hiding this comment

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

Hey Man 이거 클래스 이름을 메서드 이름으로 통일하기로 했던 것 같은데?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

걸렸다

Comment on lines +10 to +17
@Component
public class VoteGenerator {

private final VoteRepository voteRepository;

public VoteGenerator(VoteRepository voteRepository) {
this.voteRepository = voteRepository;
}
Copy link
Member

Choose a reason for hiding this comment

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

[나중에 제안] Generator 이름들을 전부 XXXEntityGenerator로 바꿔야 하나 생각중...
이번 PR에 반영하면 사이즈가 너무 커지니까, 의견만 달아주세요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

저장된 객체를 반환한다는 점에서 그게 좋을 것 같긴 해요.

Copy link
Contributor

@unifolio0 unifolio0 left a comment

Choose a reason for hiding this comment

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

/noti
리뷰 남기고 갑니다~

Comment on lines 9 to 10
Optional<PollEntity> findByIdAndMemberId(long id, long memberId);

Copy link
Contributor

Choose a reason for hiding this comment

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

다른 인터페이스와 개행 맞춰주세요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✅ 반영 완료

Comment on lines 17 to 20
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", " "})
void 투표참여자_이름은_널이거나_빈_값_일_수_없다(String name) {
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 제가 만든 @NullAndEmptyAndBlankSource를 안쓴 이유가 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✅ 반영 완료

좋은 걸 만들어놓으셨군요

Comment on lines 17 to 24
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", " "})
void 투표_참여_코드는_널이거나_빈_값_일_수_없다(String participatecode) {
assertThatThrownBy(() -> new ParticipateCode(participatecode))
.isInstanceOf(DTClientErrorException.class)
.hasMessage(ClientErrorCode.INVALID_POLL_PARTICIPANT_CODE.getMessage());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Comment on lines +9 to +11
private final long totalCount;
private final long prosCount;
private final long consCount;
Copy link
Contributor

Choose a reason for hiding this comment

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

여기는 서비스에서 검증해서 따로 검증로직이 없는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[질문 ✋ ]

음.. 검증로직을 원래 넣었었는데 서로 시각이 다른 부분이 있을 것 같아 비토랑 커찬 시각이 궁금하네요.

해당 count 값은 애플리케이션 서버 로직 내에서 완전히 통제되는 값이에요. 즉, 외부 변인에 의해 수정될 여지가 크게 없다고 판단했어요.
또, 만약 검증로직을 넣는다면 ClientError인지, Server Error로 처리해야할지가 애매했습니다.

  1. 검증로직 필요하다고 보는가?
  2. 만약 양수 검증을 한다면 Server Error인가, Client Error인가?

Copy link
Member

Choose a reason for hiding this comment

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

저는 "DB에 있는 값을 보여주기 위한 객체"에는 유효성 검사를 잘 안하는 것 같아요.차라리 동작 안하는 것보다는 뭐라도 어떻게는 동작하게 하자는 느낌?
만약에 이 객체가 유저가 입력을 위한 객체로도 쓰인다고 하면, 그 때 추가하지 않을까 싶네요.

Copy link
Contributor

Choose a reason for hiding this comment

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

개인적으로 저는 넣는 편이긴 합니다. 다만 그 검증은 사용자에게 보여주기보단 다른 개발자의 실수 방지 용도에 더 가깝긴 할 것 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

일단 voter api를 만들기 위해 놔두겠습니다. 백엔드 회의 때 논의해보시죵

@coli-geonwoo
Copy link
Contributor Author

/noti

리뷰 반영했으니 한번 확인 부탁합니다

Copy link
Member

@leegwichan leegwichan left a comment

Choose a reason for hiding this comment

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

/noti @coli-geonwoo
미안해요. 심신미약 상태라 리뷰해야된다는 사실을 까먹었네요;; 모든 내용 전반적으로 반영해줘서 고맙고 Approve 누르겠습니다!

Copy link
Contributor

@unifolio0 unifolio0 left a comment

Choose a reason for hiding this comment

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

/noti
@coli-geonwoo
리뷰 2개 남겼어요~

Comment on lines +9 to +11
private final long totalCount;
private final long prosCount;
private final long consCount;
Copy link
Contributor

Choose a reason for hiding this comment

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

개인적으로 저는 넣는 편이긴 합니다. 다만 그 검증은 사용자에게 보여주기보단 다른 개발자의 실수 방지 용도에 더 가깝긴 할 것 같아요.

void 선거를_생성한다() {
Member member = memberGenerator.generate("email@email.com");
CustomizeTableEntity table = customizeTableGenerator.generate(member);
Poll poll = new Poll(null, table.getId(), member.getId(), PollStatus.PROGRESS, "찬성", "반대", "주제");
Copy link
Contributor

Choose a reason for hiding this comment

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

여기에는 null이 안들어가는 생성자를 안쓴 이유가 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

나중에 추가된 거라 test까지 신경 못썼어요 비꼼(비토 꼼꼼이라는 뜻) 👍

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo left a comment

Choose a reason for hiding this comment

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

/noti

비토 리뷰까지 반영해서 merge 하겠습니다~
몇가지 논의할 부분은 커찬이 파놓은 회의록에 정리해놓았어요!

Comment on lines +9 to +11
private final long totalCount;
private final long prosCount;
private final long consCount;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

일단 voter api를 만들기 위해 놔두겠습니다. 백엔드 회의 때 논의해보시죵

void 선거를_생성한다() {
Member member = memberGenerator.generate("email@email.com");
CustomizeTableEntity table = customizeTableGenerator.generate(member);
Poll poll = new Poll(null, table.getId(), member.getId(), PollStatus.PROGRESS, "찬성", "반대", "주제");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

나중에 추가된 거라 test까지 신경 못썼어요 비꼼(비토 꼼꼼이라는 뜻) 👍

@coli-geonwoo coli-geonwoo merged commit ca1c4c1 into develop Jul 26, 2025
5 checks passed
@coli-geonwoo coli-geonwoo deleted the feat/#199 branch July 26, 2025 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 투표 개최자 API 구현

3 participants