Enhance scheduling and trace functionalities: add CollectionId to ScheduleEntry and PlannedItem models, update related query handlers and frontend components to support collection information in entry traces. Improve data handling in scheduling logic and enhance user experience in trace display.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Programming.Templates;
|
||||
using TeleWave.Application.Programming.Templates.CopyTemplate;
|
||||
using TeleWave.Application.Programming.Templates.Validate;
|
||||
using TeleWave.Domain.Broadcast;
|
||||
using TeleWave.Domain.Programming;
|
||||
using TeleWave.Infrastructure.Persistence;
|
||||
using Xunit;
|
||||
|
||||
namespace TeleWave.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Операции над шаблоном против настоящей БД: копирование на другой канал и проверки по правилам.
|
||||
/// Обе штуки — сплошные запросы EF и перекладывание графов, в юнит-тестах их не поймать.
|
||||
/// </summary>
|
||||
[Collection("postgres")]
|
||||
public sealed class TemplateOperationsIntegrationTests(PostgresFixture fixture)
|
||||
{
|
||||
[SkippableFact]
|
||||
public async Task Copy_MovesLayersSlotsAndJunctions_AndReplacesTargetGrid()
|
||||
{
|
||||
Skip.IfNot(fixture.Available, "Docker недоступен");
|
||||
|
||||
await using var seedDb = fixture.CreateContext();
|
||||
var (sourceId, targetId, groupId) = await SeedPairAsync(seedDb);
|
||||
|
||||
await using var db = fixture.CreateContext();
|
||||
var result = await new CopyTemplateCommandHandler(db).Handle(
|
||||
new CopyTemplateCommand(sourceId, targetId),
|
||||
default
|
||||
);
|
||||
Assert.True(result.IsSuccess);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Assert.Equal(1, result.Value.Layers);
|
||||
Assert.Equal(1, result.Value.Slots);
|
||||
Assert.Equal(1, result.Value.Junctions);
|
||||
|
||||
await using var verify = fixture.CreateContext();
|
||||
var target = verify.Channels.Single(c => c.Id == targetId);
|
||||
var copied = verify
|
||||
.ScheduleTemplates.Include(t => t.Layers)
|
||||
.ThenInclude(l => l.Slots)
|
||||
.Single(t => t.ChannelId == targetId);
|
||||
|
||||
// У приёмника ровно один шаблон, и канал смотрит именно на него.
|
||||
Assert.Equal(copied.Id, target.TemplateId);
|
||||
Assert.Single(verify.ScheduleTemplates.Where(t => t.ChannelId == targetId));
|
||||
|
||||
var slot = copied.Layers.SelectMany(l => l.Slots).Single();
|
||||
// Группы общие — ссылка переносится как есть, а не копией группы.
|
||||
Assert.Equal(groupId, slot.GroupId);
|
||||
Assert.Single(verify.Groups.Where(g => g.Id == groupId));
|
||||
|
||||
// Стык переехал своей копией, и слот ссылается на неё, а не на стык чужого канала.
|
||||
var junction = verify.JunctionTemplates.Single(j => j.ChannelId == targetId);
|
||||
Assert.Equal(junction.Id, slot.JunctionAfterId);
|
||||
Assert.NotEqual(
|
||||
verify.JunctionTemplates.Single(j => j.ChannelId == sourceId).Id,
|
||||
slot.JunctionAfterId
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_ToItself_IsRejectedByValidator()
|
||||
{
|
||||
var validator = new CopyTemplateCommandValidator();
|
||||
var id = Guid.NewGuid();
|
||||
|
||||
Assert.False(validator.Validate(new CopyTemplateCommand(id, id)).IsValid);
|
||||
Assert.True(validator.Validate(new CopyTemplateCommand(id, Guid.NewGuid())).IsValid);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task Validate_ReportsEmptyGroupAndGridGap()
|
||||
{
|
||||
Skip.IfNot(fixture.Available, "Docker недоступен");
|
||||
|
||||
await using var seedDb = fixture.CreateContext();
|
||||
var (sourceId, _, _) = await SeedPairAsync(seedDb);
|
||||
|
||||
await using var db = fixture.CreateContext();
|
||||
var result = await new ValidateTemplateQueryHandler(db).Handle(
|
||||
new ValidateTemplateQuery(sourceId),
|
||||
default
|
||||
);
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
// Группа в сиде пустая, а слот занимает лишь два часа суток — обе проверки должны сработать.
|
||||
Assert.Contains(result.Value, i => i.Kind == TemplateIssueKind.GroupEmpty);
|
||||
Assert.Contains(result.Value, i => i.Kind == TemplateIssueKind.GridGap);
|
||||
}
|
||||
|
||||
/// <summary>Два канала: у источника слой со слотом, группой и стыком; у приёмника — пустая сетка.</summary>
|
||||
private static async Task<(Guid Source, Guid Target, Guid GroupId)> SeedPairAsync(AppDbContext db)
|
||||
{
|
||||
var suffix = Guid.NewGuid().ToString("N")[..8];
|
||||
|
||||
var group = Group.Create($"Группа {suffix}");
|
||||
db.Groups.Add(group);
|
||||
|
||||
var source = Channel.Create($"Источник {suffix}", $"src-{suffix}", DateTimeOffset.UtcNow);
|
||||
var target = Channel.Create($"Приёмник {suffix}", $"dst-{suffix}", DateTimeOffset.UtcNow);
|
||||
|
||||
var junction = JunctionTemplate.Create(source.Id, "Прайм");
|
||||
var ad = junction.AddElement(JunctionElementKind.Ad);
|
||||
ad.Update(JunctionElementKind.Ad, group.Id, null, JunctionAmountMode.Count, 2, true, null);
|
||||
db.JunctionTemplates.Add(junction);
|
||||
|
||||
var sourceTemplate = ScheduleTemplate.Create(source.Id, "Сетка источника");
|
||||
var layer = sourceTemplate.AddLayer("Прайм", 10);
|
||||
var slot = layer.AddSlot("Вечернее кино", new TimeOnly(20, 0), 120);
|
||||
slot.UpdateContent(
|
||||
slot.Title,
|
||||
SlotKind.Content,
|
||||
group.Id,
|
||||
new SlotStrategy(SlotStrategyType.Sequential).ToJson(),
|
||||
null,
|
||||
SlotBlockMode.FillSlot,
|
||||
1,
|
||||
OverflowPolicy.ContinueNext,
|
||||
junctionAfterId: junction.Id
|
||||
);
|
||||
sourceTemplate.SetDefaultJunction(junction.Id);
|
||||
source.SetTemplate(sourceTemplate.Id);
|
||||
|
||||
var targetTemplate = ScheduleTemplate.Create(target.Id, "Сетка приёмника");
|
||||
target.SetTemplate(targetTemplate.Id);
|
||||
|
||||
db.Channels.AddRange(source, target);
|
||||
db.ScheduleTemplates.AddRange(sourceTemplate, targetTemplate);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return (source.Id, target.Id, group.Id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user