-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACOTR] AI_비용_저장_동시성_이슈 해결 #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
45a4742
3208ad3
28d0854
987bbae
f48f54b
9c82401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,8 @@ public enum ErrorCode { | |
| REDIS_SUBSCRIBE_ERROR(500, "레디스 이벤트 수신 과정에서 문제가 생겼습니다"), | ||
| GITHUB_CLIENT_ERROR(500, "깃허브 클라이언트 소통과정에 문제가 발생했습니다"), | ||
| AI_RESPONSE_PARSING_ERROR(500, "AI로부터 온 질문 생성을 파싱하는 과정에 오류가 발생했습니다"), | ||
| AI_CREATE_QUESTION_ERROR(500, "AI 질문 생성과정에 오류가 발생했습니다") | ||
| AI_CREATE_QUESTION_ERROR(500, "AI 질문 생성과정에 오류가 발생했습니다"), | ||
| AI_CHARGE_NOT_FOUND(500, "당월 AI 비용을 찾을 없습니다.") | ||
| ; | ||
|
Comment on lines
+43
to
45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오류 메시지 오타 수정 및 상태코드 검토
문구 수정안: - AI_CHARGE_NOT_FOUND(500, "당월 AI 비용을 찾을 없습니다.")
+ AI_CHARGE_NOT_FOUND(500, "당월 AI 비용을 찾을 수 없습니다.")(선택) 상태코드 정합성 향상: - AI_CHARGE_NOT_FOUND(500, "당월 AI 비용을 찾을 수 없습니다.")
+ AI_CHARGE_NOT_FOUND(404, "당월 AI 비용을 찾을 수 없습니다.")🤖 Prompt for AI Agents |
||
|
|
||
| private final int statusCode; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,4 +7,6 @@ public interface AiChargeRepository { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| AiCharge getByYearAndMonth(int year, int month); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void addCharge(int year, int month, double charge); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AiCharge save(AiCharge charge); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
9
to
12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainaddCharge 결과값 반환 및 save 계약 명시화
- void addCharge(int year, int month, double charge);
+ int addCharge(int year, int month, double charge);
- AiCharge save(AiCharge charge);
+ /**
+ * 지정 연/월의 AiCharge를 저장한다.
+ * 동시성으로 인한 중복키 충돌 발생 시의 처리(무시/조회 재시도/예외)를 명확히 보장해야 한다.
+ */
+ AiCharge save(AiCharge charge);검증 스크립트(리포지토리 구현이 업데이트 카운트를 반환하는지 확인): 🏁 Script executed: #!/bin/bash
# updateChargeById 시그니처와 @Modifying 여부 확인
rg -nP -C3 'interface\s+AiChargeJpaRepository\b'
rg -nP -C2 '\bupdateChargeById\s*\('
rg -nP -C2 '@Modifying'Length of output: 4562 addCharge 반환값(int) 및 save 계약 자바독 명시화 필요
// gss-domain/src/main/java/com/devoops/domain/repository/analysis/AiChargeRepository.java
- void addCharge(int year, int month, double charge);
+ int addCharge(int year, int month, double charge);
- AiCharge save(AiCharge charge);
+ /**
+ * 지정 연/월의 AiCharge를 저장합니다.
+ * 신규 생성(insert) 또는 기존 수정(upsert) 동작을 보장하며,
+ * 동시성 중복 키 충돌 시(무시/조회 재시도/예외 처리) 동작을 문서화해야 합니다.
+ */
+ AiCharge save(AiCharge charge);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.domain.entity.analysis.AiCharge; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.domain.repository.analysis.AiChargeRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.exception.custom.GssException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.exception.errorcode.ErrorCode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.jpa.entity.analysis.AiChargeEntity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.math.BigDecimal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -17,15 +19,20 @@ public class AiChargeRepositoryImpl implements AiChargeRepository { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AiCharge getByYearAndMonth(int year, int month) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chargeJpaRepository.findByYearAndMonth(year, month) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseGet(() -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AiCharge initializeCharge = new AiCharge(year, month, BigDecimal.ZERO); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chargeJpaRepository.save(AiChargeEntity.from(initializeCharge)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).toDomainEntity(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .orElseThrow(() -> new GssException(ErrorCode.AI_CHARGE_NOT_FOUND)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toDomainEntity(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void addCharge(int year, int month, double charge) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chargeJpaRepository.updateChargeById(year, month, charge); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AiCharge save(AiCharge charge) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chargeJpaRepository.save(AiChargeEntity.from(charge)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toDomainEntity(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion save 동시성 중복키 대비 — 멱등 저장으로 보강 다중 노드/스케줄러 중복 실행 시 unique(year, month) 충돌 가능. DataIntegrityViolationException을 잡아 조회 재시도로 멱등화하세요. import com.devoops.domain.entity.analysis.AiCharge;
import com.devoops.domain.repository.analysis.AiChargeRepository;
import com.devoops.exception.custom.GssException;
import com.devoops.exception.errorcode.ErrorCode;
import com.devoops.jpa.entity.analysis.AiChargeEntity;
import java.math.BigDecimal;
+import org.springframework.dao.DataIntegrityViolationException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@@
@Override
@Transactional
public AiCharge save(AiCharge charge) {
- return chargeJpaRepository.save(AiChargeEntity.from(charge))
- .toDomainEntity();
+ try {
+ return chargeJpaRepository.save(AiChargeEntity.from(charge))
+ .toDomainEntity();
+ } catch (DataIntegrityViolationException e) {
+ // 동시성으로 이미 생성된 경우: 조회로 멱등 보장
+ return chargeJpaRepository.findByYearAndMonth(charge.getYear(), charge.getMonth())
+ .orElseThrow(() -> new GssException(ErrorCode.AI_CHARGE_NOT_FOUND))
+ .toDomainEntity();
+ }
}(운영 제안) 스케줄러 단일 실행을 위해 ShedLock 등을 사용하면 DB 충돌 자체를 크게 줄일 수 있습니다. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.devoops.service.pranalysis; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.domain.entity.analysis.AiCharge; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.devoops.domain.repository.analysis.AiChargeRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.math.BigDecimal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.YearMonth; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.ZoneId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. initializeNextMonth 멱등/동시성 안전성 부족 — 고유키 충돌 흡수 및 재조회 현재는 무조건 -import org.springframework.stereotype.Service;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
@@
- public AiCharge initializeNextMonth() {
+ @Transactional
+ public AiCharge initializeNextMonth() {
LocalDate now = LocalDate.now(SEOUL_ZONE_ID);
YearMonth nextMonth = YearMonth.from(now).plusMonths(1);
AiCharge aiCharge = new AiCharge(nextMonth.getYear(), nextMonth.getMonthValue(), BigDecimal.ZERO);
- return chargeRepository.save(aiCharge);
+ try {
+ return chargeRepository.save(aiCharge);
+ } catch (DataIntegrityViolationException e) {
+ // 이미 다른 쓰레드/노드가 생성함: 재조회로 멱등 보장
+ return chargeRepository.getByYearAndMonth(nextMonth.getYear(), nextMonth.getMonthValue());
+ }
}Also applies to: 20-25 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class AiChargeService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final ZoneId SEOUL_ZONE_ID = ZoneId.of("Asia/Seoul"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final AiChargeRepository chargeRepository; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AiCharge initializeNextMonth() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate now = LocalDate.now(SEOUL_ZONE_ID); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| YearMonth nextMonth = YearMonth.from(now).plusMonths(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AiCharge aiCharge = new AiCharge(nextMonth.getYear(), nextMonth.getMonthValue(), BigDecimal.ZERO); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chargeRepository.save(aiCharge); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AiCharge getMonthlyCharge() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate today = LocalDate.now(SEOUL_ZONE_ID); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return chargeRepository.getByYearAndMonth(today.getYear(), today.getMonthValue()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 미존재 월 예외 전파는 서비스 책임 회피 — 안전한 지연 생성으로 전환 운영 중 스케줄 누락/초기 데이터 부재 가능성을 고려해 서비스 레이어에서 “조회 또는 생성”을 보장하세요. - public AiCharge getMonthlyCharge() {
- LocalDate today = LocalDate.now(SEOUL_ZONE_ID);
- return chargeRepository.getByYearAndMonth(today.getYear(), today.getMonthValue());
- }
+ public AiCharge getOrCreateMonthlyCharge() {
+ LocalDate today = LocalDate.now(SEOUL_ZONE_ID);
+ try {
+ return chargeRepository.getByYearAndMonth(today.getYear(), today.getMonthValue());
+ } catch (com.devoops.exception.GssException ex) {
+ if (ex.getErrorCode() != com.devoops.exception.errorcode.ErrorCode.AI_CHARGE_NOT_FOUND) throw ex;
+ AiCharge newCharge = new AiCharge(today.getYear(), today.getMonthValue(), BigDecimal.ZERO);
+ try {
+ return chargeRepository.save(newCharge);
+ } catch (org.springframework.dao.DataIntegrityViolationException e) {
+ return chargeRepository.getByYearAndMonth(today.getYear(), today.getMonthValue());
+ }
+ }
+ }사용처( 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void addCharge(double consumedCharge) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate today = LocalDate.now(SEOUL_ZONE_ID); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chargeRepository.addCharge(today.getYear(), today.getMonthValue(), consumedCharge); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.devoops.config; | ||
|
|
||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.scheduling.annotation.EnableScheduling; | ||
|
|
||
| @Configuration | ||
| @EnableScheduling | ||
| public class SchedulingConfig { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.devoops.service.pranalysis; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.scheduling.annotation.Scheduled; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| class AiChargeScheduler { | ||
|
|
||
| private final AiChargeService aiChargeService; | ||
|
|
||
| @Scheduled(cron = "0 55 23 L * ?", zone = "Asia/Seoul") | ||
| public void createNextMonthCharge() { | ||
| aiChargeService.initializeNextMonth(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.devoops.service.pranalysis; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.junit.jupiter.api.Assertions.assertAll; | ||
|
|
||
| import com.devoops.BaseMcpTest; | ||
| import com.devoops.domain.entity.analysis.AiCharge; | ||
| import com.devoops.domain.repository.analysis.AiChargeRepository; | ||
| import java.time.LocalDate; | ||
| import java.time.YearMonth; | ||
| import java.time.ZoneId; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
|
|
||
| class AiChargeSchedulerTest extends BaseMcpTest { | ||
|
|
||
| @Autowired | ||
| private AiChargeScheduler aiChargeScheduler; | ||
|
|
||
| @Autowired | ||
| private AiChargeRepository aiChargeRepository; | ||
|
|
||
| @Nested | ||
| class CreateNextMonthCharge { | ||
|
|
||
| @Test | ||
| void 다음달_비용을_초기화한다() { | ||
| ZoneId zoneId = ZoneId.of("Asia/Seoul"); | ||
| YearMonth yearMonth = YearMonth.from(LocalDate.now(zoneId)); | ||
| YearMonth nextMonth = yearMonth.plusMonths(1); | ||
|
|
||
| aiChargeScheduler.createNextMonthCharge(); | ||
|
Comment on lines
+29
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain경계 시각(자정/말일) 플래키 발생 가능 — Clock 주입으로 결정론 보장 필요 테스트/서비스가 각각 원하시면 🏁 Script executed: #!/bin/bash
set -e
# Locate the test file
TEST_FILE=$(find . -type f -name AiChargeSchedulerTest.java)
echo "=== Test file: $TEST_FILE ==="
sed -n '1,200p' "$TEST_FILE"
# Locate the service file defining AiChargeService
SERVICE_FILE=$(grep -R --include="*.java" "class AiChargeService" -l .)
echo "=== Service file(s): $SERVICE_FILE ==="
# Show the createNextMonthCharge method context
grep -R --include="*.java" -n "createNextMonthCharge" -C3 $SERVICE_FILE
sed -n '1,200p' $SERVICE_FILE | sed -n '/createNextMonthCharge/,/^}/p'
# Check for LocalDate.now usage in the service
echo "=== LocalDate.now usages in service ==="
grep -R --include="*.java" "LocalDate.now" -n $SERVICE_FILE
# Check if Clock is injected anywhere in the service or test
echo "=== Clock references in codebase ==="
grep -R --include="*.java" "\bClock\b" -n .Length of output: 2565 🏁 Script executed: #!/bin/bash
set -e
# AiChargeScheduler 클래스 파일 위치 확인
SCHEDULER_FILE=$(find . -type f -name AiChargeScheduler.java)
echo "=== Scheduler file: $SCHEDULER_FILE ==="
# createNextMonthCharge 메서드 주변 코드 확인
grep -n -C3 "createNextMonthCharge" "$SCHEDULER_FILE"
# LocalDate.now 사용 여부 확인
grep -n "LocalDate.now" "$SCHEDULER_FILE"Length of output: 808 🏁 Script executed: #!/bin/bash
set -e
# Locate AiChargeService file
SERVICE_FILE=$(find . -type f -path "*com/devoops/service/pranalysis/AiChargeService.java")
echo "=== Service file: $SERVICE_FILE ==="
# Show top of file
sed -n '1,200p' "$SERVICE_FILE"
# Show initializeNextMonth method context
grep -n -C3 "initializeNextMonth" "$SERVICE_FILE"
# Check for LocalDate.now usage in service
grep -n "LocalDate.now" "$SERVICE_FILE"Length of output: 2505 AiChargeService에 Clock 주입하고 테스트에서 고정 Clock 사용 🤖 Prompt for AI Agents |
||
|
|
||
| AiCharge nextMonthAiCharge = aiChargeRepository.getByYearAndMonth( | ||
| nextMonth.getYear(), | ||
| nextMonth.getMonthValue() | ||
| ); | ||
| assertAll( | ||
| () -> assertThat(nextMonthAiCharge.getYear()).isEqualTo(nextMonth.getYear()), | ||
| () -> assertThat(nextMonthAiCharge.getMonth()).isEqualTo(nextMonth.getMonthValue()), | ||
| () -> assertThat(nextMonthAiCharge.getCharge().doubleValue()).isEqualTo(0.0) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
테스트 안정성: 날짜 고정 및 에러코드까지 검증 권장
검증 스크립트: GssException이 errorCode 게터를 노출하는지 확인
🏁 Script executed:
Length of output: 1175
테스트 안정성 강화 및 (선택) 에러코드 검증
LocalDate.of(2099, 12, 1))assertThatThrownBy(...) .extracting("errorCode") .isEqualTo(ErrorCode.AI_CHARGE_NOT_FOUND)→ GssException에
getErrorCode()가 Lombok@Getter로 생성되어 있어 검증 가능🤖 Prompt for AI Agents