Refactor LibraryDebugCollector and TelegramApi for improved data handling and clarity
ci / build-backend (push) Successful in 1m19s
ci / build-frontend (push) Successful in 42s
ci / tests (push) Successful in 1m30s
ci / sonar (push) Successful in 3m42s

Updated the LibraryDebugCollector to introduce a new CompositionEntry record for better organization of group composition data. Refactored the composition logic to utilize this new structure, enhancing readability and maintainability. In the TelegramApi, replaced inline allowed_updates array with a static readonly field to reduce redundancy and improve clarity in API call configurations. Adjusted related tests to ensure proper handling of message assertions, enhancing overall test reliability.
This commit is contained in:
Leonid Pershin
2026-07-31 10:07:28 +03:00
parent 08f56e14c2
commit 4a2e6ac7f1
8 changed files with 991 additions and 18 deletions
@@ -0,0 +1,209 @@
using TeleWave.Application.Streaming.GetIptvGuide;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;
namespace TeleWave.Application.Tests.Streaming;
/// <summary>
/// Телегид для IPTV: программа всех включённых каналов одной выгрузкой. Проверяем то, чем он
/// отличается от списка записей ленты — склейку серий в блок, отбор окна и подстановку описаний.
/// </summary>
public class IptvGuideTests
{
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 12, 0, 0, TimeSpan.Zero);
[Fact]
public async Task Guide_MergesConsecutiveEpisodesOfTheSameShow()
{
var fixture = new TestDb();
var channel = Enabled("Первый", "one");
var show = Show.Create("Футурама", ShowKind.Series);
var first = MediaAsset.Register("s01e01.mkv", ".mkv", MediaSource.Upload);
var second = MediaAsset.Register("s01e02.mkv", ".mkv", MediaSource.Upload);
show.AddEpisode(first.Id);
show.AddEpisode(second.Id);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.Add(show);
seed.MediaAssets.AddRange(first, second);
seed.ScheduleEntries.AddRange(
Program(channel.Id, first.Id, show.Id, T0, 25),
Program(channel.Id, second.Id, show.Id, T0.AddMinutes(25), 25)
);
await seed.SaveChangesAsync(CancellationToken.None);
}
var channels = await GuideAsync(fixture, T0.AddHours(-1), T0.AddHours(3));
// Две серии встык — одна передача в глазах зрителя: гид показывает программу, а не файлы.
var programme = Assert.Single(Assert.Single(channels).Programmes);
Assert.Equal("Футурама", programme.Title);
Assert.Equal(T0, programme.StartsAtUtc);
Assert.Equal(T0.AddMinutes(50), programme.EndsAtUtc);
}
[Fact]
public async Task Guide_KeepsBlocksApartWhenSomethingRanBetween()
{
var fixture = new TestDb();
var channel = Enabled("Первый", "one");
var show = Show.Create("Футурама", ShowKind.Series);
var first = MediaAsset.Register("s01e01.mkv", ".mkv", MediaSource.Upload);
var second = MediaAsset.Register("s01e02.mkv", ".mkv", MediaSource.Upload);
show.AddEpisode(first.Id);
show.AddEpisode(second.Id);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.Add(show);
seed.MediaAssets.AddRange(first, second);
seed.ScheduleEntries.AddRange(
Program(channel.Id, first.Id, show.Id, T0, 25),
// Разрыв в пять минут — между сериями шла реклама, и одной передачей это не назвать.
Program(channel.Id, second.Id, show.Id, T0.AddMinutes(30), 25)
);
await seed.SaveChangesAsync(CancellationToken.None);
}
var channels = await GuideAsync(fixture, T0.AddHours(-1), T0.AddHours(3));
var programmes = Assert.Single(channels).Programmes;
Assert.Equal(2, programmes.Count);
Assert.Equal(T0.AddMinutes(25), programmes[0].EndsAtUtc);
Assert.Equal(T0.AddMinutes(30), programmes[1].StartsAtUtc);
}
[Fact]
public async Task Guide_TakesTitleAndCoverFromEpisodeThenFallsBackToShow()
{
var fixture = new TestDb();
var channel = Enabled("Первый", "one");
var stillId = Guid.NewGuid();
var posterId = Guid.NewGuid();
var titled = Show.Create("Футурама", ShowKind.Series);
titled.SetPosterImage(posterId);
var withMetadata = MediaAsset.Register("s01e01.mkv", ".mkv", MediaSource.Upload);
var bare = MediaAsset.Register("s01e02.mkv", ".mkv", MediaSource.Upload);
titled
.AddEpisode(withMetadata.Id)
.ApplyMetadata("Космический пилот", "Фрай", stillId, null);
titled.AddEpisode(bare.Id);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.Add(titled);
seed.MediaAssets.AddRange(withMetadata, bare);
seed.ScheduleEntries.AddRange(
Program(channel.Id, withMetadata.Id, titled.Id, T0, 25),
Program(channel.Id, bare.Id, titled.Id, T0.AddMinutes(30), 25)
);
await seed.SaveChangesAsync(CancellationToken.None);
}
var channels = await GuideAsync(fixture, T0.AddHours(-1), T0.AddHours(3));
var programmes = Assert.Single(channels).Programmes;
Assert.Equal("Космический пилот", programmes[0].SubTitle);
Assert.Equal("Фрай", programmes[0].Description);
Assert.Equal(stillId, programmes[0].ImageId);
// Серия без метаданных показывается обложкой шоу — пустая карточка в плеере хуже общей.
Assert.Null(programmes[1].SubTitle);
Assert.Equal(posterId, programmes[1].ImageId);
}
[Fact]
public async Task Guide_TakesOnlyEnabledChannelsAndTheRequestedWindow()
{
var fixture = new TestDb();
var visible = Enabled("Первый", "one");
var hidden = Channel.Create("Выключенный", "off", T0);
hidden.UpdateSettings("Выключенный", isEnabled: false, null);
var show = Show.Create("Ворон", ShowKind.Single);
var asset = MediaAsset.Register("crow.mkv", ".mkv", MediaSource.Upload);
show.AddEpisode(asset.Id);
await using (var seed = fixture.New())
{
seed.Channels.AddRange(visible, hidden);
seed.Shows.Add(show);
seed.MediaAssets.Add(asset);
seed.ScheduleEntries.AddRange(
Program(visible.Id, asset.Id, show.Id, T0, 60),
// За окном — не в выгрузке; выключенный канал не показываем вовсе.
Program(visible.Id, asset.Id, show.Id, T0.AddHours(10), 60),
Program(hidden.Id, asset.Id, show.Id, T0, 60),
// Заставки и врезки в гид не идут: у них нет шоу, а значит и передачи.
ScheduleEntry.Bumper(
visible.Id,
asset.Id,
T0.AddHours(2),
T0.AddHours(2).AddSeconds(10),
null,
null
)
);
await seed.SaveChangesAsync(CancellationToken.None);
}
var channels = await GuideAsync(fixture, T0.AddHours(-1), T0.AddHours(3));
var dto = Assert.Single(channels);
Assert.Equal("one", dto.Slug);
var programme = Assert.Single(dto.Programmes);
Assert.Equal("Ворон", programme.Title);
}
[Fact]
public async Task Guide_WithoutChannels_ReturnsEmpty()
{
var fixture = new TestDb();
var channels = await GuideAsync(fixture, T0, T0.AddHours(3));
Assert.Empty(channels);
}
private static async Task<IReadOnlyList<IptvChannelDto>> GuideAsync(
TestDb fixture,
DateTimeOffset from,
DateTimeOffset to
)
{
await using var db = fixture.New();
return await new GetIptvGuideQueryHandler(db).Handle(
new GetIptvGuideQuery(from, to),
CancellationToken.None
);
}
private static Channel Enabled(string name, string slug)
{
var channel = Channel.Create(name, slug, T0);
channel.UpdateSettings(name, isEnabled: true, null);
return channel;
}
private static ScheduleEntry Program(
Guid channelId,
Guid assetId,
Guid showId,
DateTimeOffset startsAt,
int minutes
) =>
ScheduleEntry.Program(
channelId,
assetId,
startsAt,
startsAt.AddMinutes(minutes),
showId,
0
);
}