Files
TeleWave/backend/tests/TeleWave.Application.Tests/Broadcast/BumperTemplateHandlersTests.cs
T
Leonid Pershin 3d2ae20368
ci / build-backend (push) Successful in 1m27s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 10m43s
ci / sonar (push) Successful in 6m18s
Refactor CI workflow for improved test coverage reporting and enhance BumperPlaceholders logic
Updated the CI workflow to run tests for individual projects, ensuring accurate coverage reports are generated for each module. Enhanced the BumperPlaceholders class to handle dangling separators more effectively, improving text resolution logic and maintaining clarity in output.
2026-07-27 22:46:02 +03:00

368 lines
14 KiB
C#

using Microsoft.EntityFrameworkCore;
using NSubstitute;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Images;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Images;
using TeleWave.Domain.Programming;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
/// <summary>
/// Команды блока заставки. Блок общий для всех каналов, поэтому удаление используемого запрещено:
/// иначе оно молча выключило бы заставки в чужих каналах.
/// </summary>
public class BumperTemplateHandlersTests
{
private static async Task<(TestDb Fixture, BumperTemplate Template)> SeedAsync()
{
var fixture = new TestDb();
var template = BumperTemplate.Create("Блок", "Текст 1");
await using var seed = fixture.New();
seed.BumperTemplates.Add(template);
await seed.SaveChangesAsync(CancellationToken.None);
return (fixture, template);
}
[Fact]
public async Task Create_StartsFromNowNextPreset()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await new CreateBumperTemplateCommandHandler(db).Handle(
new CreateBumperTemplateCommand("Ночной"),
CancellationToken.None
);
await db.SaveChangesAsync(CancellationToken.None);
Assert.True(result.IsSuccess);
await using var verify = fixture.New();
var template = await verify.BumperTemplates.Include(t => t.Variants).SingleAsync();
var variant = Assert.Single(template.Variants);
// Пустой блок в редакторе выглядит поломанным — новый начинается с «Сейчас/Далее».
Assert.Equal(
["СЕЙЧАС", "{now.title}", "ДАЛЕЕ", "{next.title}"],
variant.Lines.OrderBy(l => l.Position).Select(l => l.Text)
);
Assert.Equal(BumperFont.Sans, template.Font);
}
[Fact]
public async Task UpdateStyle_BumpsRevision()
{
var (fixture, template) = await SeedAsync();
await using var db = fixture.New();
var result = await new UpdateBumperTemplateCommandHandler(db).Handle(
new UpdateBumperTemplateCommand(
template.Id,
new BumperStyle(
"Ночной",
BumperFont.Serif,
"0x111111",
"0x222222",
"0x333333",
"black"
)
),
CancellationToken.None
);
await db.SaveChangesAsync(CancellationToken.None);
Assert.True(result.IsSuccess);
await using var verify = fixture.New();
var stored = await verify.BumperTemplates.AsNoTracking().SingleAsync();
Assert.Equal("Ночной", stored.Name);
Assert.Equal(BumperFont.Serif, stored.Font);
// Оформление входит в сигнатуру рендера: без роста ревизии в эфире остались бы старые кадры.
Assert.Equal(1, stored.Revision);
}
[Fact]
public async Task UpdateStyle_UnknownTemplate_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await new UpdateBumperTemplateCommandHandler(db).Handle(
new UpdateBumperTemplateCommand(
Guid.NewGuid(),
new BumperStyle("x", BumperFont.Sans, "a", "b", "c", "d")
),
CancellationToken.None
);
Assert.Equal(BumperErrors.TemplateNotFound, result.Error);
}
[Fact]
public async Task Audio_SetAndClear_TouchStorageAndDuration()
{
var (fixture, template) = await SeedAsync();
var storage = Substitute.For<IBumperTemplateStorage>();
await using (var db = fixture.New())
{
var set = await new SetBumperTemplateAudioCommandHandler(db).Handle(
new SetBumperTemplateAudioCommand(template.Id, ".mp3", 12.5),
CancellationToken.None
);
Assert.True(set.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
await using (var verify = fixture.New())
{
var stored = await verify.BumperTemplates.AsNoTracking().SingleAsync();
Assert.Equal(".mp3", stored.AudioExtension);
Assert.Equal(12.5, stored.AudioDurationSeconds);
}
await using (var db = fixture.New())
{
var cleared = await new ClearBumperTemplateAudioCommandHandler(db, storage).Handle(
new ClearBumperTemplateAudioCommand(template.Id),
CancellationToken.None
);
Assert.True(cleared.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
// Файл на диске должен уйти вместе с отметкой в базе.
await storage.Received(1).DeleteAudioAsync(template.Id, Arg.Any<CancellationToken>());
await using var final = fixture.New();
Assert.Null((await final.BumperTemplates.AsNoTracking().SingleAsync()).AudioExtension);
}
[Fact]
public async Task Audio_UnknownTemplate_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var storage = Substitute.For<IBumperTemplateStorage>();
var set = await new SetBumperTemplateAudioCommandHandler(db).Handle(
new SetBumperTemplateAudioCommand(Guid.NewGuid(), ".mp3", 1),
CancellationToken.None
);
var cleared = await new ClearBumperTemplateAudioCommandHandler(db, storage).Handle(
new ClearBumperTemplateAudioCommand(Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(BumperErrors.TemplateNotFound, set.Error);
Assert.Equal(BumperErrors.TemplateNotFound, cleared.Error);
}
[Fact]
public async Task Background_SetAndClear_UseImageRegistry()
{
var (fixture, template) = await SeedAsync();
var image = Image.Create(ImageCategory.BumperBackground, ".png", "bg.png");
await using (var seed = fixture.New())
{
seed.Images.Add(image);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using (var db = fixture.New())
{
var unknownImage = await new SetBumperTemplateBackgroundCommandHandler(db).Handle(
new SetBumperTemplateBackgroundCommand(template.Id, Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(ImageErrors.NotFound, unknownImage.Error);
var set = await new SetBumperTemplateBackgroundCommandHandler(db).Handle(
new SetBumperTemplateBackgroundCommand(template.Id, image.Id),
CancellationToken.None
);
Assert.True(set.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
await using (var verify = fixture.New())
Assert.Equal(
image.Id,
(await verify.BumperTemplates.AsNoTracking().SingleAsync()).BackgroundImageId
);
await using (var db = fixture.New())
{
var cleared = await new ClearBumperTemplateBackgroundCommandHandler(db).Handle(
new ClearBumperTemplateBackgroundCommand(template.Id),
CancellationToken.None
);
Assert.True(cleared.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
await using var final = fixture.New();
Assert.Null((await final.BumperTemplates.AsNoTracking().SingleAsync()).BackgroundImageId);
}
[Fact]
public async Task Background_UnknownTemplate_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var set = await new SetBumperTemplateBackgroundCommandHandler(db).Handle(
new SetBumperTemplateBackgroundCommand(Guid.NewGuid(), Guid.NewGuid()),
CancellationToken.None
);
var cleared = await new ClearBumperTemplateBackgroundCommandHandler(db).Handle(
new ClearBumperTemplateBackgroundCommand(Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(BumperErrors.TemplateNotFound, set.Error);
Assert.Equal(BumperErrors.TemplateNotFound, cleared.Error);
}
[Fact]
public async Task Delete_RefusesUsedTemplate_AndRemovesFreeOne()
{
var (fixture, template) = await SeedAsync();
var storage = Substitute.For<IBumperTemplateStorage>();
var junction = JunctionTemplate.Create("Прайм");
await using (var seed = fixture.New())
{
junction
.AddElement(JunctionElementKind.Bumper)
.Update(
new JunctionElementSettings(
JunctionElementKind.Bumper,
null,
template.Id,
null,
JunctionAmountMode.Count,
1,
false,
null,
1,
null
)
);
seed.JunctionTemplates.Add(junction);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using (var db = fixture.New())
{
var used = await new DeleteBumperTemplateCommandHandler(db, storage).Handle(
new DeleteBumperTemplateCommand(template.Id),
CancellationToken.None
);
Assert.Equal(BumperErrors.TemplateInUse, used.Error);
}
await using (var db = fixture.New())
{
var stored = await db
.JunctionTemplates.Include(j => j.Elements)
.SingleAsync(j => j.Id == junction.Id);
stored.RemoveElement(stored.Elements[0].Id);
await db.SaveChangesAsync(CancellationToken.None);
}
await using (var db = fixture.New())
{
var removed = await new DeleteBumperTemplateCommandHandler(db, storage).Handle(
new DeleteBumperTemplateCommand(template.Id),
CancellationToken.None
);
Assert.True(removed.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
await storage.Received(1).DeleteAudioAsync(template.Id, Arg.Any<CancellationToken>());
await using var final = fixture.New();
Assert.Empty(final.BumperTemplates);
}
[Fact]
public async Task Delete_UnknownTemplate_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await new DeleteBumperTemplateCommandHandler(
db,
Substitute.For<IBumperTemplateStorage>()
).Handle(new DeleteBumperTemplateCommand(Guid.NewGuid()), CancellationToken.None);
Assert.Equal(BumperErrors.TemplateNotFound, result.Error);
}
[Fact]
public async Task List_MapsTemplatesWithUsageCount()
{
var fixture = new TestDb();
var used = BumperTemplate.Create("Анонсовый", "Текст 1");
used.UpdateStyle(
new BumperStyle(
"Анонсовый",
BumperFont.Serif,
"0x111111",
"0x222222",
"0x333333",
"white"
)
);
used.SetAudio(".mp3", 9);
used.Variants[0]
.SetLines([
BumperLine.Create(0, BumperLineStyle.Title, BumperLineColor.Text, "{next.title}"),
]);
var free = BumperTemplate.Create("Блок", "Текст 1");
var junction = JunctionTemplate.Create("Прайм");
foreach (var _ in Enumerable.Range(0, 2))
junction
.AddElement(JunctionElementKind.Bumper)
.Update(
new JunctionElementSettings(
JunctionElementKind.Bumper,
null,
used.Id,
null,
JunctionAmountMode.Count,
1,
false,
null,
1,
null
)
);
await using (var seed = fixture.New())
{
seed.BumperTemplates.AddRange(used, free);
seed.JunctionTemplates.Add(junction);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var templates = await new ListBumperTemplatesQueryHandler(db).Handle(
new ListBumperTemplatesQuery(),
CancellationToken.None
);
// Сортировка по имени: «Анонсовый» раньше «Блока».
Assert.Equal(["Анонсовый", "Блок"], templates.Select(t => t.Name));
var dto = templates[0];
Assert.Equal(2, dto.UsageCount);
Assert.Equal(BumperFont.Serif, dto.Font);
Assert.True(dto.HasAudio);
Assert.Equal(9, dto.AudioDurationSeconds);
Assert.Equal("{next.title}", Assert.Single(Assert.Single(dto.Variants).Lines).Text);
Assert.Equal(0, templates[1].UsageCount);
}
}