Files
TeleWave/backend/tests/TeleWave.Application.Tests/Programming/DynamicGroupsTests.cs
T
Leonid Pershin 375e0a810b
ci / build-backend (push) Successful in 1m36s
ci / build-frontend (push) Successful in 52s
ci / tests (push) Successful in 2m3s
ci / sonar (push) Successful in 6m21s
Refactor group mode switching logic to ensure proper list management
Updated the Group and UpdateGroupCommandHandler classes to clarify the behavior of group mode switching. The transition to dynamic mode now clears the existing item list to prevent old items from interfering with the new rule-based composition. Enhanced documentation and comments to explain the implications of mode changes. Updated tests to reflect the new behavior, ensuring that the group correctly handles item lists during mode transitions. Localization strings were also updated to inform users about the changes in item management during mode switching.
2026-07-31 04:22:36 +03:00

250 lines
10 KiB
C#

using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Groups.ExcludeGroupElement;
using TeleWave.Application.Programming.Groups.UpdateGroup;
using TeleWave.Application.Programming.Planning;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using TeleWave.Domain.Programming;
using Xunit;
namespace TeleWave.Application.Tests.Programming;
/// <summary>
/// Динамическая группа: состав считает правило, а список позиций хранит только ручные поправки.
/// Проверяем именно то, что уходит в эфир, — иначе расхождение между показанным и сыгранным.
/// </summary>
public class DynamicGroupsTests
{
[Fact]
public async Task Composition_IsRuleResult_PinnedFirstThenAlphabetical()
{
var fixture = new TestDb();
var genre = Genre.Create("Мультфильм", "animation");
var beta = WithGenre(Show.Create("Бета", ShowKind.Series), genre);
var alpha = WithGenre(Show.Create("Альфа", ShowKind.Series), genre);
// Закреплённое правилу не подходит, но остаётся в составе — и идёт первым.
var pinned = Show.Create("Закреплённое", ShowKind.Single);
var group = Group.Create("Мультфильмы");
group.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());
group.SetMode(GroupMode.Dynamic);
// Закрепляют уже в динамическом режиме: там список — это поправки к правилу.
group.AddElement(GroupElementKind.Show, pinned.Id);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.Shows.AddRange(alpha, beta, pinned);
seed.Groups.Add(group);
await seed.SaveChangesAsync(CancellationToken.None);
}
var composition = await ResolveAsync(fixture, group.Id);
Assert.Equal([pinned.Id, alpha.Id, beta.Id], composition.Select(e => e.Id));
Assert.True(composition[0].Pinned);
Assert.All(composition.Skip(1), e => Assert.False(e.Pinned));
}
[Fact]
public async Task Excluded_DropsOutOfComposition()
{
var fixture = new TestDb();
var genre = Genre.Create("Мультфильм", "animation");
var keep = WithGenre(Show.Create("Остаётся", ShowKind.Series), genre);
var drop = WithGenre(Show.Create("Выкинуть", ShowKind.Series), genre);
var group = Group.Create("Мультфильмы");
group.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());
group.SetMode(GroupMode.Dynamic);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.Shows.AddRange(keep, drop);
seed.Groups.Add(group);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using (var db = fixture.New())
{
var result = await new ExcludeGroupElementCommandHandler(
db,
GroupServices.Stats(db)
).Handle(
new ExcludeGroupElementCommand(group.Id, GroupElementKind.Show, drop.Id, true),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
var composition = await ResolveAsync(fixture, group.Id);
Assert.Equal([keep.Id], composition.Select(e => e.Id));
// Снятие исключения возвращает элемент правилу.
await using (var db = fixture.New())
{
await new ExcludeGroupElementCommandHandler(db, GroupServices.Stats(db)).Handle(
new ExcludeGroupElementCommand(group.Id, GroupElementKind.Show, drop.Id, false),
CancellationToken.None
);
await db.SaveChangesAsync(CancellationToken.None);
}
Assert.Equal(2, (await ResolveAsync(fixture, group.Id)).Count);
}
[Fact]
public async Task SwitchToDynamic_DropsTheOldListAndLetsTheRuleDecide()
{
// Переключили «по правилу» — значит состав считает правило. Прежний список, перенесённый
// закреплённым, оставался бы в группе навсегда: сузить набор правилом было бы нечем,
// и «динамическая» отличалась бы от статической только названием.
var fixture = new TestDb();
var genre = Genre.Create("Мультфильм", "animation");
var byRule = WithGenre(Show.Create("По правилу", ShowKind.Series), genre);
var old = Show.Create("Из старого списка", ShowKind.Series);
var group = Group.Create("Группа");
group.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());
group.AddElement(GroupElementKind.Show, old.Id);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.Shows.AddRange(byRule, old);
seed.Groups.Add(group);
await seed.SaveChangesAsync(CancellationToken.None);
}
await UpdateModeAsync(fixture, group.Id, GroupMode.Dynamic);
await using var check = fixture.New();
var stored = check.Groups.Include(g => g.Items).Single();
Assert.Equal(GroupMode.Dynamic, stored.Mode);
Assert.Empty(stored.Items);
// В составе — только то, что нашло правило: шоу из прежнего списка под него не подходит.
var composition = await ResolveAsync(fixture, group.Id);
Assert.Equal([byRule.Id], composition.Select(e => e.Id));
}
[Fact]
public async Task SwitchToStatic_MaterializesComputedComposition()
{
var fixture = new TestDb();
var genre = Genre.Create("Мультфильм", "animation");
var first = WithGenre(Show.Create("A", ShowKind.Series), genre);
var second = WithGenre(Show.Create("B", ShowKind.Series), genre);
var group = Group.Create("Мультфильмы");
group.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());
group.SetMode(GroupMode.Dynamic);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.Shows.AddRange(first, second);
seed.Groups.Add(group);
await seed.SaveChangesAsync(CancellationToken.None);
}
await UpdateModeAsync(fixture, group.Id, GroupMode.Static);
await using var check = fixture.New();
var stored = check.Groups.Include(g => g.Items).Single();
Assert.Equal(GroupMode.Static, stored.Mode);
Assert.Equal(2, stored.Items.Count);
Assert.All(stored.Items, item => Assert.Equal(GroupItemRole.Member, item.Role));
}
[Fact]
public async Task Planner_ExpandsDynamicGroupByRule()
{
// Главное свойство: правило применяется на самой генерации, а не когда-то до неё.
var fixture = new TestDb();
var genre = Genre.Create("Мультфильм", "animation");
var asset = MediaAsset.Register("a.mkv", ".mkv", MediaSource.Upload);
asset.MarkProcessing();
asset.MarkReady(
new MediaReadyInfo(
TimeSpan.FromMinutes(22),
2,
660,
1920,
1080,
"h264",
"aac",
"assets/a"
)
);
var show = WithGenre(Show.Create("A", ShowKind.Series), genre);
show.AddEpisode(asset.Id);
var group = Group.Create("Мультфильмы");
group.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());
group.SetMode(GroupMode.Dynamic);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.MediaAssets.Add(asset);
seed.Shows.Add(show);
seed.Groups.Add(group);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var expanded = await new GroupExpander(db, GroupServices.Dynamic(db)).ExpandAsync(
[group.Id],
Guid.NewGuid(),
CancellationToken.None
);
var element = Assert.Single(expanded[group.Id]);
Assert.Equal(show.Id, element.ElementId);
Assert.Single(element.Units);
}
private static Show WithGenre(Show show, Genre genre)
{
show.SetGenres([genre.Id]);
return show;
}
private static async Task<IReadOnlyList<ResolvedElement>> ResolveAsync(
TestDb fixture,
Guid groupId
)
{
await using var db = fixture.New();
var group = await db.Groups.Include(g => g.Items).FirstAsync(g => g.Id == groupId);
return await GroupServices.Dynamic(db).ResolveAsync(group, CancellationToken.None);
}
private static async Task UpdateModeAsync(TestDb fixture, Guid groupId, GroupMode mode)
{
await using var db = fixture.New();
var group = await db.Groups.FirstAsync(g => g.Id == groupId);
var result = await new UpdateGroupCommandHandler(
db,
GroupServices.Dynamic(db),
GroupServices.Stats(db)
).Handle(
new UpdateGroupCommand(
groupId,
group.Name,
group.Description,
GroupFilter.FromJson(group.FilterJson),
mode
),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
}