Files
ersatztv/ErsatzTV.Tests/Controllers/ResolutionControllerTests.cs
T
timothyandClaude Opus 4.8 fecf16d3d2 feat(api): #288 wrap raw VMs, nullable-honest DTOs, search pageNum
24 #nullable enable flips across ErsatzTV.Core/Api; Collection/Schedule/
SmartCollection/Resolution VMs wrapped in ResponseModels (Version now
header-only, SPA-verified); pageNum threaded into GET /api/search.

Refs #288 #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 02:08:12 +02:00

138 lines
5.0 KiB
C#

using ErsatzTV.Application.Resolutions;
using ErsatzTV.Controllers.Api;
using ErsatzTV.Controllers.Api.Requests;
using ErsatzTV.Core;
using ErsatzTV.Core.Api.Settings;
using ErsatzTV.Core.Errors;
using LanguageExt;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using static LanguageExt.Prelude;
namespace ErsatzTV.Tests.Controllers;
[TestFixture]
public class ResolutionControllerTests
{
private ResolutionController _controller = null!;
private IMediator _mediator = null!;
[SetUp]
public void SetUp()
{
_mediator = Substitute.For<IMediator>();
_controller = new ResolutionController(_mediator);
}
[Test]
public async Task GetAll_Should_Map_View_Models_To_Response_Models()
{
_mediator.Send(Arg.Any<GetAllResolutions>(), Arg.Any<CancellationToken>())
.Returns(
new List<ResolutionViewModel>
{
new(1, "1920x1080", 1920, 1080, false),
new(2, "1280x720", 1280, 720, true)
});
List<ResolutionResponseModel> result = await _controller.GetAll(CancellationToken.None);
result.ShouldBe(
[
new ResolutionResponseModel(1, "1920x1080", 1920, 1080, false),
new ResolutionResponseModel(2, "1280x720", 1280, 720, true)
]);
}
[Test]
public async Task GetResolutionByName_Should_Return_ResponseModel_Not_ViewModel()
{
_mediator.Send(Arg.Any<GetResolutionByName>(), Arg.Any<CancellationToken>())
.Returns(Option<ResolutionViewModel>.Some(new ResolutionViewModel(1, "1920x1080", 1920, 1080, false)));
ActionResult<ResolutionResponseModel> result =
await _controller.GetResolutionByName("1920x1080", CancellationToken.None);
var ok = result.Result.ShouldBeOfType<OkObjectResult>();
ok.Value.ShouldBe(new ResolutionResponseModel(1, "1920x1080", 1920, 1080, false));
}
[Test]
public async Task GetResolutionByName_Should_Return_404_For_None()
{
_mediator.Send(Arg.Any<GetResolutionByName>(), Arg.Any<CancellationToken>())
.Returns(Option<ResolutionViewModel>.None);
ActionResult<ResolutionResponseModel> result =
await _controller.GetResolutionByName("nope", CancellationToken.None);
result.Result.ShouldBeOfType<NotFoundResult>();
}
[Test]
public async Task Create_Should_Return_201_With_Location_And_Body()
{
_mediator.Send(Arg.Any<CreateCustomResolution>(), Arg.Any<CancellationToken>())
.Returns(Option<BaseError>.None);
_mediator.Send(Arg.Any<GetResolutionByName>(), Arg.Any<CancellationToken>())
.Returns(Option<ResolutionViewModel>.Some(new ResolutionViewModel(9, "640x480", 640, 480, true)));
IActionResult result = await _controller.Create(new CreateResolutionRequest(640, 480), CancellationToken.None);
var created = result.ShouldBeOfType<CreatedResult>();
created.StatusCode.ShouldBe(201);
created.Location.ShouldBe("/api/settings/resolutions/9");
created.Value.ShouldBe(new ResolutionResponseModel(9, "640x480", 640, 480, true));
await _mediator.Received(1).Send(
Arg.Is<CreateCustomResolution>(c => c.Width == 640 && c.Height == 480),
Arg.Any<CancellationToken>());
}
[Test]
public async Task Create_Should_Return_422_When_Resolution_Is_Not_Unique()
{
_mediator.Send(Arg.Any<CreateCustomResolution>(), Arg.Any<CancellationToken>())
.Returns(Option<BaseError>.Some(BaseError.New("Resolution width and height must be unique")));
IActionResult result = await _controller.Create(new CreateResolutionRequest(1920, 1080), CancellationToken.None);
result.ShouldBeOfType<UnprocessableEntityObjectResult>();
}
[Test]
public async Task Delete_Should_Return_204_On_Success()
{
_mediator.Send(Arg.Any<DeleteCustomResolution>(), Arg.Any<CancellationToken>())
.Returns(Option<BaseError>.None);
IActionResult result = await _controller.Delete(9, CancellationToken.None);
result.ShouldBeOfType<NoContentResult>();
}
[Test]
public async Task Delete_Should_Return_404_For_Unknown_Resolution()
{
_mediator.Send(Arg.Any<DeleteCustomResolution>(), Arg.Any<CancellationToken>())
.Returns(Option<BaseError>.Some(new NotFoundError("Resolution 42 does not exist.")));
IActionResult result = await _controller.Delete(42, CancellationToken.None);
result.ShouldBeOfType<NotFoundObjectResult>();
}
[Test]
public async Task Delete_Should_Return_422_For_NonCustom_Resolution()
{
_mediator.Send(Arg.Any<DeleteCustomResolution>(), Arg.Any<CancellationToken>())
.Returns(Option<BaseError>.Some(BaseError.New("Resolution 3 is not a custom resolution.")));
IActionResult result = await _controller.Delete(3, CancellationToken.None);
result.ShouldBeOfType<UnprocessableEntityObjectResult>();
}
}