using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.Genres;
using TeleWave.Application.Metadata;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Images;
using TeleWave.Domain.Library;
using Xunit;
namespace TeleWave.Application.Tests.Metadata;
///
/// Применение метаданных: постер в реестр изображений, жанры, франшиза и отказы. Общий путь для
/// точечного и массового применения — расхождение между ними отлаживать было бы нечем.
///
public class ShowMetadataApplierTests
{
[Fact]
public async Task RegistersPoster_InImageRegistry()
{
var fixture = new TestDb();
var show = Show.Create("Шоу", ShowKind.Single);
await SeedAsync(fixture, show);
var downloader = Substitute.For();
downloader
.DownloadAsync("https://poster", Arg.Any())
.Returns(new DownloadedImage(Encoding.UTF8.GetBytes("img"), ".jpg"));
var store = Substitute.For();
await using var db = fixture.New();
var result = await Applier(db, Meta(posterUrl: "https://poster"), downloader, store)
.ApplyAsync(await LoadAsync(db, show.Id), "tmdb", "42", CancellationToken.None);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
await using var check = fixture.New();
var image = check.Images.Single();
Assert.Equal(ImageCategory.ShowPoster, image.Category);
Assert.Equal(image.Id, check.Shows.Single().PosterImageId);
await store
.Received(1)
.SaveAsync(image.Id, ".jpg", Arg.Any(), Arg.Any());
}
[Fact]
public async Task KeepsPoster_WhenDownloadFails()
{
// Источник отдал ссылку, но картинка не скачалась — это не повод валить применение.
var fixture = new TestDb();
var show = Show.Create("Шоу", ShowKind.Single);
await SeedAsync(fixture, show);
var downloader = Substitute.For();
downloader
.DownloadAsync(Arg.Any(), Arg.Any())
.Returns((DownloadedImage?)null);
await using var db = fixture.New();
var result = await Applier(db, Meta(posterUrl: "https://poster"), downloader)
.ApplyAsync(await LoadAsync(db, show.Id), "tmdb", "42", CancellationToken.None);
Assert.True(result.IsSuccess);
Assert.Empty(db.Images);
}
[Fact]
public async Task RemembersFranchise_FromSource()
{
var fixture = new TestDb();
var show = Show.Create("Рокки", ShowKind.Single);
await SeedAsync(fixture, show);
await using var db = fixture.New();
await Applier(db, Meta(franchise: new MetadataFranchise("1241", "Рокки")))
.ApplyAsync(await LoadAsync(db, show.Id), "tmdb", "42", CancellationToken.None);
await db.SaveChangesAsync(CancellationToken.None);
await using var check = fixture.New();
var stored = check.Shows.Single();
Assert.Equal("1241", stored.FranchiseExternalId);
Assert.Equal("Рокки", stored.FranchiseName);
}
[Fact]
public async Task Fails_WhenProviderUnavailable()
{
var fixture = new TestDb();
var show = Show.Create("Шоу", ShowKind.Single);
await SeedAsync(fixture, show);
await using var db = fixture.New();
var resolver = Substitute.For();
var applier = new ShowMetadataApplier(
db,
resolver,
Substitute.For(),
Substitute.For(),
new GenreMatcher(db),
NullLogger.Instance
);
var result = await applier.ApplyAsync(
await LoadAsync(db, show.Id),
"tmdb",
"42",
CancellationToken.None
);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task Fails_WhenSourceKnowsNothing()
{
var fixture = new TestDb();
var show = Show.Create("Шоу", ShowKind.Single);
await SeedAsync(fixture, show);
await using var db = fixture.New();
var result = await Applier(db, meta: null)
.ApplyAsync(await LoadAsync(db, show.Id), "tmdb", "42", CancellationToken.None);
Assert.False(result.IsSuccess);
}
private static ShowMetadata Meta(
string? posterUrl = null,
MetadataFranchise? franchise = null
) => new("42", "Шоу", 2000, "описание", posterUrl, [], null, franchise);
private static ShowMetadataApplier Applier(
Infrastructure.Persistence.AppDbContext db,
ShowMetadata? meta,
IImageDownloader? downloader = null,
IImageStore? store = null
)
{
var provider = Substitute.For();
provider
.GetShowAsync(Arg.Any(), Arg.Any(), Arg.Any())
.Returns(meta);
var resolver = Substitute.For();
resolver.Resolve("tmdb").Returns(provider);
return new ShowMetadataApplier(
db,
resolver,
downloader ?? Substitute.For(),
store ?? Substitute.For(),
new GenreMatcher(db),
NullLogger.Instance
);
}
private static async Task SeedAsync(TestDb fixture, Show show)
{
await using var seed = fixture.New();
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
private static async Task LoadAsync(
Infrastructure.Persistence.AppDbContext db,
Guid showId
) =>
await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstAsync(
db.Shows,
s => s.Id == showId
);
}