Files
ersatztv/ErsatzTV.Tests/Application/ProgramSchedules/ProgramScheduleHandlerTests.cs
T

269 lines
9.2 KiB
C#

using System.Threading.Channels;
using ErsatzTV.Application;
using ErsatzTV.Application.ProgramSchedules;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Tests.Support;
using LanguageExt;
using NUnit.Framework;
using Shouldly;
using Unit = LanguageExt.Unit;
namespace ErsatzTV.Tests.Application.ProgramSchedules;
[TestFixture]
public class ProgramScheduleHandlerTests
{
private InMemoryTvContext _db = null!;
private ChannelWriter<IBackgroundServiceRequest> _worker = null!;
[SetUp]
public async Task SetUp()
{
_db = await InMemoryTvContext.CreateAsync();
_worker = System.Threading.Channels.Channel.CreateUnbounded<IBackgroundServiceRequest>().Writer;
}
[TearDown]
public async Task TearDown() => await _db.DisposeAsync();
[Test]
public async Task Create_and_update_persist_PadToNearestMinute()
{
var createHandler = new CreateProgramScheduleHandler(_db.Factory);
Either<BaseError, CreateProgramScheduleResult> createResult = await createHandler.Handle(
new CreateProgramSchedule(
"Pad Test",
KeepMultiPartEpisodesTogether: true,
TreatCollectionsAsShows: true,
ShuffleScheduleItems: false,
RandomStartPoint: false,
FixedStartTimeBehavior.Flexible,
PadToNearestMinute: 30),
CancellationToken.None);
int scheduleId = createResult.Match(Left: _ => throw new AssertionException("Expected a Right result"), Right: r => r.ProgramScheduleId);
await using (TvContext context = _db.CreateContext())
{
ProgramSchedule? schedule = await context.ProgramSchedules.FindAsync(scheduleId);
schedule.ShouldNotBeNull();
schedule.PadToNearestMinute.ShouldBe(30);
}
var updateHandler = new UpdateProgramScheduleHandler(_db.Factory, _worker);
Either<BaseError, UpdateProgramScheduleResult> updateResult = await updateHandler.Handle(
new UpdateProgramSchedule(
scheduleId,
"Pad Test",
KeepMultiPartEpisodesTogether: true,
TreatCollectionsAsShows: true,
ShuffleScheduleItems: false,
RandomStartPoint: false,
FixedStartTimeBehavior.Flexible,
PadToNearestMinute: null),
CancellationToken.None);
updateResult.IsRight.ShouldBeTrue();
await using (TvContext context = _db.CreateContext())
{
ProgramSchedule? schedule = await context.ProgramSchedules.FindAsync(scheduleId);
schedule.ShouldNotBeNull();
schedule.PadToNearestMinute.ShouldBeNull();
}
}
[Test]
public async Task Update_Should_Return_NotFoundError_When_Schedule_Missing()
{
var handler = new UpdateProgramScheduleHandler(_db.Factory, _worker);
Either<BaseError, UpdateProgramScheduleResult> result =
await handler.Handle(MakeUpdate(999, "Missing"), CancellationToken.None);
LeftOf(result).ShouldBeOfType<NotFoundError>();
}
[Test]
public async Task Delete_Should_Return_NotFoundError_When_Schedule_Missing()
{
var handler = new DeleteProgramScheduleHandler(_db.Factory);
Either<BaseError, Unit> result =
await handler.Handle(new DeleteProgramSchedule(999), CancellationToken.None);
LeftOf(result).ShouldBeOfType<NotFoundError>();
}
[Test]
public async Task AddItem_Should_Return_NotFoundError_When_Schedule_Missing()
{
var handler = new AddProgramScheduleItemHandler(_db.Factory, _worker);
Either<BaseError, ProgramScheduleItemViewModel> result =
await handler.Handle(MakeAdd(999, CollectionType.SearchQuery), CancellationToken.None);
LeftOf(result).ShouldBeOfType<NotFoundError>();
}
[Test]
public async Task AddItem_Should_Return_ValidationError_When_Collection_Type_Is_Invalid()
{
int scheduleId = await SeedSchedule();
var handler = new AddProgramScheduleItemHandler(_db.Factory, _worker);
Either<BaseError, ProgramScheduleItemViewModel> result =
await handler.Handle(MakeAdd(scheduleId, CollectionType.Collection), CancellationToken.None);
BaseError error = LeftOf(result);
error.ShouldNotBeOfType<NotFoundError>();
error.Value.ShouldContain("[Collection] is required");
}
[Test]
public async Task ReplaceItems_Should_Return_NotFoundError_When_Schedule_Missing()
{
var handler = new ReplaceProgramScheduleItemsHandler(_db.Factory, _worker);
Either<BaseError, IEnumerable<ProgramScheduleItemViewModel>> result =
await handler.Handle(
new ReplaceProgramScheduleItems(999, [MakeReplace(0)]),
CancellationToken.None);
LeftOf(result).ShouldBeOfType<NotFoundError>();
}
[Test]
public async Task DeleteItem_Should_Return_NotFoundError_When_Item_Missing()
{
int scheduleId = await SeedSchedule();
var handler = new DeleteProgramScheduleItemHandler(_db.Factory, _worker);
Either<BaseError, Unit> result =
await handler.Handle(new DeleteProgramScheduleItem(scheduleId, 999), CancellationToken.None);
LeftOf(result).ShouldBeOfType<NotFoundError>();
}
private async Task<int> SeedSchedule()
{
await using TvContext context = _db.CreateContext();
var schedule = new ProgramSchedule
{
Name = "Handlers",
Items = [],
Playouts = [],
ProgramScheduleAlternates = []
};
context.ProgramSchedules.Add(schedule);
await context.SaveChangesAsync();
return schedule.Id;
}
private static UpdateProgramSchedule MakeUpdate(int scheduleId, string name) =>
new(
scheduleId,
name,
KeepMultiPartEpisodesTogether: true,
TreatCollectionsAsShows: true,
ShuffleScheduleItems: false,
RandomStartPoint: false,
FixedStartTimeBehavior.Flexible,
PadToNearestMinute: null);
private static AddProgramScheduleItem MakeAdd(int scheduleId, CollectionType collectionType) =>
new(
scheduleId,
StartType.Dynamic,
StartTime: null,
FixedStartTimeBehavior: null,
PlayoutMode.One,
collectionType,
CollectionId: null,
MultiCollectionId: null,
SmartCollectionId: null,
RerunCollectionId: null,
MediaItemId: null,
PlaylistId: null,
SearchTitle: "News",
SearchQuery: "news",
PlaybackOrder.Shuffle,
MarathonGroupBy.None,
MarathonShuffleGroups: false,
MarathonShuffleItems: false,
MarathonBatchSize: null,
FillWithGroupMode.None,
MultipleMode.Count,
MultipleCount: "1",
PlayoutDuration: null,
TailMode.None,
DiscardToFillAttempts: null,
CustomTitle: null,
GuideMode.Normal,
PreRollFillerId: null,
MidRollFillerId: null,
PostRollFillerId: null,
TailFillerId: null,
FallbackFillerId: null,
WatermarkIds: [],
GraphicsElementIds: [],
PreferredAudioLanguageCode: null,
PreferredAudioTitle: null,
PreferredSubtitleLanguageCode: null,
SubtitleMode: null);
private static ReplaceProgramScheduleItem MakeReplace(int index)
{
AddProgramScheduleItem add = MakeAdd(1, CollectionType.SearchQuery);
return new ReplaceProgramScheduleItem(
null,
index,
add.StartType,
add.StartTime,
add.FixedStartTimeBehavior,
add.PlayoutMode,
add.CollectionType,
add.CollectionId,
add.MultiCollectionId,
add.SmartCollectionId,
add.RerunCollectionId,
add.MediaItemId,
add.PlaylistId,
add.SearchTitle,
add.SearchQuery,
add.PlaybackOrder,
add.MarathonGroupBy,
add.MarathonShuffleGroups,
add.MarathonShuffleItems,
add.MarathonBatchSize,
add.FillWithGroupMode,
add.MultipleMode,
add.MultipleCount,
add.PlayoutDuration,
add.TailMode,
add.DiscardToFillAttempts,
add.CustomTitle,
add.GuideMode,
add.PreRollFillerId,
add.MidRollFillerId,
add.PostRollFillerId,
add.TailFillerId,
add.FallbackFillerId,
add.WatermarkIds,
add.GraphicsElementIds,
add.PreferredAudioLanguageCode,
add.PreferredAudioTitle,
add.PreferredSubtitleLanguageCode,
add.SubtitleMode);
}
private static BaseError LeftOf<TR>(Either<BaseError, TR> either) =>
either.Match(Left: e => e, Right: _ => throw new AssertionException("Expected a Left result"));
}