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