* rename collection type * split collections into separate pages * add rerun collection types, migration, editor * add rerun to classic schedule items * rerun plumbing in classic playout builder * start to implement rerun enumerator * add scheduledAt to enumerator movenext * maintain rerun history in db * fix shuffle * fix rerun allowed playback orders * fix updating rerun collections * update changelog; fix editing * update changelog
234 lines
9.5 KiB
C#
234 lines
9.5 KiB
C#
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Domain.Scheduling;
|
|
using ErsatzTV.Core.Scheduling;
|
|
using ErsatzTV.Core.Scheduling.BlockScheduling;
|
|
using Newtonsoft.Json;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
|
|
namespace ErsatzTV.Core.Tests.Scheduling.BlockScheduling;
|
|
|
|
public static class BlockPlayoutChangeDetectionTests
|
|
{
|
|
[TestFixture]
|
|
public class FindUpdatedItems
|
|
{
|
|
// takes playout items, item block keys and effective blocks
|
|
// returns blocks to schedule and playout items to remove
|
|
|
|
// test case: nothing has changed
|
|
|
|
// test case: block has moved from one time to another time, nothing after
|
|
// test case: block has moved from one time to another time, same block after
|
|
// test case: block has moved from one time to another time, different block with same collection after
|
|
// test case: block was moved from one time to another time, different block with different collection after
|
|
|
|
// test case: block was removed, nothing after
|
|
// test case: block was removed, same block after
|
|
// test case: block was removed, different block with same collection after
|
|
// test case: block was removed, different block with different collection after
|
|
|
|
// test case: block was added, nothing after
|
|
// test case: block was added, same block after
|
|
// test case: block was added, different block with same collection after
|
|
// test case: block was added, different block with different collection after
|
|
|
|
|
|
[Test]
|
|
public void Should_Work_When_Nothing_Has_Changed()
|
|
{
|
|
DateTimeOffset dateUpdated = DateTimeOffset.Now;
|
|
List<Block> blocks = Blocks(dateUpdated);
|
|
var template = new Template { Id = 1, DateUpdated = dateUpdated.UtcDateTime };
|
|
var playoutTemplate = new PlayoutTemplate { Id = 10, DateUpdated = dateUpdated.UtcDateTime };
|
|
|
|
Block block1 = blocks[0];
|
|
Block block2 = blocks[1];
|
|
var blockKey1 = new BlockKey(block1, template, playoutTemplate);
|
|
var blockKey2 = new BlockKey(block2, template, playoutTemplate);
|
|
var collectionKey1 = CollectionKey.ForBlockItem(block1.Items.Head());
|
|
var collectionKey2 = CollectionKey.ForBlockItem(block2.Items.Head());
|
|
|
|
// 9am-9:20am
|
|
PlayoutItem playoutItem1 = PlayoutItem(blockKey1, collectionKey1, GetLocalDate(2024, 1, 17).AddHours(9));
|
|
// 1pm-1:20pm
|
|
PlayoutItem playoutItem2 = PlayoutItem(blockKey2, collectionKey2, GetLocalDate(2024, 1, 17).AddHours(13));
|
|
|
|
List<PlayoutItem> playoutItems = [playoutItem1, playoutItem2];
|
|
Dictionary<PlayoutItem, BlockKey> itemBlockKeys =
|
|
new()
|
|
{
|
|
{ playoutItem1, blockKey1 },
|
|
{ playoutItem2, blockKey2 }
|
|
};
|
|
|
|
List<EffectiveBlock> effectiveBlocks =
|
|
[
|
|
new(block1, blockKey1, GetLocalDate(2024, 1, 17).AddHours(9), 1),
|
|
new(block2, blockKey2, GetLocalDate(2024, 1, 17).AddHours(13), 2)
|
|
];
|
|
|
|
Map<CollectionKey, string> collectionEtags = LanguageExt.Map<CollectionKey, string>.Empty;
|
|
collectionEtags = collectionEtags.Add(collectionKey1, JsonConvert.SerializeObject(collectionKey1));
|
|
collectionEtags = collectionEtags.Add(collectionKey2, JsonConvert.SerializeObject(collectionKey2));
|
|
|
|
var buildStart = GetLocalDate(2024, 1, 17);
|
|
Tuple<List<EffectiveBlock>, List<PlayoutItem>> result = BlockPlayoutChangeDetection.FindUpdatedItems(
|
|
buildStart,
|
|
playoutItems,
|
|
itemBlockKeys,
|
|
effectiveBlocks,
|
|
collectionEtags);
|
|
|
|
// nothing to schedule
|
|
result.Item1.Count.ShouldBe(0);
|
|
|
|
// do not need to remove any playout items or history
|
|
result.Item2.Count.ShouldBe(0);
|
|
}
|
|
|
|
[Test]
|
|
public void Should_Not_Remove_Items_From_Outside_The_Scheduling_Window()
|
|
{
|
|
// This test demonstrates a bug where playout items from the past are removed
|
|
// because their generating block is not in the current scheduling window.
|
|
|
|
DateTimeOffset dateUpdated = DateTimeOffset.Now;
|
|
List<Block> blocks = Blocks(dateUpdated);
|
|
var template1 = new Template { Id = 1, DateUpdated = dateUpdated.UtcDateTime };
|
|
var playoutTemplate1 = new PlayoutTemplate { Id = 10, DateUpdated = dateUpdated.UtcDateTime };
|
|
|
|
var template2 = new Template { Id = 2, DateUpdated = dateUpdated.UtcDateTime };
|
|
var playoutTemplate2 = new PlayoutTemplate { Id = 20, DateUpdated = dateUpdated.UtcDateTime };
|
|
|
|
Block block1 = blocks[0]; // Yesterday's block
|
|
Block block2 = blocks[1]; // Today's block
|
|
|
|
var blockKey1 = new BlockKey(block1, template1, playoutTemplate1);
|
|
var blockKey2 = new BlockKey(block2, template2, playoutTemplate2);
|
|
|
|
var collectionKey1 = CollectionKey.ForBlockItem(block1.Items.Head());
|
|
var collectionKey2 = CollectionKey.ForBlockItem(block2.Items.Head());
|
|
|
|
// Playout item from yesterday
|
|
PlayoutItem playoutItem1 = PlayoutItem(blockKey1, collectionKey1, GetLocalDate(2024, 1, 17).AddHours(9));
|
|
|
|
List<PlayoutItem> playoutItems = [playoutItem1];
|
|
Dictionary<PlayoutItem, BlockKey> itemBlockKeys =
|
|
new()
|
|
{
|
|
{ playoutItem1, blockKey1 }
|
|
};
|
|
|
|
// Effective blocks for today - does not include yesterday's block
|
|
List<EffectiveBlock> effectiveBlocks =
|
|
[
|
|
new(block2, blockKey2, GetLocalDate(2024, 1, 18).AddHours(9), 1)
|
|
];
|
|
|
|
Map<CollectionKey, string> collectionEtags = LanguageExt.Map<CollectionKey, string>.Empty;
|
|
collectionEtags = collectionEtags.Add(collectionKey1, JsonConvert.SerializeObject(collectionKey1));
|
|
collectionEtags = collectionEtags.Add(collectionKey2, JsonConvert.SerializeObject(collectionKey2));
|
|
|
|
var buildStart = GetLocalDate(2024, 1, 18);
|
|
Tuple<List<EffectiveBlock>, List<PlayoutItem>> result = BlockPlayoutChangeDetection.FindUpdatedItems(
|
|
buildStart,
|
|
playoutItems,
|
|
itemBlockKeys,
|
|
effectiveBlocks,
|
|
collectionEtags);
|
|
|
|
// should schedule today's block
|
|
result.Item1.Count.ShouldBe(1);
|
|
result.Item1.Head().Block.ShouldBe(block2);
|
|
|
|
// should NOT remove yesterday's item
|
|
result.Item2.Count.ShouldBe(0);
|
|
}
|
|
|
|
private static List<Block> Blocks(DateTimeOffset dateUpdated)
|
|
{
|
|
List<Block> blocks =
|
|
[
|
|
// SHOW A
|
|
new()
|
|
{
|
|
Id = 1,
|
|
Items =
|
|
[
|
|
new BlockItem
|
|
{
|
|
Id = 1,
|
|
Index = 1,
|
|
BlockId = 1,
|
|
CollectionType = CollectionType.TelevisionShow,
|
|
MediaItemId = 1,
|
|
PlaybackOrder = PlaybackOrder.Chronological
|
|
}
|
|
],
|
|
DateUpdated = dateUpdated.UtcDateTime
|
|
},
|
|
// SHOW B
|
|
new()
|
|
{
|
|
Id = 2,
|
|
Items =
|
|
[
|
|
new BlockItem
|
|
{
|
|
Id = 2,
|
|
Index = 1,
|
|
BlockId = 2,
|
|
CollectionType = CollectionType.TelevisionShow,
|
|
MediaItemId = 2,
|
|
PlaybackOrder = PlaybackOrder.Chronological
|
|
}
|
|
],
|
|
DateUpdated = dateUpdated.UtcDateTime
|
|
},
|
|
// SHOW C
|
|
new()
|
|
{
|
|
Id = 3,
|
|
Items =
|
|
[
|
|
new BlockItem
|
|
{
|
|
Id = 3,
|
|
Index = 1,
|
|
BlockId = 3,
|
|
CollectionType = CollectionType.TelevisionShow,
|
|
MediaItemId = 3,
|
|
PlaybackOrder = PlaybackOrder.Chronological
|
|
}
|
|
],
|
|
DateUpdated = dateUpdated.UtcDateTime
|
|
}
|
|
];
|
|
|
|
foreach (Block block in blocks)
|
|
{
|
|
foreach (BlockItem blockItem in block.Items)
|
|
{
|
|
blockItem.Block = block;
|
|
}
|
|
}
|
|
|
|
return blocks;
|
|
}
|
|
|
|
private static PlayoutItem PlayoutItem(BlockKey blockKey, CollectionKey collectionKey, DateTimeOffset start) =>
|
|
new()
|
|
{
|
|
Start = start.UtcDateTime,
|
|
Finish = start.UtcDateTime.AddMinutes(20),
|
|
BlockKey = JsonConvert.SerializeObject(blockKey),
|
|
CollectionKey = JsonConvert.SerializeObject(collectionKey),
|
|
CollectionEtag = JsonConvert.SerializeObject(collectionKey)
|
|
};
|
|
|
|
private static DateTimeOffset GetLocalDate(int year, int month, int day) =>
|
|
new(year, month, day, 0, 0, 0, TimeSpan.FromHours(-6));
|
|
}
|
|
}
|