Implement bulk tagging and enriching of shows with new API endpoints and frontend integration
ci / build-backend (push) Successful in 2m4s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 1m59s
ci / sonar (push) Successful in 6m6s

Added new API endpoints for bulk tagging and enriching shows, allowing for mass updates of genres and audience ratings. Implemented backend logic to handle bulk operations and updated the Dependency Injection configuration to include necessary services. Enhanced the frontend with new components for selecting shows and applying bulk actions, improving the user experience for managing multiple shows simultaneously. Localization updates were made to support these new features in both English and Russian.
This commit is contained in:
Leonid Pershin
2026-07-27 05:18:18 +03:00
parent 91af589547
commit 1a7f73a5bd
25 changed files with 1011 additions and 107 deletions
@@ -0,0 +1,148 @@
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Library.BulkTagShows;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using Xunit;
namespace TeleWave.Application.Tests.Library;
/// <summary>
/// Массовая разметка: жанры и рейтинг — то, на что опираются группы контента, и проставлять их
/// по одному шоу нереально. Проверяем, что пакет не затирает лишнего.
/// </summary>
public class BulkTagShowsTests
{
[Fact]
public async Task AddsGenres_WithoutErasingExisting()
{
var fixture = new TestDb();
var existing = Genre.Create("Комедия", "comedy");
var added = Genre.Create("Мультфильм", "animation");
var show = Show.Create("A", ShowKind.Series);
show.SetGenres([existing.Id]);
await using (var seed = fixture.New())
{
seed.Genres.AddRange(existing, added);
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await RunAsync(fixture, new BulkTagShowsCommand([show.Id], GenreIds: [added.Id]));
await using var check = fixture.New();
var stored = check.Shows.Include(s => s.Genres).Single();
Assert.Equal(2, stored.Genres.Count);
// Основной жанр не должен незаметно переехать: его показывает список шоу.
Assert.Equal(existing.Id, stored.PrimaryGenreId);
}
[Fact]
public async Task ReplacesGenres_WhenAsked()
{
var fixture = new TestDb();
var old = Genre.Create("Комедия", "comedy");
var fresh = Genre.Create("Мультфильм", "animation");
var show = Show.Create("A", ShowKind.Series);
show.SetGenres([old.Id]);
await using (var seed = fixture.New())
{
seed.Genres.AddRange(old, fresh);
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await RunAsync(
fixture,
new BulkTagShowsCommand([show.Id], GenreIds: [fresh.Id], ReplaceGenres: true)
);
await using var check = fixture.New();
var stored = check.Shows.Include(s => s.Genres).Single();
Assert.Equal(fresh.Id, stored.Genres.Single().GenreId);
}
[Fact]
public async Task SetsAudience_AndLeavesGenresAlone()
{
var fixture = new TestDb();
var genre = Genre.Create("Комедия", "comedy");
var show = Show.Create("A", ShowKind.Series);
show.SetGenres([genre.Id]);
await using (var seed = fixture.New())
{
seed.Genres.Add(genre);
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await RunAsync(
fixture,
new BulkTagShowsCommand([show.Id], Audience: ShowAudience.Pg13, SetAudience: true)
);
await using var check = fixture.New();
var stored = check.Shows.Include(s => s.Genres).Single();
Assert.Equal(ShowAudience.Pg13, stored.Audience);
Assert.Single(stored.Genres);
}
[Fact]
public async Task FailsForUnknownGenre()
{
// Ссылка на несуществующий жанр упала бы нарушением внешнего ключа посреди пакета.
var fixture = new TestDb();
var show = Show.Create("A", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new BulkTagShowsCommandHandler(db).Handle(
new BulkTagShowsCommand([show.Id], GenreIds: [Guid.NewGuid()]),
CancellationToken.None
);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task SkipsMissingShows_WithoutFailing()
{
var fixture = new TestDb();
var show = Show.Create("A", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
var updated = await RunAsync(
fixture,
new BulkTagShowsCommand(
[show.Id, Guid.NewGuid()],
Audience: ShowAudience.R,
SetAudience: true
)
);
// Пока собирали галочки, шоу могли удалить — это не повод валить всю операцию.
Assert.Equal(1, updated);
}
private static async Task<int> RunAsync(TestDb fixture, BulkTagShowsCommand command)
{
await using var db = fixture.New();
var result = await new BulkTagShowsCommandHandler(db).Handle(
command,
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
return result.Value;
}
}