Route assertions, paging clamp, 404/422/400 paths and mediator interaction for the new PlayoutController/TroubleshootController endpoints; handler tests for ValidateSequentialSchedule (valid/invalid/throwing) and GetPlayoutHistoryDetails (found/not-found/malformed JSON via in-memory SQLite). Refs #145 #158 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
140 lines
5.3 KiB
C#
140 lines
5.3 KiB
C#
using System.IO.Abstractions;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
using System.Threading.Channels;
|
|
using ErsatzTV.Application;
|
|
using ErsatzTV.Application.Troubleshooting;
|
|
using ErsatzTV.Application.Troubleshooting.Queries;
|
|
using ErsatzTV.Controllers.Api;
|
|
using ErsatzTV.Controllers.Api.Requests;
|
|
using ErsatzTV.Core.Api.Troubleshooting;
|
|
using ErsatzTV.Core.Interfaces.Repositories;
|
|
using ErsatzTV.Core.Interfaces.Troubleshooting;
|
|
using MediatR;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using NSubstitute;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
|
|
namespace ErsatzTV.Tests.Controllers;
|
|
|
|
[TestFixture]
|
|
public class TroubleshootControllerTests
|
|
{
|
|
private TroubleshootController _controller = null!;
|
|
private IMediator _mediator = null!;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_mediator = Substitute.For<IMediator>();
|
|
_controller = new TroubleshootController(
|
|
Channel.CreateUnbounded<IFFmpegWorkerRequest>().Writer,
|
|
Substitute.For<IFileSystem>(),
|
|
Substitute.For<IConfigElementRepository>(),
|
|
Substitute.For<ITroubleshootingNotifier>(),
|
|
_mediator);
|
|
}
|
|
|
|
[Test]
|
|
public void Controller_Should_Expose_Idiomatic_Rest_Route_For_Info()
|
|
{
|
|
MethodInfo action = typeof(TroubleshootController).GetMethod(nameof(TroubleshootController.GetInfo))
|
|
?? throw new AssertionException("Missing action GetInfo");
|
|
|
|
var attribute = action.GetCustomAttributes<HttpGetAttribute>().Single();
|
|
attribute.Template.ShouldBe("api/troubleshoot/info");
|
|
attribute.Name.ShouldBe("GetTroubleshootingInfo");
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetInfo_Should_Serialize_General_Section_And_Carry_Platform_Capabilities()
|
|
{
|
|
var info = new TroubleshootingInfo(
|
|
"1.2.3",
|
|
new Dictionary<string, string> { ["ETV_FOO"] = "bar" },
|
|
[],
|
|
[],
|
|
[],
|
|
new ErsatzTV.Application.FFmpegProfiles.FFmpegSettingsViewModel(),
|
|
[],
|
|
[],
|
|
[],
|
|
false,
|
|
false,
|
|
"nvidia output",
|
|
"qsv output",
|
|
"vaapi output",
|
|
"videotoolbox output");
|
|
|
|
_mediator.Send(Arg.Any<GetTroubleshootingInfo>(), Arg.Any<CancellationToken>())
|
|
.Returns(info);
|
|
|
|
TroubleshootingInfoResponseModel result = await _controller.GetInfo(CancellationToken.None);
|
|
|
|
result.NvidiaCapabilities.ShouldBe("nvidia output");
|
|
result.QsvCapabilities.ShouldBe("qsv output");
|
|
result.VaapiCapabilities.ShouldBe("vaapi output");
|
|
result.VideoToolboxCapabilities.ShouldBe("videotoolbox output");
|
|
|
|
using JsonDocument document = JsonDocument.Parse(result.GeneralJson);
|
|
document.RootElement.GetProperty("Version").GetString().ShouldBe("1.2.3");
|
|
document.RootElement.GetProperty("Environment").GetProperty("ETV_FOO").GetString().ShouldBe("bar");
|
|
document.RootElement.GetProperty("AviSynth").GetProperty("Demuxer").GetBoolean().ShouldBeFalse();
|
|
}
|
|
|
|
[Test]
|
|
public void Controller_Should_Expose_Idiomatic_Rest_Route_For_ValidateSchedule()
|
|
{
|
|
MethodInfo action = typeof(TroubleshootController).GetMethod(nameof(TroubleshootController.ValidateSchedule))
|
|
?? throw new AssertionException("Missing action ValidateSchedule");
|
|
|
|
var attribute = action.GetCustomAttributes<HttpPostAttribute>().Single();
|
|
attribute.Template.ShouldBe("api/troubleshoot/validate-schedule");
|
|
attribute.Name.ShouldBe("ValidateSequentialSchedule");
|
|
}
|
|
|
|
[Test]
|
|
public void ValidateSchedule_Should_Use_Stable_Request_Dto()
|
|
{
|
|
MethodInfo action = typeof(TroubleshootController).GetMethod(nameof(TroubleshootController.ValidateSchedule))
|
|
?? throw new AssertionException("Missing action ValidateSchedule");
|
|
|
|
action.GetParameters()[0].ParameterType.ShouldBe(typeof(ValidateSequentialScheduleRequest));
|
|
}
|
|
|
|
[TestCase("")]
|
|
[TestCase(" ")]
|
|
public async Task ValidateSchedule_Should_Return_400_For_Empty_Yaml(string yaml)
|
|
{
|
|
IActionResult result = await _controller.ValidateSchedule(
|
|
new ValidateSequentialScheduleRequest(yaml, false),
|
|
CancellationToken.None);
|
|
|
|
var badRequest = result.ShouldBeOfType<BadRequestObjectResult>();
|
|
badRequest.Value.ShouldBeOfType<ProblemDetails>().Status.ShouldBe(400);
|
|
await _mediator.DidNotReceive().Send(Arg.Any<ValidateSequentialSchedule>(), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Test]
|
|
public async Task ValidateSchedule_Should_Map_Result_To_Response()
|
|
{
|
|
_mediator.Send(Arg.Any<ValidateSequentialSchedule>(), Arg.Any<CancellationToken>())
|
|
.Returns(new ValidateSequentialScheduleViewModel(false, ["boom"], "{}"));
|
|
|
|
IActionResult result = await _controller.ValidateSchedule(
|
|
new ValidateSequentialScheduleRequest("content: []", true),
|
|
CancellationToken.None);
|
|
|
|
var response = result.ShouldBeOfType<OkObjectResult>().Value
|
|
.ShouldBeOfType<ValidateSequentialScheduleResponseModel>();
|
|
response.IsValid.ShouldBeFalse();
|
|
response.Messages.ShouldBe(["boom"]);
|
|
response.Json.ShouldBe("{}");
|
|
|
|
await _mediator.Received(1).Send(
|
|
Arg.Is<ValidateSequentialSchedule>(q => q.Yaml == "content: []" && q.IsImport),
|
|
Arg.Any<CancellationToken>());
|
|
}
|
|
}
|