Enhance GridScheduleGenerator and related components for improved slot handling and cursor management
Updated the GridScheduleGenerator to support rebuilding future schedules while correctly managing aired positions. Introduced a new AiredPosition record to track the last aired state of slots, ensuring that the cursor rewinds to the correct position during rebuilds. Refactored the BuildInputAsync method to accept aired positions, and modified the LoadAiredPositionsAsync method for accurate retrieval of past entries. Enhanced the SchedulePlanner to utilize shared cursor states between main and background loops, preventing duplicate series plays. Updated documentation to reflect these changes and added integration tests to verify the correct behavior of the new functionality.
This commit is contained in:
@@ -115,6 +115,54 @@ public sealed class GridScheduleGeneratorIntegrationTests(PostgresFixture fixtur
|
||||
Assert.Equal(past.Count, survived.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Пересборка выбрасывает хвост, который курсор уже прошёл. Без отмотки к отыгранному каждое
|
||||
/// применение проматывало бы библиотеку на горизонт вперёд, теряя невышедшие серии.
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task Generate_Rebuild_RewindsCursorToWhatActuallyAired()
|
||||
{
|
||||
Skip.IfNot(fixture.Available, "Docker недоступен");
|
||||
|
||||
await using var seedDb = fixture.CreateContext();
|
||||
// Серий заведомо больше, чем влезет в горизонт: иначе сериал успеет пойти по кругу,
|
||||
// и «продолжает с той же серии» проверять станет нечем.
|
||||
var world = await SeedAsync(seedDb, episodes: 100);
|
||||
|
||||
// Первый прогон строит сутки вперёд от момента двухчасовой давности.
|
||||
await using var first = fixture.CreateContext();
|
||||
await Generator(first).GenerateAsync(world.ChannelId, Now.AddHours(-2), false, default);
|
||||
|
||||
await using var beforeDb = fixture.CreateContext();
|
||||
var aired = beforeDb
|
||||
.ScheduleEntries.Where(e =>
|
||||
e.ChannelId == world.ChannelId
|
||||
&& e.Kind == ScheduleEntryKind.Program
|
||||
&& e.StartsAtUtc < Now
|
||||
)
|
||||
.OrderByDescending(e => e.StartsAtUtc)
|
||||
.First();
|
||||
var beforeRebuild = beforeDb.SlotStates.Single(s => s.SlotId == world.SlotId);
|
||||
// Курсор ушёл за эфир: он прошёл весь построенный хвост, а не только вышедшее.
|
||||
Assert.True(beforeRebuild.NextUnitIndex > aired.EpisodeIndex + 1);
|
||||
|
||||
await using var rebuild = fixture.CreateContext();
|
||||
await Generator(rebuild).GenerateAsync(world.ChannelId, Now, true, default);
|
||||
|
||||
await using var verify = fixture.CreateContext();
|
||||
var firstAfterRebuild = verify
|
||||
.ScheduleEntries.Where(e =>
|
||||
e.ChannelId == world.ChannelId
|
||||
&& e.Kind == ScheduleEntryKind.Program
|
||||
&& e.StartsAtUtc >= Now
|
||||
)
|
||||
.OrderBy(e => e.StartsAtUtc)
|
||||
.First();
|
||||
|
||||
// Пересобранный эфир продолжает ровно с той серии, что шла следующей по отыгранному.
|
||||
Assert.Equal(aired.EpisodeIndex + 1, firstAfterRebuild.EpisodeIndex);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task Generate_CollectionInGroup_StampsCollectionOnEntries()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user