Files
TeleWave/backend/tests/TeleWave.Application.Tests/Broadcast/BumperPreviewTests.cs
T
Leonid Pershin 790d01b587
ci / build-backend (push) Successful in 2m34s
ci / build-frontend (push) Successful in 41s
ci / tests (push) Successful in 2m55s
ci / sonar (push) Successful in 5m21s
Update README.md with additional SonarCloud badges and improve CI workflow for coverage reporting
Enhanced the README.md file by adding new SonarCloud badges for coverage, bugs, code smells, security rating, and maintainability rating. Updated the CI workflow to remove coverage collection from the test step, as it is now handled by SonarCloud, streamlining the process and ensuring accurate badge representation.
2026-07-27 03:19:18 +03:00

191 lines
7.2 KiB
C#

using Microsoft.Extensions.Options;
using NSubstitute;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Streaming;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Images;
using TeleWave.Domain.Library;
using TeleWave.Domain.Programming;
using TeleWave.Infrastructure.Persistence;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
/// <summary>
/// Превью ТВ-заставки: рендерится каждый подблок блока, длительность выравнивается по длине
/// сегмента HLS, а названия «из/в» берутся из групп этого канала — иначе превью показывало бы
/// случайные шоу из библиотеки.
/// </summary>
public class BumperPreviewTests
{
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
private static RenderBumperPreviewCommandHandler Handler(
AppDbContext db,
IBumperRenderer? renderer = null,
IBumperTemplateStorage? storage = null,
IImageStore? imageStore = null
) =>
new(
db,
renderer ?? Substitute.For<IBumperRenderer>(),
storage ?? Substitute.For<IBumperTemplateStorage>(),
imageStore ?? Substitute.For<IImageStore>(),
Options.Create(new BumperOptions()),
Options.Create(new StreamingOptions { SegmentSeconds = 5 })
);
[Fact]
public async Task Render_UnknownChannel_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await Handler(db)
.Handle(
new RenderBumperPreviewCommand(Guid.NewGuid(), Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(ChannelErrors.NotFound, result.Error);
}
[Fact]
public async Task Render_UnknownTemplate_ReturnsBumperTemplateNotFound()
{
var fixture = new TestDb();
var channel = Channel.Create("Первый", "one", T0);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await Handler(db)
.Handle(
new RenderBumperPreviewCommand(channel.Id, Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(ChannelErrors.BumperTemplateNotFound, result.Error);
}
[Fact]
public async Task Render_DrawsEveryVariant_WithChannelShowNamesAndAlignedDuration()
{
var fixture = new TestDb();
var channel = Channel.Create("Первый", "one", T0);
var template = channel.BumperTemplates[0];
var second = template.AddVariant("Второй подблок");
// Шоу канала: слот сетки ссылается на группу, в группе — шоу.
var show = Show.Create("Наше шоу", ShowKind.Series);
var group = Group.Create("Кино");
group.AddElement(GroupElementKind.Show, show.Id);
var grid = ScheduleTemplate.Create(channel.Id, "Сетка");
var slot = grid.Background!.AddSlot("Круглосуточно", new TimeOnly(6, 0), 24 * 60);
slot.UpdateContent(
new SlotContent(
slot.Title,
SlotKind.Content,
group.Id,
null,
null,
SlotBlockMode.FillSlot,
1,
OverflowPolicy.ContinueNext
)
);
channel.SetTemplate(grid.Id);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.Add(show);
seed.Groups.Add(group);
seed.ScheduleTemplates.Add(grid);
await seed.SaveChangesAsync(CancellationToken.None);
}
var renderer = Substitute.For<IBumperRenderer>();
await using var db = fixture.New();
var result = await Handler(db, renderer)
.Handle(
new RenderBumperPreviewCommand(channel.Id, template.Id),
CancellationToken.None
);
Assert.True(result.IsSuccess);
// Оба подблока отрисованы, каждый — в свой ассет-превью.
await renderer
.Received(1)
.RenderAsync(
BumperPreview.AssetId(template.Variants[0].Id),
Arg.Any<BumperRenderSpec>(),
Arg.Any<CancellationToken>()
);
await renderer
.Received(1)
.RenderAsync(
BumperPreview.AssetId(second.Id),
Arg.Any<BumperRenderSpec>(),
Arg.Any<CancellationToken>()
);
var specs = renderer
.ReceivedCalls()
.Select(c => c.GetArguments()[1])
.OfType<BumperRenderSpec>()
.ToList();
// Своего звука у блока нет: 8 секунд по умолчанию, выровненные вверх до сегмента в 5 сек.
Assert.All(specs, s => Assert.Equal(10, s.DurationSeconds));
Assert.All(specs, s => Assert.Equal("Наше шоу", s.NowTitle));
// Второго шоу у канала нет — подставляется заглушка.
Assert.All(specs, s => Assert.Equal("Второе шоу", s.NextTitle));
Assert.All(specs, s => Assert.Null(s.BackgroundFile));
Assert.All(specs, s => Assert.Null(s.PosterFile));
}
[Fact]
public async Task Render_WithBackgroundImage_ResolvesPathFromRegistry()
{
var fixture = new TestDb();
var channel = Channel.Create("Первый", "one", T0);
var template = channel.BumperTemplates[0];
var image = Image.Create(ImageCategory.BumperBackground, ".png", "bg.png");
template.SetBackgroundImage(image.Id);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Images.Add(image);
await seed.SaveChangesAsync(CancellationToken.None);
}
var renderer = Substitute.For<IBumperRenderer>();
var imageStore = Substitute.For<IImageStore>();
imageStore.ResolvePath(image.Id, ".png").Returns("/data/images/bg.png");
await using var db = fixture.New();
var result = await Handler(db, renderer, imageStore: imageStore)
.Handle(
new RenderBumperPreviewCommand(channel.Id, template.Id),
CancellationToken.None
);
Assert.True(result.IsSuccess);
var spec = renderer
.ReceivedCalls()
.Select(c => c.GetArguments()[1])
.OfType<BumperRenderSpec>()
.First();
Assert.Equal("/data/images/bg.png", spec.BackgroundFile);
// Шоу у канала нет — обе подписи заглушечные.
Assert.Equal("Первое шоу", spec.NowTitle);
}
}