using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Media.MovieImport; using TeleWave.Application.Metadata; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Library; using Xunit; namespace TeleWave.Application.Tests.Media; /// /// Подбор фильма по имени файла. Главное свойство то же, что у массового обогащения: уверенным /// считается только дословное совпадение — чужой постер у фильма в эфире хуже, чем его отсутствие. /// public class MovieMatcherTests { private static IMetadataProvider ProviderWith(params MetadataCandidate[] candidates) { var provider = Substitute.For(); provider .SearchShowsAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(candidates); return provider; } private static IMetadataProviderResolver Resolver(IMetadataProvider provider) { var resolver = Substitute.For(); resolver.Resolve("tmdb").Returns(provider); resolver.AvailableKeys.Returns(["tmdb"]); return resolver; } [Fact] public async Task Confident_WhenTitleAndYearMatch() { var fixture = new TestDb(); var provider = ProviderWith( new MetadataCandidate("42", "Die Hard 2", 1990, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var rows = await matcher.MatchAsync( ["Die Hard 2 [1990 HDRip].avi"], "tmdb", CancellationToken.None ); var row = Assert.Single(rows); Assert.Equal(MovieMatchStatus.Confident, row.Status); Assert.Equal("42", row.PickedExternalId); Assert.Equal("Die Hard 2", row.ParsedTitle); Assert.Equal(1990, row.ParsedYear); } [Fact] public async Task Confident_WhenYearIsOffByOne() { // Год проката и год производства у релизов расходятся на год — это норма, а не промах. var fixture = new TestDb(); var provider = ProviderWith( new MetadataCandidate("7", "The Crow", 1994, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var rows = await matcher.MatchAsync( ["The Crow 1993 BDRip"], "tmdb", CancellationToken.None ); Assert.Equal(MovieMatchStatus.Confident, Assert.Single(rows).Status); } [Fact] public async Task Uncertain_WhenYearIsFarOff() { var fixture = new TestDb(); var provider = ProviderWith( new MetadataCandidate("7", "The Crow", 1994, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var rows = await matcher.MatchAsync( ["The Crow 2024 BDRip"], "tmdb", CancellationToken.None ); var row = Assert.Single(rows); Assert.Equal(MovieMatchStatus.Uncertain, row.Status); Assert.Null(row.PickedExternalId); // Кандидаты всё равно показываются: выбрать из них — секунда, искать заново — минута. Assert.NotEmpty(row.Candidates); } [Fact] public async Task Uncertain_WhenTwoCandidatesShareTitleAndYear() { var fixture = new TestDb(); var provider = ProviderWith( new MetadataCandidate("1", "Gladiator", 2000, null, null, ShowKind.Single), new MetadataCandidate("2", "Gladiator", 2000, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var rows = await matcher.MatchAsync( ["Gladiator.2000.BDRip"], "tmdb", CancellationToken.None ); Assert.Equal(MovieMatchStatus.Uncertain, Assert.Single(rows).Status); } [Fact] public async Task NotFound_WhenProviderReturnsNothing() { // Транслит: источник по «Shou Trumana» не найдёт ничего, и это отдельный статус — строку // надо не выбрать, а переписать. var fixture = new TestDb(); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(ProviderWith())); var rows = await matcher.MatchAsync( ["Shou.Trumana.1998.BDRip.AVC.mkv"], "tmdb", CancellationToken.None ); var row = Assert.Single(rows); Assert.Equal(MovieMatchStatus.NotFound, row.Status); Assert.Empty(row.Candidates); } [Fact] public async Task AlreadyInLibrary_WhenExternalIdIsTaken() { var fixture = new TestDb(); var existing = Show.Create("Гладиатор", ShowKind.Single); existing.ApplyMetadata("tmdb", "98", null, 2000, null); await using (var seed = fixture.New()) { seed.Shows.Add(existing); await seed.SaveChangesAsync(CancellationToken.None); } var provider = ProviderWith( new MetadataCandidate("98", "Gladiator", 2000, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var rows = await matcher.MatchAsync( ["Gladiator.2000.BDRip"], "tmdb", CancellationToken.None ); var row = Assert.Single(rows); Assert.Equal(MovieMatchStatus.AlreadyInLibrary, row.Status); Assert.Equal(existing.Id, row.ExistingShowId); Assert.Equal("Гладиатор", row.ExistingShowName); } [Fact] public async Task MatchQuery_UsesTitleTypedByHand() { // Транслит правится руками: имя файла остаётся ключом строки, а ищется вписанное название. var fixture = new TestDb(); var provider = ProviderWith( new MetadataCandidate("5", "Шоу Трумана", 1998, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var row = await matcher.MatchQueryAsync( "Shou.Trumana.1998.BDRip.AVC.mkv", "Шоу Трумана", 1998, "tmdb", CancellationToken.None ); Assert.Equal("Shou.Trumana.1998.BDRip.AVC.mkv", row.Name); Assert.Equal(MovieMatchStatus.Confident, row.Status); Assert.Equal("5", row.PickedExternalId); } [Fact] public async Task PickConfident_ReturnsNothing_WhenMatchIsLoose() { // Автоматика inbox обязана молчать там, где таблица разбора показала бы жёлтым. var fixture = new TestDb(); var provider = ProviderWith( new MetadataCandidate("3", "Совсем другое кино", 1999, null, null, ShowKind.Single) ); await using var db = fixture.New(); var matcher = new MovieMatcher(db, Resolver(provider)); var picked = await matcher.PickConfidentAsync( "Green.Mile.1999.bdrip.mkv", "tmdb", CancellationToken.None ); Assert.Null(picked); } }