Files
TeleWave/backend/tests/TeleWave.Application.Tests/Library/CollectionSuggestionsTests.cs
T
Leonid Pershin ac41111fa0
ci / build-backend (push) Successful in 1m40s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 1m46s
ci / sonar (push) Successful in 6m47s
Implement functionality to add shows to existing collections from suggestions
Added a new endpoint for adding shows to already assembled franchises based on the same suggestion key. Updated the CollectionSuggestion and CollectionSuggestionDto records to include a CollectionId for tracking existing collections. Enhanced the CollectionSuggestions component in the frontend to support this new functionality, allowing users to add missing parts to existing collections. Updated localization strings to reflect the new addition feature in both English and Russian, ensuring clarity for users. Adjusted tests to verify the correct behavior of the addition process and its integration with existing collection logic.
2026-07-28 14:49:27 +03:00

273 lines
11 KiB
C#

using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Library.Collections;
using TeleWave.Application.Library.Collections.AddShowsToCollection;
using TeleWave.Application.Library.Collections.Suggest;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using Xunit;
namespace TeleWave.Application.Tests.Library;
/// <summary>
/// Сборка франшиз: предложения по метаданным и по названиям плюс массовое добавление в коллекцию.
/// Ошибка здесь означает чужой фильм в середине франшизы, поэтому осторожность важнее полноты.
/// </summary>
public class CollectionSuggestionsTests
{
[Fact]
public async Task SuggestsFranchise_FromMetadata()
{
var fixture = new TestDb();
var first = Movie("Гарри Поттер и философский камень", 2001, franchise: "1241");
var second = Movie("Гарри Поттер и тайная комната", 2002, franchise: "1241");
var other = Movie("Совсем другое", 1999, franchise: "777");
await SeedAsync(fixture, first, second, other);
var suggestions = await BuildAsync(fixture);
var franchise = Assert.Single(
suggestions,
s => s.Source == CollectionSuggestionSource.Metadata
);
Assert.Equal("Франшиза 1241", franchise.Name);
// Порядок — по году: это и есть порядок просмотра.
Assert.Equal([first.Id, second.Id], franchise.Parts.Select(p => p.ShowId));
}
[Fact]
public async Task SuggestsByName_WhenMetadataMissing()
{
var fixture = new TestDb();
var first = Movie("Рокки", 1976);
var second = Movie("Рокки 2", 1979);
await SeedAsync(fixture, first, second);
var suggestions = await BuildAsync(fixture);
var guess = Assert.Single(
suggestions,
s => s.Source == CollectionSuggestionSource.NameGuess
);
Assert.Equal(2, guess.Parts.Count);
}
[Fact]
public async Task DoesNotGuess_OnShortCommonStart()
{
// «Человек-паук» и «Человек дождя» — не франшиза, хотя начало совпадает.
var fixture = new TestDb();
await SeedAsync(fixture, Movie("Человек дождя", 1988), Movie("Человек-слон", 1980));
var suggestions = await BuildAsync(fixture);
Assert.DoesNotContain(suggestions, s => s.Source == CollectionSuggestionSource.NameGuess);
}
/// <summary>
/// Часть, приехавшая после сборки коллекции, сама франшизы не образует — её предлагают
/// добавить в существующую. Иначе она не показывалась бы нигде: создавать вторую франшизу
/// с тем же именем нельзя.
/// </summary>
[Fact]
public async Task SuggestsAddition_ToExistingCollection_ByMetadata()
{
var fixture = new TestDb();
var first = Movie("Рокки", 1976, franchise: "1241");
var second = Movie("Рокки 2", 1979, franchise: "1241");
var collection = Collection.Create("Рокки");
collection.AddShow(first.Id);
await SeedWithCollectionAsync(fixture, collection, first, second);
var suggestion = Assert.Single(await BuildAsync(fixture));
Assert.Equal(collection.Id, suggestion.CollectionId);
Assert.Equal(CollectionSuggestionSource.Metadata, suggestion.Source);
Assert.Equal("Рокки", suggestion.Name);
// В дополнении только недостающее: то, что уже в коллекции, предлагать нечего.
Assert.Equal([second.Id], suggestion.Parts.Select(p => p.ShowId));
}
/// <summary>Без метаданных дополнение опирается на догадку — и подписано как догадка.</summary>
[Fact]
public async Task SuggestsAddition_ToExistingCollection_ByName()
{
var fixture = new TestDb();
var first = Movie("Рокки", 1976);
var second = Movie("Рокки 2", 1979);
var collection = Collection.Create("Рокки");
collection.AddShow(first.Id);
await SeedWithCollectionAsync(fixture, collection, first, second);
var suggestion = Assert.Single(await BuildAsync(fixture));
Assert.Equal(collection.Id, suggestion.CollectionId);
Assert.Equal(CollectionSuggestionSource.NameGuess, suggestion.Source);
Assert.Equal([second.Id], suggestion.Parts.Select(p => p.ShowId));
}
/// <summary>Чужой фильм в существующую франшизу не уезжает — правила те же, что при сборке.</summary>
[Fact]
public async Task DoesNotSuggestAddition_ForUnrelatedMovie()
{
var fixture = new TestDb();
var member = Movie("Рокки", 1976, franchise: "1241");
var stranger = Movie("Человек дождя", 1988, franchise: "777");
var collection = Collection.Create("Рокки");
collection.AddShow(member.Id);
await SeedWithCollectionAsync(fixture, collection, member, stranger);
Assert.Empty(await BuildAsync(fixture));
}
/// <summary>
/// Фильм, попавший в дополнение, из предложений «создать» уходит: два предложения на один
/// фильм заставили бы выбирать вслепую, а создание к тому же упёрлось бы в занятое имя.
/// </summary>
[Fact]
public async Task AdditionWins_OverNewFranchise()
{
var fixture = new TestDb();
var member = Movie("Рокки", 1976, franchise: "1241");
var second = Movie("Рокки 2", 1979, franchise: "1241");
var third = Movie("Рокки 3", 1982, franchise: "1241");
var collection = Collection.Create("Рокки");
collection.AddShow(member.Id);
await SeedWithCollectionAsync(fixture, collection, member, second, third);
var suggestion = Assert.Single(await BuildAsync(fixture));
Assert.Equal(collection.Id, suggestion.CollectionId);
Assert.Equal([second.Id, third.Id], suggestion.Parts.Select(p => p.ShowId));
}
[Fact]
public async Task AddSuggestedShows_AppendsToCollection()
{
var fixture = new TestDb();
var first = Movie("Рокки", 1976, franchise: "1241");
var second = Movie("Рокки 2", 1979, franchise: "1241");
var collection = Collection.Create("Рокки");
collection.AddShow(first.Id);
await SeedWithCollectionAsync(fixture, collection, first, second);
var key = (await BuildAsync(fixture)).Single().Key;
await using (var db = fixture.New())
{
var result = await new AddSuggestedShowsCommandHandler(
db,
new CollectionSuggestionBuilder(db),
new CollectionPosterPicker(db)
).Handle(new AddSuggestedShowsCommand(key), CancellationToken.None);
Assert.True(result.IsSuccess);
Assert.Equal(1, result.Value);
await db.SaveChangesAsync(CancellationToken.None);
}
await using var check = fixture.New();
var stored = check.Collections.Include(c => c.Items).Single();
// Дополнение встаёт в конец в хронологии — франшиза остаётся в порядке просмотра.
Assert.Equal(
[first.Id, second.Id],
stored.Items.OrderBy(i => i.Position).Select(i => i.ShowId)
);
// Предложение исчерпано: добавлять больше нечего.
Assert.Empty(await BuildAsync(fixture));
}
/// <summary>Ключ предложения «создать» дополнением не применяется — коллекции у него нет.</summary>
[Fact]
public async Task AddSuggestedShows_RejectsCreationKey()
{
var fixture = new TestDb();
await SeedAsync(fixture, Movie("Рокки", 1976), Movie("Рокки 2", 1979));
var key = (await BuildAsync(fixture)).Single().Key;
await using var db = fixture.New();
var result = await new AddSuggestedShowsCommandHandler(
db,
new CollectionSuggestionBuilder(db),
new CollectionPosterPicker(db)
).Handle(new AddSuggestedShowsCommand(key), CancellationToken.None);
Assert.False(result.IsSuccess);
Assert.Equal(CollectionErrors.SuggestionNotFound.Code, result.Error.Code);
}
[Fact]
public async Task AddShowsToCollection_CreatesNewAndOrdersByYear()
{
var fixture = new TestDb();
var later = Movie("Вторая", 1979);
var earlier = Movie("Первая", 1976);
await SeedAsync(fixture, later, earlier);
await using var db = fixture.New();
var result = await new AddShowsToCollectionCommandHandler(db).Handle(
new AddShowsToCollectionCommand([later.Id, earlier.Id], NewCollectionName: "Франшиза"),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
await using var check = fixture.New();
var collection = check.Collections.Include(c => c.Items).Single();
Assert.Equal(2, result.Value.Added);
Assert.Equal(
[earlier.Id, later.Id],
collection.Items.OrderBy(i => i.Position).Select(i => i.ShowId)
);
}
private static Show Movie(string name, int? year, string? franchise = null)
{
var show = Show.Create(name, ShowKind.Single);
if (franchise is not null)
show.ApplyMetadata(
"tmdb",
Guid.NewGuid().ToString("N"),
null,
year,
null,
new MetadataFranchise(franchise, $"Франшиза {franchise}")
);
else
show.ApplyMetadata("manual", Guid.NewGuid().ToString("N"), null, year, null);
return show;
}
private static async Task SeedAsync(TestDb fixture, params Show[] shows)
{
await using var seed = fixture.New();
seed.Shows.AddRange(shows);
await seed.SaveChangesAsync(CancellationToken.None);
}
private static async Task SeedWithCollectionAsync(
TestDb fixture,
Collection collection,
params Show[] shows
)
{
await using var seed = fixture.New();
seed.Shows.AddRange(shows);
seed.Collections.Add(collection);
await seed.SaveChangesAsync(CancellationToken.None);
}
private static async Task<IReadOnlyList<CollectionSuggestion>> BuildAsync(TestDb fixture)
{
await using var db = fixture.New();
return await new CollectionSuggestionBuilder(db).BuildAsync(CancellationToken.None);
}
}