using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Library.Genres; using TeleWave.Application.Metadata; using TeleWave.Application.Metadata.ApplyShowMetadata; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Library; using Xunit; namespace TeleWave.Application.Tests.Metadata; public class ApplyShowMetadataTests { [Theory] [InlineData(ShowKind.Series)] [InlineData(ShowKind.Single)] public async Task PassesShowKindToProvider(ShowKind kind) { // Тип определяет, в какое пространство идентификаторов пойдёт TMDb, и берётся он у самого // шоу, а не из команды: иначе запросом можно было бы записать сериалу метаданные // одноимённого фильма. var arranged = await ArrangeAsync(kind); Respond(arranged.Provider, new ShowMetadata("42", "A", 2000, null, null)); var result = await ApplyAsync(arranged); Assert.True(result); await arranged.Provider.Received(1).GetShowAsync("42", kind, Arg.Any()); } [Fact] public async Task AppliesRecognizedRating() { var arranged = await ArrangeAsync(ShowKind.Single); Respond(arranged.Provider, new ShowMetadata("42", "A", 2000, null, null, null, "PG-13")); await ApplyAsync(arranged); Assert.Equal(ShowAudience.Pg13, await SavedAudienceAsync(arranged)); } [Fact] public async Task KeepsRating_WhenSourceGivesNothingUsable() { // «Not Rated» — это отсутствие данных. Снять им проставленный рейтинг нельзя: обновление // метаданных тихо открыло бы взрослому шоу дорогу в детское время. var arranged = await ArrangeAsync(ShowKind.Single, ShowAudience.R); Respond( arranged.Provider, new ShowMetadata("42", "A", 2000, null, null, null, "Not Rated") ); await ApplyAsync(arranged); Assert.Equal(ShowAudience.R, await SavedAudienceAsync(arranged)); } [Fact] public async Task Fails_ForInterstitial() { var arranged = await ArrangeAsync(ShowKind.Interstitial); Assert.False(await ApplyAsync(arranged)); await arranged .Provider.DidNotReceive() .GetShowAsync(Arg.Any(), Arg.Any(), Arg.Any()); } private static void Respond(IMetadataProvider provider, ShowMetadata meta) => provider .GetShowAsync("42", Arg.Any(), Arg.Any()) .Returns(meta); private static async Task ApplyAsync(Arranged arranged) { await using var db = arranged.Fixture.New(); var handler = new ApplyShowMetadataCommandHandler( db, new ShowMetadataApplier( db, arranged.Resolver, Substitute.For(), Substitute.For(), new GenreMatcher(db), NullLogger.Instance ) ); var result = await handler.Handle( new ApplyShowMetadataCommand(arranged.ShowId, "tmdb", "42"), CancellationToken.None ); // В обычном пути сохраняет UnitOfWorkBehavior; при прямом вызове хендлера его нет. if (result.IsSuccess) await db.SaveChangesAsync(CancellationToken.None); return result.IsSuccess; } private static async Task SavedAudienceAsync(Arranged arranged) { await using var db = arranged.Fixture.New(); return (await db.Shows.FirstAsync(s => s.Id == arranged.ShowId)).Audience; } private sealed record Arranged( TestDb Fixture, IMetadataProvider Provider, IMetadataProviderResolver Resolver, Guid ShowId ); private static async Task ArrangeAsync(ShowKind kind, ShowAudience? audience = null) { var fixture = new TestDb(); var show = Show.Create("A", kind); show.SetAudience(audience); await using (var seed = fixture.New()) { seed.Shows.Add(show); await seed.SaveChangesAsync(CancellationToken.None); } var provider = Substitute.For(); var resolver = Substitute.For(); resolver.Resolve("tmdb").Returns(provider); return new Arranged(fixture, provider, resolver, show.Id); } }