Files
TeleWave/backend/tests/TeleWave.Application.Tests/Media/ImportMoviesTests.cs
T
Leonid Pershin d0bc0465e7
ci / build-backend (push) Successful in 1m33s
ci / build-frontend (push) Successful in 55s
ci / tests (push) Successful in 3m27s
ci / sonar (push) Successful in 6m55s
Enhance movie import process and metadata handling
Refactored the movie import functionality to ensure that the title from the source is used for the show name, while the file name is retained as the original title. Improved the handling of metadata during the import process, allowing for better integration of original titles from various sources. Updated related classes and methods to streamline the import workflow and enhance user experience. Added tests to verify the correct assignment of titles and original names during the import process. Updated documentation to reflect these changes.
2026-07-28 03:14:19 +03:00

364 lines
13 KiB
C#

using Microsoft.EntityFrameworkCore;
using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.Genres;
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)
);
var result = await new ImportMoviesCommandHandler(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)
);
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)
);
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));
}
}