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.
230 lines
8.5 KiB
C#
230 lines
8.5 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using TeleWave.Application.Broadcast;
|
|
using TeleWave.Application.Programming.Planning.Trace;
|
|
using TeleWave.Application.Programming.Templates;
|
|
using TeleWave.Application.Tests.Support;
|
|
using TeleWave.Domain.Broadcast;
|
|
using TeleWave.Domain.Library;
|
|
using TeleWave.Domain.Media;
|
|
using TeleWave.Domain.Programming;
|
|
using TeleWave.Domain.Programming.Planning;
|
|
using Xunit;
|
|
|
|
namespace TeleWave.Application.Tests.Programming;
|
|
|
|
/// <summary>
|
|
/// «Почему это здесь»: трейс пишется в момент генерации и разбирается вместе с текущим состоянием
|
|
/// сетки. Слот мог быть с тех пор изменён или удалён — подписи тогда пустеют, но ответ остаётся.
|
|
/// </summary>
|
|
public class EntryTraceTests
|
|
{
|
|
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 18, 0, 0, TimeSpan.Zero);
|
|
|
|
/// <summary>Те же настройки, что при записи трейса генератором.</summary>
|
|
private static readonly JsonSerializerOptions TraceJson = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
Converters = { new JsonStringEnumConverter() },
|
|
};
|
|
|
|
[Fact]
|
|
public async Task Trace_UnknownEntry_ReturnsNotFound()
|
|
{
|
|
var fixture = new TestDb();
|
|
await using var db = fixture.New();
|
|
|
|
var result = await new GetEntryTraceQueryHandler(db).Handle(
|
|
new GetEntryTraceQuery(Guid.NewGuid()),
|
|
CancellationToken.None
|
|
);
|
|
|
|
Assert.Equal(ChannelErrors.NotFound, result.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Trace_ResolvesSlotLayerGroupAndJunction()
|
|
{
|
|
var fixture = new TestDb();
|
|
var channel = Channel.Create("Первый", "one", T0);
|
|
var group = Group.Create("Кино");
|
|
var collection = Collection.Create("Франшиза");
|
|
var show = Show.Create("Фильм", ShowKind.Single);
|
|
var asset = MediaAsset.Register("film.mkv", ".mkv", MediaSource.Upload);
|
|
|
|
var junction = JunctionTemplate.Create(channel.Id, "Прайм");
|
|
var template = ScheduleTemplate.Create(channel.Id, "Сетка");
|
|
var layer = template.AddLayer("Прайм", 10);
|
|
var slot = layer.AddSlot("Вечернее кино", new TimeOnly(20, 0), 120);
|
|
slot.UpdateContent(
|
|
new SlotContent(
|
|
slot.Title,
|
|
SlotKind.Content,
|
|
group.Id,
|
|
new SlotStrategy(SlotStrategyType.RandomWithCooldown, CooldownDays: 14).ToJson(),
|
|
null,
|
|
SlotBlockMode.FillSlot,
|
|
1,
|
|
OverflowPolicy.ContinueNext,
|
|
JunctionAfterId: junction.Id
|
|
)
|
|
);
|
|
|
|
var trace = new PlanTrace(
|
|
slot.Id,
|
|
SlotKind.Content,
|
|
GroupElementKind.Show,
|
|
show.Id,
|
|
SlotStrategyKind.RandomWithCooldown,
|
|
CandidatesAfterCooldown: 4,
|
|
DriftMinutes: 3,
|
|
Snapped: true
|
|
);
|
|
var entry = ScheduleEntry.FromSlot(
|
|
channel.Id,
|
|
asset.Id,
|
|
ScheduleEntryKind.Program,
|
|
T0,
|
|
T0.AddMinutes(90),
|
|
new ScheduleEntryOrigin(
|
|
show.Id,
|
|
2,
|
|
slot.Id,
|
|
JsonSerializer.Serialize(trace, TraceJson),
|
|
collection.Id
|
|
)
|
|
);
|
|
|
|
await using (var seed = fixture.New())
|
|
{
|
|
seed.Channels.Add(channel);
|
|
seed.Groups.Add(group);
|
|
seed.Collections.Add(collection);
|
|
seed.Shows.Add(show);
|
|
seed.MediaAssets.Add(asset);
|
|
seed.JunctionTemplates.Add(junction);
|
|
seed.ScheduleTemplates.Add(template);
|
|
seed.ScheduleEntries.Add(entry);
|
|
await seed.SaveChangesAsync(CancellationToken.None);
|
|
}
|
|
|
|
await using var db = fixture.New();
|
|
var result = await new GetEntryTraceQueryHandler(db).Handle(
|
|
new GetEntryTraceQuery(entry.Id),
|
|
CancellationToken.None
|
|
);
|
|
|
|
Assert.True(result.IsSuccess);
|
|
var dto = result.Value;
|
|
Assert.Equal("Фильм", dto.ShowName);
|
|
Assert.Equal(2, dto.EpisodeIndex);
|
|
Assert.Equal("Франшиза", dto.CollectionName);
|
|
Assert.Equal("Прайм", dto.LayerName);
|
|
Assert.Equal(10, dto.LayerPriority);
|
|
Assert.Equal("Вечернее кино", dto.SlotTitle);
|
|
Assert.Equal(new TimeOnly(20, 0), dto.SlotTargetStart);
|
|
Assert.Equal(120, dto.SlotDurationMinutes);
|
|
Assert.Equal("Кино", dto.GroupName);
|
|
Assert.Equal(SlotStrategyKind.RandomWithCooldown, dto.Strategy);
|
|
// Остывание берётся из текущей стратегии слота, а число кандидатов — из трейса.
|
|
Assert.Equal(14, dto.CooldownDays);
|
|
Assert.Equal(4, dto.CandidatesAfterCooldown);
|
|
Assert.Equal(3, dto.DriftMinutes);
|
|
Assert.True(dto.Snapped);
|
|
Assert.Equal("Прайм", dto.JunctionName);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData("{не json}")]
|
|
public async Task Trace_WithoutUsableTrace_ReturnsBareEntry(string? traceJson)
|
|
{
|
|
var fixture = new TestDb();
|
|
var channel = Channel.Create("Первый", "one", T0);
|
|
var show = Show.Create("Фильм", ShowKind.Single);
|
|
var asset = MediaAsset.Register("film.mkv", ".mkv", MediaSource.Upload);
|
|
var entry = ScheduleEntry.FromSlot(
|
|
channel.Id,
|
|
asset.Id,
|
|
ScheduleEntryKind.Program,
|
|
T0,
|
|
T0.AddMinutes(90),
|
|
new ScheduleEntryOrigin(show.Id, 1, null, traceJson)
|
|
);
|
|
|
|
await using (var seed = fixture.New())
|
|
{
|
|
seed.Channels.Add(channel);
|
|
seed.Shows.Add(show);
|
|
seed.MediaAssets.Add(asset);
|
|
seed.ScheduleEntries.Add(entry);
|
|
await seed.SaveChangesAsync(CancellationToken.None);
|
|
}
|
|
|
|
await using var db = fixture.New();
|
|
var result = await new GetEntryTraceQueryHandler(db).Handle(
|
|
new GetEntryTraceQuery(entry.Id),
|
|
CancellationToken.None
|
|
);
|
|
|
|
// Старая запись (или битый трейс) — ответ есть, подписи пустые.
|
|
Assert.True(result.IsSuccess);
|
|
Assert.Equal("Фильм", result.Value.ShowName);
|
|
Assert.Null(result.Value.LayerName);
|
|
Assert.Null(result.Value.SlotTitle);
|
|
Assert.Null(result.Value.Strategy);
|
|
Assert.Equal(0, result.Value.DriftMinutes);
|
|
Assert.False(result.Value.Snapped);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Trace_WithDeletedSlot_KeepsWhatTraceRemembers()
|
|
{
|
|
var fixture = new TestDb();
|
|
var channel = Channel.Create("Первый", "one", T0);
|
|
var asset = MediaAsset.Register("film.mkv", ".mkv", MediaSource.Upload);
|
|
var trace = new PlanTrace(
|
|
Guid.NewGuid(),
|
|
SlotKind.Repeat,
|
|
null,
|
|
null,
|
|
SlotStrategyKind.Sequential,
|
|
null,
|
|
DriftMinutes: 0,
|
|
Snapped: false
|
|
);
|
|
var entry = ScheduleEntry.FromSlot(
|
|
channel.Id,
|
|
asset.Id,
|
|
ScheduleEntryKind.Program,
|
|
T0,
|
|
T0.AddMinutes(30),
|
|
new ScheduleEntryOrigin(null, null, null, JsonSerializer.Serialize(trace, TraceJson))
|
|
);
|
|
|
|
await using (var seed = fixture.New())
|
|
{
|
|
seed.Channels.Add(channel);
|
|
seed.MediaAssets.Add(asset);
|
|
seed.ScheduleEntries.Add(entry);
|
|
await seed.SaveChangesAsync(CancellationToken.None);
|
|
}
|
|
|
|
await using var db = fixture.New();
|
|
var result = await new GetEntryTraceQueryHandler(db).Handle(
|
|
new GetEntryTraceQuery(entry.Id),
|
|
CancellationToken.None
|
|
);
|
|
|
|
Assert.True(result.IsSuccess);
|
|
// Слота уже нет — из трейса остаётся его тип и стратегия, остальное пустеет.
|
|
Assert.Equal(SlotKind.Repeat, result.Value.SlotKind);
|
|
Assert.Equal(SlotStrategyKind.Sequential, result.Value.Strategy);
|
|
Assert.Null(result.Value.SlotTitle);
|
|
Assert.Null(result.Value.GroupName);
|
|
Assert.Null(result.Value.JunctionName);
|
|
Assert.Null(result.Value.ShowName);
|
|
}
|
|
}
|