using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Library.Collections.AddShowsToCollection; using TeleWave.Application.Library.Genres; using TeleWave.Application.Metadata; using TeleWave.Application.Metadata.EnrichShows; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Library; using Xunit; namespace TeleWave.Application.Tests.Library; /// Массовые операции над отмеченными шоу: раскладка по франшизам и обогащение. public class AddShowsToCollectionTests { [Fact] public async Task AddsToExistingCollection_SkippingThoseAlreadyIn() { var fixture = new TestDb(); var inside = Show.Create("Уже внутри", ShowKind.Single); var outside = Show.Create("Ещё нет", ShowKind.Single); var collection = Collection.Create("Франшиза"); collection.AddShow(inside.Id); await using (var seed = fixture.New()) { seed.Shows.AddRange(inside, outside); seed.Collections.Add(collection); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); var result = await new AddShowsToCollectionCommandHandler(db).Handle( new AddShowsToCollectionCommand([inside.Id, outside.Id], collection.Id), CancellationToken.None ); await db.SaveChangesAsync(CancellationToken.None); Assert.True(result.IsSuccess); // Повторное добавление молча пропускается: падать из-за одного повтора бессмысленно. Assert.Equal(1, result.Value.Added); Assert.Equal("Франшиза", result.Value.Name); await using var check = fixture.New(); Assert.Equal(2, check.Collections.Include(c => c.Items).Single().Items.Count); } [Fact] public async Task Fails_WhenCollectionIsGone() { var fixture = new TestDb(); var show = Show.Create("Шоу", ShowKind.Single); 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 AddShowsToCollectionCommandHandler(db).Handle( new AddShowsToCollectionCommand([show.Id], Guid.NewGuid()), CancellationToken.None ); Assert.False(result.IsSuccess); } [Fact] public async Task Fails_WhenNoneOfTheShowsExist() { var fixture = new TestDb(); await using var db = fixture.New(); var result = await new AddShowsToCollectionCommandHandler(db).Handle( new AddShowsToCollectionCommand([Guid.NewGuid()], NewCollectionName: "Франшиза"), CancellationToken.None ); Assert.False(result.IsSuccess); } [Fact] public async Task Enrich_FailsWhenProviderUnavailable() { var fixture = new TestDb(); await using var db = fixture.New(); var resolver = Substitute.For(); // Явно: NSubstitute сам подставляет заглушку вместо интерфейсного null, и без этого // «источник не настроен» не воспроизводится. resolver.Resolve(Arg.Any()).Returns((IMetadataProvider?)null); var result = await new EnrichShowsMetadataCommandHandler( db, resolver, new ShowMetadataApplier( db, resolver, Substitute.For(), Substitute.For(), new GenreMatcher(db), NullLogger.Instance ) ).Handle(new EnrichShowsMetadataCommand([Guid.NewGuid()], "tmdb"), CancellationToken.None); Assert.False(result.IsSuccess); } [Fact] public async Task Enrich_IgnoresInterstitials() { // Ролики метаданными не обогащаются: у них нет ни года, ни постера. var fixture = new TestDb(); var clip = Show.Create("Ролик", ShowKind.Interstitial); await using (var seed = fixture.New()) { seed.Shows.Add(clip); await seed.SaveChangesAsync(CancellationToken.None); } var provider = Substitute.For(); var resolver = Substitute.For(); resolver.Resolve("tmdb").Returns(provider); await using var db = fixture.New(); var result = await new EnrichShowsMetadataCommandHandler( db, resolver, new ShowMetadataApplier( db, resolver, Substitute.For(), Substitute.For(), new GenreMatcher(db), NullLogger.Instance ) ).Handle(new EnrichShowsMetadataCommand([clip.Id], "tmdb"), CancellationToken.None); Assert.True(result.IsSuccess); Assert.Equal(0, result.Value.Applied); Assert.Empty(result.Value.Skipped); await provider .DidNotReceive() .SearchShowsAsync(Arg.Any(), Arg.Any(), Arg.Any()); } }