Files
ersatztv/ErsatzTV.Infrastructure.Tests/Images/RemoteLogoCacherTests.cs
T

80 lines
3.1 KiB
C#

using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Streaming;
using ErsatzTV.Infrastructure.Images;
using LanguageExt;
using NSubstitute;
using NUnit.Framework;
using Shouldly;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using static LanguageExt.Prelude;
using Image = SixLabors.ImageSharp.Image;
namespace ErsatzTV.Infrastructure.Tests.Images;
[TestFixture]
public class RemoteLogoCacherTests
{
private static readonly Uri Uri = new("https://example.com/logo.png");
[Test]
public async Task Should_Fetch_Validate_And_Cache_Returning_The_Name()
{
MemoryStream png = await RealPng(64, 64);
var fetcher = Substitute.For<IRemoteImageFetcher>();
fetcher.Fetch(Uri, Arg.Any<CancellationToken>()).Returns(png);
var validator = Substitute.For<IRemoteImageValidator>();
validator.Validate(png, Uri, Arg.Any<CancellationToken>()).Returns(Task.CompletedTask);
var cache = Substitute.For<IImageCache>();
cache.SaveArtworkToCache(Arg.Any<Stream>(), ArtworkKind.Logo).Returns(Right<BaseError, string>("abc123"));
var cacher = new RemoteLogoCacher(fetcher, validator, cache);
Either<BaseError, string> result = await cacher.CacheFromUrl(Uri, CancellationToken.None);
result.IsRight.ShouldBeTrue();
result.IfRight(name => name.ShouldBe("abc123"));
}
[Test]
public async Task Should_Return_Left_When_The_Fetch_Throws()
{
var fetcher = Substitute.For<IRemoteImageFetcher>();
fetcher.Fetch(Uri, Arg.Any<CancellationToken>()).Returns<Stream>(_ => throw new TimeoutException("timed out"));
var cacher = new RemoteLogoCacher(fetcher, Substitute.For<IRemoteImageValidator>(), Substitute.For<IImageCache>());
Either<BaseError, string> result = await cacher.CacheFromUrl(Uri, CancellationToken.None);
result.IsLeft.ShouldBeTrue();
result.IfLeft(e => e.Value.ShouldContain("timed out"));
}
[Test]
public async Task Should_Return_Left_When_Validation_Rejects_A_Bomb()
{
MemoryStream png = await RealPng(64, 64);
var fetcher = Substitute.For<IRemoteImageFetcher>();
fetcher.Fetch(Uri, Arg.Any<CancellationToken>()).Returns(png);
var validator = Substitute.For<IRemoteImageValidator>();
validator.Validate(png, Uri, Arg.Any<CancellationToken>())
.Returns<Task>(_ => throw new InvalidOperationException("over the 50000000 pixel limit"));
var cacher = new RemoteLogoCacher(fetcher, validator, Substitute.For<IImageCache>());
Either<BaseError, string> result = await cacher.CacheFromUrl(Uri, CancellationToken.None);
result.IsLeft.ShouldBeTrue();
result.IfLeft(e => e.Value.ShouldContain("pixel limit"));
}
private static async Task<MemoryStream> RealPng(int w, int h)
{
using var img = new Image<Rgba32>(w, h);
var ms = new MemoryStream();
await img.SaveAsync(ms, new PngEncoder());
ms.Position = 0;
return ms;
}
}