Files
TeleWave/backend/tests/TeleWave.Application.Tests/Media/ImportMoviesTests.cs
T
Leonid Pershin 3fecd81333
ci / build-backend (push) Successful in 1m37s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 1m46s
ci / sonar (push) Successful in 4m38s
Enhance ShowMetadataApplier with logging for poster retrieval failures
Updated the ShowMetadataApplier class to include logging for scenarios where show posters are not retrieved successfully. Introduced logging statements to capture warnings when poster URLs are empty or when downloads fail, improving traceability and debugging capabilities. Additionally, updated related tests to ensure proper logging behavior during poster processing.
2026-07-30 02:00:07 +03:00

371 lines
14 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
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;
/// <summary>
/// Массовое заведение фильмов: шоу на файл, метаданные из источника, файл в очередь. Фильмы
/// разбираются независимо — одна битая строка не должна отменять остальные сорок.
/// </summary>
public class ImportMoviesTests
{
private const string Provider = "tmdb";
private static IMediaStorage StorageWith(params string[] files)
{
var storage = Substitute.For<IMediaStorage>();
storage
.ListManualInbox(Arg.Any<int>())
.Returns([
.. files.Select(f => new IMediaStorage.ManualInboxFile(
f,
Path.GetFileName(f),
1024
)),
]);
return storage;
}
private static IMetadataProviderResolver Resolver(ShowMetadata? metadata = null)
{
var provider = Substitute.For<IMetadataProvider>();
provider
.GetShowAsync(Arg.Any<string>(), Arg.Any<ShowKind>(), Arg.Any<CancellationToken>())
.Returns(metadata);
var resolver = Substitute.For<IMetadataProviderResolver>();
resolver.Resolve(Provider).Returns(provider);
resolver.AvailableKeys.Returns([Provider]);
return resolver;
}
private static async Task<ImportMoviesResultDto> ImportAsync(
TestDb fixture,
IMediaStorage storage,
IMetadataProviderResolver resolver,
IMediaProcessingQueue queue,
params MovieImportItem[] items
)
{
await using var db = fixture.New();
var applier = new ShowMetadataApplier(
db,
resolver,
Substitute.For<IImageDownloader>(),
Substitute.For<IImageStore>(),
new GenreMatcher(db),
NullLogger<ShowMetadataApplier>.Instance
);
var result = await new ImportMoviesCommandHandler(
db,
new ManualInboxTaker(db, storage),
queue,
applier
).Handle(new ImportMoviesCommand(items, Provider), CancellationToken.None);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
return result.Value;
}
[Fact]
public async Task CreatesShowPerFile_AndSendsItToProcessing()
{
var fixture = new TestDb();
var storage = StorageWith("Gladiator.2000.BDRip.mkv");
var queue = Substitute.For<IMediaProcessingQueue>();
var result = await ImportAsync(
fixture,
storage,
Resolver(new ShowMetadata("98", "Gladiator", 2000, "desc", null, [], null)),
queue,
new MovieImportItem("Gladiator.2000.BDRip.mkv", null, "Gladiator", 2000, "98")
);
Assert.Equal(1, result.Created);
Assert.Equal(1, result.Enriched);
Assert.Empty(result.Failed);
await using var db = fixture.New();
var show = await db.Shows.Include(s => s.Episodes).SingleAsync(CancellationToken.None);
Assert.Equal(ShowKind.Single, show.Kind);
Assert.Equal("Gladiator", show.Name);
Assert.Equal(2000, show.Year);
Assert.Single(show.Episodes);
// Файл ушёл в очередь обработки, а спутники из manual/ подчищены.
queue.Received(1).Enqueue(Arg.Any<Guid>());
await storage
.Received(1)
.CleanupManualLeftoversAsync("Gladiator.2000.BDRip.mkv", Arg.Any<CancellationToken>());
}
[Fact]
public async Task NamesShowAsInTheSource_AndKeepsFileTitleAsOriginal()
{
// В таблице разбора «Название» — строка поиска: «Die Hard 2» там стоит ровно для того,
// чтобы найти «Крепкий орешек 2». В библиотеку должно попасть название источника,
// а латинское — остаться оригинальным.
var fixture = new TestDb();
var result = await ImportAsync(
fixture,
StorageWith("Die Hard 2 [1990 HDRip].avi"),
Resolver(
new ShowMetadata(
"42",
"Крепкий орешек 2",
1990,
"desc",
null,
[],
null,
null,
"Die Hard 2"
)
),
Substitute.For<IMediaProcessingQueue>(),
new MovieImportItem("Die Hard 2 [1990 HDRip].avi", null, "Die Hard 2", 1990, "42")
);
Assert.Equal(1, result.Enriched);
await using var db = fixture.New();
var show = await db.Shows.SingleAsync(CancellationToken.None);
Assert.Equal("Крепкий орешек 2", show.Name);
Assert.Equal("Die Hard 2", show.OriginalName);
}
[Fact]
public async Task KeepsTypedTitleAsOriginal_WhenSourceHasNoOriginal()
{
// У OMDb оригинального названия нет — тогда оригинальным становится то, что вписали
// в таблице: по нему потом ищутся релизы.
var fixture = new TestDb();
var result = await ImportAsync(
fixture,
StorageWith("Spaun.1997.VHSRip.avi"),
Resolver(new ShowMetadata("5", "Спаун", 1997, null, null, [], null, null)),
Substitute.For<IMediaProcessingQueue>(),
new MovieImportItem("Spaun.1997.VHSRip.avi", null, "Spaun", 1997, "5")
);
Assert.Equal(1, result.Enriched);
await using var db = fixture.New();
var show = await db.Shows.SingleAsync(CancellationToken.None);
Assert.Equal("Спаун", show.Name);
Assert.Equal("Spaun", show.OriginalName);
}
[Fact]
public async Task CreatesShowWithoutMetadata_WhenCandidateIsNotChosen()
{
// Строка, где источник ничего не дал: шоу с файлом уже полезно, метаданные дотянет
// массовое обогащение.
var fixture = new TestDb();
var result = await ImportAsync(
fixture,
StorageWith("Shou.Trumana.1998.BDRip.mkv"),
Resolver(),
Substitute.For<IMediaProcessingQueue>(),
new MovieImportItem("Shou.Trumana.1998.BDRip.mkv", null, "Шоу Трумана", 1998, null)
);
Assert.Equal(1, result.Created);
Assert.Equal(0, result.Enriched);
Assert.Empty(result.Failed);
await using var db = fixture.New();
var show = await db.Shows.SingleAsync(CancellationToken.None);
Assert.Equal("Шоу Трумана", show.Name);
Assert.Null(show.MetadataExternalId);
}
[Fact]
public async Task AttachesAlreadyUploadedAsset_WithoutQueueingItTwice()
{
var fixture = new TestDb();
var asset = MediaAsset.Register("Die Hard 2.avi", ".avi", MediaSource.Upload);
await using (var seed = fixture.New())
{
seed.MediaAssets.Add(asset);
await seed.SaveChangesAsync(CancellationToken.None);
}
var queue = Substitute.For<IMediaProcessingQueue>();
var result = await ImportAsync(
fixture,
StorageWith(),
Resolver(),
queue,
new MovieImportItem(null, asset.Id, "Die Hard 2", 1990, null)
);
Assert.Equal(1, result.Created);
// Загруженный файл встал в очередь ещё при загрузке — второй раз ставить нечего.
queue.DidNotReceive().Enqueue(Arg.Any<Guid>());
await using var db = fixture.New();
var show = await db.Shows.Include(s => s.Episodes).SingleAsync(CancellationToken.None);
Assert.Equal(asset.Id, Assert.Single(show.Episodes).MediaAssetId);
}
[Fact]
public async Task ReportsMissingFile_AndImportsTheRest()
{
var fixture = new TestDb();
var result = await ImportAsync(
fixture,
StorageWith("Gladiator.2000.BDRip.mkv"),
Resolver(),
Substitute.For<IMediaProcessingQueue>(),
new MovieImportItem("Ушёл.из.каталога.mkv", null, "Пропавший", null, null),
new MovieImportItem("Gladiator.2000.BDRip.mkv", null, "Gladiator", 2000, null)
);
Assert.Equal(1, result.Created);
var failure = Assert.Single(result.Failed);
Assert.Equal("Пропавший", failure.Title);
}
[Fact]
public async Task RejectsFileAlreadyInLibrary()
{
var fixture = new TestDb();
await using (var seed = fixture.New())
{
seed.MediaAssets.Add(
MediaAsset.Register("Gladiator.2000.BDRip.mkv", ".mkv", MediaSource.Upload)
);
await seed.SaveChangesAsync(CancellationToken.None);
}
var result = await ImportAsync(
fixture,
StorageWith("Gladiator.2000.BDRip.mkv"),
Resolver(),
Substitute.For<IMediaProcessingQueue>(),
new MovieImportItem("Gladiator.2000.BDRip.mkv", null, "Gladiator", 2000, null)
);
Assert.Equal(0, result.Created);
Assert.Single(result.Failed);
}
[Fact]
public async Task AutoAttach_CreatesMovie_OnConfidentMatch()
{
var fixture = new TestDb();
var asset = MediaAsset.Register("Gladiator.2000.BDRip.mkv", ".mkv", MediaSource.Inbox);
await using (var seed = fixture.New())
{
seed.MediaAssets.Add(asset);
await seed.SaveChangesAsync(CancellationToken.None);
}
var provider = Substitute.For<IMetadataProvider>();
provider
.SearchShowsAsync(Arg.Any<string>(), Arg.Any<ShowKind>(), Arg.Any<CancellationToken>())
.Returns([new MetadataCandidate("98", "Gladiator", 2000, null, null, ShowKind.Single)]);
provider
.GetShowAsync("98", Arg.Any<ShowKind>(), Arg.Any<CancellationToken>())
.Returns(new ShowMetadata("98", "Gladiator", 2000, "desc", null, [], null));
var resolver = Substitute.For<IMetadataProviderResolver>();
resolver.Resolve(Provider).Returns(provider);
resolver.AvailableKeys.Returns([Provider]);
await using (var db = fixture.New())
{
var applier = new ShowMetadataApplier(
db,
resolver,
Substitute.For<IImageDownloader>(),
Substitute.For<IImageStore>(),
new GenreMatcher(db),
NullLogger<ShowMetadataApplier>.Instance
);
var result = await new AutoAttachMovieCommandHandler(
db,
resolver,
new MovieMatcher(db, resolver),
applier
).Handle(
new AutoAttachMovieCommand(asset.Id, "Gladiator.2000.BDRip.mkv"),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
}
await using var check = fixture.New();
var show = await check.Shows.Include(s => s.Episodes).SingleAsync(CancellationToken.None);
Assert.Equal("Gladiator", show.Name);
Assert.Equal("98", show.MetadataExternalId);
Assert.Equal(asset.Id, Assert.Single(show.Episodes).MediaAssetId);
}
[Fact]
public async Task AutoAttach_DoesNothing_WhenMatchIsLoose()
{
// Файл остаётся ассетом без шоу — его разберёт человек в таблице разбора.
var fixture = new TestDb();
var asset = MediaAsset.Register("Voron.1994.TRIPLE.mkv", ".mkv", MediaSource.Inbox);
await using (var seed = fixture.New())
{
seed.MediaAssets.Add(asset);
await seed.SaveChangesAsync(CancellationToken.None);
}
var provider = Substitute.For<IMetadataProvider>();
provider
.SearchShowsAsync(Arg.Any<string>(), Arg.Any<ShowKind>(), Arg.Any<CancellationToken>())
.Returns([new MetadataCandidate("7", "The Crow", 1994, null, null, ShowKind.Single)]);
var resolver = Substitute.For<IMetadataProviderResolver>();
resolver.Resolve(Provider).Returns(provider);
resolver.AvailableKeys.Returns([Provider]);
await using var db = fixture.New();
var applier = new ShowMetadataApplier(
db,
resolver,
Substitute.For<IImageDownloader>(),
Substitute.For<IImageStore>(),
new GenreMatcher(db),
NullLogger<ShowMetadataApplier>.Instance
);
var result = await new AutoAttachMovieCommandHandler(
db,
resolver,
new MovieMatcher(db, resolver),
applier
).Handle(
new AutoAttachMovieCommand(asset.Id, "Voron.1994.TRIPLE.mkv"),
CancellationToken.None
);
Assert.True(result.IsSuccess);
Assert.False(result.Value);
Assert.Empty(await db.Shows.ToListAsync(CancellationToken.None));
}
}