Refactor Channel endpoints and data models: replace jingle functionality with bumper templates, update related commands and handlers, and enhance API routes for managing bumper templates. Remove obsolete jingle-related code and adjust channel data structures to support new bumper template features.
This commit is contained in:
@@ -20,6 +20,26 @@ public class SchedulePlannerTests
|
||||
private static Dictionary<Guid, TimeSpan> Durations(params (Guid Id, int Minutes)[] items) =>
|
||||
items.ToDictionary(x => x.Id, x => TimeSpan.FromMinutes(x.Minutes));
|
||||
|
||||
private static readonly Guid DefaultTemplate = Guid.NewGuid();
|
||||
|
||||
/// <summary>Конфиг заставок с одним дефолтным блоком (8с), если явно не заданы блоки.</summary>
|
||||
private static PlannerBumperConfig Bumper(
|
||||
bool enabled,
|
||||
bool onlyBetweenDifferentShows,
|
||||
TimeSpan minInterval,
|
||||
BumperSelection selection = BumperSelection.Rotation,
|
||||
params PlannerBumperTemplate[] templates
|
||||
) =>
|
||||
new(
|
||||
enabled,
|
||||
onlyBetweenDifferentShows,
|
||||
minInterval,
|
||||
selection,
|
||||
templates.Length == 0
|
||||
? [new PlannerBumperTemplate(DefaultTemplate, TimeSpan.FromSeconds(8))]
|
||||
: templates
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Count_Block_ProducesConsecutiveEpisodes_BackToBack()
|
||||
{
|
||||
@@ -212,7 +232,7 @@ public class SchedulePlannerTests
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(50)) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(true, TimeSpan.FromSeconds(8), OnlyBetweenDifferentShows: true, MinInterval: TimeSpan.Zero),
|
||||
Bumpers = Bumper(true, onlyBetweenDifferentShows: true, TimeSpan.Zero),
|
||||
};
|
||||
|
||||
// Чередуем выбор: roll 0 → a, roll 1 → b (веса 1/1, total 2).
|
||||
@@ -228,6 +248,7 @@ public class SchedulePlannerTests
|
||||
Assert.Equal(Guid.Empty, first.MediaAssetId); // ассет подставит оркестратор
|
||||
Assert.Equal(a.ShowId, first.FromShowId);
|
||||
Assert.Equal(b.ShowId, first.ToShowId);
|
||||
Assert.Equal(DefaultTemplate, first.BumperTemplateId);
|
||||
Assert.Equal(TimeSpan.FromSeconds(8), first.EndsAtUtc - first.StartsAtUtc);
|
||||
// Встык: программа → заставка → программа.
|
||||
Assert.Equal(result.Entries[0].EndsAtUtc, first.StartsAtUtc);
|
||||
@@ -246,7 +267,7 @@ public class SchedulePlannerTests
|
||||
horizonEnd: Start.AddMinutes(50)
|
||||
) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(true, TimeSpan.FromSeconds(8), OnlyBetweenDifferentShows: true, MinInterval: TimeSpan.Zero),
|
||||
Bumpers = Bumper(true, onlyBetweenDifferentShows: true, TimeSpan.Zero),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0));
|
||||
@@ -266,7 +287,7 @@ public class SchedulePlannerTests
|
||||
horizonEnd: Start.AddMinutes(50)
|
||||
) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(true, TimeSpan.FromSeconds(8), OnlyBetweenDifferentShows: false, MinInterval: TimeSpan.Zero),
|
||||
Bumpers = Bumper(true, onlyBetweenDifferentShows: false, TimeSpan.Zero),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0));
|
||||
@@ -284,12 +305,7 @@ public class SchedulePlannerTests
|
||||
// Два перехода в горизонте (~на 20-й и ~40-й минуте), но интервал 30 мин пропускает второй.
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(50)) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(
|
||||
true,
|
||||
TimeSpan.FromSeconds(8),
|
||||
OnlyBetweenDifferentShows: true,
|
||||
MinInterval: TimeSpan.FromMinutes(30)
|
||||
),
|
||||
Bumpers = Bumper(true, onlyBetweenDifferentShows: true, TimeSpan.FromMinutes(30)),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1));
|
||||
@@ -298,37 +314,70 @@ public class SchedulePlannerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_StaticMode_UsesJinglesFromPoolInRotation()
|
||||
public void Bumpers_Rotation_CyclesTemplatesAndAdvancesCursor()
|
||||
{
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
Guid j0 = Guid.NewGuid(),
|
||||
j1 = Guid.NewGuid();
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20), (j0, 1), (j1, 1));
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20));
|
||||
Guid t0 = Guid.NewGuid(),
|
||||
t1 = Guid.NewGuid();
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(50)) with
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(80)) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(
|
||||
Bumpers = Bumper(
|
||||
true,
|
||||
TimeSpan.FromSeconds(8),
|
||||
OnlyBetweenDifferentShows: true,
|
||||
MinInterval: TimeSpan.Zero,
|
||||
Mode: BumperMode.Static,
|
||||
JinglePool: [j0, j1]
|
||||
onlyBetweenDifferentShows: true,
|
||||
TimeSpan.Zero,
|
||||
BumperSelection.Rotation,
|
||||
new PlannerBumperTemplate(t0, TimeSpan.FromSeconds(8)),
|
||||
new PlannerBumperTemplate(t1, TimeSpan.FromSeconds(4))
|
||||
),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1));
|
||||
|
||||
var bumpers = result.Entries.Where(e => e.Kind == ScheduleEntryKind.Bumper).ToList();
|
||||
Assert.Equal(2, bumpers.Count);
|
||||
Assert.Equal([j0, j1], bumpers.Select(e => e.MediaAssetId)); // ротация пула
|
||||
Assert.All(bumpers, e => Assert.Null(e.FromShowId)); // статик — не по паре шоу
|
||||
Assert.Equal(2, result.NextJingleIndex);
|
||||
Assert.True(bumpers.Count >= 2);
|
||||
// Ротация: первый блок → t0 (8с), второй → t1 (4с). Все — плейсхолдеры по паре шоу.
|
||||
Assert.Equal(t0, bumpers[0].BumperTemplateId);
|
||||
Assert.Equal(TimeSpan.FromSeconds(8), bumpers[0].EndsAtUtc - bumpers[0].StartsAtUtc);
|
||||
Assert.Equal(t1, bumpers[1].BumperTemplateId);
|
||||
Assert.Equal(TimeSpan.FromSeconds(4), bumpers[1].EndsAtUtc - bumpers[1].StartsAtUtc);
|
||||
Assert.All(bumpers, e => Assert.Equal(Guid.Empty, e.MediaAssetId));
|
||||
Assert.Equal(bumpers.Count, result.NextBumperIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_StaticMode_EmptyPool_NoBumpers()
|
||||
public void Bumpers_AlwaysFirst_AlwaysUsesFirstTemplate()
|
||||
{
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20));
|
||||
Guid t0 = Guid.NewGuid(),
|
||||
t1 = Guid.NewGuid();
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(80)) with
|
||||
{
|
||||
Bumpers = Bumper(
|
||||
true,
|
||||
onlyBetweenDifferentShows: true,
|
||||
TimeSpan.Zero,
|
||||
BumperSelection.AlwaysFirst,
|
||||
new PlannerBumperTemplate(t0, TimeSpan.FromSeconds(8)),
|
||||
new PlannerBumperTemplate(t1, TimeSpan.FromSeconds(4))
|
||||
),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1));
|
||||
|
||||
var bumpers = result.Entries.Where(e => e.Kind == ScheduleEntryKind.Bumper).ToList();
|
||||
Assert.True(bumpers.Count >= 2);
|
||||
Assert.All(bumpers, e => Assert.Equal(t0, e.BumperTemplateId));
|
||||
Assert.Equal(0, result.NextBumperIndex); // курсор ротации не двигается
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_NoTemplates_NoBumpers()
|
||||
{
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
@@ -338,11 +387,10 @@ public class SchedulePlannerTests
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(
|
||||
true,
|
||||
TimeSpan.FromSeconds(8),
|
||||
OnlyBetweenDifferentShows: true,
|
||||
MinInterval: TimeSpan.Zero,
|
||||
Mode: BumperMode.Static,
|
||||
JinglePool: []
|
||||
Selection: BumperSelection.Rotation,
|
||||
Templates: []
|
||||
),
|
||||
};
|
||||
|
||||
@@ -351,35 +399,6 @@ public class SchedulePlannerTests
|
||||
Assert.DoesNotContain(result.Entries, e => e.Kind == ScheduleEntryKind.Bumper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_BothMode_AlternatesDynamicAndStatic()
|
||||
{
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var j0 = Guid.NewGuid();
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20), (j0, 1));
|
||||
|
||||
// Три перехода: динамика, джингл, динамика.
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(80)) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(
|
||||
true,
|
||||
TimeSpan.FromSeconds(8),
|
||||
OnlyBetweenDifferentShows: true,
|
||||
MinInterval: TimeSpan.Zero,
|
||||
Mode: BumperMode.Both,
|
||||
JinglePool: [j0]
|
||||
),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1));
|
||||
|
||||
var bumpers = result.Entries.Where(e => e.Kind == ScheduleEntryKind.Bumper).ToList();
|
||||
Assert.True(bumpers.Count >= 2);
|
||||
Assert.Equal(Guid.Empty, bumpers[0].MediaAssetId); // первый — динамический (плейсхолдер)
|
||||
Assert.Equal(j0, bumpers[1].MediaAssetId); // второй — статичный джингл
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_Disabled_ProduceNoBumperEntries()
|
||||
{
|
||||
@@ -389,7 +408,7 @@ public class SchedulePlannerTests
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(50)) with
|
||||
{
|
||||
Bumpers = new PlannerBumperConfig(false, TimeSpan.FromSeconds(8), OnlyBetweenDifferentShows: true, MinInterval: TimeSpan.Zero),
|
||||
Bumpers = Bumper(false, onlyBetweenDifferentShows: true, TimeSpan.Zero),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1));
|
||||
|
||||
Reference in New Issue
Block a user