mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 10:48:49 -04:00
added resting units to seeker funnel and preview
This commit is contained in:
1 parent
902330e935
commit
0b3a16599d
7 files changed
+190
-5
No files matched your search
@@ -118,6 +118,22 @@ public class SearchStatsControllerTests : IDisposable
|
||||
funnel.CountedAt.ShouldBe(Now.AddMinutes(-5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSummary_Funnel_SearchedUnitsSubtractsRestingUnits()
|
||||
{
|
||||
ArrInstance radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
||||
Guid currentCycleId = Guid.NewGuid();
|
||||
AddInstanceConfig(radarr.Id, currentCycleId);
|
||||
AddCycle(currentCycleId, radarr.Id, eligibleNow: 10, remainingUnits: 4, startedAt: Now.AddDays(-1),
|
||||
funnelCountedAt: Now, queuedUnits: 1, recentlyGrabbedUnits: 1, restingUnits: 2);
|
||||
|
||||
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
||||
|
||||
InstanceFunnelStat funnel = summary.PerInstanceStats.ShouldHaveSingleItem().Funnel.ShouldNotBeNull();
|
||||
funnel.RestingUnits.ShouldBe(2);
|
||||
funnel.SearchedUnits.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSummary_Funnel_SearchedUnitsNeverGoesNegative()
|
||||
{
|
||||
@@ -289,7 +305,8 @@ public class SearchStatsControllerTests : IDisposable
|
||||
int searchedUnits = 0,
|
||||
DateTimeOffset? funnelCountedAt = null,
|
||||
int queuedUnits = 0,
|
||||
int recentlyGrabbedUnits = 0)
|
||||
int recentlyGrabbedUnits = 0,
|
||||
int restingUnits = 0)
|
||||
{
|
||||
_eventsContext.SeekerCycles.Add(new SeekerCycle
|
||||
{
|
||||
@@ -309,6 +326,7 @@ public class SearchStatsControllerTests : IDisposable
|
||||
TagFilteredItems = 20,
|
||||
QueuedUnits = queuedUnits,
|
||||
RecentlyGrabbedUnits = recentlyGrabbedUnits,
|
||||
RestingUnits = restingUnits,
|
||||
FunnelCountedAt = funnelCountedAt,
|
||||
});
|
||||
_eventsContext.SaveChanges();
|
||||
|
||||
@@ -157,7 +157,7 @@ public class SeekerPreviewControllerTests : IDisposable
|
||||
return sonarr;
|
||||
}
|
||||
|
||||
private ArrInstance AddRadarr(Guid? currentCycleId = null, int minCycleTimeDays = 7)
|
||||
private ArrInstance AddRadarr(Guid? currentCycleId = null, int minCycleTimeDays = 7, bool restUnitsEnabled = false)
|
||||
{
|
||||
ArrInstance radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
||||
_dataContext.SeekerInstanceConfigs.Add(new SeekerInstanceConfig
|
||||
@@ -166,6 +166,7 @@ public class SeekerPreviewControllerTests : IDisposable
|
||||
Enabled = true,
|
||||
CurrentCycleId = currentCycleId ?? Guid.NewGuid(),
|
||||
MinCycleTimeDays = minCycleTimeDays,
|
||||
RestUnitsEnabled = restUnitsEnabled,
|
||||
});
|
||||
_dataContext.SaveChanges();
|
||||
return radarr;
|
||||
@@ -247,6 +248,53 @@ public class SeekerPreviewControllerTests : IDisposable
|
||||
response.Funnel.CountedAt.ShouldBe(Now);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPreview_RestingMovie_MapsTheRestFieldsAndCountsItInTheFunnel()
|
||||
{
|
||||
Guid cycleId = Guid.NewGuid();
|
||||
ArrInstance radarr = AddRadarr(cycleId, restUnitsEnabled: true);
|
||||
AddCycle(cycleId, radarr.Id, Now.AddDays(-1), searchedUnits: 1);
|
||||
_eventsContext.SeekerHistory.Add(new SeekerHistory
|
||||
{
|
||||
ArrInstanceId = radarr.Id,
|
||||
ExternalItemId = 1,
|
||||
ItemType = InstanceType.Radarr,
|
||||
CycleId = Guid.NewGuid(),
|
||||
LastSearchedAt = Now.AddDays(-2),
|
||||
ItemTitle = "Movie 1",
|
||||
ConsecutiveMisses = 4,
|
||||
});
|
||||
_eventsContext.SaveChanges();
|
||||
SetupMovies(Movie(1), Movie(2));
|
||||
|
||||
SeekerPreviewResponse response = Preview(await _controller.GetPreview(radarr.Id, CancellationToken.None));
|
||||
|
||||
SeekerPreviewExclusionResponse excluded = response.Excluded.ShouldHaveSingleItem();
|
||||
excluded.Name.ShouldBe("Movie 1");
|
||||
excluded.Reason.ShouldBe(SeekerExclusionReason.Resting);
|
||||
excluded.ConsecutiveMisses.ShouldBe(4);
|
||||
excluded.RestCyclesLeft.ShouldBe(2);
|
||||
excluded.RestUntil.ShouldBe(Now.AddDays(-2).AddDays(30));
|
||||
response.NextUp.ShouldHaveSingleItem().Name.ShouldBe("Movie 2");
|
||||
response.Funnel.RestingUnits.ShouldBe(1);
|
||||
response.Funnel.SearchedUnits.ShouldBe(0);
|
||||
response.Funnel.RemainingUnits.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPreview_NonRestingExclusion_LeavesTheRestFieldsNull()
|
||||
{
|
||||
ArrInstance radarr = AddRadarr();
|
||||
SetupMovies(Movie(1), Movie(3, status: "announced"));
|
||||
|
||||
SeekerPreviewResponse response = Preview(await _controller.GetPreview(radarr.Id, CancellationToken.None));
|
||||
|
||||
SeekerPreviewExclusionResponse excluded = response.Excluded.ShouldHaveSingleItem();
|
||||
excluded.ConsecutiveMisses.ShouldBeNull();
|
||||
excluded.RestCyclesLeft.ShouldBeNull();
|
||||
excluded.RestUntil.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPreview_QueuedMovie_LeavesItOutOfNextUp()
|
||||
{
|
||||
|
||||
+3
-1
@@ -15,6 +15,7 @@ public sealed record InstanceFunnelStat
|
||||
public int EligibleUnits { get; init; }
|
||||
public int QueuedUnits { get; init; }
|
||||
public int RecentlyGrabbedUnits { get; init; }
|
||||
public int RestingUnits { get; init; }
|
||||
public int SearchedUnits { get; init; }
|
||||
public int RemainingUnits { get; init; }
|
||||
public DateTimeOffset CountedAt { get; init; }
|
||||
@@ -33,7 +34,8 @@ public sealed record InstanceFunnelStat
|
||||
EligibleUnits = cycle.EligibleNow,
|
||||
QueuedUnits = cycle.QueuedUnits,
|
||||
RecentlyGrabbedUnits = cycle.RecentlyGrabbedUnits,
|
||||
SearchedUnits = Math.Max(0, cycle.EligibleNow - cycle.QueuedUnits - cycle.RecentlyGrabbedUnits - cycle.RemainingUnits),
|
||||
RestingUnits = cycle.RestingUnits,
|
||||
SearchedUnits = Math.Max(0, cycle.EligibleNow - cycle.QueuedUnits - cycle.RecentlyGrabbedUnits - cycle.RestingUnits - cycle.RemainingUnits),
|
||||
RemainingUnits = cycle.RemainingUnits,
|
||||
CountedAt = countedAt,
|
||||
};
|
||||
|
||||
+15
@@ -9,4 +9,19 @@ public sealed record SeekerPreviewExclusionResponse
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public SeekerExclusionReason Reason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set while the reason is <see cref="SeekerExclusionReason.Resting"/>, as are the other rest fields.
|
||||
/// </summary>
|
||||
public int? ConsecutiveMisses { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Cycle ends before the item is searched again.
|
||||
/// </summary>
|
||||
public int? RestCyclesLeft { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When the rest ends, however many cycles it has left.
|
||||
/// </summary>
|
||||
public DateTimeOffset? RestUntil { get; init; }
|
||||
}
|
||||
@@ -154,7 +154,14 @@ public sealed class SeekerPreviewController : ControllerBase
|
||||
.Select(c => new SeekerPreviewUnitResponse { Name = c.Name, Reason = c.Reason, ContentKind = c.ContentKind })
|
||||
.ToList(),
|
||||
Excluded = evaluation.Excluded
|
||||
.Select(e => new SeekerPreviewExclusionResponse { Name = e.Name, Reason = e.Reason })
|
||||
.Select(e => new SeekerPreviewExclusionResponse
|
||||
{
|
||||
Name = e.Name,
|
||||
Reason = e.Reason,
|
||||
ConsecutiveMisses = e.Rest?.ConsecutiveMisses,
|
||||
RestCyclesLeft = e.Rest?.CyclesLeft,
|
||||
RestUntil = e.Rest?.Until,
|
||||
})
|
||||
.ToList(),
|
||||
});
|
||||
}
|
||||
@@ -172,7 +179,8 @@ public sealed class SeekerPreviewController : ControllerBase
|
||||
EligibleUnits = evaluation.Units.Count,
|
||||
QueuedUnits = evaluation.QueuedUnits,
|
||||
RecentlyGrabbedUnits = evaluation.RecentlyGrabbedUnits,
|
||||
SearchedUnits = evaluation.Units.Count - evaluation.QueuedUnits - evaluation.RecentlyGrabbedUnits - remaining,
|
||||
RestingUnits = evaluation.RestingUnits,
|
||||
SearchedUnits = Math.Max(0, evaluation.Units.Count - evaluation.QueuedUnits - evaluation.RecentlyGrabbedUnits - evaluation.RestingUnits - remaining),
|
||||
RemainingUnits = remaining,
|
||||
CountedAt = _timeProvider.GetUtcNow(),
|
||||
};
|
||||
|
||||
@@ -3108,6 +3108,99 @@ public class SeekerTests : IDisposable
|
||||
newCycle.FunnelCountedAt.ShouldBe(_fixture.TimeProvider.GetUtcNow());
|
||||
}
|
||||
|
||||
private void AddRestingMovieHistory(ArrInstance instance, long movieId, Guid cycleId)
|
||||
{
|
||||
_fixture.EventsContext.SeekerHistory.Add(new SeekerHistory
|
||||
{
|
||||
ArrInstanceId = instance.Id,
|
||||
ExternalItemId = movieId,
|
||||
ItemType = InstanceType.Radarr,
|
||||
CycleId = cycleId,
|
||||
LastSearchedAt = _fixture.TimeProvider.GetUtcNow().AddDays(-2),
|
||||
ItemTitle = $"Movie {movieId}",
|
||||
ConsecutiveMisses = 3,
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_Radarr_RestingMovie_RecordsRestingUnitsOnTheCurrentCycle()
|
||||
{
|
||||
// Arrange
|
||||
(ArrInstance radarrInstance, _) = await SetupFunnelInstanceAsync(InstanceType.Radarr, c => c.RestUnitsEnabled = true);
|
||||
AddRestingMovieHistory(radarrInstance, 1, Guid.NewGuid());
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
StubFunnelMovies(radarrInstance, [ReleasedMovie(1), ReleasedMovie(2), ReleasedMovie(3)]);
|
||||
|
||||
// Act
|
||||
await CreateSut().ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
SeekerCycle cycle = await GetCycleAsync(radarrInstance.Id);
|
||||
cycle.RestingUnits.ShouldBe(1);
|
||||
cycle.QueuedUnits.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_Radarr_EveryUnsearchedUnitRests_TriggersNoSearch()
|
||||
{
|
||||
// Arrange: movie 1 was searched in the open cycle, movies 2 and 3 rest
|
||||
Guid currentCycleId = Guid.NewGuid();
|
||||
(ArrInstance radarrInstance, IArrClient arrClient) = await SetupFunnelInstanceAsync(InstanceType.Radarr, c =>
|
||||
{
|
||||
c.RestUnitsEnabled = true;
|
||||
c.CurrentCycleId = currentCycleId;
|
||||
});
|
||||
_fixture.EventsContext.SeekerCycles.Add(new SeekerCycle
|
||||
{
|
||||
Id = currentCycleId,
|
||||
ArrInstanceId = radarrInstance.Id,
|
||||
StartedAt = _fixture.TimeProvider.GetUtcNow().AddHours(-1),
|
||||
SearchedUnits = 1,
|
||||
});
|
||||
_fixture.EventsContext.SeekerHistory.Add(new SeekerHistory
|
||||
{
|
||||
ArrInstanceId = radarrInstance.Id,
|
||||
ExternalItemId = 1,
|
||||
ItemType = InstanceType.Radarr,
|
||||
CycleId = currentCycleId,
|
||||
LastSearchedAt = _fixture.TimeProvider.GetUtcNow().AddHours(-1),
|
||||
ItemTitle = "Movie 1",
|
||||
});
|
||||
AddRestingMovieHistory(radarrInstance, 2, Guid.NewGuid());
|
||||
AddRestingMovieHistory(radarrInstance, 3, Guid.NewGuid());
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
StubFunnelMovies(radarrInstance, [ReleasedMovie(1), ReleasedMovie(2), ReleasedMovie(3)]);
|
||||
|
||||
// Act
|
||||
await CreateSut().ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await arrClient.DidNotReceive().SearchItemAsync(radarrInstance, Arg.Any<SearchItem>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_Radarr_EveryUnitRestsAndNoneWasSearched_DoesNotCompleteTheCycle()
|
||||
{
|
||||
// Arrange
|
||||
(ArrInstance radarrInstance, IArrClient arrClient) = await SetupFunnelInstanceAsync(InstanceType.Radarr, c => c.RestUnitsEnabled = true);
|
||||
AddRestingMovieHistory(radarrInstance, 1, Guid.NewGuid());
|
||||
AddRestingMovieHistory(radarrInstance, 2, Guid.NewGuid());
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
StubFunnelMovies(radarrInstance, [ReleasedMovie(1), ReleasedMovie(2)]);
|
||||
|
||||
// Act
|
||||
await CreateSut().ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
await arrClient.DidNotReceive().SearchItemAsync(radarrInstance, Arg.Any<SearchItem>());
|
||||
await _fixture.EventPublisher.DidNotReceive()
|
||||
.PublishSeekerCycleCompleted(Arg.Any<SeekerCycle>(), Arg.Any<string>());
|
||||
|
||||
SeekerCycle cycle = await GetCycleAsync(radarrInstance.Id);
|
||||
cycle.RestingUnits.ShouldBe(2);
|
||||
cycle.CompletedAt.ShouldBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Post-Release Grace Period Tests
|
||||
|
||||
@@ -591,6 +591,7 @@ public sealed class Seeker : IHandler
|
||||
cycle.TagFilteredItems = evaluation.TagFilteredItems;
|
||||
cycle.QueuedUnits = evaluation.QueuedUnits;
|
||||
cycle.RecentlyGrabbedUnits = evaluation.RecentlyGrabbedUnits;
|
||||
cycle.RestingUnits = evaluation.RestingUnits;
|
||||
cycle.FunnelCountedAt = _timeProvider.GetUtcNow();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user