diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/SeekerTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/SeekerTests.cs index 0fad7b7a..20980f72 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/SeekerTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/SeekerTests.cs @@ -8276,6 +8276,85 @@ public class SeekerTests : IDisposable tracker.SeasonNumber.ShouldBe(0); } + [Fact] + public async Task ExecuteAsync_Sonarr_LegacySeasonZeroHistory_StillSearchesSpecial() + { + // Arrange + SeekerConfig config = await _fixture.DataContext.SeekerConfigs.FirstAsync(); + config.SearchEnabled = true; + config.ProactiveSearchEnabled = true; + await _fixture.DataContext.SaveChangesAsync(); + await _fixture.EventsContext.SaveChangesAsync(); + + ArrInstance sonarrInstance = TestDataContextFactory.AddSonarrInstance(_fixture.DataContext); + Guid currentCycleId = Guid.NewGuid(); + DateTime now = _fixture.TimeProvider.GetUtcNow().UtcDateTime; + + _fixture.DataContext.SeekerInstanceConfigs.Add(new SeekerInstanceConfig + { + ArrInstanceId = sonarrInstance.Id, + ArrInstance = sonarrInstance, + Enabled = true, + MonitoredOnly = false, + CurrentCycleId = currentCycleId, + }); + await _fixture.DataContext.SaveChangesAsync(); + + _fixture.EventsContext.SeekerHistory.Add(new SeekerHistory + { + ArrInstanceId = sonarrInstance.Id, + ExternalItemId = 10, + ItemType = InstanceType.Sonarr, + SeasonNumber = 0, + EpisodeId = 0, + CycleId = currentCycleId, + LastSearchedAt = now, + ItemTitle = "Test Series Specials", + }); + await _fixture.EventsContext.SaveChangesAsync(); + + IArrClient mockArrClient = Substitute.For(); + + _fixture.ArrQueueReader + .ReadAllAsync(mockArrClient, Arg.Any()) + .Returns([]); + + _sonarrClient + .StreamAllSeriesAsync(sonarrInstance, Arg.Any()) + .Returns(ToAsyncEnumerable([ + new SearchableSeries { Id = 10, Title = "Test Series", Status = "continuing", Monitored = true, Tags = [], SeriesType = "standard" } + ])); + + DateTime pastDate = now.AddDays(-30); + StubSonarrWanted(sonarrInstance, + [ + new SearchableEpisode { Id = 200, SeriesId = 10, SeasonNumber = 0, EpisodeNumber = 5, Monitored = true, AirDateUtc = pastDate, HasFile = false } + ]); + + SeriesSearchItem? capturedSearchItem = null; + mockArrClient + .SearchItemAsync(Arg.Any(), Arg.Any()) + .Returns(ci => + { + capturedSearchItem = ci.ArgAt(1) as SeriesSearchItem; + return 100L; + }); + + _fixture.ArrClientFactory + .GetClient(InstanceType.Sonarr, Arg.Any()) + .Returns(mockArrClient); + + SeekerJob sut = CreateSut(); + + // Act + await sut.ExecuteAsync(); + + // Assert + capturedSearchItem.ShouldNotBeNull(); + capturedSearchItem.SearchType.ShouldBe(SeriesSearchType.Episode); + capturedSearchItem.Id.ShouldBe(200); + } + [Fact] public async Task ExecuteAsync_Sonarr_TwoSpecials_PicksEarliestAirDateFirstThenTheOther() { diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs b/code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs index 988fb94a..96015ec8 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Jobs/Seeker.cs @@ -880,7 +880,7 @@ public sealed class Seeker : IHandler arrInstance, seriesById, wanted, cancellationToken); HashSet<(long SeriesId, int SeasonNumber)> searchedSeasons = currentCycleHistory - .Where(h => h.EpisodeId == 0) + .Where(h => h.EpisodeId == 0 && h.SeasonNumber > 0) .Select(h => (h.ExternalItemId, h.SeasonNumber)) .ToHashSet(); HashSet searchedEpisodeIds = currentCycleHistory