mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2955 lines
124 KiB
2955 lines
124 KiB
using Destructurama; |
|
using ErsatzTV.Core.Domain; |
|
using ErsatzTV.Core.Domain.Filler; |
|
using ErsatzTV.Core.Interfaces.Metadata; |
|
using ErsatzTV.Core.Interfaces.Repositories; |
|
using ErsatzTV.Core.Interfaces.Scheduling; |
|
using ErsatzTV.Core.Scheduling; |
|
using ErsatzTV.Core.Tests.Fakes; |
|
using FluentAssertions; |
|
using Microsoft.Extensions.Logging; |
|
using Moq; |
|
using NUnit.Framework; |
|
using Serilog; |
|
|
|
namespace ErsatzTV.Core.Tests.Scheduling; |
|
|
|
[TestFixture] |
|
public class PlayoutBuilderTests |
|
{ |
|
private readonly ILogger<PlayoutBuilder> _logger; |
|
|
|
public PlayoutBuilderTests() |
|
{ |
|
Log.Logger = new LoggerConfiguration() |
|
.MinimumLevel.Debug() |
|
.WriteTo.Console() |
|
.Destructure.UsingAttributes() |
|
.CreateLogger(); |
|
|
|
ILoggerFactory loggerFactory = new LoggerFactory().AddSerilog(Log.Logger); |
|
|
|
_logger = loggerFactory?.CreateLogger<PlayoutBuilder>(); |
|
} |
|
|
|
[TestFixture] |
|
public class NewPlayout : PlayoutBuilderTests |
|
{ |
|
[Test] |
|
[Timeout(2000)] |
|
public async Task OnlyZeroDurationItem_Should_Abort() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.Zero, DateTime.Today) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Random); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset); |
|
|
|
result.Items.Should().BeEmpty(); |
|
} |
|
|
|
[Test] |
|
public async Task ZeroDurationItem_Should_BeSkipped() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.Zero, DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Random); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(2); |
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
[Timeout(2000)] |
|
public async Task OnlyFileNotFoundItem_Should_Abort() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today) |
|
}; |
|
|
|
mediaItems[0].State = MediaItemState.FileNotFound; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
configRepo.Setup( |
|
repo => repo.GetValue<bool>( |
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key))) |
|
.ReturnsAsync(Some(true)); |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset); |
|
|
|
configRepo.Verify(); |
|
|
|
result.Items.Should().BeEmpty(); |
|
} |
|
|
|
[Test] |
|
public async Task FileNotFoundItem_Should_BeSkipped() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
mediaItems[0].State = MediaItemState.FileNotFound; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
configRepo.Setup( |
|
repo => repo.GetValue<bool>( |
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key))) |
|
.ReturnsAsync(Some(true)); |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
configRepo.Verify(); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(2); |
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
[Timeout(2000)] |
|
public async Task OnlyUnavailableItem_Should_Abort() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today) |
|
}; |
|
|
|
mediaItems[0].State = MediaItemState.Unavailable; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
configRepo.Setup( |
|
repo => repo.GetValue<bool>( |
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key))) |
|
.ReturnsAsync(Some(true)); |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset); |
|
|
|
configRepo.Verify(); |
|
|
|
result.Items.Should().BeEmpty(); |
|
} |
|
|
|
[Test] |
|
public async Task UnavailableItem_Should_BeSkipped() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
mediaItems[0].State = MediaItemState.Unavailable; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
configRepo.Setup( |
|
repo => repo.GetValue<bool>( |
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key))) |
|
.ReturnsAsync(Some(true)); |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
configRepo.Verify(); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(2); |
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task FileNotFound_Should_NotBeSkippedIfConfigured() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
mediaItems[0].State = MediaItemState.FileNotFound; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
configRepo.Setup( |
|
repo => repo.GetValue<bool>( |
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key))) |
|
.ReturnsAsync(Some(false)); |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task Unavailable_Should_NotBeSkippedIfConfigured() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
mediaItems[0].State = MediaItemState.Unavailable; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
configRepo.Setup( |
|
repo => repo.GetValue<bool>( |
|
It.Is<ConfigElementKey>(k => k.Key == ConfigElementKey.PlayoutSkipMissingItems.Key))) |
|
.ReturnsAsync(Some(false)); |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Random, configRepo); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task InitialFlood_Should_StartAtMidnight() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Random); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items.Head().FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task InitialFlood_Should_StartAtMidnight_With_LateStart() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Random); |
|
DateTimeOffset start = HoursAfterMidnight(1); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(2); |
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[1].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(12)); |
|
} |
|
|
|
[Test] |
|
public async Task ChronologicalContent_Should_CreateChronologicalItems() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(4); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(4); |
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(4)); |
|
} |
|
|
|
[Test] |
|
public async Task ChronologicalFlood_Should_AnchorAndMaintainExistingPlayout() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(1); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
|
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(1); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
result2.Items.Count.Should().Be(2); |
|
result2.Items.Last().StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result2.Items.Last().MediaItemId.Should().Be(2); |
|
|
|
result2.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(12)); |
|
result2.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
} |
|
|
|
[Test] |
|
public async Task ChronologicalFlood_Should_AnchorAndReturnNewPlayoutItems() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(1); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(1); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(12); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
result2.Items.Count.Should().Be(3); |
|
result2.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result2.Items[1].MediaItemId.Should().Be(2); |
|
result2.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result2.Items[2].MediaItemId.Should().Be(1); |
|
|
|
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(18)); |
|
result2.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
} |
|
|
|
[Test] |
|
public async Task ShuffleFloodReset_Should_IgnoreAnchors() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), |
|
TestMovie(3, TimeSpan.FromHours(1), DateTime.Today.AddHours(2)), |
|
TestMovie(4, TimeSpan.FromHours(1), DateTime.Today.AddHours(3)), |
|
TestMovie(5, TimeSpan.FromHours(1), DateTime.Today.AddHours(4)), |
|
TestMovie(6, TimeSpan.FromHours(1), DateTime.Today.AddHours(5)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
|
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
|
|
int firstSeedValue = result.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(0); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Reset, start2, finish2); |
|
|
|
result2.Items.Count.Should().Be(6); |
|
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
|
|
result2.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
|
|
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
firstSeedValue.Should().NotBe(secondSeedValue); |
|
} |
|
|
|
[Test] |
|
public async Task ContinuePlayout_ShuffleFlood_Should_MaintainRandomSeed() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), |
|
TestMovie(3, TimeSpan.FromHours(1), DateTime.Today.AddHours(3)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Seed.Should().BeGreaterThan(0); |
|
|
|
int firstSeedValue = result.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(0); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
firstSeedValue.Should().Be(secondSeedValue); |
|
|
|
result2.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task FloodContent_Should_FloodAroundFixedContent_One() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = null, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemOne |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(3), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(5); |
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[3].MediaItemId.Should().Be(3); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task FloodContent_Should_FloodAroundFixedContent_One_Multiple_Days() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = null, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemOne |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(3), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(30); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(28); |
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[3].MediaItemId.Should().Be(3); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[5].MediaItemId.Should().Be(1); |
|
result.Items[6].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(7)); |
|
result.Items[6].MediaItemId.Should().Be(2); |
|
result.Items[7].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(8)); |
|
result.Items[7].MediaItemId.Should().Be(1); |
|
result.Items[8].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(9)); |
|
result.Items[8].MediaItemId.Should().Be(2); |
|
result.Items[9].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(10)); |
|
result.Items[9].MediaItemId.Should().Be(1); |
|
result.Items[10].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(11)); |
|
result.Items[10].MediaItemId.Should().Be(2); |
|
result.Items[11].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[11].MediaItemId.Should().Be(1); |
|
result.Items[12].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(13)); |
|
result.Items[12].MediaItemId.Should().Be(2); |
|
result.Items[13].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(14)); |
|
result.Items[13].MediaItemId.Should().Be(1); |
|
result.Items[14].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(15)); |
|
result.Items[14].MediaItemId.Should().Be(2); |
|
result.Items[15].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(16)); |
|
result.Items[15].MediaItemId.Should().Be(1); |
|
result.Items[16].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(17)); |
|
result.Items[16].MediaItemId.Should().Be(2); |
|
result.Items[17].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[17].MediaItemId.Should().Be(1); |
|
result.Items[18].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(19)); |
|
result.Items[18].MediaItemId.Should().Be(2); |
|
result.Items[19].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(20)); |
|
result.Items[19].MediaItemId.Should().Be(1); |
|
result.Items[20].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(21)); |
|
result.Items[20].MediaItemId.Should().Be(2); |
|
result.Items[21].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(22)); |
|
result.Items[21].MediaItemId.Should().Be(1); |
|
result.Items[22].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(23)); |
|
result.Items[22].MediaItemId.Should().Be(2); |
|
result.Items[23].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[23].MediaItemId.Should().Be(1); |
|
result.Items[24].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[24].MediaItemId.Should().Be(2); |
|
result.Items[25].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[25].MediaItemId.Should().Be(1); |
|
result.Items[26].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[26].MediaItemId.Should().Be(3); |
|
result.Items[27].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5)); |
|
result.Items[27].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(30)); |
|
} |
|
|
|
[Test] |
|
public async Task FloodContent_Should_FloodAroundFixedContent_Multiple() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = null, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(3), |
|
Count = 2, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(7); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[3].MediaItemId.Should().Be(3); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5)); |
|
result.Items[4].MediaItemId.Should().Be(4); |
|
|
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[5].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(7)); |
|
} |
|
|
|
[Test] |
|
public async Task FloodContent_Should_FloodWithFixedStartTime() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = TimeSpan.FromHours(7), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemOne |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(12), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(24); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(7)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(8)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(9)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(10)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(11)); |
|
result.Items[4].MediaItemId.Should().Be(1); |
|
|
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[5].MediaItemId.Should().Be(3); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(31)); |
|
} |
|
|
|
[Test] |
|
public async Task ContinuePlayout_FloodContent_Should_FloodWithFixedStartTime_FromAnchor() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = TimeSpan.FromHours(7), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemOne |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(12), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(9).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
InFlood = true |
|
}, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(32); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); |
|
|
|
result.Items.Count.Should().Be(5); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(9)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(10)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(11)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[3].MediaItemId.Should().Be(3); |
|
|
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(7)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.InFlood.Should().BeTrue(); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(32)); |
|
} |
|
|
|
[Test] |
|
public async Task FloodContent_Should_FloodAroundFixedContent_DurationWithoutOfflineTail() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = null, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(2), |
|
PlayoutDuration = TimeSpan.FromHours(2), |
|
TailMode = TailMode.None, // immediately continue |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(7); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
|
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(3); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2.75)); |
|
result.Items[3].MediaItemId.Should().Be(1); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3.75)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
|
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4.75)); |
|
result.Items[5].MediaItemId.Should().Be(1); |
|
result.Items[6].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5.75)); |
|
result.Items[6].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6.75)); |
|
} |
|
|
|
[Test] |
|
public async Task MultipleContent_Should_WrapAroundDynamicContent_DurationWithoutOfflineTail() |
|
{ |
|
var multipleCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Multiple Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var dynamicCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Dynamic Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(multipleCollection.Id, multipleCollection.MediaItems.ToList()), |
|
(dynamicCollection.Id, dynamicCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = multipleCollection, |
|
CollectionId = multipleCollection.Id, |
|
StartTime = null, |
|
Count = 2, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = dynamicCollection, |
|
CollectionId = dynamicCollection.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(2), |
|
TailMode = TailMode.None, // immediately continue |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
|
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(3); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2.75)); |
|
result.Items[3].MediaItemId.Should().Be(1); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3.75)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
|
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4.75)); |
|
result.Items[5].MediaItemId.Should().Be(4); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6.25)); |
|
} |
|
|
|
[Test] |
|
public async Task ContinuePlayout_Alternating_MultipleContent_Should_Maintain_Counts() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Multiple Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Multiple Items 2", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
Count = 3, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
StartTime = null, |
|
Count = 3, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(1).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
MultipleRemaining = 2 |
|
}, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(5); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); |
|
|
|
result.Items.Count.Should().Be(4); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[1].MediaItemId.Should().Be(1); |
|
|
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[2].MediaItemId.Should().Be(2); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(1); |
|
result.Anchor.MultipleRemaining.Should().Be(1); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(5)); |
|
} |
|
|
|
[Test] |
|
public async Task Auto_Zero_MultipleCount() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Multiple Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(3, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Multiple Items 2", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(5, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
Count = 0, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
StartTime = null, |
|
Count = 0, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(5); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(5); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(3); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[3].MediaItemId.Should().Be(4); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); |
|
result.Items[4].MediaItemId.Should().Be(5); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(0); |
|
result.Anchor.MultipleRemaining.Should().BeNull(); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(5)); |
|
} |
|
|
|
[Test] |
|
public async Task ContinuePlayout_Alternating_Duration_Should_Maintain_Duration() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Duration Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Duration Items 2", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(3), |
|
TailMode = TailMode.None, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(3), |
|
TailMode = TailMode.None, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(1).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
DurationFinish = HoursAfterMidnight(3).UtcDateTime |
|
}, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(5); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); |
|
|
|
result.Items.Count.Should().Be(4); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[1].MediaItemId.Should().Be(1); |
|
|
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[2].MediaItemId.Should().Be(2); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(1); |
|
result.Anchor.DurationFinish.Should().Be(HoursAfterMidnight(6).UtcDateTime); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(5)); |
|
} |
|
|
|
[Test] |
|
public async Task Alternating_Duration_With_Filler_Should_Alternate_Schedule_Items() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Duration Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromMinutes(55), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Duration Items 2", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(2, TimeSpan.FromMinutes(55), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionThree = new Collection |
|
{ |
|
Id = 3, |
|
Name = "Filler Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromMinutes(5), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()), |
|
(collectionThree.Id, collectionThree.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(3), |
|
PlaybackOrder = PlaybackOrder.Chronological, |
|
TailMode = TailMode.Filler, |
|
TailFiller = new FillerPreset |
|
{ |
|
FillerKind = FillerKind.Tail, |
|
Collection = collectionThree, |
|
CollectionId = collectionThree.Id |
|
} |
|
}, |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(3), |
|
PlaybackOrder = PlaybackOrder.Chronological, |
|
TailMode = TailMode.Filler, |
|
TailFiller = new FillerPreset |
|
{ |
|
FillerKind = FillerKind.Tail, |
|
Collection = collectionThree, |
|
CollectionId = collectionThree.Id |
|
} |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(12); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromMinutes(0)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromMinutes(55)); |
|
result.Items[1].MediaItemId.Should().Be(1); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(new TimeSpan(1, 50, 0)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(new TimeSpan(2, 45, 0)); |
|
result.Items[3].MediaItemId.Should().Be(3); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(new TimeSpan(2, 50, 0)); |
|
result.Items[4].MediaItemId.Should().Be(3); |
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(new TimeSpan(2, 55, 0)); |
|
result.Items[5].MediaItemId.Should().Be(3); |
|
|
|
result.Items[6].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[6].MediaItemId.Should().Be(2); |
|
result.Items[7].StartOffset.TimeOfDay.Should().Be(new TimeSpan(3, 55, 0)); |
|
result.Items[7].MediaItemId.Should().Be(2); |
|
result.Items[8].StartOffset.TimeOfDay.Should().Be(new TimeSpan(4, 50, 0)); |
|
result.Items[8].MediaItemId.Should().Be(2); |
|
|
|
result.Items[9].StartOffset.TimeOfDay.Should().Be(new TimeSpan(5, 45, 0)); |
|
result.Items[9].MediaItemId.Should().Be(3); |
|
result.Items[10].StartOffset.TimeOfDay.Should().Be(new TimeSpan(5, 50, 0)); |
|
result.Items[10].MediaItemId.Should().Be(3); |
|
result.Items[11].StartOffset.TimeOfDay.Should().Be(new TimeSpan(5, 55, 0)); |
|
result.Items[11].MediaItemId.Should().Be(3); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(0); |
|
result.Anchor.DurationFinish.Should().BeNull(); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task Multiple_With_Filler_Should_Keep_Filler_After_End_Of_Playout() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Duration Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromMinutes(61), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Filler Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(2, TimeSpan.FromMinutes(4), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
Count = 1, |
|
PlaybackOrder = PlaybackOrder.Chronological, |
|
PostRollFiller = new FillerPreset |
|
{ |
|
FillerKind = FillerKind.PostRoll, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
FillerMode = FillerMode.Count, |
|
Count = 1 |
|
} |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(1); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(2); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromMinutes(0)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromMinutes(61)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(0); |
|
result.Anchor.DurationFinish.Should().BeNull(); |
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddMinutes(65)); |
|
} |
|
|
|
[Test] |
|
public async Task Duration_Should_Skip_Items_That_Are_Too_Long() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Duration Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = |
|
new FakeMediaCollectionRepository(Map((collectionOne.Id, collectionOne.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(1), |
|
PlaybackOrder = PlaybackOrder.Chronological, |
|
TailMode = TailMode.None |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); |
|
result.Items[0].MediaItemId.Should().Be(2); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[1].MediaItemId.Should().Be(4); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[2].MediaItemId.Should().Be(2); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[3].MediaItemId.Should().Be(4); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5)); |
|
result.Items[5].MediaItemId.Should().Be(4); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(0); |
|
result.Anchor.DurationFinish.Should().BeNull(); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(6)); |
|
} |
|
|
|
[Test] |
|
public async Task Two_Day_Playout_Should_Create_Date_Anchors_For_Midnight() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), new DateTime(2002, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(6), new DateTime(2003, 1, 1)), |
|
TestMovie(3, TimeSpan.FromHours(6), new DateTime(2004, 1, 1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromDays(2); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(8); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[1].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[2].MediaItemId.Should().Be(3); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[2].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[3].MediaItemId.Should().Be(1); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[3].FinishOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[4].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[5].MediaItemId.Should().Be(3); |
|
result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[5].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[6].MediaItemId.Should().Be(1); |
|
result.Items[6].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[6].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[7].MediaItemId.Should().Be(2); |
|
result.Items[7].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[7].FinishOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
|
|
result.ProgramScheduleAnchors.Count.Should().Be(2); |
|
result.ProgramScheduleAnchors.Count(a => a.EnumeratorState.Index == 4 % 3).Should().Be(1); |
|
result.ProgramScheduleAnchors.Count(a => a.EnumeratorState.Index == 8 % 3).Should().Be(1); |
|
|
|
int seed = result.ProgramScheduleAnchors[0].EnumeratorState.Seed; |
|
result.ProgramScheduleAnchors.All(a => a.EnumeratorState.Seed == seed).Should().BeTrue(); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddDays(2)); |
|
} |
|
} |
|
|
|
[TestFixture] |
|
public class ResetPlayout : PlayoutBuilderTests |
|
{ |
|
[Test] |
|
public async Task ShuffleFlood_Should_IgnoreAnchors() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), |
|
TestMovie(3, TimeSpan.FromHours(1), DateTime.Today.AddHours(2)), |
|
TestMovie(4, TimeSpan.FromHours(1), DateTime.Today.AddHours(3)), |
|
TestMovie(5, TimeSpan.FromHours(1), DateTime.Today.AddHours(4)), |
|
TestMovie(6, TimeSpan.FromHours(1), DateTime.Today.AddHours(5)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
|
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
|
|
int firstSeedValue = result.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(0); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Reset, start2, finish2); |
|
|
|
result2.Items.Count.Should().Be(6); |
|
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
|
|
result2.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
|
|
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
firstSeedValue.Should().NotBe(secondSeedValue); |
|
} |
|
} |
|
|
|
[TestFixture] |
|
public class RefreshPlayout : PlayoutBuilderTests |
|
{ |
|
[Test] |
|
public async Task Two_Day_Playout_Should_Refresh_From_Midnight_Anchor() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Duration Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), new DateTime(2002, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(6), new DateTime(2003, 1, 1)), |
|
TestMovie(3, TimeSpan.FromHours(6), new DateTime(2004, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = |
|
new FakeMediaCollectionRepository(Map((collectionOne.Id, collectionOne.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
|
|
// this should be ignored |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(1).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
DurationFinish = HoursAfterMidnight(3).UtcDateTime |
|
}, |
|
|
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
playout.ProgramScheduleAnchors.Add( |
|
new PlayoutProgramScheduleAnchor |
|
{ |
|
AnchorDate = HoursAfterMidnight(24).UtcDateTime, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
CollectionType = ProgramScheduleItemCollectionType.Collection, |
|
EnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 1, |
|
Seed = 12345 |
|
}, |
|
Playout = playout |
|
}); |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(24); |
|
DateTimeOffset finish = start + TimeSpan.FromDays(1); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Refresh, start, finish); |
|
|
|
result.Items.Count.Should().Be(4); |
|
result.Items[0].MediaItemId.Should().Be(2); |
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
result.Items[0].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[1].MediaItemId.Should().Be(3); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result.Items[1].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[2].FinishOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
result.Items[3].FinishOffset.TimeOfDay.Should().Be(TimeSpan.Zero); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(48)); |
|
} |
|
} |
|
|
|
[TestFixture] |
|
public class ContinuePlayout : PlayoutBuilderTests |
|
{ |
|
[Test] |
|
public async Task ChronologicalFlood_Should_AnchorAndMaintainExistingPlayout() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(1); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
|
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(1); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
result2.Items.Count.Should().Be(2); |
|
result2.Items.Last().StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result2.Items.Last().MediaItemId.Should().Be(2); |
|
|
|
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(12)); |
|
result2.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
} |
|
|
|
[Test] |
|
public async Task ChronologicalFlood_Should_AnchorAndReturnNewPlayoutItems() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(1); |
|
result.Items.Head().MediaItemId.Should().Be(1); |
|
|
|
result.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(6)); |
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(1); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(12); |
|
|
|
Playout result2 = await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
result2.Items.Count.Should().Be(3); |
|
result2.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
result2.Items[1].MediaItemId.Should().Be(2); |
|
result2.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result2.Items[2].MediaItemId.Should().Be(1); |
|
|
|
result2.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(18)); |
|
result2.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
} |
|
|
|
[Test] |
|
public async Task ChronologicalFlood_Should_AnchorAndReturnNewPlayoutItems_MultiDay() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(6), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(6), DateTime.Today.AddHours(1)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForItems(mediaItems, PlaybackOrder.Chronological); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromDays(1); |
|
|
|
await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
playout.Items.Count.Should().Be(4); |
|
playout.Items.Map(i => i.MediaItemId).ToList().Should().Equal(1, 2, 1, 2); |
|
|
|
playout.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddDays(1)); |
|
playout.ProgramScheduleAnchors.Count.Should().Be(1); |
|
playout.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
|
|
PlayoutProgramScheduleAnchor headAnchor = playout.ProgramScheduleAnchors.Head(); |
|
|
|
// throw in a detractor anchor - playout builder should prioritize the "continue" anchor |
|
playout.ProgramScheduleAnchors.Insert( |
|
0, |
|
new PlayoutProgramScheduleAnchor |
|
{ |
|
Id = headAnchor.Id + 1, |
|
Collection = headAnchor.Collection, |
|
CollectionId = headAnchor.CollectionId, |
|
Playout = playout, |
|
PlayoutId = playout.Id, |
|
AnchorDate = DateTime.Today.ToUniversalTime(), |
|
CollectionType = headAnchor.CollectionType, |
|
EnumeratorState = new CollectionEnumeratorState |
|
{ Index = headAnchor.EnumeratorState.Index + 1, Seed = headAnchor.EnumeratorState.Seed }, |
|
MediaItem = headAnchor.MediaItem, |
|
MediaItemId = headAnchor.MediaItemId, |
|
MultiCollection = headAnchor.MultiCollection, |
|
MultiCollectionId = headAnchor.MultiCollectionId, |
|
SmartCollection = headAnchor.SmartCollection, |
|
SmartCollectionId = headAnchor.SmartCollectionId |
|
}); |
|
|
|
// continue 1h later |
|
DateTimeOffset start2 = HoursAfterMidnight(1); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(1); |
|
|
|
await builder.Build(playout, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
playout.Items.Count.Should().Be(5); |
|
playout.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); |
|
playout.Items[0].MediaItemId.Should().Be(1); |
|
playout.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
playout.Items[1].MediaItemId.Should().Be(2); |
|
playout.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
playout.Items[2].MediaItemId.Should().Be(1); |
|
playout.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
playout.Items[3].MediaItemId.Should().Be(2); |
|
playout.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); |
|
playout.Items[4].MediaItemId.Should().Be(1); |
|
|
|
playout.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(30)); |
|
|
|
playout.ProgramScheduleAnchors.Count.Should().Be(2); |
|
playout.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
|
|
// continue 1h later |
|
DateTimeOffset start3 = HoursAfterMidnight(2); |
|
DateTimeOffset finish3 = start3 + TimeSpan.FromDays(1); |
|
|
|
await builder.Build(playout, PlayoutBuildMode.Continue, start3, finish3); |
|
|
|
playout.Items.Count.Should().Be(5); |
|
playout.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); |
|
playout.Items[0].MediaItemId.Should().Be(1); |
|
playout.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6)); |
|
playout.Items[1].MediaItemId.Should().Be(2); |
|
playout.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
playout.Items[2].MediaItemId.Should().Be(1); |
|
playout.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(18)); |
|
playout.Items[3].MediaItemId.Should().Be(2); |
|
playout.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(0)); |
|
playout.Items[4].MediaItemId.Should().Be(1); |
|
|
|
playout.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddHours(30)); |
|
|
|
playout.ProgramScheduleAnchors.Count.Should().Be(2); |
|
playout.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(1); |
|
} |
|
|
|
[Test] |
|
public async Task ShuffleFlood_Should_MaintainRandomSeed() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), |
|
TestMovie(3, TimeSpan.FromHours(1), DateTime.Today.AddHours(3)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
result.ProgramScheduleAnchors.Count.Should().Be(1); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Seed.Should().BeGreaterThan(0); |
|
result.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
|
|
int firstSeedValue = result.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(0); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
int secondSeedValue = result2.ProgramScheduleAnchors.Head().EnumeratorState.Seed; |
|
|
|
firstSeedValue.Should().Be(secondSeedValue); |
|
|
|
result2.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); |
|
} |
|
|
|
[Test] |
|
public async Task ShuffleFlood_Should_MaintainRandomSeed_MultipleDays() |
|
{ |
|
var mediaItems = new List<MediaItem>(); |
|
for (int i = 1; i <= 25; i++) |
|
{ |
|
mediaItems.Add(TestMovie(i, TimeSpan.FromMinutes(55), DateTime.Today.AddHours(i))); |
|
} |
|
|
|
(PlayoutBuilder builder, Playout playout) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5); |
|
DateTimeOffset finish = start + TimeSpan.FromDays(2); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(53); |
|
result.ProgramScheduleAnchors.Count.Should().Be(2); |
|
|
|
result.ProgramScheduleAnchors.All(x => x.AnchorDate is not null).Should().BeTrue(); |
|
PlayoutProgramScheduleAnchor lastCheckpoint = result.ProgramScheduleAnchors |
|
.OrderByDescending(a => a.AnchorDate ?? DateTime.MinValue) |
|
.First(); |
|
lastCheckpoint.EnumeratorState.Seed.Should().BeGreaterThan(0); |
|
lastCheckpoint.EnumeratorState.Index.Should().Be(3); |
|
|
|
// we need to mess up the ordering to trigger the problematic behavior |
|
// this simulates the way the rows are loaded with EF |
|
PlayoutProgramScheduleAnchor oldest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate).Last(); |
|
PlayoutProgramScheduleAnchor newest = result.ProgramScheduleAnchors.OrderByDescending(a => a.AnchorDate).First(); |
|
|
|
result.ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor> |
|
{ |
|
oldest, |
|
newest |
|
}; |
|
|
|
int firstSeedValue = lastCheckpoint.EnumeratorState.Seed; |
|
|
|
DateTimeOffset start2 = start.AddHours(1); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2); |
|
|
|
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
PlayoutProgramScheduleAnchor continueAnchor = |
|
result2.ProgramScheduleAnchors.First(x => x.AnchorDate is null); |
|
int secondSeedValue = continueAnchor.EnumeratorState.Seed; |
|
|
|
// the continue anchor should have the same seed as the most recent (last) checkpoint from the first run |
|
firstSeedValue.Should().Be(secondSeedValue); |
|
} |
|
|
|
[Test] |
|
public async Task ShuffleFlood_MultipleSmartCollections_Should_MaintainRandomSeed() |
|
{ |
|
var mediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), DateTime.Today), |
|
TestMovie(2, TimeSpan.FromHours(1), DateTime.Today.AddHours(1)), |
|
TestMovie(3, TimeSpan.FromHours(1), DateTime.Today.AddHours(3)) |
|
}; |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForSmartCollectionItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(6); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(6); |
|
result.ProgramScheduleAnchors.Count.Should().Be(2); |
|
PlayoutProgramScheduleAnchor primaryAnchor = result.ProgramScheduleAnchors.First(a => a.SmartCollectionId == 1); |
|
primaryAnchor.EnumeratorState.Seed.Should().BeGreaterThan(0); |
|
primaryAnchor.EnumeratorState.Index.Should().Be(0); |
|
|
|
int firstSeedValue = primaryAnchor.EnumeratorState.Seed; |
|
|
|
DateTimeOffset start2 = HoursAfterMidnight(0); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6); |
|
|
|
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
primaryAnchor = result2.ProgramScheduleAnchors.First(a => a.SmartCollectionId == 1); |
|
int secondSeedValue = primaryAnchor.EnumeratorState.Seed; |
|
|
|
firstSeedValue.Should().Be(secondSeedValue); |
|
|
|
primaryAnchor.EnumeratorState.Index.Should().Be(0); |
|
} |
|
|
|
[Test] |
|
public async Task ShuffleFlood_MultipleSmartCollections_Should_MaintainRandomSeed_MultipleDays() |
|
{ |
|
var mediaItems = new List<MediaItem>(); |
|
for (int i = 1; i <= 100; i++) |
|
{ |
|
mediaItems.Add(TestMovie(i, TimeSpan.FromMinutes(55), DateTime.Today.AddHours(i))); |
|
} |
|
|
|
(PlayoutBuilder builder, Playout playout) = |
|
TestDataFloodForSmartCollectionItems(mediaItems, PlaybackOrder.Shuffle); |
|
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5); |
|
DateTimeOffset finish = start + TimeSpan.FromDays(2); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Reset, start, finish); |
|
|
|
result.Items.Count.Should().Be(53); |
|
result.ProgramScheduleAnchors.Count.Should().Be(4); |
|
|
|
result.ProgramScheduleAnchors.All(x => x.AnchorDate is not null).Should().BeTrue(); |
|
PlayoutProgramScheduleAnchor lastCheckpoint = result.ProgramScheduleAnchors |
|
.Filter(psa => psa.SmartCollectionId == 1) |
|
.OrderByDescending(a => a.AnchorDate ?? DateTime.MinValue) |
|
.First(); |
|
lastCheckpoint.EnumeratorState.Seed.Should().BeGreaterThan(0); |
|
lastCheckpoint.EnumeratorState.Index.Should().Be(53); |
|
|
|
int firstSeedValue = lastCheckpoint.EnumeratorState.Seed; |
|
|
|
for (var i = 1; i < 20; i++) |
|
{ |
|
DateTimeOffset start2 = start.AddHours(i); |
|
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(2); |
|
|
|
Playout result2 = await builder.Build(result, PlayoutBuildMode.Continue, start2, finish2); |
|
|
|
PlayoutProgramScheduleAnchor continueAnchor = |
|
result2.ProgramScheduleAnchors |
|
.Filter(psa => psa.SmartCollectionId == 1) |
|
.First(x => x.AnchorDate is null); |
|
int secondSeedValue = continueAnchor.EnumeratorState.Seed; |
|
|
|
// the continue anchor should have the same seed as the most recent (last) checkpoint from the first run |
|
firstSeedValue.Should().Be(secondSeedValue); |
|
} |
|
} |
|
|
|
[Test] |
|
public async Task FloodContent_Should_FloodWithFixedStartTime_FromAnchor() |
|
{ |
|
var floodCollection = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Flood Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)), |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1)) |
|
} |
|
}; |
|
|
|
var fixedCollection = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Fixed Items", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)), |
|
TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(floodCollection.Id, floodCollection.MediaItems.ToList()), |
|
(fixedCollection.Id, fixedCollection.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = floodCollection, |
|
CollectionId = floodCollection.Id, |
|
StartTime = TimeSpan.FromHours(7), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemOne |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = fixedCollection, |
|
CollectionId = fixedCollection.Id, |
|
StartTime = TimeSpan.FromHours(12), |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(9).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
InFlood = true |
|
}, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(32); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); |
|
|
|
result.Items.Count.Should().Be(5); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(9)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(10)); |
|
result.Items[1].MediaItemId.Should().Be(2); |
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(11)); |
|
result.Items[2].MediaItemId.Should().Be(1); |
|
|
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(12)); |
|
result.Items[3].MediaItemId.Should().Be(3); |
|
|
|
result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(7)); |
|
result.Items[4].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.InFlood.Should().BeTrue(); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(32)); |
|
} |
|
|
|
[Test] |
|
public async Task Alternating_MultipleContent_Should_Maintain_Counts() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Multiple Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Multiple Items 2", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
Count = 3, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemMultiple |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
StartTime = null, |
|
Count = 3, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(1).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
MultipleRemaining = 2 |
|
}, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(5); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); |
|
|
|
result.Items.Count.Should().Be(4); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[1].MediaItemId.Should().Be(1); |
|
|
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[2].MediaItemId.Should().Be(2); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(1); |
|
result.Anchor.MultipleRemaining.Should().Be(1); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(5)); |
|
} |
|
|
|
[Test] |
|
public async Task Alternating_Duration_Should_Maintain_Duration() |
|
{ |
|
var collectionOne = new Collection |
|
{ |
|
Id = 1, |
|
Name = "Duration Items 1", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var collectionTwo = new Collection |
|
{ |
|
Id = 2, |
|
Name = "Duration Items 2", |
|
MediaItems = new List<MediaItem> |
|
{ |
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)) |
|
} |
|
}; |
|
|
|
var fakeRepository = new FakeMediaCollectionRepository( |
|
Map( |
|
(collectionOne.Id, collectionOne.MediaItems.ToList()), |
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList()))); |
|
|
|
var items = new List<ProgramScheduleItem> |
|
{ |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
Collection = collectionOne, |
|
CollectionId = collectionOne.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(3), |
|
TailMode = TailMode.None, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
}, |
|
new ProgramScheduleItemDuration |
|
{ |
|
Id = 2, |
|
Index = 2, |
|
Collection = collectionTwo, |
|
CollectionId = collectionTwo.Id, |
|
StartTime = null, |
|
PlayoutDuration = TimeSpan.FromHours(3), |
|
TailMode = TailMode.None, |
|
PlaybackOrder = PlaybackOrder.Chronological |
|
} |
|
}; |
|
|
|
var playout = new Playout |
|
{ |
|
ProgramSchedule = new ProgramSchedule |
|
{ |
|
Items = items |
|
}, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Anchor = new PlayoutAnchor |
|
{ |
|
NextStart = HoursAfterMidnight(1).UtcDateTime, |
|
ScheduleItemsEnumeratorState = new CollectionEnumeratorState |
|
{ |
|
Index = 0, |
|
Seed = 1 |
|
}, |
|
DurationFinish = HoursAfterMidnight(3).UtcDateTime |
|
}, |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
var configRepo = new Mock<IConfigElementRepository>(); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
fakeRepository, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
DateTimeOffset start = HoursAfterMidnight(0); |
|
DateTimeOffset finish = start + TimeSpan.FromHours(5); |
|
|
|
Playout result = await builder.Build(playout, PlayoutBuildMode.Continue, start, finish); |
|
|
|
result.Items.Count.Should().Be(4); |
|
|
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1)); |
|
result.Items[0].MediaItemId.Should().Be(1); |
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2)); |
|
result.Items[1].MediaItemId.Should().Be(1); |
|
|
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3)); |
|
result.Items[2].MediaItemId.Should().Be(2); |
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4)); |
|
result.Items[3].MediaItemId.Should().Be(2); |
|
|
|
result.Anchor.ScheduleItemsEnumeratorState.Index.Should().Be(1); |
|
result.Anchor.DurationFinish.Should().Be(HoursAfterMidnight(6).UtcDateTime); |
|
result.Anchor.NextStartOffset.Should().Be(HoursAfterMidnight(5)); |
|
} |
|
} |
|
|
|
private static DateTimeOffset HoursAfterMidnight(int hours) |
|
{ |
|
DateTimeOffset now = DateTimeOffset.Now; |
|
return now - now.TimeOfDay + TimeSpan.FromHours(hours); |
|
} |
|
|
|
private static ProgramScheduleItem Flood(Collection mediaCollection, PlaybackOrder playbackOrder) => |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
CollectionType = ProgramScheduleItemCollectionType.Collection, |
|
Collection = mediaCollection, |
|
CollectionId = mediaCollection.Id, |
|
StartTime = null, |
|
PlaybackOrder = playbackOrder, |
|
}; |
|
|
|
private static ProgramScheduleItem Flood( |
|
SmartCollection smartCollection, |
|
SmartCollection fillerCollection, |
|
PlaybackOrder playbackOrder) => |
|
new ProgramScheduleItemFlood |
|
{ |
|
Id = 1, |
|
Index = 1, |
|
CollectionType = ProgramScheduleItemCollectionType.SmartCollection, |
|
SmartCollection = smartCollection, |
|
SmartCollectionId = smartCollection.Id, |
|
StartTime = null, |
|
PlaybackOrder = playbackOrder, |
|
FallbackFiller = new FillerPreset |
|
{ |
|
Id = 1, |
|
CollectionType = ProgramScheduleItemCollectionType.SmartCollection, |
|
SmartCollection = fillerCollection, |
|
SmartCollectionId = fillerCollection.Id, |
|
FillerKind = FillerKind.Fallback |
|
} |
|
}; |
|
|
|
private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) => |
|
new() |
|
{ |
|
Id = id, |
|
MovieMetadata = new List<MovieMetadata> { new() { ReleaseDate = aired } }, |
|
MediaVersions = new List<MediaVersion> |
|
{ |
|
new() |
|
{ |
|
Duration = duration, MediaFiles = new List<MediaFile> |
|
{ |
|
new() { Path = $"/fake/path/{id}" } |
|
} |
|
} |
|
} |
|
}; |
|
|
|
private TestData TestDataFloodForItems( |
|
List<MediaItem> mediaItems, |
|
PlaybackOrder playbackOrder, |
|
Mock<IConfigElementRepository> configMock = null) |
|
{ |
|
var mediaCollection = new Collection |
|
{ |
|
Id = 1, |
|
MediaItems = mediaItems |
|
}; |
|
|
|
Mock<IConfigElementRepository> configRepo = configMock ?? new Mock<IConfigElementRepository>(); |
|
|
|
var collectionRepo = new FakeMediaCollectionRepository(Map((mediaCollection.Id, mediaItems))); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
collectionRepo, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
var items = new List<ProgramScheduleItem> { Flood(mediaCollection, playbackOrder) }; |
|
|
|
var playout = new Playout |
|
{ |
|
Id = 1, |
|
ProgramSchedule = new ProgramSchedule { Items = items }, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
return new TestData(builder, playout); |
|
} |
|
|
|
private TestData TestDataFloodForSmartCollectionItems( |
|
List<MediaItem> mediaItems, |
|
PlaybackOrder playbackOrder, |
|
Mock<IConfigElementRepository> configMock = null) |
|
{ |
|
var mediaCollection = new SmartCollection |
|
{ |
|
Id = 1, |
|
Query = "asdf" |
|
}; |
|
|
|
var fillerCollection = new SmartCollection |
|
{ |
|
Id = 2, |
|
Query = "ghjk" |
|
}; |
|
|
|
Mock<IConfigElementRepository> configRepo = configMock ?? new Mock<IConfigElementRepository>(); |
|
|
|
var collectionRepo = new FakeMediaCollectionRepository( |
|
Map( |
|
(mediaCollection.Id, mediaItems), |
|
(fillerCollection.Id, mediaItems.Take(1).ToList()) |
|
) |
|
); |
|
var televisionRepo = new FakeTelevisionRepository(); |
|
var artistRepo = new Mock<IArtistRepository>(); |
|
var factory = new Mock<IMultiEpisodeShuffleCollectionEnumeratorFactory>(); |
|
var localFileSystem = new Mock<ILocalFileSystem>(); |
|
var builder = new PlayoutBuilder( |
|
configRepo.Object, |
|
collectionRepo, |
|
televisionRepo, |
|
artistRepo.Object, |
|
factory.Object, |
|
localFileSystem.Object, |
|
_logger); |
|
|
|
var items = new List<ProgramScheduleItem> { Flood(mediaCollection, fillerCollection, playbackOrder) }; |
|
|
|
var playout = new Playout |
|
{ |
|
Id = 1, |
|
ProgramSchedule = new ProgramSchedule { Items = items }, |
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" }, |
|
Items = new List<PlayoutItem>(), |
|
ProgramScheduleAnchors = new List<PlayoutProgramScheduleAnchor>(), |
|
ProgramScheduleAlternates = new List<ProgramScheduleAlternate>() |
|
}; |
|
|
|
return new TestData(builder, playout); |
|
} |
|
|
|
private record TestData(PlayoutBuilder Builder, Playout Playout); |
|
}
|
|
|