mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-09-12 21:37:53 -04:00
added response contract tests for queue cleaner
This commit is contained in:
1 parent
488ccf136c
commit
7a511dfb45
2 files changed
+253
No files matched your search
+192
@@ -0,0 +1,192 @@
|
||||
using Cleanuparr.Api.Features.QueueCleaner.Contracts.Requests;
|
||||
using Cleanuparr.Api.Features.QueueCleaner.Controllers;
|
||||
using Cleanuparr.Api.Tests.TestHelpers;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Cleanuparr.Persistence;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Cleanuparr.Api.Tests.Features.QueueCleaner;
|
||||
|
||||
/// <summary>
|
||||
/// Records today's wire shape, leaks included.
|
||||
/// </summary>
|
||||
public class QueueCleanerResponseContractTests : IDisposable
|
||||
{
|
||||
private readonly DataContext _dataContext;
|
||||
private readonly QueueCleanerConfigController _configController;
|
||||
private readonly QueueRulesController _rulesController;
|
||||
|
||||
public QueueCleanerResponseContractTests()
|
||||
{
|
||||
_dataContext = ConfigControllerTestDataFactory.CreateDataContext();
|
||||
|
||||
_configController = new QueueCleanerConfigController(
|
||||
Substitute.For<ILogger<QueueCleanerConfigController>>(),
|
||||
_dataContext,
|
||||
Substitute.For<IJobManagementService>());
|
||||
ConfigControllerTestDataFactory.ConfigureProblemDetails(_configController);
|
||||
|
||||
IRuleIntervalValidator validator = Substitute.For<IRuleIntervalValidator>();
|
||||
validator.ValidateStallRuleIntervals(Arg.Any<StallRule>(), Arg.Any<List<StallRule>>())
|
||||
.Returns(ValidationResult.Success());
|
||||
validator.ValidateSlowRuleIntervals(Arg.Any<SlowRule>(), Arg.Any<List<SlowRule>>())
|
||||
.Returns(ValidationResult.Success());
|
||||
|
||||
_rulesController = new QueueRulesController(
|
||||
Substitute.For<ILogger<QueueRulesController>>(),
|
||||
_dataContext,
|
||||
validator);
|
||||
ConfigControllerTestDataFactory.ConfigureProblemDetails(_rulesController);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataContext.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetQueueCleanerConfig_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
IActionResult result = await _configController.GetQueueCleanerConfig();
|
||||
|
||||
ResponseContract.Keys(result).ShouldBe(
|
||||
[
|
||||
"cronExpression",
|
||||
"downloadingMetadataMaxStrikes",
|
||||
"enabled",
|
||||
"failedImport",
|
||||
"id",
|
||||
"ignoredDownloads",
|
||||
"processNoContentId",
|
||||
"slowRules",
|
||||
"stallRules",
|
||||
"useAdvancedScheduling",
|
||||
]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStallRules_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
await _rulesController.CreateStallRule(NewStallDto("contract"));
|
||||
|
||||
IActionResult result = await _rulesController.GetStallRules();
|
||||
|
||||
ResponseContract.FirstItemKeys(result).ShouldBe(StallRuleKeys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateStallRule_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
IActionResult result = await _rulesController.CreateStallRule(NewStallDto("contract"));
|
||||
|
||||
ResponseContract.Keys(result).ShouldBe(StallRuleKeys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateStallRule_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
IActionResult created = await _rulesController.CreateStallRule(NewStallDto("contract"));
|
||||
Guid id = created.ShouldBeOfType<CreatedAtActionResult>().Value.ShouldBeOfType<StallRule>().Id;
|
||||
|
||||
IActionResult result = await _rulesController.UpdateStallRule(id, NewStallDto("renamed"));
|
||||
|
||||
ResponseContract.Keys(result).ShouldBe(StallRuleKeys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSlowRules_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
await _rulesController.CreateSlowRule(NewSlowDto("contract"));
|
||||
|
||||
IActionResult result = await _rulesController.GetSlowRules();
|
||||
|
||||
ResponseContract.FirstItemKeys(result).ShouldBe(SlowRuleKeys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateSlowRule_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
IActionResult result = await _rulesController.CreateSlowRule(NewSlowDto("contract"));
|
||||
|
||||
ResponseContract.Keys(result).ShouldBe(SlowRuleKeys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSlowRule_ReturnsTheDocumentedKeys()
|
||||
{
|
||||
IActionResult created = await _rulesController.CreateSlowRule(NewSlowDto("contract"));
|
||||
Guid id = created.ShouldBeOfType<CreatedAtActionResult>().Value.ShouldBeOfType<SlowRule>().Id;
|
||||
|
||||
IActionResult result = await _rulesController.UpdateSlowRule(id, NewSlowDto("renamed"));
|
||||
|
||||
ResponseContract.Keys(result).ShouldBe(SlowRuleKeys);
|
||||
}
|
||||
|
||||
private static readonly string[] StallRuleKeys =
|
||||
[
|
||||
"changeCategory",
|
||||
"deletePrivateTorrentsFromClient",
|
||||
"enabled",
|
||||
"id",
|
||||
"maxCompletionPercentage",
|
||||
"maxStrikes",
|
||||
"minCompletionPercentage",
|
||||
"minimumProgress",
|
||||
"name",
|
||||
"privacyType",
|
||||
"queueCleanerConfig",
|
||||
"queueCleanerConfigId",
|
||||
"resetStrikesOnProgress",
|
||||
];
|
||||
|
||||
private static readonly string[] SlowRuleKeys =
|
||||
[
|
||||
"changeCategory",
|
||||
"deletePrivateTorrentsFromClient",
|
||||
"enabled",
|
||||
"id",
|
||||
"ignoreAboveSize",
|
||||
"ignoreWhileAltSpeedActive",
|
||||
"maxCompletionPercentage",
|
||||
"maxStrikes",
|
||||
"maxTimeHours",
|
||||
"minCompletionPercentage",
|
||||
"minSpeed",
|
||||
"name",
|
||||
"privacyType",
|
||||
"queueCleanerConfig",
|
||||
"queueCleanerConfigId",
|
||||
"resetStrikesOnProgress",
|
||||
];
|
||||
|
||||
private static StallRuleDto NewStallDto(string name) => new()
|
||||
{
|
||||
Name = name,
|
||||
Enabled = true,
|
||||
MaxStrikes = 3,
|
||||
PrivacyType = TorrentPrivacyType.Public,
|
||||
MinCompletionPercentage = 0,
|
||||
MaxCompletionPercentage = 100,
|
||||
ResetStrikesOnProgress = true,
|
||||
};
|
||||
|
||||
private static SlowRuleDto NewSlowDto(string name) => new()
|
||||
{
|
||||
Name = name,
|
||||
Enabled = true,
|
||||
MaxStrikes = 3,
|
||||
PrivacyType = TorrentPrivacyType.Public,
|
||||
MinCompletionPercentage = 0,
|
||||
MaxCompletionPercentage = 100,
|
||||
ResetStrikesOnProgress = true,
|
||||
MinSpeed = "1MB",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Text.Json;
|
||||
using Cleanuparr.Api.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Cleanuparr.Api.Tests.TestHelpers;
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a controller result through the API's own JSON options.
|
||||
/// </summary>
|
||||
public static class ResponseContract
|
||||
{
|
||||
private static readonly JsonSerializerOptions Options = BuildOptions();
|
||||
|
||||
private static JsonSerializerOptions BuildOptions()
|
||||
{
|
||||
JsonSerializerOptions options = new(JsonSerializerDefaults.Web);
|
||||
CleanuparrJsonConfiguration.ConfigureApiInbound(options);
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the value carried by an <see cref="ObjectResult"/> and parses it back.
|
||||
/// </summary>
|
||||
public static JsonElement Body(IActionResult result)
|
||||
{
|
||||
if (result is not ObjectResult objectResult)
|
||||
{
|
||||
throw new InvalidOperationException($"Expected an ObjectResult, got {result.GetType().Name}.");
|
||||
}
|
||||
|
||||
if (objectResult.Value is null)
|
||||
{
|
||||
throw new InvalidOperationException("The result carries no value.");
|
||||
}
|
||||
|
||||
string json = JsonSerializer.Serialize(objectResult.Value, objectResult.Value.GetType(), Options);
|
||||
return JsonDocument.Parse(json).RootElement.Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Top-level property names, sorted so assertions read in a stable order.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> Keys(IActionResult result) => Keys(Body(result));
|
||||
|
||||
/// <inheritdoc cref="Keys(IActionResult)" />
|
||||
public static IReadOnlyList<string> Keys(JsonElement element) =>
|
||||
element.EnumerateObject()
|
||||
.Select(property => property.Name)
|
||||
.OrderBy(name => name, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Property names of the first array element, empty when the array is empty.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> FirstItemKeys(IActionResult result)
|
||||
{
|
||||
JsonElement body = Body(result);
|
||||
JsonElement? first = body.EnumerateArray().Cast<JsonElement?>().FirstOrDefault();
|
||||
return first is null ? [] : Keys(first.Value);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user