using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Library.Genres; using TeleWave.Application.Media.ManualInbox; using TeleWave.Application.Media.MovieImport; using TeleWave.Application.Metadata; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Library; using TeleWave.Domain.Media; using Xunit; namespace TeleWave.Application.Tests.Media; /// /// Пути разбора фильмов, где всё идёт не по-хорошему: источник не настроен, ассет пропал, файл уже /// в чужом шоу. Каждый из них должен быть тихим отказом, а не исключением: разбор пакетный. /// public class MovieImportHandlerTests { private const string Provider = "tmdb"; private static IMetadataProviderResolver EmptyResolver() { var resolver = Substitute.For(); resolver.AvailableKeys.Returns([]); // Явный null: без него NSubstitute подсунул бы автозаглушку провайдера, и «источник // не настроен» перестало бы быть тем, что проверяет тест. resolver.Resolve(Arg.Any()).Returns((IMetadataProvider?)null); return resolver; } private static ShowMetadataApplier Applier( IAppDbContext db, IMetadataProviderResolver resolver ) => new( db, resolver, Substitute.For(), Substitute.For(), new GenreMatcher(db) ); [Fact] public async Task Match_ReturnsNothing_WhenProviderIsNotConfigured() { // Ключа источника нет — искать нечем, и это не ошибка: таблица разбора просто пуста. var fixture = new TestDb(); await using var db = fixture.New(); var matcher = new MovieMatcher(db, EmptyResolver()); var rows = await new MatchMoviesQueryHandler(matcher).Handle( new MatchMoviesQuery(["Gladiator.2000.mkv"], Provider), CancellationToken.None ); Assert.Empty(rows); } [Fact] public async Task MatchOne_ReturnsNotFound_WhenProviderIsNotConfigured() { var fixture = new TestDb(); await using var db = fixture.New(); var matcher = new MovieMatcher(db, EmptyResolver()); var row = await new MatchMovieQueryHandler(matcher).Handle( new MatchMovieQuery("file.mkv", "Гладиатор", 2000, Provider), CancellationToken.None ); Assert.Equal(MovieMatchStatus.NotFound, row.Status); Assert.Equal("file.mkv", row.Name); } [Fact] public async Task Import_ReportsMissingAsset() { var fixture = new TestDb(); await using var db = fixture.New(); var storage = Substitute.For(); storage.ListManualInbox(Arg.Any()).Returns([]); var result = await new ImportMoviesCommandHandler( db, new ManualInboxTaker(db, storage), Substitute.For(), Applier(db, EmptyResolver()) ).Handle( new ImportMoviesCommand( [new MovieImportItem(null, Guid.NewGuid(), "Пропавший", null, null)], Provider ), CancellationToken.None ); Assert.True(result.IsSuccess); Assert.Equal(0, result.Value.Created); Assert.Single(result.Value.Failed); } [Fact] public async Task AutoAttach_DoesNothing_WhenProviderIsNotConfigured() { var fixture = new TestDb(); var asset = MediaAsset.Register("a.mkv", ".mkv", MediaSource.Inbox); await using (var seed = fixture.New()) { seed.MediaAssets.Add(asset); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); var resolver = EmptyResolver(); var result = await new AutoAttachMovieCommandHandler( db, resolver, new MovieMatcher(db, resolver), Applier(db, resolver) ).Handle(new AutoAttachMovieCommand(asset.Id, "a.mkv"), CancellationToken.None); Assert.True(result.IsSuccess); Assert.False(result.Value); } [Fact] public async Task AutoAttach_FailsOnMissingAsset() { var fixture = new TestDb(); await using var db = fixture.New(); var provider = Substitute.For(); var resolver = Substitute.For(); resolver.AvailableKeys.Returns([Provider]); resolver.Resolve(Provider).Returns(provider); var result = await new AutoAttachMovieCommandHandler( db, resolver, new MovieMatcher(db, resolver), Applier(db, resolver) ).Handle(new AutoAttachMovieCommand(Guid.NewGuid(), "a.mkv"), CancellationToken.None); Assert.False(result.IsSuccess); } [Fact] public async Task AutoAttach_SkipsAssetAlreadyInShow() { // Ассет мог уехать в шоу вручную, пока файл ждал очереди, — второе шоу по нему не заводим. var fixture = new TestDb(); var asset = MediaAsset.Register("a.mkv", ".mkv", MediaSource.Inbox); var show = Show.Create("Уже есть", ShowKind.Single); show.AddEpisode(asset.Id); await using (var seed = fixture.New()) { seed.MediaAssets.Add(asset); seed.Shows.Add(show); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); var provider = Substitute.For(); var resolver = Substitute.For(); resolver.AvailableKeys.Returns([Provider]); resolver.Resolve(Provider).Returns(provider); var result = await new AutoAttachMovieCommandHandler( db, resolver, new MovieMatcher(db, resolver), Applier(db, resolver) ).Handle(new AutoAttachMovieCommand(asset.Id, "a.mkv"), CancellationToken.None); Assert.True(result.IsSuccess); Assert.False(result.Value); await provider .DidNotReceive() .SearchShowsAsync(Arg.Any(), Arg.Any(), Arg.Any()); } }