Files
TeleWave/backend/tests/TeleWave.Application.Tests/Programming/GroupSuggestionsTests.cs
T
Leonid Pershin 24fdbdf680
ci / build-backend (push) Successful in 1m55s
ci / build-frontend (push) Successful in 44s
ci / tests (push) Successful in 2m8s
ci / sonar (push) Successful in 4m37s
Add group suggestions feature with API endpoints and frontend integration
Implemented new API endpoints for suggesting groups based on the current library, allowing admins to retrieve and create groups from suggestions. Updated the backend to include error handling for suggestions and added necessary DTOs. Enhanced the frontend with new API functions and UI components to display suggestions, improving the user experience for group management. Localization updates were made to support new features in both English and Russian.
2026-07-27 04:18:30 +03:00

192 lines
7.3 KiB
C#

using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Groups.Suggest;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using TeleWave.Domain.Programming;
using Xunit;
namespace TeleWave.Application.Tests.Programming;
/// <summary>
/// Предложения групп: разбор библиотеки должен быть объяснимым — что показали, то и создастся.
/// </summary>
public class GroupSuggestionsTests
{
[Fact]
public async Task SuggestsGenre_WhenAtLeastTwoShowsShareIt()
{
var fixture = new TestDb();
var cartoons = Genre.Create("Мультфильм", "animation");
var drama = Genre.Create("Драма", "drama");
var first = WithGenre(Show.Create("A", ShowKind.Series), cartoons);
var second = WithGenre(Show.Create("B", ShowKind.Series), cartoons);
// Один драматический — на группу не тянет.
var lonely = WithGenre(Show.Create("C", ShowKind.Single), drama);
await using (var seed = fixture.New())
{
seed.Genres.AddRange(cartoons, drama);
seed.Shows.AddRange(first, second, lonely);
await seed.SaveChangesAsync(CancellationToken.None);
}
var suggestions = await BuildAsync(fixture);
var genre = Assert.Single(suggestions, s => s.Kind == GroupSuggestionKind.Genre);
Assert.Equal("Мультфильм", genre.Name);
Assert.Equal(2, genre.ShowIds.Count);
Assert.Equal([cartoons.Id], genre.Filter!.GenreIds);
}
[Fact]
public async Task SuggestsBigSeries_Separately()
{
var fixture = new TestDb();
var big = Show.Create("Гравити Фолз", ShowKind.Series);
for (var i = 0; i < 20; i++)
big.AddEpisode(Guid.NewGuid());
var small = Show.Create("Короткое", ShowKind.Series);
small.AddEpisode(Guid.NewGuid());
await using (var seed = fixture.New())
{
seed.Shows.AddRange(big, small);
await seed.SaveChangesAsync(CancellationToken.None);
}
var suggestions = await BuildAsync(fixture);
var series = Assert.Single(suggestions, s => s.Kind == GroupSuggestionKind.BigSeries);
Assert.Equal("Гравити Фолз", series.Name);
Assert.Equal([big.Id], series.ShowIds);
// Правила у одиночного шоу нет: «именно это шоу» фильтром не выражается.
Assert.Null(series.Filter);
}
[Fact]
public async Task SkipsAudienceTier_WhenItCoversEverything()
{
// Если под «не строже PG» попадает вся библиотека, группа повторяет её — предлагать нечего.
var fixture = new TestDb();
var first = Show.Create("A", ShowKind.Series);
first.SetAudience(ShowAudience.G);
var second = Show.Create("B", ShowKind.Series);
second.SetAudience(ShowAudience.Pg);
await using (var seed = fixture.New())
{
seed.Shows.AddRange(first, second);
await seed.SaveChangesAsync(CancellationToken.None);
}
var suggestions = await BuildAsync(fixture);
Assert.DoesNotContain(suggestions, s => s.Kind == GroupSuggestionKind.Audience);
}
[Fact]
public async Task SuggestsAudienceTier_WhenStricterContentExists()
{
var fixture = new TestDb();
var kids = Show.Create("A", ShowKind.Series);
kids.SetAudience(ShowAudience.G);
var adult = Show.Create("B", ShowKind.Series);
adult.SetAudience(ShowAudience.R);
var unrated = Show.Create("C", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.AddRange(kids, adult, unrated);
await seed.SaveChangesAsync(CancellationToken.None);
}
var suggestions = await BuildAsync(fixture);
var tier = suggestions.First(s => s.Kind == GroupSuggestionKind.Audience);
// Непроставленный рейтинг остаётся в выборке — как в правиле набора и в планировщике.
Assert.Equal(2, tier.ShowIds.Count);
Assert.Contains(kids.Id, tier.ShowIds);
Assert.Contains(unrated.Id, tier.ShowIds);
}
[Fact]
public async Task CreateFromSuggestion_SavesFilterAndComposition()
{
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);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.Shows.AddRange(first, second);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new CreateGroupFromSuggestionCommandHandler(
db,
Builder(db),
new GroupStatsService(db, new GroupElementResolver(db))
);
var created = await handler.Handle(
new CreateGroupFromSuggestionCommand($"genre:{genre.Id:N}"),
CancellationToken.None
);
Assert.True(created.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
await using var check = fixture.New();
var group = check.Groups.Include(g => g.Items).Single();
Assert.Equal("Мультфильм", group.Name);
Assert.Equal(2, group.Items.Count);
Assert.All(group.Items, item => Assert.Equal(GroupElementKind.Show, item.ElementKind));
Assert.Equal([genre.Id], GroupFilter.FromJson(group.FilterJson)!.GenreIds);
// Повторное создание того же — конфликт: две одноимённые группы в слоте неразличимы.
var again = await handler.Handle(
new CreateGroupFromSuggestionCommand($"genre:{genre.Id:N}"),
CancellationToken.None
);
Assert.False(again.IsSuccess);
}
[Fact]
public async Task CreateFromSuggestion_FailsForUnknownKey()
{
var fixture = new TestDb();
await using (var seed = fixture.New())
{
seed.Shows.Add(Show.Create("A", ShowKind.Series));
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new CreateGroupFromSuggestionCommandHandler(
db,
Builder(db),
new GroupStatsService(db, new GroupElementResolver(db))
).Handle(new CreateGroupFromSuggestionCommand("genre:нет-такого"), CancellationToken.None);
Assert.False(result.IsSuccess);
}
private static Show WithGenre(Show show, Genre genre)
{
show.SetGenres([genre.Id]);
return show;
}
private static GroupSuggestionBuilder Builder(Infrastructure.Persistence.AppDbContext db) =>
new(db, new GroupElementResolver(db));
private static async Task<IReadOnlyList<GroupSuggestion>> BuildAsync(TestDb fixture)
{
await using var db = fixture.New();
return await Builder(db).BuildAsync(CancellationToken.None);
}
}