using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.Collections.AddShowsToCollection;
using TeleWave.Application.Library.Genres;
using TeleWave.Application.Metadata;
using TeleWave.Application.Metadata.EnrichShows;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Library;
using Xunit;
namespace TeleWave.Application.Tests.Library;
/// Массовые операции над отмеченными шоу: раскладка по франшизам и обогащение.
public class AddShowsToCollectionTests
{
[Fact]
public async Task AddsToExistingCollection_SkippingThoseAlreadyIn()
{
var fixture = new TestDb();
var inside = Show.Create("Уже внутри", ShowKind.Single);
var outside = Show.Create("Ещё нет", ShowKind.Single);
var collection = Collection.Create("Франшиза");
collection.AddShow(inside.Id);
await using (var seed = fixture.New())
{
seed.Shows.AddRange(inside, outside);
seed.Collections.Add(collection);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new AddShowsToCollectionCommandHandler(db).Handle(
new AddShowsToCollectionCommand([inside.Id, outside.Id], collection.Id),
CancellationToken.None
);
await db.SaveChangesAsync(CancellationToken.None);
Assert.True(result.IsSuccess);
// Повторное добавление молча пропускается: падать из-за одного повтора бессмысленно.
Assert.Equal(1, result.Value.Added);
Assert.Equal("Франшиза", result.Value.Name);
await using var check = fixture.New();
Assert.Equal(2, check.Collections.Include(c => c.Items).Single().Items.Count);
}
[Fact]
public async Task Fails_WhenCollectionIsGone()
{
var fixture = new TestDb();
var show = Show.Create("Шоу", ShowKind.Single);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new AddShowsToCollectionCommandHandler(db).Handle(
new AddShowsToCollectionCommand([show.Id], Guid.NewGuid()),
CancellationToken.None
);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task Fails_WhenNoneOfTheShowsExist()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await new AddShowsToCollectionCommandHandler(db).Handle(
new AddShowsToCollectionCommand([Guid.NewGuid()], NewCollectionName: "Франшиза"),
CancellationToken.None
);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task Enrich_FailsWhenProviderUnavailable()
{
var fixture = new TestDb();
await using var db = fixture.New();
var resolver = Substitute.For();
// Явно: NSubstitute сам подставляет заглушку вместо интерфейсного null, и без этого
// «источник не настроен» не воспроизводится.
resolver.Resolve(Arg.Any()).Returns((IMetadataProvider?)null);
var result = await new EnrichShowsMetadataCommandHandler(
db,
resolver,
new ShowMetadataApplier(
db,
resolver,
Substitute.For(),
Substitute.For(),
new GenreMatcher(db),
NullLogger.Instance
)
).Handle(new EnrichShowsMetadataCommand([Guid.NewGuid()], "tmdb"), CancellationToken.None);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task Enrich_IgnoresInterstitials()
{
// Ролики метаданными не обогащаются: у них нет ни года, ни постера.
var fixture = new TestDb();
var clip = Show.Create("Ролик", ShowKind.Interstitial);
await using (var seed = fixture.New())
{
seed.Shows.Add(clip);
await seed.SaveChangesAsync(CancellationToken.None);
}
var provider = Substitute.For();
var resolver = Substitute.For();
resolver.Resolve("tmdb").Returns(provider);
await using var db = fixture.New();
var result = await new EnrichShowsMetadataCommandHandler(
db,
resolver,
new ShowMetadataApplier(
db,
resolver,
Substitute.For(),
Substitute.For(),
new GenreMatcher(db),
NullLogger.Instance
)
).Handle(new EnrichShowsMetadataCommand([clip.Id], "tmdb"), CancellationToken.None);
Assert.True(result.IsSuccess);
Assert.Equal(0, result.Value.Applied);
Assert.Empty(result.Value.Skipped);
await provider
.DidNotReceive()
.SearchShowsAsync(Arg.Any(), Arg.Any(), Arg.Any());
}
}