POST /api/artwork/uploads (multipart/form-data) accepting logo and
watermark images. Validates content type (png/jpeg/gif/webp) and size
(SystemEnvironment.MaximumUploadMb, default 10MB) mirroring the Blazor
upload path; stores via IImageCache.SaveArtworkToCache; returns
{ path, contentType } consumable by channel create/update.
Includes handler + controller tests, OpenAPI 422 contract-test entry,
and regenerated v1.json.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88 lines
3.2 KiB
C#
88 lines
3.2 KiB
C#
using ErsatzTV.Application.Artworks;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Api.Artwork;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Interfaces.Images;
|
|
using LanguageExt;
|
|
using NSubstitute;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
using static LanguageExt.Prelude;
|
|
|
|
namespace ErsatzTV.Tests.Application.Artworks;
|
|
|
|
[TestFixture]
|
|
public class UploadArtworkHandlerTests
|
|
{
|
|
private IImageCache _imageCache = null!;
|
|
private UploadArtworkHandler _handler = null!;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
_imageCache = Substitute.For<IImageCache>();
|
|
_handler = new UploadArtworkHandler(_imageCache);
|
|
}
|
|
|
|
[Test]
|
|
public async Task Handle_Should_Return_Logo_Path_With_Iptv_Logos_Prefix()
|
|
{
|
|
_imageCache.SaveArtworkToCache(Arg.Any<Stream>(), ArtworkKind.Logo)
|
|
.Returns(Right<BaseError, string>("abc123.png"));
|
|
|
|
using var stream = new MemoryStream();
|
|
Either<BaseError, ArtworkUploadResponseModel> result =
|
|
await _handler.Handle(new UploadArtwork(stream, "image/png", ArtworkKind.Logo), CancellationToken.None);
|
|
|
|
ArtworkUploadResponseModel response = RightOf(result);
|
|
response.Path.ShouldBe("iptv/logos/abc123.png");
|
|
response.ContentType.ShouldBe("image/png");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Handle_Should_Return_Bare_File_Name_For_Watermark()
|
|
{
|
|
_imageCache.SaveArtworkToCache(Arg.Any<Stream>(), ArtworkKind.Watermark)
|
|
.Returns(Right<BaseError, string>("def456.webp"));
|
|
|
|
using var stream = new MemoryStream();
|
|
Either<BaseError, ArtworkUploadResponseModel> result = await _handler.Handle(
|
|
new UploadArtwork(stream, "image/webp", ArtworkKind.Watermark),
|
|
CancellationToken.None);
|
|
|
|
RightOf(result).Path.ShouldBe("def456.webp");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Handle_Should_Reject_Unsupported_Content_Type()
|
|
{
|
|
using var stream = new MemoryStream();
|
|
Either<BaseError, ArtworkUploadResponseModel> result = await _handler.Handle(
|
|
new UploadArtwork(stream, "image/bmp", ArtworkKind.Logo),
|
|
CancellationToken.None);
|
|
|
|
LeftOf(result).Value.ShouldContain("Unsupported image content type");
|
|
await _imageCache.DidNotReceive().SaveArtworkToCache(Arg.Any<Stream>(), Arg.Any<ArtworkKind>());
|
|
}
|
|
|
|
[Test]
|
|
public async Task Handle_Should_Propagate_Cache_Save_Failure()
|
|
{
|
|
_imageCache.SaveArtworkToCache(Arg.Any<Stream>(), ArtworkKind.Logo)
|
|
.Returns(Left<BaseError, string>(BaseError.New("disk full")));
|
|
|
|
using var stream = new MemoryStream();
|
|
Either<BaseError, ArtworkUploadResponseModel> result = await _handler.Handle(
|
|
new UploadArtwork(stream, "image/png", ArtworkKind.Logo),
|
|
CancellationToken.None);
|
|
|
|
LeftOf(result).Value.ShouldBe("disk full");
|
|
}
|
|
|
|
private static TR RightOf<TR>(Either<BaseError, TR> either) =>
|
|
either.Match(Right: v => v, Left: e => throw new AssertionException($"Expected Right, got Left: {e.Value}"));
|
|
|
|
private static BaseError LeftOf<TR>(Either<BaseError, TR> either) =>
|
|
either.Match(Right: _ => throw new AssertionException("Expected Left, got Right"), Left: e => e);
|
|
}
|