diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Events/EventCleanupServiceTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Events/EventCleanupServiceTests.cs index 3404c303..4b5f52f3 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Events/EventCleanupServiceTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Events/EventCleanupServiceTests.cs @@ -200,13 +200,20 @@ public class EventCleanupServiceTests : IDisposable EventCleanupService service = new EventCleanupService(_logger, _serviceProvider.GetRequiredService(), timeProvider, new DryRunActivity()); Guid oldCompletedId = Guid.NewGuid(); - eventsContext.SeekerCycles.Add(new SeekerCycle - { - Id = oldCompletedId, - StartedAt = timeProvider.GetUtcNow().AddDays(-40), - CompletedAt = timeProvider.GetUtcNow().AddDays(-31), - CompletionReason = SeekerCycleCompletionReason.Completed, - }); + eventsContext.SeekerCycles.AddRange( + new SeekerCycle + { + Id = oldCompletedId, + StartedAt = timeProvider.GetUtcNow().AddDays(-40), + CompletedAt = timeProvider.GetUtcNow().AddDays(-31), + CompletionReason = SeekerCycleCompletionReason.Completed, + }, + new SeekerCycle + { + Id = Guid.NewGuid(), + StartedAt = timeProvider.GetUtcNow().AddDays(-1), + CompletedAt = null, + }); await eventsContext.SaveChangesAsync(); // Act @@ -216,6 +223,44 @@ public class EventCleanupServiceTests : IDisposable (await eventsContext.SeekerCycles.CountAsync(c => c.Id == oldCompletedId)).ShouldBe(0); } + [Fact] + public async Task PruneSeekerCyclesAsync_KeepsNewestCycleEvenWhenOld() + { + // Arrange + using EventsContext eventsContext = TestDataContextFactory.CreateEvents(); + FakeTimeProvider timeProvider = new(); + EventCleanupService service = new EventCleanupService(_logger, _serviceProvider.GetRequiredService(), timeProvider, new DryRunActivity()); + + Guid arrInstanceId = Guid.NewGuid(); + Guid olderId = Guid.NewGuid(); + Guid newestId = Guid.NewGuid(); + eventsContext.SeekerCycles.AddRange( + new SeekerCycle + { + Id = olderId, + ArrInstanceId = arrInstanceId, + StartedAt = timeProvider.GetUtcNow().AddDays(-50), + CompletedAt = timeProvider.GetUtcNow().AddDays(-45), + CompletionReason = SeekerCycleCompletionReason.Completed, + }, + new SeekerCycle + { + Id = newestId, + ArrInstanceId = arrInstanceId, + StartedAt = timeProvider.GetUtcNow().AddDays(-40), + CompletedAt = timeProvider.GetUtcNow().AddDays(-35), + CompletionReason = SeekerCycleCompletionReason.Completed, + }); + await eventsContext.SaveChangesAsync(); + + // Act + await service.PruneSeekerCyclesAsync(eventsContext, retentionDays: 30); + + // Assert + (await eventsContext.SeekerCycles.CountAsync(c => c.Id == olderId)).ShouldBe(0); + (await eventsContext.SeekerCycles.CountAsync(c => c.Id == newestId)).ShouldBe(1); + } + [Fact] public async Task PruneSeekerCyclesAsync_KeepsOpenAndRecentCycles() { diff --git a/code/backend/Cleanuparr.Infrastructure/Events/EventCleanupService.cs b/code/backend/Cleanuparr.Infrastructure/Events/EventCleanupService.cs index d5b1384d..121a6130 100644 --- a/code/backend/Cleanuparr.Infrastructure/Events/EventCleanupService.cs +++ b/code/backend/Cleanuparr.Infrastructure/Events/EventCleanupService.cs @@ -223,6 +223,8 @@ public class EventCleanupService : BackgroundService DateTimeOffset cutoff = _timeProvider.GetUtcNow().AddDays(-retentionDays); int deleted = await eventsContext.SeekerCycles .Where(c => c.CompletedAt != null && c.CompletedAt < cutoff) + .Where(c => eventsContext.SeekerCycles.Any(o => + o.ArrInstanceId == c.ArrInstanceId && o.IsDryRun == c.IsDryRun && o.StartedAt > c.StartedAt)) .ExecuteDeleteAsync(); if (deleted > 0)