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);
}
}