KrxBackfillTests.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. using Application.Helpers;
  2. namespace Application.Tests;
  3. [TestClass]
  4. public sealed class KrxBackfillTests
  5. {
  6. private static IReadOnlySet<DateOnly> NoHolidays => new HashSet<DateOnly>();
  7. [TestMethod]
  8. public async Task RunAsync_IteratesBusinessDays_RecentFirst_SkippingWeekends()
  9. {
  10. // 2025-01-06(월) ~ 2025-01-10(금) 은 평일 5일, 2025-01-11(토)/12(일) 은 주말.
  11. // endDate=일(12), startDate=토(11 하루 전 없음) → 창을 넓게 잡고 주말이 제외되는지 확인.
  12. var start = new DateOnly(2025, 1, 6); // 월
  13. var end = new DateOnly(2025, 1, 12); // 일
  14. var fetchedDays = new List<DateOnly>();
  15. var count = await KrxBackfill.RunAsync(
  16. existsForDate: (_, _) => Task.FromResult(false),
  17. fetchAndUpsertForDate: (day, _) => { fetchedDays.Add(day); return Task.CompletedTask; },
  18. startDate: start,
  19. endDate: end,
  20. holidays: NoHolidays,
  21. maxPerRun: 100,
  22. delayMs: 0,
  23. ct: CancellationToken.None);
  24. // 주말(11 토, 12 일) 제외 → 평일 5일 (10 금 → 6 월 순, recent-first)
  25. Assert.AreEqual(5, count);
  26. CollectionAssert.AreEqual(
  27. new[]
  28. {
  29. new DateOnly(2025, 1, 10),
  30. new DateOnly(2025, 1, 9),
  31. new DateOnly(2025, 1, 8),
  32. new DateOnly(2025, 1, 7),
  33. new DateOnly(2025, 1, 6)
  34. },
  35. fetchedDays,
  36. "endDate 부터 과거로(recent-first), 주말은 건너뛴다");
  37. }
  38. [TestMethod]
  39. public async Task RunAsync_SkipsHolidays()
  40. {
  41. var start = new DateOnly(2025, 1, 6); // 월
  42. var end = new DateOnly(2025, 1, 10); // 금
  43. var holidays = new HashSet<DateOnly> { new(2025, 1, 8) }; // 수 휴장
  44. var fetchedDays = new List<DateOnly>();
  45. var count = await KrxBackfill.RunAsync(
  46. existsForDate: (_, _) => Task.FromResult(false),
  47. fetchAndUpsertForDate: (day, _) => { fetchedDays.Add(day); return Task.CompletedTask; },
  48. startDate: start,
  49. endDate: end,
  50. holidays: holidays,
  51. maxPerRun: 100,
  52. delayMs: 0,
  53. ct: CancellationToken.None);
  54. Assert.AreEqual(4, count, "휴장일(8 수) 제외 → 4일");
  55. CollectionAssert.DoesNotContain(fetchedDays, new DateOnly(2025, 1, 8));
  56. }
  57. [TestMethod]
  58. public async Task RunAsync_SkipsAlreadyExistingDays_Resumable()
  59. {
  60. var start = new DateOnly(2025, 1, 6); // 월
  61. var end = new DateOnly(2025, 1, 10); // 금
  62. // 8 수, 9 목 은 이미 적재됨 → skip
  63. var existing = new HashSet<DateOnly> { new(2025, 1, 8), new(2025, 1, 9) };
  64. var fetchedDays = new List<DateOnly>();
  65. var count = await KrxBackfill.RunAsync(
  66. existsForDate: (day, _) => Task.FromResult(existing.Contains(day)),
  67. fetchAndUpsertForDate: (day, _) => { fetchedDays.Add(day); return Task.CompletedTask; },
  68. startDate: start,
  69. endDate: end,
  70. holidays: NoHolidays,
  71. maxPerRun: 100,
  72. delayMs: 0,
  73. ct: CancellationToken.None);
  74. Assert.AreEqual(3, count, "이미 적재된 2일 제외 → 3일 (resumable)");
  75. CollectionAssert.AreEqual(
  76. new[]
  77. {
  78. new DateOnly(2025, 1, 10),
  79. new DateOnly(2025, 1, 7),
  80. new DateOnly(2025, 1, 6)
  81. },
  82. fetchedDays);
  83. }
  84. [TestMethod]
  85. public async Task RunAsync_StopsAfterMaxPerRun()
  86. {
  87. // 넉넉한 창(3주) 에서 상한 3 으로 잘리는지
  88. var start = new DateOnly(2025, 1, 1);
  89. var end = new DateOnly(2025, 1, 21);
  90. var fetchedDays = new List<DateOnly>();
  91. var count = await KrxBackfill.RunAsync(
  92. existsForDate: (_, _) => Task.FromResult(false),
  93. fetchAndUpsertForDate: (day, _) => { fetchedDays.Add(day); return Task.CompletedTask; },
  94. startDate: start,
  95. endDate: end,
  96. holidays: NoHolidays,
  97. maxPerRun: 3,
  98. delayMs: 0,
  99. ct: CancellationToken.None);
  100. Assert.AreEqual(3, count, "maxPerRun 도달 시 즉시 멈춤");
  101. Assert.AreEqual(3, fetchedDays.Count);
  102. // recent-first: 21(화),20(월),17(금) — 18/19 는 주말
  103. CollectionAssert.AreEqual(
  104. new[]
  105. {
  106. new DateOnly(2025, 1, 21),
  107. new DateOnly(2025, 1, 20),
  108. new DateOnly(2025, 1, 17)
  109. },
  110. fetchedDays);
  111. }
  112. [TestMethod]
  113. public async Task RunAsync_MaxPerRunZeroOrNegative_FetchesNothing()
  114. {
  115. var fetched = 0;
  116. var count = await KrxBackfill.RunAsync(
  117. existsForDate: (_, _) => Task.FromResult(false),
  118. fetchAndUpsertForDate: (_, _) => { fetched++; return Task.CompletedTask; },
  119. startDate: new DateOnly(2025, 1, 1),
  120. endDate: new DateOnly(2025, 1, 31),
  121. holidays: NoHolidays,
  122. maxPerRun: 0,
  123. delayMs: 0,
  124. ct: CancellationToken.None);
  125. Assert.AreEqual(0, count);
  126. Assert.AreEqual(0, fetched, "maxPerRun<=0 이면 fetch 호출 자체가 없다");
  127. }
  128. [TestMethod]
  129. public async Task RunAsync_EmptyWindow_WhenStartAfterEnd_FetchesNothing()
  130. {
  131. var fetched = 0;
  132. var count = await KrxBackfill.RunAsync(
  133. existsForDate: (_, _) => Task.FromResult(false),
  134. fetchAndUpsertForDate: (_, _) => { fetched++; return Task.CompletedTask; },
  135. startDate: new DateOnly(2025, 2, 1),
  136. endDate: new DateOnly(2025, 1, 1), // start > end
  137. holidays: NoHolidays,
  138. maxPerRun: 100,
  139. delayMs: 0,
  140. ct: CancellationToken.None);
  141. Assert.AreEqual(0, count);
  142. Assert.AreEqual(0, fetched);
  143. }
  144. }