mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 18:58:59 -04:00
592 lines
23 KiB
C#
592 lines
23 KiB
C#
using System.Text.Json;
|
|
using Cleanuparr.Api.Features.Seeker.Contracts.Responses;
|
|
using Cleanuparr.Api.Features.Seeker.Controllers;
|
|
using Cleanuparr.Api.Tests.Features.Seeker.TestHelpers;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Persistence;
|
|
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
|
using Cleanuparr.Persistence.Models.Configuration.General;
|
|
using Cleanuparr.Persistence.Models.Configuration.Seeker;
|
|
using Cleanuparr.Persistence.Models.Events;
|
|
using Cleanuparr.Persistence.Models.State;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using Shouldly;
|
|
|
|
namespace Cleanuparr.Api.Tests.Features.Seeker;
|
|
|
|
public class SearchStatsControllerTests : IDisposable
|
|
{
|
|
private static readonly DateTimeOffset Now = new(2026, 1, 1, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
private readonly DataContext _dataContext;
|
|
private readonly EventsContext _eventsContext;
|
|
private readonly SearchStatsController _controller;
|
|
|
|
public SearchStatsControllerTests()
|
|
{
|
|
_dataContext = SeekerTestDataFactory.CreateDataContext();
|
|
_eventsContext = SeekerTestDataFactory.CreateEventsContext();
|
|
_controller = new SearchStatsController(_dataContext, _eventsContext, new FakeTimeProvider(Now));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_dataContext.Dispose();
|
|
_eventsContext.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private static SearchStatsSummaryResponse Summary(IActionResult result) =>
|
|
result.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<SearchStatsSummaryResponse>();
|
|
|
|
private static JsonElement GetResponseBody(IActionResult result)
|
|
{
|
|
var okResult = result.ShouldBeOfType<OkObjectResult>();
|
|
var json = JsonSerializer.Serialize(okResult.Value);
|
|
return JsonDocument.Parse(json).RootElement;
|
|
}
|
|
|
|
#region GetSummary
|
|
|
|
[Fact]
|
|
public async Task GetSummary_CountsSearchesInsideTheSevenAndThirtyDayWindows()
|
|
{
|
|
AddSearchEvent(timestamp: Now.UtcDateTime.AddDays(-1));
|
|
AddSearchEvent(timestamp: Now.UtcDateTime.AddDays(-10));
|
|
AddSearchEvent(timestamp: Now.UtcDateTime.AddDays(-40));
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
summary.TotalSearchesAllTime.ShouldBe(3);
|
|
summary.SearchesLast7Days.ShouldBe(1);
|
|
summary.SearchesLast30Days.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_ExcludesSearchesOnTheFarSideOfEachWindow()
|
|
{
|
|
AddSearchEvent(timestamp: Now.UtcDateTime.AddDays(-7).AddMinutes(-1));
|
|
AddSearchEvent(timestamp: Now.UtcDateTime.AddDays(-30).AddMinutes(-1));
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
summary.TotalSearchesAllTime.ShouldBe(2);
|
|
summary.SearchesLast7Days.ShouldBe(0);
|
|
summary.SearchesLast30Days.ShouldBe(1);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetSummary Funnel
|
|
|
|
[Fact]
|
|
public async Task GetSummary_Funnel_IsNullUntilARunCountedIt()
|
|
{
|
|
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));
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
summary.PerInstanceStats.ShouldHaveSingleItem().Funnel.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_Funnel_MapsTheCycleColumns()
|
|
{
|
|
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.AddMinutes(-5), queuedUnits: 2, recentlyGrabbedUnits: 1);
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceFunnelStat funnel = summary.PerInstanceStats.ShouldHaveSingleItem().Funnel.ShouldNotBeNull();
|
|
funnel.LibraryItems.ShouldBe(50);
|
|
funnel.ReleasedItems.ShouldBe(40);
|
|
funnel.MonitoredItems.ShouldBe(30);
|
|
funnel.TagFilteredItems.ShouldBe(20);
|
|
funnel.EligibleUnits.ShouldBe(10);
|
|
funnel.QueuedUnits.ShouldBe(2);
|
|
funnel.RecentlyGrabbedUnits.ShouldBe(1);
|
|
funnel.RemainingUnits.ShouldBe(4);
|
|
funnel.SearchedUnits.ShouldBe(3);
|
|
funnel.CountedAt.ShouldBe(Now.AddMinutes(-5));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_Funnel_SearchedUnitsNeverGoesNegative()
|
|
{
|
|
ArrInstance radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
Guid currentCycleId = Guid.NewGuid();
|
|
AddInstanceConfig(radarr.Id, currentCycleId);
|
|
AddCycle(currentCycleId, radarr.Id, eligibleNow: 3, remainingUnits: 2, startedAt: Now.AddDays(-1),
|
|
funnelCountedAt: Now, queuedUnits: 2, recentlyGrabbedUnits: 1);
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
summary.PerInstanceStats.ShouldHaveSingleItem().Funnel.ShouldNotBeNull().SearchedUnits.ShouldBe(0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetSummary PerInstanceStats
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_UsesTheCurrentCycleRow()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
Guid currentCycleId = Guid.NewGuid();
|
|
AddInstanceConfig(radarr.Id, currentCycleId);
|
|
AddCycle(currentCycleId, radarr.Id, eligibleNow: 10, remainingUnits: 4, startedAt: Now.AddDays(-2), searchedUnits: 3);
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.CycleItemsTotal.ShouldBe(10);
|
|
stat.CycleItemsRemaining.ShouldBe(4);
|
|
stat.CycleItemsSearched.ShouldBe(3);
|
|
stat.CycleStartedAt.ShouldBe(Now.AddDays(-2));
|
|
stat.CycleCompletedAt.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_CycleItemsSearched_ExcludesQueuedItems()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
Guid currentCycleId = Guid.NewGuid();
|
|
AddInstanceConfig(radarr.Id, currentCycleId);
|
|
|
|
// 5 eligible, 2 remaining, 1 actually searched: 2 are sitting in the download queue
|
|
AddCycle(currentCycleId, radarr.Id, eligibleNow: 5, remainingUnits: 2, startedAt: Now.AddDays(-1), searchedUnits: 1);
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.CycleItemsSearched.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_DryRunOn_NoDryCycle_ValuesAreZeroOrNull()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
(await _dataContext.GeneralConfigs.FirstAsync()).DryRun = true;
|
|
await _dataContext.SaveChangesAsync();
|
|
|
|
Guid currentCycleId = Guid.NewGuid();
|
|
AddInstanceConfig(radarr.Id, currentCycleId);
|
|
AddCycle(currentCycleId, radarr.Id, eligibleNow: 10, remainingUnits: 4, startedAt: Now.AddDays(-2), isDryRun: false);
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.CycleItemsSearched.ShouldBe(0);
|
|
stat.CycleItemsTotal.ShouldBe(0);
|
|
stat.CycleItemsRemaining.ShouldBe(0);
|
|
stat.CycleStartedAt.ShouldBeNull();
|
|
stat.CycleCompletedAt.ShouldBeNull();
|
|
stat.CurrentCycleId.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_LastCycle_IsTheNewestCompletedOtherCycle()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
Guid currentCycleId = Guid.NewGuid();
|
|
AddInstanceConfig(radarr.Id, currentCycleId);
|
|
AddCycle(currentCycleId, radarr.Id, eligibleNow: 5, remainingUnits: 5, startedAt: Now);
|
|
|
|
Guid olderCompletedId = Guid.NewGuid();
|
|
Guid newerCompletedId = Guid.NewGuid();
|
|
AddCycle(olderCompletedId, radarr.Id, eligibleNow: 3, remainingUnits: 0, startedAt: Now.AddDays(-20), completedAt: Now.AddDays(-19));
|
|
AddCycle(newerCompletedId, radarr.Id, eligibleNow: 3, remainingUnits: 0, startedAt: Now.AddDays(-10), completedAt: Now.AddDays(-9));
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.LastCycleStartedAt.ShouldBe(Now.AddDays(-10));
|
|
stat.LastCycleCompletedAt.ShouldBe(Now.AddDays(-9));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_DryRunOn_ShowsNewestDryCycleAndIgnoresLiveCycles()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
(await _dataContext.GeneralConfigs.FirstAsync()).DryRun = true;
|
|
await _dataContext.SaveChangesAsync();
|
|
|
|
Guid currentCycleId = Guid.NewGuid();
|
|
AddInstanceConfig(radarr.Id, currentCycleId);
|
|
AddCycle(currentCycleId, radarr.Id, eligibleNow: 99, remainingUnits: 99, startedAt: Now.AddDays(-30), isDryRun: false);
|
|
|
|
Guid dryCycleId = Guid.NewGuid();
|
|
AddCycle(dryCycleId, radarr.Id, eligibleNow: 7, remainingUnits: 2, startedAt: Now.AddHours(-1), isDryRun: true);
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.CurrentCycleId.ShouldBe(dryCycleId);
|
|
stat.CycleItemsTotal.ShouldBe(7);
|
|
stat.CycleItemsRemaining.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_NoCycleRow_ValuesAreZeroOrNull()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
AddInstanceConfig(radarr.Id, Guid.NewGuid());
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.CycleItemsSearched.ShouldBe(0);
|
|
stat.CycleItemsTotal.ShouldBe(0);
|
|
stat.CycleItemsRemaining.ShouldBe(0);
|
|
stat.CycleStartedAt.ShouldBeNull();
|
|
stat.CycleCompletedAt.ShouldBeNull();
|
|
stat.LastCycleStartedAt.ShouldBeNull();
|
|
stat.LastCycleCompletedAt.ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSummary_PerInstanceStats_NoCurrentCycleRow_LastCycleIsTheNewestCompleted()
|
|
{
|
|
ArrInstance radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
AddInstanceConfig(radarr.Id, Guid.NewGuid());
|
|
AddCycle(Guid.NewGuid(), radarr.Id, eligibleNow: 3, remainingUnits: 0, startedAt: Now.AddDays(-10), completedAt: Now.AddDays(-9));
|
|
|
|
SearchStatsSummaryResponse summary = Summary(await _controller.GetSummary());
|
|
|
|
InstanceSearchStat stat = summary.PerInstanceStats.ShouldHaveSingleItem();
|
|
stat.CycleStartedAt.ShouldBeNull();
|
|
stat.LastCycleStartedAt.ShouldBe(Now.AddDays(-10));
|
|
stat.LastCycleCompletedAt.ShouldBe(Now.AddDays(-9));
|
|
}
|
|
|
|
private void AddInstanceConfig(Guid arrInstanceId, Guid currentCycleId)
|
|
{
|
|
_dataContext.SeekerInstanceConfigs.Add(new SeekerInstanceConfig
|
|
{
|
|
ArrInstanceId = arrInstanceId,
|
|
Enabled = true,
|
|
CurrentCycleId = currentCycleId,
|
|
});
|
|
_dataContext.SaveChanges();
|
|
}
|
|
|
|
private void AddCycle(
|
|
Guid id,
|
|
Guid arrInstanceId,
|
|
int eligibleNow,
|
|
int remainingUnits,
|
|
DateTimeOffset startedAt,
|
|
DateTimeOffset? completedAt = null,
|
|
bool isDryRun = false,
|
|
int searchedUnits = 0,
|
|
DateTimeOffset? funnelCountedAt = null,
|
|
int queuedUnits = 0,
|
|
int recentlyGrabbedUnits = 0)
|
|
{
|
|
_eventsContext.SeekerCycles.Add(new SeekerCycle
|
|
{
|
|
Id = id,
|
|
ArrInstanceId = arrInstanceId,
|
|
StartedAt = startedAt,
|
|
CompletedAt = completedAt,
|
|
CompletionReason = completedAt is null ? null : SeekerCycleCompletionReason.Completed,
|
|
EligibleAtStart = eligibleNow,
|
|
EligibleNow = eligibleNow,
|
|
RemainingUnits = remainingUnits,
|
|
SearchedUnits = searchedUnits,
|
|
IsDryRun = isDryRun,
|
|
LibraryItems = 50,
|
|
ReleasedItems = 40,
|
|
MonitoredItems = 30,
|
|
TagFilteredItems = 20,
|
|
QueuedUnits = queuedUnits,
|
|
RecentlyGrabbedUnits = recentlyGrabbedUnits,
|
|
FunnelCountedAt = funnelCountedAt,
|
|
});
|
|
_eventsContext.SaveChanges();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetEvents with SearchEventData
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithNoSearchEventData_ReturnsUnknownDefaults()
|
|
{
|
|
AddSearchEvent();
|
|
|
|
var result = await _controller.GetEvents();
|
|
var body = GetResponseBody(result);
|
|
|
|
var item = body.GetProperty("Items")[0];
|
|
item.GetProperty("ItemTitle").GetString().ShouldBe("Unknown");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSearchEventData_ReturnsAllFields()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
|
|
AddSearchEvent(
|
|
arrInstanceId: radarr.Id,
|
|
itemTitle: "Movie A",
|
|
searchType: SeekerSearchType.Proactive,
|
|
searchReason: SeekerSearchReason.Missing,
|
|
grabbedItems: ["Movie A (2024)"]);
|
|
|
|
var result = await _controller.GetEvents();
|
|
var body = GetResponseBody(result);
|
|
|
|
var item = body.GetProperty("Items")[0];
|
|
item.GetProperty("ArrInstanceId").GetString().ShouldBe(radarr.Id.ToString());
|
|
item.GetProperty("InstanceType").GetString().ShouldBe(nameof(InstanceType.Radarr));
|
|
item.GetProperty("ItemTitle").GetString().ShouldBe("Movie A");
|
|
item.GetProperty("SearchType").GetString().ShouldBe(nameof(SeekerSearchType.Proactive));
|
|
item.GetProperty("SearchReason").GetString().ShouldBe(nameof(SeekerSearchReason.Missing));
|
|
item.GetProperty("GrabbedItems")[0].GetString().ShouldBe("Movie A (2024)");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithReplacementSearchType_ParsesCorrectEnum()
|
|
{
|
|
AddSearchEvent(
|
|
itemTitle: "Series A",
|
|
searchType: SeekerSearchType.Replacement,
|
|
searchReason: SeekerSearchReason.Replacement);
|
|
|
|
var result = await _controller.GetEvents();
|
|
var body = GetResponseBody(result);
|
|
|
|
var item = body.GetProperty("Items")[0];
|
|
item.GetProperty("SearchType").GetString().ShouldBe(nameof(SeekerSearchType.Replacement));
|
|
item.GetProperty("SearchReason").GetString().ShouldBe(nameof(SeekerSearchReason.Replacement));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetEvents Filtering
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithInstanceIdFilter_FiltersByArrInstanceId()
|
|
{
|
|
var radarr = SeekerTestDataFactory.AddRadarrInstance(_dataContext);
|
|
var sonarr = SeekerTestDataFactory.AddSonarrInstance(_dataContext);
|
|
|
|
AddSearchEvent(arrInstanceId: radarr.Id, itemTitle: "Radarr Movie");
|
|
AddSearchEvent(arrInstanceId: sonarr.Id, itemTitle: "Sonarr Series");
|
|
|
|
var result = await _controller.GetEvents(instanceId: radarr.Id);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ArrInstanceId").GetString().ShouldBe(radarr.Id.ToString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithCycleIdFilter_ReturnsOnlyMatchingCycle()
|
|
{
|
|
var cycleA = Guid.NewGuid();
|
|
var cycleB = Guid.NewGuid();
|
|
|
|
AddSearchEvent(cycleId: cycleA, itemTitle: "Cycle A Movie");
|
|
AddSearchEvent(cycleId: cycleB, itemTitle: "Cycle B Movie");
|
|
|
|
var result = await _controller.GetEvents(cycleId: cycleA);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ItemTitle").GetString().ShouldBe("Cycle A Movie");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSearchFilter_FiltersOnItemTitle()
|
|
{
|
|
AddSearchEvent(itemTitle: "The Matrix");
|
|
AddSearchEvent(itemTitle: "Breaking Bad");
|
|
|
|
var result = await _controller.GetEvents(search: "matrix");
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithPagination_ReturnsCorrectPageAndCount()
|
|
{
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
AddSearchEvent(itemTitle: $"Event {i}");
|
|
}
|
|
|
|
var result = await _controller.GetEvents(page: 2, pageSize: 2);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(5);
|
|
body.GetProperty("TotalPages").GetInt32().ShouldBe(3);
|
|
body.GetProperty("Page").GetInt32().ShouldBe(2);
|
|
body.GetProperty("Items").GetArrayLength().ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSortByTitleAscending_OrdersAlphabetically()
|
|
{
|
|
AddSearchEvent(itemTitle: "Charlie");
|
|
AddSearchEvent(itemTitle: "Alpha");
|
|
AddSearchEvent(itemTitle: "Bravo");
|
|
|
|
var result = await _controller.GetEvents(
|
|
sortBy: SearchEventsSortBy.Title,
|
|
sortDirection: Cleanuparr.Domain.Enums.SortDirection.Asc);
|
|
var body = GetResponseBody(result);
|
|
|
|
var items = body.GetProperty("Items");
|
|
items[0].GetProperty("ItemTitle").GetString().ShouldBe("Alpha");
|
|
items[1].GetProperty("ItemTitle").GetString().ShouldBe("Bravo");
|
|
items[2].GetProperty("ItemTitle").GetString().ShouldBe("Charlie");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSortByTimestampAscending_OldestFirst()
|
|
{
|
|
AddSearchEvent(itemTitle: "Newest", timestamp: Now.UtcDateTime);
|
|
AddSearchEvent(itemTitle: "Oldest", timestamp: Now.UtcDateTime.AddHours(-2));
|
|
AddSearchEvent(itemTitle: "Middle", timestamp: Now.UtcDateTime.AddHours(-1));
|
|
|
|
var result = await _controller.GetEvents(sortDirection: Cleanuparr.Domain.Enums.SortDirection.Asc);
|
|
var body = GetResponseBody(result);
|
|
|
|
var items = body.GetProperty("Items");
|
|
items[0].GetProperty("ItemTitle").GetString().ShouldBe("Oldest");
|
|
items[2].GetProperty("ItemTitle").GetString().ShouldBe("Newest");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSearchStatusFilter_ReturnsOnlyMatchingStatuses()
|
|
{
|
|
AddSearchEvent(itemTitle: "A", searchStatus: SearchCommandStatus.Completed);
|
|
AddSearchEvent(itemTitle: "B", searchStatus: SearchCommandStatus.Failed);
|
|
AddSearchEvent(itemTitle: "C", searchStatus: SearchCommandStatus.TimedOut);
|
|
|
|
var result = await _controller.GetEvents(
|
|
searchStatus: [SearchCommandStatus.Completed, SearchCommandStatus.Failed]);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSearchTypeFilter_ReturnsOnlyMatchingType()
|
|
{
|
|
AddSearchEvent(itemTitle: "Proactive Movie", searchType: SeekerSearchType.Proactive);
|
|
AddSearchEvent(itemTitle: "Replacement Movie", searchType: SeekerSearchType.Replacement);
|
|
|
|
var result = await _controller.GetEvents(searchType: SeekerSearchType.Replacement);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ItemTitle").GetString().ShouldBe("Replacement Movie");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithSearchReasonFilter_ReturnsOnlyMatchingReason()
|
|
{
|
|
AddSearchEvent(itemTitle: "Missing", searchReason: SeekerSearchReason.Missing);
|
|
AddSearchEvent(itemTitle: "Cutoff", searchReason: SeekerSearchReason.QualityCutoffNotMet);
|
|
|
|
var result = await _controller.GetEvents(searchReason: SeekerSearchReason.QualityCutoffNotMet);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ItemTitle").GetString().ShouldBe("Cutoff");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithContentKindFilter_ReturnsOnlyMatchingKind()
|
|
{
|
|
AddSearchEvent(itemTitle: "Show S01", contentKind: SeekerContentKind.Standard);
|
|
AddSearchEvent(itemTitle: "Show S00E01", contentKind: SeekerContentKind.Special);
|
|
|
|
IActionResult result = await _controller.GetEvents(contentKind: SeekerContentKind.Special);
|
|
JsonElement body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ItemTitle").GetString().ShouldBe("Show S00E01");
|
|
body.GetProperty("Items")[0].GetProperty("ContentKind").GetString().ShouldBe("Special");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithGrabbedTrue_KeepsOnlyEventsWithGrabbedItems()
|
|
{
|
|
AddSearchEvent(itemTitle: "With Grabs", grabbedItems: ["movie (2024)"]);
|
|
AddSearchEvent(itemTitle: "No Grabs", grabbedItems: []);
|
|
|
|
var result = await _controller.GetEvents(grabbed: true);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ItemTitle").GetString().ShouldBe("With Grabs");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetEvents_WithGrabbedFalse_KeepsOnlyEventsWithoutGrabbedItems()
|
|
{
|
|
AddSearchEvent(itemTitle: "With Grabs", grabbedItems: ["movie (2024)"]);
|
|
AddSearchEvent(itemTitle: "No Grabs", grabbedItems: []);
|
|
|
|
var result = await _controller.GetEvents(grabbed: false);
|
|
var body = GetResponseBody(result);
|
|
|
|
body.GetProperty("TotalCount").GetInt32().ShouldBe(1);
|
|
body.GetProperty("Items")[0].GetProperty("ItemTitle").GetString().ShouldBe("No Grabs");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helpers
|
|
|
|
private void AddSearchEvent(
|
|
string? itemTitle = null,
|
|
SeekerSearchType searchType = SeekerSearchType.Proactive,
|
|
SeekerSearchReason searchReason = SeekerSearchReason.Missing,
|
|
SeekerContentKind? contentKind = null,
|
|
List<string>? grabbedItems = null,
|
|
Guid? arrInstanceId = null,
|
|
Guid? cycleId = null,
|
|
SearchCommandStatus? searchStatus = null,
|
|
DateTime? timestamp = null)
|
|
{
|
|
var appEvent = new AppEvent
|
|
{
|
|
EventType = EventType.SearchTriggered,
|
|
Message = "Search triggered",
|
|
Severity = EventSeverity.Information,
|
|
ArrInstanceId = arrInstanceId,
|
|
CycleId = cycleId,
|
|
SearchStatus = searchStatus,
|
|
Timestamp = timestamp ?? Now.UtcDateTime
|
|
};
|
|
|
|
if (itemTitle is not null)
|
|
{
|
|
appEvent.ItemTitle = itemTitle;
|
|
appEvent.SearchType = searchType;
|
|
appEvent.SearchReason = searchReason;
|
|
appEvent.ContentKind = contentKind;
|
|
appEvent.GrabbedItems = grabbedItems ?? [];
|
|
}
|
|
|
|
_eventsContext.Events.Add(appEvent);
|
|
_eventsContext.SaveChanges();
|
|
}
|
|
|
|
#endregion
|
|
}
|