Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m23s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 14m21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 19m9s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 12m1s
Co-authored-by: Timothy <timothy.look@gmail.com> Co-committed-by: Timothy <timothy.look@gmail.com>
163 lines
5.7 KiB
C#
163 lines
5.7 KiB
C#
using ErsatzTV.Application.Channels;
|
|
using ErsatzTV.Core;
|
|
using LanguageExt;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Errors;
|
|
using ErsatzTV.Infrastructure.Data;
|
|
using ErsatzTV.Tests.Support;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NSubstitute;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
using static LanguageExt.Prelude;
|
|
|
|
namespace ErsatzTV.Tests.Application.Channels;
|
|
|
|
[TestFixture]
|
|
public class CreateChannelHandlerTests : ChannelHandlerTestBase
|
|
{
|
|
private CreateChannelHandler MakeHandler() => new(Worker, Db.Factory, SearchTargets, RemoteLogoCacher);
|
|
|
|
[Test]
|
|
public async Task Should_Create_Channel_When_Valid()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(MakeCreate(number: "7", name: "News"), CancellationToken.None);
|
|
|
|
result.IsRight.ShouldBeTrue();
|
|
|
|
await using TvContext context = Db.CreateContext();
|
|
Channel channel = await context.Channels.SingleAsync(c => c.Number == "7" && c.Name == "News");
|
|
channel.Origin.ShouldBe(ChannelOrigin.UserCreated);
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Reject_Duplicate_Number_With_422_Error()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
await SeedChannel(1, "5");
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(MakeCreate(number: "5"), CancellationToken.None);
|
|
|
|
BaseError error = LeftOf(result);
|
|
error.ShouldNotBeOfType<NotFoundError>();
|
|
error.Value.ShouldContain("unique");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Reject_ShowInEpg_When_Disabled()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(
|
|
MakeCreate(number: "8", isEnabled: false, showInEpg: true),
|
|
CancellationToken.None);
|
|
|
|
BaseError error = LeftOf(result);
|
|
error.ShouldNotBeOfType<NotFoundError>();
|
|
error.Value.ShouldContain("EPG");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Reject_Invalid_External_Logo_Url()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(
|
|
MakeCreate(number: "9", logoPath: "ftp://example.com/logo.png"),
|
|
CancellationToken.None);
|
|
|
|
BaseError error = LeftOf(result);
|
|
error.ShouldNotBeOfType<NotFoundError>();
|
|
error.Value.ShouldContain("logo");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Reject_Empty_Group()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(MakeCreate(number: "11", group: " "), CancellationToken.None);
|
|
|
|
BaseError error = LeftOf(result);
|
|
error.ShouldNotBeOfType<NotFoundError>();
|
|
error.Value.ShouldContain("group");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Reject_Nonexistent_FFmpegProfile()
|
|
{
|
|
// intentionally do not seed an FFmpegProfile
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(MakeCreate(number: "12", ffmpegProfileId: 999), CancellationToken.None);
|
|
|
|
BaseError error = LeftOf(result);
|
|
error.ShouldNotBeOfType<NotFoundError>();
|
|
error.Value.ShouldContain("FFmpegProfile");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Download_External_Url_Logo_And_Store_Cache_Name()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
RemoteLogoCacher.CacheFromUrl(Arg.Any<Uri>(), Arg.Any<CancellationToken>())
|
|
.Returns(Right<BaseError, string>("cachedhash"));
|
|
|
|
Either<BaseError, CreateChannelResult> result = await MakeHandler().Handle(
|
|
MakeCreate(number: "20", logoPath: "https://example.com/logo.png"),
|
|
CancellationToken.None);
|
|
|
|
result.IsRight.ShouldBeTrue();
|
|
await using TvContext db = Db.CreateContext();
|
|
Artwork logo = db.Channels.Include(c => c.Artwork).Single(c => c.Number == "20")
|
|
.Artwork.Single(a => a.ArtworkKind == ArtworkKind.Logo);
|
|
logo.Path.ShouldBe("cachedhash");
|
|
logo.IsExternalUrl().ShouldBeFalse();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Fail_The_Create_When_The_Logo_Download_Fails()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
RemoteLogoCacher.CacheFromUrl(Arg.Any<Uri>(), Arg.Any<CancellationToken>())
|
|
.Returns(Left<BaseError, string>(BaseError.New("Could not download logo: host timed out")));
|
|
|
|
Either<BaseError, CreateChannelResult> result = await MakeHandler().Handle(
|
|
MakeCreate(number: "21", logoPath: "https://example.com/logo.png"),
|
|
CancellationToken.None);
|
|
|
|
result.IsLeft.ShouldBeTrue();
|
|
LeftOf(result).Value.ShouldContain("Could not download logo");
|
|
|
|
await using TvContext db = Db.CreateContext();
|
|
(await db.Channels.AnyAsync(c => c.Number == "21")).ShouldBeFalse();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Should_Not_Call_The_Cacher_For_An_Uploaded_Logo_Path()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
|
|
Either<BaseError, CreateChannelResult> result = await MakeHandler().Handle(
|
|
MakeCreate(number: "22", logoPath: "iptv/logos/deadbeef"),
|
|
CancellationToken.None);
|
|
|
|
result.IsRight.ShouldBeTrue();
|
|
await RemoteLogoCacher.DidNotReceive().CacheFromUrl(Arg.Any<Uri>(), Arg.Any<CancellationToken>());
|
|
|
|
await using TvContext db = Db.CreateContext();
|
|
Artwork logo = db.Channels.Include(c => c.Artwork).Single(c => c.Number == "22")
|
|
.Artwork.Single(a => a.ArtworkKind == ArtworkKind.Logo);
|
|
logo.Path.ShouldBe("deadbeef");
|
|
}
|
|
|
|
private static BaseError LeftOf<TR>(Either<BaseError, TR> either) =>
|
|
either.Match(Left: e => e, Right: _ => throw new AssertionException("Expected a Left result"));
|
|
}
|