mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-05-09 07:13:59 -04:00
301 lines
11 KiB
C#
301 lines
11 KiB
C#
using System.Text.Json;
|
|
using Cleanuparr.Api.Features.Seeker.Controllers;
|
|
using Cleanuparr.Api.Tests.Features.Seeker.TestHelpers;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Persistence;
|
|
using Cleanuparr.Persistence.Models.Events;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Shouldly;
|
|
|
|
namespace Cleanuparr.Api.Tests.Features.Seeker;
|
|
|
|
public class SearchStatsControllerTests : IDisposable
|
|
{
|
|
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);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_dataContext.Dispose();
|
|
_eventsContext.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private static JsonElement GetResponseBody(IActionResult result)
|
|
{
|
|
var okResult = result.ShouldBeOfType<OkObjectResult>();
|
|
var json = JsonSerializer.Serialize(okResult.Value);
|
|
return JsonDocument.Parse(json).RootElement;
|
|
}
|
|
|
|
#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: DateTime.UtcNow);
|
|
AddSearchEvent(itemTitle: "Oldest", timestamp: DateTime.UtcNow.AddHours(-2));
|
|
AddSearchEvent(itemTitle: "Middle", timestamp: DateTime.UtcNow.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_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,
|
|
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 ?? DateTime.UtcNow
|
|
};
|
|
|
|
_eventsContext.Events.Add(appEvent);
|
|
_eventsContext.SaveChanges();
|
|
|
|
if (itemTitle is not null)
|
|
{
|
|
_eventsContext.SearchEventData.Add(new SearchEventData
|
|
{
|
|
AppEventId = appEvent.Id,
|
|
ItemTitle = itemTitle,
|
|
SearchType = searchType,
|
|
SearchReason = searchReason,
|
|
GrabbedItems = grabbedItems ?? [],
|
|
});
|
|
_eventsContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|