Conversation
Summary by CodeRabbit릴리스 노트
🐰 환불 기한 1년 제한 기능 추가워크스루토큰 구매에 1년 환불 만료 기한을 도입합니다. 도메인 모델, 상태 열거형, 응답 매핑, 서비스 검증 로직, 그리고 통합 및 단위 테스트를 수정하여 구매 1년 후 환불 불가능하도록 제한합니다. 변경 사항
예상 코드 리뷰 노력🎯 3 (Moderate) | ⏱️ ~20 minutes 관련 가능성 있는 PR
추천 검토자
시 🐰
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 Pull Request는 토큰 구매에 대한 환불 정책을 강화하기 위해 환불 기한을 1년으로 제한하는 기능을 구현합니다. 기존 환불 가능 여부 로직에 기한 만료 조건을 추가하고, 사용자에게 표시되는 환불 상태를 세분화하며, 환불 요청 처리 시 기한 만료 여부를 엄격하게 검증하도록 시스템을 개선했습니다. 이는 비즈니스 요구사항을 반영하고 데이터 일관성을 유지하는 데 기여합니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. 일 년의 시간, 환불의 문 닫히네. 신중한 구매. Footnotes
|
| public boolean isRefundable() { | ||
| return state == TokenPurchaseState.REFUNDABLE && purchaseCount.equals(remainingCount); | ||
| return state == TokenPurchaseState.REFUNDABLE && purchaseCount.equals(remainingCount) && !isRefundExpired(); | ||
| } | ||
|
|
||
| public boolean isRefundExpired() { | ||
| return getCreatedAt().plusYears(REFUND_EXPIRY_YEARS).isBefore(LocalDateTime.now()); | ||
| } |
There was a problem hiding this comment.
도메인 모델 내에서 LocalDateTime.now()를 직접 호출하면 시스템 시계에 대한 숨은 의존성이 생겨 테스트하기 어려운 코드가 될 수 있습니다. 시간과 관련된 로직은 외부에서 현재 시간을 주입받는 형태로 변경하는 것을 고려해보세요. 이렇게 하면 테스트 시 시간을 제어하기 용이해져 코드의 안정성과 예측 가능성을 높일 수 있습니다.
예를 들어, isRefundable과 isRefundExpired 메서드가 LocalDateTime을 인자로 받도록 수정하고, 서비스 레이어에서 LocalDateTime.now()를 전달하는 방식입니다. 이 변경은 isNotRefundable 등 관련 메서드들의 시그니처 변경을 수반합니다. 이렇게 수정하면 TokenPurchaseTest에서 리플렉션을 사용하여 createdAt을 설정하는 대신, 테스트 시간을 명시적으로 전달하여 더 깔끔한 테스트를 작성할 수 있습니다.
| public boolean isRefundable() { | |
| return state == TokenPurchaseState.REFUNDABLE && purchaseCount.equals(remainingCount); | |
| return state == TokenPurchaseState.REFUNDABLE && purchaseCount.equals(remainingCount) && !isRefundExpired(); | |
| } | |
| public boolean isRefundExpired() { | |
| return getCreatedAt().plusYears(REFUND_EXPIRY_YEARS).isBefore(LocalDateTime.now()); | |
| } | |
| public boolean isRefundable(java.time.LocalDateTime now) { | |
| return state == TokenPurchaseState.REFUNDABLE && purchaseCount.equals(remainingCount) && !isRefundExpired(now); | |
| } | |
| public boolean isRefundExpired(java.time.LocalDateTime now) { | |
| return getCreatedAt().plusYears(REFUND_EXPIRY_YEARS).isBefore(now); | |
| } |
Test Results 49 files 49 suites 1m 29s ⏱️ Results for commit e2a560c. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/test/java/com/samhap/kokomen/token/controller/TokenControllerTest.java`:
- Around line 57-58: Replace the fully-qualified type declaration on the
jdbcTemplate field in TokenControllerTest with an import: add import for
org.springframework.jdbc.core.JdbcTemplate at the top and change the field to
"private JdbcTemplate jdbcTemplate;" (keeping the `@Autowired` annotation and
field name intact) so the class uses the simple type name for improved
readability.
- Around line 633-667: The test 구매일로부터_1년_경과한_토큰_환불_실패 currently only asserts
status 400 which could be triggered by other validation errors; update
TokenControllerTest (the test method 구매일로부터_1년_경과한_토큰_환불_실패) to also assert the
failure reason by checking the response body for the specific error code or
message your controller returns for expired refunds (e.g. an "expired" error
code or message like "TOKEN_REFUND_EXPIRED" or "refund period expired") using
mockMvc.perform(...).andExpect(...). For example, after the existing
.andExpect(status().isBadRequest()) add an assertion that inspects the JSON
error field (jsonPath("$.code") or jsonPath("$.message")) or the response string
to ensure the error matches the expiration case so the test cannot pass for
unrelated BadRequest causes.
In `@src/test/java/com/samhap/kokomen/token/domain/TokenPurchaseTest.java`:
- Around line 43-50: The test method 구매일로부터_정확히_1년이면_환불_가능하다 has a mismatch
between its name and setup: it sets createdAt to
LocalDateTime.now().minusYears(1).plusMinutes(1) which is not exactly one year;
either change the createdAt value to LocalDateTime.now().minusYears(1) (adjust
any helper call setCreatedAt(tokenPurchase, ...)) or rename the test to reflect
“약 1년”/“1년보다 조금 지난 경우”; update the TokenPurchaseFixtureBuilder usage and the
setCreatedAt call accordingly so the test name and setup are consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: cb6175bc-bc1b-4167-9747-7d5c6db25b2a
📒 Files selected for processing (6)
src/main/java/com/samhap/kokomen/token/domain/TokenPurchase.javasrc/main/java/com/samhap/kokomen/token/domain/TokenPurchaseState.javasrc/main/java/com/samhap/kokomen/token/dto/TokenPurchaseResponse.javasrc/main/java/com/samhap/kokomen/token/service/TokenFacadeService.javasrc/test/java/com/samhap/kokomen/token/controller/TokenControllerTest.javasrc/test/java/com/samhap/kokomen/token/domain/TokenPurchaseTest.java
| @Autowired | ||
| private org.springframework.jdbc.core.JdbcTemplate jdbcTemplate; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
타입 선언은 import를 사용하면 가독성이 더 좋습니다.
필드에서 FQCN 직접 사용 대신 import로 정리하면 테스트 클래스 읽기가 수월해집니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/com/samhap/kokomen/token/controller/TokenControllerTest.java`
around lines 57 - 58, Replace the fully-qualified type declaration on the
jdbcTemplate field in TokenControllerTest with an import: add import for
org.springframework.jdbc.core.JdbcTemplate at the top and change the field to
"private JdbcTemplate jdbcTemplate;" (keeping the `@Autowired` annotation and
field name intact) so the class uses the simple type name for improved
readability.
| @Test | ||
| void 구매일로부터_1년_경과한_토큰_환불_실패() throws Exception { | ||
| // given | ||
| Member member = memberRepository.save(MemberFixtureBuilder.builder().build()); | ||
| tokenService.createTokensForNewMember(member.getId()); | ||
|
|
||
| TokenPurchase tokenPurchase = tokenPurchaseRepository.save( | ||
| TokenPurchaseFixtureBuilder.builder() | ||
| .memberId(member.getId()) | ||
| .count(10) | ||
| .remainingCount(10) | ||
| .state(TokenPurchaseState.REFUNDABLE) | ||
| .build() | ||
| ); | ||
|
|
||
| // created_at을 1년 전으로 변경 (native SQL로 @CreatedDate 필드 직접 수정) | ||
| jdbcTemplate.update( | ||
| "UPDATE token_purchase SET created_at = ? WHERE id = ?", | ||
| java.time.LocalDateTime.now().minusYears(1).minusDays(1), | ||
| tokenPurchase.getId() | ||
| ); | ||
|
|
||
| MockHttpSession session = new MockHttpSession(); | ||
| session.setAttribute("MEMBER_ID", member.getId()); | ||
|
|
||
| TokenRefundRequest request = new TokenRefundRequest(RefundReasonCode.CHANGE_OF_MIND, null); | ||
|
|
||
| // when & then | ||
| mockMvc.perform(patch("/api/v1/token-purchases/{tokenPurchaseId}/refund", tokenPurchase.getId()) | ||
| .contentType(MediaType.APPLICATION_JSON) | ||
| .content(objectMapper.writeValueAsString(request)) | ||
| .header("Cookie", "JSESSIONID=" + session.getId()) | ||
| .session(session)) | ||
| .andExpect(status().isBadRequest()); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
실패 원인까지 검증해 테스트 의도를 고정해 주세요.
현재는 400만 확인해서, 만료 외 다른 BadRequest로도 통과할 수 있습니다. 만료 메시지(또는 에러 코드)까지 함께 검증하는 편이 안전합니다.
🔧 보강 예시
mockMvc.perform(patch("/api/v1/token-purchases/{tokenPurchaseId}/refund", tokenPurchase.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request))
.header("Cookie", "JSESSIONID=" + session.getId())
.session(session))
- .andExpect(status().isBadRequest());
+ .andExpect(status().isBadRequest())
+ .andExpect(result -> assertThat(result.getResponse().getContentAsString())
+ .contains("구매일로부터 1년이 경과하여 환불이 불가합니다."));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/com/samhap/kokomen/token/controller/TokenControllerTest.java`
around lines 633 - 667, The test 구매일로부터_1년_경과한_토큰_환불_실패 currently only asserts
status 400 which could be triggered by other validation errors; update
TokenControllerTest (the test method 구매일로부터_1년_경과한_토큰_환불_실패) to also assert the
failure reason by checking the response body for the specific error code or
message your controller returns for expired refunds (e.g. an "expired" error
code or message like "TOKEN_REFUND_EXPIRED" or "refund period expired") using
mockMvc.perform(...).andExpect(...). For example, after the existing
.andExpect(status().isBadRequest()) add an assertion that inspects the JSON
error field (jsonPath("$.code") or jsonPath("$.message")) or the response string
to ensure the error matches the expiration case so the test cannot pass for
unrelated BadRequest causes.
| void 구매일로부터_정확히_1년이면_환불_가능하다() { | ||
| // given | ||
| TokenPurchase tokenPurchase = TokenPurchaseFixtureBuilder.builder() | ||
| .state(TokenPurchaseState.REFUNDABLE) | ||
| .count(10) | ||
| .remainingCount(10) | ||
| .build(); | ||
| setCreatedAt(tokenPurchase, LocalDateTime.now().minusYears(1).plusMinutes(1)); |
There was a problem hiding this comment.
테스트 이름과 설정 시간이 불일치합니다.
구매일로부터_정확히_1년이면_환불_가능하다인데, 실제 데이터는 minusYears(1).plusMinutes(1)로 “정확히 1년”이 아닙니다. 테스트명을 바꾸거나 시간을 정확히 1년으로 맞춰 주세요.
🔧 수정 예시
- void 구매일로부터_정확히_1년이면_환불_가능하다() {
+ void 구매일로부터_1년_미만이면_환불_가능하다() {
...
- setCreatedAt(tokenPurchase, LocalDateTime.now().minusYears(1).plusMinutes(1));
+ setCreatedAt(tokenPurchase, LocalDateTime.now().minusYears(1).plusMinutes(1));또는
- setCreatedAt(tokenPurchase, LocalDateTime.now().minusYears(1).plusMinutes(1));
+ setCreatedAt(tokenPurchase, LocalDateTime.now().minusYears(1));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/java/com/samhap/kokomen/token/domain/TokenPurchaseTest.java` around
lines 43 - 50, The test method 구매일로부터_정확히_1년이면_환불_가능하다 has a mismatch between
its name and setup: it sets createdAt to
LocalDateTime.now().minusYears(1).plusMinutes(1) which is not exactly one year;
either change the createdAt value to LocalDateTime.now().minusYears(1) (adjust
any helper call setCreatedAt(tokenPurchase, ...)) or rename the test to reflect
“약 1년”/“1년보다 조금 지난 경우”; update the TokenPurchaseFixtureBuilder usage and the
setCreatedAt call accordingly so the test name and setup are consistent.
closed #345
작업 내용
진짜 나는 카와이한 오상훈이다. 나보다 잘생긴놈 나오라고해.
스크린샷
참고 사항