using Application.Helpers; namespace Application.Tests; [TestClass] public sealed class DartBackfillTests { // ── ClampToSpecWindow — corp_code 미지정 3개월 스펙 제한 ── [TestMethod] public void ClampToSpecWindow_WithinLimit_NotClamped() { var today = new DateOnly(2026, 7, 7); var begin = today.AddDays(-30); var (result, clamped) = DartBackfill.ClampToSpecWindow(begin, today); Assert.IsFalse(clamped, "30일 창은 3개월 이내 — 클램프 없음"); Assert.AreEqual(begin, result); } [TestMethod] public void ClampToSpecWindow_ExactBoundary_NotClamped() { var today = new DateOnly(2026, 7, 7); var begin = today.AddMonths(-3); // 정확히 3개월 = 허용 var (result, clamped) = DartBackfill.ClampToSpecWindow(begin, today); Assert.IsFalse(clamped, "정확히 3개월 경계는 허용"); Assert.AreEqual(begin, result); } [TestMethod] public void ClampToSpecWindow_OverLimit_ClampedToThreeMonths() { var today = new DateOnly(2026, 7, 7); var begin = today.AddDays(-365); // BackfillDays=365 과설정 var (result, clamped) = DartBackfill.ClampToSpecWindow(begin, today); Assert.IsTrue(clamped, "3개월 초과 창은 클램프"); Assert.AreEqual(today.AddMonths(-3), result, "클램프 결과는 today-3개월"); } // ── SplitWeekly — 초기 백필 주 단위 분할 경계 ── [TestMethod] public void SplitWeekly_ExactTwoWeeks_TwoChunksNoOverlapNoGap() { var begin = new DateOnly(2026, 6, 1); var end = new DateOnly(2026, 6, 14); // 14일 = 정확히 2주 var chunks = DartBackfill.SplitWeekly(begin, end); Assert.AreEqual(2, chunks.Count); Assert.AreEqual((new DateOnly(2026, 6, 1), new DateOnly(2026, 6, 7)), chunks[0]); Assert.AreEqual((new DateOnly(2026, 6, 8), new DateOnly(2026, 6, 14)), chunks[1], "청크 간 겹침/공백 없음"); } [TestMethod] public void SplitWeekly_PartialLastChunk_TruncatedAtEnd() { var begin = new DateOnly(2026, 6, 1); var end = new DateOnly(2026, 6, 10); // 10일 = 1주 + 3일 var chunks = DartBackfill.SplitWeekly(begin, end); Assert.AreEqual(2, chunks.Count); Assert.AreEqual((new DateOnly(2026, 6, 8), new DateOnly(2026, 6, 10)), chunks[1], "마지막 청크는 end 에서 잘림"); } [TestMethod] public void SplitWeekly_SingleDay_OneChunk() { var day = new DateOnly(2026, 7, 7); var chunks = DartBackfill.SplitWeekly(day, day); Assert.AreEqual(1, chunks.Count); Assert.AreEqual((day, day), chunks[0]); } [TestMethod] public void SplitWeekly_BeginAfterEnd_Empty() { var chunks = DartBackfill.SplitWeekly(new DateOnly(2026, 7, 8), new DateOnly(2026, 7, 7)); Assert.AreEqual(0, chunks.Count, "역전 범위는 빈 목록"); } [TestMethod] public void SplitWeekly_CoversWholeRange_NoDateMissing() { var begin = new DateOnly(2026, 1, 1); var end = new DateOnly(2026, 3, 31); // 90일 창 var chunks = DartBackfill.SplitWeekly(begin, end); // 연속성: 각 청크의 다음 시작일 = 이전 종료일 + 1 Assert.AreEqual(begin, chunks[0].Begin); Assert.AreEqual(end, chunks[^1].End); for (var i = 1; i < chunks.Count; i++) { Assert.AreEqual(chunks[i - 1].End.AddDays(1), chunks[i].Begin, $"청크 {i} 경계 연속성"); } } // ── ShouldStopEarly — 증분 수집 조기 종료 보수 조건 ── [TestMethod] public void ShouldStopEarly_Page1_AllExisting_False() { // 같은 날 재실행/부분 적재 방어 — 1페이지 전체 기존이어도 종료하지 않는다 Assert.IsFalse(DartBackfill.ShouldStopEarly(pageNo: 1, rowCount: 100, newCount: 0)); } [TestMethod] public void ShouldStopEarly_Page2_AllExisting_True() { Assert.IsTrue(DartBackfill.ShouldStopEarly(pageNo: 2, rowCount: 100, newCount: 0)); } [TestMethod] public void ShouldStopEarly_Page2_HasNewRows_False() { Assert.IsFalse(DartBackfill.ShouldStopEarly(pageNo: 2, rowCount: 100, newCount: 1), "신규가 있으면 계속 페이징"); } [TestMethod] public void ShouldStopEarly_EmptyPage_False() { Assert.IsFalse(DartBackfill.ShouldStopEarly(pageNo: 2, rowCount: 0, newCount: 0), "빈 페이지는 조기 종료 판정 대상 아님 (루프가 자체 종료)"); } }