using System.Diagnostics; using Application.Helpers; namespace Application.Tests; [TestClass] public sealed class SeibroRollingSweepTests { [TestMethod] public async Task RunAsync_UnprocessedFirst_ThenStaleOrder_StableForTies() { // 미처리(null) 우선(입력 순서 유지), 그다음 오래된(stale) 순 var targets = new List<(string Key, DateTime? LastUpdatedAt)> { ("A", new DateTime(2026, 1, 5)), ("B", null), ("C", new DateTime(2026, 1, 1)), ("D", null) }; var processed = new List(); var count = await SeibroRollingSweep.RunAsync( targets: targets, fetchAndUpsert: (key, _) => { processed.Add(key); return Task.CompletedTask; }, maxPerRun: 100, delayMs: 0, ct: CancellationToken.None); Assert.AreEqual(4, count); CollectionAssert.AreEqual(new[] { "B", "D", "C", "A" }, processed, "미처리(B,D 입력순) → stale(C=1/1) → 최신(A=1/5)"); } [TestMethod] public async Task RunAsync_StopsAfterMaxPerRun() { var targets = Enumerable.Range(0, 10).Select(c => (Key: c, LastUpdatedAt: (DateTime?)null)).ToList(); var fetched = 0; var count = await SeibroRollingSweep.RunAsync( targets: targets, fetchAndUpsert: (_, _) => { fetched++; return Task.CompletedTask; }, maxPerRun: 3, delayMs: 0, ct: CancellationToken.None); Assert.AreEqual(3, count, "maxPerRun 도달 시 즉시 멈춤 (quota 상한)"); Assert.AreEqual(3, fetched); } [TestMethod] public async Task RunAsync_MaxPerRunZeroOrNegative_FetchesNothing() { var fetched = 0; var count = await SeibroRollingSweep.RunAsync( targets: [("A", (DateTime?)null)], fetchAndUpsert: (_, _) => { fetched++; return Task.CompletedTask; }, maxPerRun: 0, delayMs: 0, ct: CancellationToken.None); Assert.AreEqual(0, count); Assert.AreEqual(0, fetched, "maxPerRun<=0 이면 fetch 호출 자체가 없다"); } [TestMethod] public async Task RunAsync_DelaysBetweenFetches_NotBeforeFirst() { // 지연 계약: 호출 사이에만 delayMs — 3건 × 60ms → 최소 120ms (첫 호출 전엔 지연 없음) var targets = Enumerable.Range(0, 3).Select(c => (Key: c, LastUpdatedAt: (DateTime?)null)).ToList(); var sw = Stopwatch.StartNew(); await SeibroRollingSweep.RunAsync( targets: targets, fetchAndUpsert: (_, _) => Task.CompletedTask, maxPerRun: 100, delayMs: 60, ct: CancellationToken.None); Assert.IsTrue(sw.ElapsedMilliseconds >= 120, $"3건이면 사이 지연 2회 ≥ 120ms (실측 {sw.ElapsedMilliseconds}ms)"); // 단건이면 지연 없음 — delayMs 가 매우 커도 즉시 끝난다 sw.Restart(); await SeibroRollingSweep.RunAsync( targets: [(0, (DateTime?)null)], fetchAndUpsert: (_, _) => Task.CompletedTask, maxPerRun: 1, delayMs: 30_000, ct: CancellationToken.None); Assert.IsTrue(sw.ElapsedMilliseconds < 5_000, $"첫 호출 전에는 지연이 없다 (실측 {sw.ElapsedMilliseconds}ms)"); } [TestMethod] public async Task RunAsync_EmptyTargets_ReturnsZero() { var count = await SeibroRollingSweep.RunAsync( targets: [], fetchAndUpsert: (_, _) => Task.CompletedTask, maxPerRun: 100, delayMs: 0, ct: CancellationToken.None); Assert.AreEqual(0, count); } }