289 lines
10 KiB
C#
289 lines
10 KiB
C#
using ErsatzTV.Application.ChannelTemplates;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Api.ChannelTemplates;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Domain.Filler;
|
|
using ErsatzTV.Core.Errors;
|
|
using ErsatzTV.Core.Interfaces.Repositories;
|
|
using ErsatzTV.Core.Scheduling;
|
|
using ErsatzTV.Infrastructure.Data;
|
|
using ErsatzTV.Infrastructure.Data.Repositories;
|
|
using ErsatzTV.Tests.Support;
|
|
using LanguageExt;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
using Unit = LanguageExt.Unit;
|
|
|
|
namespace ErsatzTV.Tests.Application.ChannelTemplates;
|
|
|
|
[TestFixture]
|
|
public class ChannelTemplateHandlerTests
|
|
{
|
|
private InMemoryTvContext _db = null!;
|
|
private IConfigElementRepository _config = null!;
|
|
|
|
[SetUp]
|
|
public async Task SetUp()
|
|
{
|
|
_db = await InMemoryTvContext.CreateAsync();
|
|
_config = new ConfigElementRepository(_db.Factory);
|
|
}
|
|
|
|
[TearDown]
|
|
public async Task TearDown() => await _db.DisposeAsync();
|
|
|
|
[Test]
|
|
public async Task Create_Should_Persist_Template_With_Reference_Ids()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedFiller(2, FillerKind.PreRoll);
|
|
await SeedFiller(3, FillerKind.MidRoll);
|
|
await SeedFiller(4, FillerKind.PostRoll);
|
|
var handler = new CreateChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, ChannelTemplateResponseModel> result =
|
|
await handler.Handle(MakeCreate("Custom"), CancellationToken.None);
|
|
|
|
ChannelTemplateResponseModel vm = RightOf(result);
|
|
vm.Id.ShouldBeGreaterThan(0);
|
|
vm.Name.ShouldBe("Custom");
|
|
vm.IsSystem.ShouldBeFalse();
|
|
vm.PreRollFillerId.ShouldBe(2);
|
|
vm.MidRollFillerId.ShouldBe(3);
|
|
vm.PostRollFillerId.ShouldBe(4);
|
|
|
|
await using TvContext context = _db.CreateContext();
|
|
context.ChannelTemplates.Single().Name.ShouldBe("Custom");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Create_Should_Return_NotFoundError_When_Profile_Missing()
|
|
{
|
|
var handler = new CreateChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, ChannelTemplateResponseModel> result =
|
|
await handler.Handle(MakeCreate("Missing", ffmpegProfileId: 99), CancellationToken.None);
|
|
|
|
LeftOf(result).ShouldBeOfType<NotFoundError>();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Create_Should_Return_Error_When_Trimmed_Name_Already_Exists()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedTemplate(7, "Standard");
|
|
var handler = new CreateChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, ChannelTemplateResponseModel> result =
|
|
await handler.Handle(MakeCreate("Standard "), CancellationToken.None);
|
|
|
|
LeftOf(result).Value.ShouldBe("Channel template name must be unique.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Create_Should_Validate_Filler_Kind()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedFiller(2, FillerKind.PostRoll);
|
|
var handler = new CreateChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, ChannelTemplateResponseModel> result =
|
|
await handler.Handle(MakeCreate("Bad", preRollFillerId: 2), CancellationToken.None);
|
|
|
|
LeftOf(result).ShouldBeOfType<NotFoundError>();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Update_Should_Reject_System_Template()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedTemplate(7, "Standard", isSystem: true);
|
|
var handler = new UpdateChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, ChannelTemplateResponseModel> result =
|
|
await handler.Handle(MakeUpdate(7, "Changed"), CancellationToken.None);
|
|
|
|
LeftOf(result).Value.ShouldBe("System templates cannot be updated.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task Delete_Should_Reject_Default_Template()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedTemplate(7, "Default");
|
|
await _config.Upsert(ConfigElementKey.ChannelTemplatesDefaultTemplateId, 7, CancellationToken.None);
|
|
var handler = new DeleteChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, Unit> result = await handler.Handle(new DeleteChannelTemplate(7), CancellationToken.None);
|
|
|
|
LeftOf(result).Value.ShouldBe("Default channel template cannot be deleted.");
|
|
}
|
|
|
|
[Test]
|
|
public async Task SetDefault_Should_Update_Config_And_Return_Template()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedTemplate(7, "Default");
|
|
var handler = new SetDefaultChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Either<BaseError, ChannelTemplateResponseModel> result =
|
|
await handler.Handle(new SetDefaultChannelTemplate(7), CancellationToken.None);
|
|
|
|
ChannelTemplateResponseModel vm = RightOf(result);
|
|
vm.IsDefault.ShouldBeTrue();
|
|
Option<int> defaultId =
|
|
await _config.GetValue<int>(ConfigElementKey.ChannelTemplatesDefaultTemplateId, CancellationToken.None);
|
|
defaultId.IfNone(0).ShouldBe(7);
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetAll_Should_Flag_Configured_Default()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedTemplate(7, "Standard");
|
|
await SeedTemplate(8, "Music videos");
|
|
await _config.Upsert(ConfigElementKey.ChannelTemplatesDefaultTemplateId, 8, CancellationToken.None);
|
|
var handler = new GetAllChannelTemplatesHandler(_db.Factory, _config);
|
|
|
|
List<ChannelTemplateResponseModel> result =
|
|
await handler.Handle(new GetAllChannelTemplates(), CancellationToken.None);
|
|
|
|
result.Single(t => t.Id == 7).IsDefault.ShouldBeFalse();
|
|
result.Single(t => t.Id == 8).IsDefault.ShouldBeTrue();
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetDefault_Should_Fall_Back_To_First_System_Template_By_Name()
|
|
{
|
|
await SeedProfile(1);
|
|
await SeedTemplate(1, "Zulu", isSystem: true);
|
|
await SeedTemplate(99, "Alpha", isSystem: true);
|
|
var handler = new GetDefaultChannelTemplateHandler(_db.Factory, _config);
|
|
|
|
Option<ChannelTemplateResponseModel> result =
|
|
await handler.Handle(new GetDefaultChannelTemplate(), CancellationToken.None);
|
|
|
|
ChannelTemplateResponseModel vm = result.IfNone(() => throw new AssertionException("Expected a template"));
|
|
vm.Id.ShouldBe(99);
|
|
vm.Name.ShouldBe("Alpha");
|
|
vm.IsDefault.ShouldBeTrue();
|
|
}
|
|
|
|
private async Task SeedProfile(int id)
|
|
{
|
|
await using TvContext context = _db.CreateContext();
|
|
context.FFmpegProfiles.Add(new FFmpegProfile { Id = id, Name = $"profile-{id}" });
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
private async Task SeedFiller(int id, FillerKind fillerKind)
|
|
{
|
|
await using TvContext context = _db.CreateContext();
|
|
context.FillerPresets.Add(new FillerPreset
|
|
{
|
|
Id = id,
|
|
Name = $"filler-{id}",
|
|
FillerKind = fillerKind,
|
|
FillerMode = FillerMode.Count,
|
|
Count = 1,
|
|
CollectionType = CollectionType.Collection
|
|
});
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
private async Task SeedTemplate(int id, string name, bool isSystem = false)
|
|
{
|
|
await using TvContext context = _db.CreateContext();
|
|
context.ChannelTemplates.Add(new ChannelTemplate
|
|
{
|
|
Id = id,
|
|
Name = name,
|
|
Description = string.Empty,
|
|
IsSystem = isSystem,
|
|
FFmpegProfileId = 1,
|
|
StreamSelectorMode = ChannelStreamSelectorMode.Default,
|
|
StreamSelector = string.Empty,
|
|
PreferredAudioLanguageCode = string.Empty,
|
|
PreferredAudioTitle = string.Empty,
|
|
PlayoutSource = ChannelPlayoutSource.Generated,
|
|
PlayoutMode = ChannelPlayoutMode.Continuous,
|
|
StreamingMode = StreamingMode.TransportStreamHybrid,
|
|
PreferredSubtitleLanguageCode = string.Empty,
|
|
SubtitleMode = ChannelSubtitleMode.None,
|
|
MusicVideoCreditsMode = ChannelMusicVideoCreditsMode.None,
|
|
MusicVideoCreditsTemplate = string.Empty,
|
|
SongVideoMode = ChannelSongVideoMode.Default,
|
|
TranscodeMode = ChannelTranscodeMode.OnDemand,
|
|
IdleBehavior = ChannelIdleBehavior.StopOnDisconnect,
|
|
FixedStartTimeBehavior = FixedStartTimeBehavior.Flexible
|
|
});
|
|
await context.SaveChangesAsync();
|
|
}
|
|
|
|
private static CreateChannelTemplate MakeCreate(
|
|
string name,
|
|
int ffmpegProfileId = 1,
|
|
int? preRollFillerId = 2) =>
|
|
new(
|
|
name,
|
|
"Description",
|
|
ffmpegProfileId,
|
|
null,
|
|
null,
|
|
preRollFillerId,
|
|
3,
|
|
4,
|
|
ChannelStreamSelectorMode.Default,
|
|
string.Empty,
|
|
string.Empty,
|
|
string.Empty,
|
|
ChannelPlayoutSource.Generated,
|
|
ChannelPlayoutMode.Continuous,
|
|
StreamingMode.TransportStreamHybrid,
|
|
string.Empty,
|
|
ChannelSubtitleMode.None,
|
|
ChannelMusicVideoCreditsMode.None,
|
|
string.Empty,
|
|
ChannelSongVideoMode.Default,
|
|
ChannelTranscodeMode.OnDemand,
|
|
ChannelIdleBehavior.StopOnDisconnect,
|
|
true,
|
|
false,
|
|
FixedStartTimeBehavior.Flexible);
|
|
|
|
private static UpdateChannelTemplate MakeUpdate(int id, string name) =>
|
|
new(
|
|
id,
|
|
name,
|
|
"Description",
|
|
1,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
ChannelStreamSelectorMode.Default,
|
|
string.Empty,
|
|
string.Empty,
|
|
string.Empty,
|
|
ChannelPlayoutSource.Generated,
|
|
ChannelPlayoutMode.Continuous,
|
|
StreamingMode.TransportStreamHybrid,
|
|
string.Empty,
|
|
ChannelSubtitleMode.None,
|
|
ChannelMusicVideoCreditsMode.None,
|
|
string.Empty,
|
|
ChannelSongVideoMode.Default,
|
|
ChannelTranscodeMode.OnDemand,
|
|
ChannelIdleBehavior.StopOnDisconnect,
|
|
false,
|
|
false,
|
|
FixedStartTimeBehavior.Flexible);
|
|
|
|
private static BaseError LeftOf<TR>(Either<BaseError, TR> either) =>
|
|
either.Match(Left: e => e, Right: _ => throw new AssertionException("Expected a Left result"));
|
|
|
|
private static TR RightOf<TR>(Either<BaseError, TR> either) =>
|
|
either.Match(Left: e => throw new AssertionException($"Expected a Right result but got {e.Value}"), Right: r => r);
|
|
}
|