mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 10:48:49 -04:00
258 lines
8.0 KiB
C#
258 lines
8.0 KiB
C#
using System.Text.Json;
|
|
using Cleanuparr.Api.Features.Events.Contracts.Responses;
|
|
using Cleanuparr.Api.Features.Logging.Contracts.Responses;
|
|
using Cleanuparr.Api.Features.Strikes.Contracts.Responses;
|
|
using Cleanuparr.Api.Hubs;
|
|
using Cleanuparr.Api.Tests.TestHelpers;
|
|
using Cleanuparr.Domain.Enums;
|
|
using Cleanuparr.Infrastructure.Health;
|
|
using Cleanuparr.Infrastructure.Logging;
|
|
using Cleanuparr.Infrastructure.Models;
|
|
using Cleanuparr.Persistence.Models.Events;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace Cleanuparr.Api.Tests.Hubs;
|
|
|
|
/// <summary>
|
|
/// Records the message name and wire shape of every push the hub adapters emit.
|
|
/// </summary>
|
|
public class HubNotifierContractTests
|
|
{
|
|
private readonly HubMessage<AppHub> _appHub = new();
|
|
private readonly HubMessage<HealthStatusHub> _healthHub = new();
|
|
|
|
[Fact]
|
|
public async Task NotifyEventAsync_SendsEventReceivedWithTheDocumentedKeys()
|
|
{
|
|
await new EventNotifier(_appHub.Context, TimeProvider.System).NotifyEventAsync(new AppEvent
|
|
{
|
|
EventType = EventType.QueueItemDeleted,
|
|
Message = "deleted",
|
|
Severity = EventSeverity.Important,
|
|
});
|
|
|
|
(string method, object? payload) = _appHub.Single();
|
|
|
|
method.ShouldBe("EventReceived");
|
|
payload.ShouldBeOfType<EventListItem>();
|
|
ResponseContract.Keys(ResponseContract.Payload(payload)).ShouldBe(
|
|
[
|
|
"arrInstanceId",
|
|
"cleanReason",
|
|
"cleanedCategory",
|
|
"completedAt",
|
|
"cycleId",
|
|
"deleteReason",
|
|
"downloadClientId",
|
|
"eventType",
|
|
"failedImportReasons",
|
|
"grabbedItems",
|
|
"id",
|
|
"isCategoryTag",
|
|
"isDryRun",
|
|
"itemHash",
|
|
"itemTitle",
|
|
"jobRunId",
|
|
"message",
|
|
"newCategory",
|
|
"oldCategory",
|
|
"removeFromClient",
|
|
"searchReason",
|
|
"searchStatus",
|
|
"searchType",
|
|
"seedRatio",
|
|
"seedingTimeHours",
|
|
"severity",
|
|
"strikeCount",
|
|
"strikeId",
|
|
"timestamp",
|
|
"trackingId",
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyManualEventAsync_SendsManualEventReceivedWithTheDocumentedKeys()
|
|
{
|
|
await new EventNotifier(_appHub.Context, TimeProvider.System).NotifyManualEventAsync(new ManualEvent
|
|
{
|
|
Message = "needs attention",
|
|
Severity = EventSeverity.Warning,
|
|
Type = ManualEventType.SearchNotTriggered,
|
|
});
|
|
|
|
(string method, object? payload) = _appHub.Single();
|
|
|
|
method.ShouldBe("ManualEventReceived");
|
|
payload.ShouldBeOfType<ManualEventResponse>();
|
|
ResponseContract.Keys(ResponseContract.Payload(payload)).ShouldBe(
|
|
[
|
|
"downloadClientName",
|
|
"downloadClientType",
|
|
"id",
|
|
"instanceType",
|
|
"instanceUrl",
|
|
"isDryRun",
|
|
"isResolved",
|
|
"itemHash",
|
|
"itemTitle",
|
|
"jobRunId",
|
|
"message",
|
|
"resolvedAt",
|
|
"severity",
|
|
"strikeCount",
|
|
"timestamp",
|
|
"type",
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyStrikeAsync_SendsStrikeReceivedWithTheDocumentedKeys()
|
|
{
|
|
DateTimeOffset now = new(2026, 1, 1, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
await new EventNotifier(_appHub.Context, new FakeTimeProvider(now))
|
|
.NotifyStrikeAsync(Guid.NewGuid(), StrikeType.FailedImport, "HASH", "title", isDryRun: true);
|
|
|
|
(string method, object? payload) = _appHub.Single();
|
|
|
|
method.ShouldBe("StrikeReceived");
|
|
RecentStrikeDto strike = payload.ShouldBeOfType<RecentStrikeDto>();
|
|
strike.CreatedAt.ShouldBe(now);
|
|
ResponseContract.Keys(ResponseContract.Payload(payload)).ShouldBe(
|
|
[
|
|
"createdAt",
|
|
"downloadId",
|
|
"id",
|
|
"isDryRun",
|
|
"title",
|
|
"type",
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public void NotifyLog_SendsLogReceivedWithTheDocumentedKeys()
|
|
{
|
|
new LogNotifier(_appHub.Context).NotifyLog(new LogEntry
|
|
{
|
|
Timestamp = DateTimeOffset.UnixEpoch,
|
|
Level = "Information",
|
|
Message = "hello",
|
|
});
|
|
|
|
(string method, object? payload) = _appHub.Single();
|
|
|
|
method.ShouldBe("LogReceived");
|
|
payload.ShouldBeOfType<LogEntryResponse>();
|
|
ResponseContract.Keys(ResponseContract.Payload(payload)).ShouldBe(
|
|
[
|
|
"category",
|
|
"downloadClientName",
|
|
"downloadClientType",
|
|
"exception",
|
|
"instanceName",
|
|
"jobName",
|
|
"jobRunId",
|
|
"level",
|
|
"message",
|
|
"timestamp",
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyAppStatusAsync_SendsAppStatusUpdatedWithTheDocumentedKeys()
|
|
{
|
|
await new StatusNotifier(_appHub.Context).NotifyAppStatusAsync(new AppStatus("1.0.0", "1.1.0"));
|
|
|
|
(string method, object? payload) = _appHub.Single();
|
|
|
|
method.ShouldBe("AppStatusUpdated");
|
|
payload.ShouldBeOfType<AppStatus>();
|
|
ResponseContract.Keys(ResponseContract.Payload(payload)).ShouldBe(["currentVersion", "latestVersion"]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifySearchStatsUpdatedAsync_SendsSearchStatsUpdatedWithoutAPayload()
|
|
{
|
|
await new StatusNotifier(_appHub.Context).NotifySearchStatsUpdatedAsync();
|
|
|
|
_appHub.SingleWithoutPayload().ShouldBe("SearchStatsUpdated");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyCustomFormatScoresUpdatedAsync_SendsCfScoresUpdatedWithoutAPayload()
|
|
{
|
|
await new StatusNotifier(_appHub.Context).NotifyCustomFormatScoresUpdatedAsync();
|
|
|
|
_appHub.SingleWithoutPayload().ShouldBe("CfScoresUpdated");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("HealthStatusChanged")]
|
|
[InlineData("ClientDegraded")]
|
|
[InlineData("ClientRecovered")]
|
|
public async Task HealthMessages_CarryTheDocumentedKeys(string expectedMethod)
|
|
{
|
|
HealthNotifier notifier = new(_healthHub.Context);
|
|
HealthStatus status = new()
|
|
{
|
|
ClientId = Guid.NewGuid(),
|
|
ClientName = "qbit",
|
|
ClientTypeName = DownloadClientTypeName.qBittorrent,
|
|
IsHealthy = true,
|
|
LastChecked = DateTimeOffset.UnixEpoch,
|
|
};
|
|
|
|
await (expectedMethod switch
|
|
{
|
|
"ClientDegraded" => notifier.NotifyClientDegradedAsync(status),
|
|
"ClientRecovered" => notifier.NotifyClientRecoveredAsync(status),
|
|
_ => notifier.NotifyHealthStatusChangedAsync(status),
|
|
});
|
|
|
|
(string method, object? payload) = _healthHub.Single();
|
|
|
|
method.ShouldBe(expectedMethod);
|
|
payload.ShouldBeOfType<HealthStatus>();
|
|
ResponseContract.Keys(ResponseContract.Payload(payload)).ShouldBe(
|
|
[
|
|
"clientId",
|
|
"clientName",
|
|
"clientTypeName",
|
|
"errorMessage",
|
|
"isHealthy",
|
|
"lastChecked",
|
|
"responseTime",
|
|
]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyClientRemovedAsync_SendsABareGuid()
|
|
{
|
|
Guid clientId = Guid.NewGuid();
|
|
|
|
await new HealthNotifier(_healthHub.Context).NotifyClientRemovedAsync(clientId);
|
|
|
|
(string method, object? payload) = _healthHub.Single();
|
|
|
|
method.ShouldBe("ClientRemoved");
|
|
payload.ShouldBe(clientId);
|
|
ResponseContract.Payload(payload!).ValueKind.ShouldBe(JsonValueKind.String);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyArrInstanceRemovedAsync_SendsABareGuid()
|
|
{
|
|
Guid instanceId = Guid.NewGuid();
|
|
|
|
await new HealthNotifier(_healthHub.Context).NotifyArrInstanceRemovedAsync(instanceId);
|
|
|
|
(string method, object? payload) = _healthHub.Single();
|
|
|
|
method.ShouldBe("ArrInstanceRemoved");
|
|
payload.ShouldBe(instanceId);
|
|
ResponseContract.Payload(payload!).ValueKind.ShouldBe(JsonValueKind.String);
|
|
}
|
|
}
|