diff --git a/src/main/java/com/bop/youthpick/sync/entity/PolicyBatchHistory.java b/src/main/java/com/bop/youthpick/sync/entity/PolicyBatchHistory.java index eaf2dee..fea0945 100644 --- a/src/main/java/com/bop/youthpick/sync/entity/PolicyBatchHistory.java +++ b/src/main/java/com/bop/youthpick/sync/entity/PolicyBatchHistory.java @@ -93,12 +93,34 @@ public void succeed( this.errorCount = errorCount; } + /** 원인 불명 조기 실패용(예: API fetch 자체가 실패해 아무것도 처리 못 한 경우) — 카운트는 전부 0으로 남는다. */ public void fail(String message) { this.status = BatchStatus.FAILED; this.finishedAt = LocalDateTime.now(); - this.failureMessage = - message != null && message.length() > FAILURE_MESSAGE_MAX - ? message.substring(0, FAILURE_MESSAGE_MAX) - : message; + this.failureMessage = truncate(message); + } + + /** 실패율 초과 등 일부라도 처리된 뒤 실패 처리하는 경우 — succeed와 동일한 카운트를 함께 남긴다. */ + public void fail( + String message, + int newCount, + int updatedCount, + int unchangedCount, + int missingCount, + int errorCount) { + this.status = BatchStatus.FAILED; + this.finishedAt = LocalDateTime.now(); + this.failureMessage = truncate(message); + this.newCount = newCount; + this.updatedCount = updatedCount; + this.unchangedCount = unchangedCount; + this.missingCount = missingCount; + this.errorCount = errorCount; + } + + private static String truncate(String message) { + return message != null && message.length() > FAILURE_MESSAGE_MAX + ? message.substring(0, FAILURE_MESSAGE_MAX) + : message; } } diff --git a/src/main/java/com/bop/youthpick/sync/service/PolicySyncService.java b/src/main/java/com/bop/youthpick/sync/service/PolicySyncService.java index 9566cfa..444d40f 100644 --- a/src/main/java/com/bop/youthpick/sync/service/PolicySyncService.java +++ b/src/main/java/com/bop/youthpick/sync/service/PolicySyncService.java @@ -106,7 +106,13 @@ private PolicyBatchHistory doRunFullSync() { "정책 수집 실패율 10%% 초과 — 에러 건수: %d/%d (%.2f%%)" .formatted( writeResult.errorCount(), totalProcessed, errorRate * 100); - history.fail(errorMsg); + history.fail( + errorMsg, + writeResult.newCount(), + writeResult.updatedCount(), + plan.unchangedCount(), + missingMarked, + writeResult.errorCount()); log.error("정책 수집 완료되었으나 실패율 기준 초과로 작업 실패 처리함: {}", errorMsg); } else { history.succeed( diff --git a/src/test/java/com/bop/youthpick/sync/entity/PolicyBatchHistoryTest.java b/src/test/java/com/bop/youthpick/sync/entity/PolicyBatchHistoryTest.java index 7d08b45..a80c9ce 100644 --- a/src/test/java/com/bop/youthpick/sync/entity/PolicyBatchHistoryTest.java +++ b/src/test/java/com/bop/youthpick/sync/entity/PolicyBatchHistoryTest.java @@ -63,4 +63,31 @@ class PolicyBatchHistoryTest { assertThat(history.getFailureMessage()).hasSize(1000); } + + @Test + void 카운트를_포함한_fail은_FAILED_상태와_함께_신규_변경_유지_누락_실패_건수를_기록한다() { + PolicyBatchHistory history = PolicyBatchHistory.request(BatchMode.FULL); + history.start(); + + history.fail("정책 수집 실패율 10% 초과 — 에러 건수: 12/12 (100.00%)", 0, 0, 0, 0, 12); + + assertThat(history.getStatus()).isEqualTo(BatchStatus.FAILED); + assertThat(history.getFinishedAt()).isNotNull(); + assertThat(history.getFailureMessage()).contains("12/12"); + assertThat(history.getNewCount()).isZero(); + assertThat(history.getUpdatedCount()).isZero(); + assertThat(history.getUnchangedCount()).isZero(); + assertThat(history.getMissingCount()).isZero(); + assertThat(history.getErrorCount()).isEqualTo(12); + } + + @Test + void 카운트를_포함한_fail도_실패_메시지가_컬럼_한도를_넘으면_잘라서_기록한다() { + PolicyBatchHistory history = PolicyBatchHistory.request(BatchMode.FULL); + + history.fail("가".repeat(2000), 1, 2, 3, 4, 5); + + assertThat(history.getFailureMessage()).hasSize(1000); + assertThat(history.getErrorCount()).isEqualTo(5); + } } diff --git a/src/test/java/com/bop/youthpick/sync/service/PolicySyncServiceTest.java b/src/test/java/com/bop/youthpick/sync/service/PolicySyncServiceTest.java index b1a16c8..6beb27e 100644 --- a/src/test/java/com/bop/youthpick/sync/service/PolicySyncServiceTest.java +++ b/src/test/java/com/bop/youthpick/sync/service/PolicySyncServiceTest.java @@ -229,6 +229,33 @@ void cleanUp() { assertThat(result.getStatus()).isEqualTo(BatchStatus.FAILED); assertThat(result.getFailureMessage()).contains("정책 수집 실패율 10% 초과").contains("2/10"); + // #205: FAILED 처리돼도 실제 처리된 신규/변경/유지/누락/실패 건수는 버려지지 않고 그대로 남아야 한다. + assertThat(result.getNewCount()).isEqualTo(8); + assertThat(result.getUpdatedCount()).isZero(); + assertThat(result.getUnchangedCount()).isZero(); + assertThat(result.getMissingCount()).isZero(); + assertThat(result.getErrorCount()).isEqualTo(2); + } + + @Test + void 실패율_초과로_전량_실패해도_실패_건수가_이력에_그대로_기록된다() throws IOException { + // #205 재현: 모든 건이 저장 실패(예: 컬럼 길이 초과)해도 카운트가 0으로 버려지면 안 된다. + String tooLongTitle = "가".repeat(301); + List items = new java.util.ArrayList<>(); + for (int i = 0; i < 12; i++) { + items.add(item("{\"plcyNo\":\"P-BAD-" + i + "\",\"plcyNm\":\"" + tooLongTitle + "\"}")); + } + when(policyApiClient.fetchAll()).thenReturn(items); + + PolicyBatchHistory result = policySyncService.runFullSync(); + + assertThat(result.getStatus()).isEqualTo(BatchStatus.FAILED); + assertThat(result.getFailureMessage()).contains("12/12"); + assertThat(result.getNewCount()).isZero(); + assertThat(result.getUpdatedCount()).isZero(); + assertThat(result.getUnchangedCount()).isZero(); + assertThat(result.getMissingCount()).isZero(); + assertThat(result.getErrorCount()).isEqualTo(12); } @Test