Add unit tests for dynamic group mode handling and exclusions
Implemented several unit tests to validate the behavior of dynamic group modes, including the preservation of pinned items when switching to dynamic mode, the dropping of exclusions when reverting to static mode, and the default pinning of new elements in dynamic groups. Additional tests ensure that exclusions are correctly applied and removed, and that static groups refuse exclusions. These tests enhance the coverage and reliability of the group management functionality.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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;
|
||||
|
||||
/// <summary>Массовые операции над отмеченными шоу: раскладка по франшизам и обогащение.</summary>
|
||||
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<IMetadataProviderResolver>();
|
||||
// Явно: NSubstitute сам подставляет заглушку вместо интерфейсного null, и без этого
|
||||
// «источник не настроен» не воспроизводится.
|
||||
resolver.Resolve(Arg.Any<string>()).Returns((IMetadataProvider?)null);
|
||||
|
||||
var result = await new EnrichShowsMetadataCommandHandler(
|
||||
db,
|
||||
resolver,
|
||||
new ShowMetadataApplier(
|
||||
db,
|
||||
resolver,
|
||||
Substitute.For<IImageDownloader>(),
|
||||
Substitute.For<IImageStore>(),
|
||||
new GenreMatcher(db)
|
||||
)
|
||||
).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<IMetadataProvider>();
|
||||
var resolver = Substitute.For<IMetadataProviderResolver>();
|
||||
resolver.Resolve("tmdb").Returns(provider);
|
||||
|
||||
await using var db = fixture.New();
|
||||
var result = await new EnrichShowsMetadataCommandHandler(
|
||||
db,
|
||||
resolver,
|
||||
new ShowMetadataApplier(
|
||||
db,
|
||||
resolver,
|
||||
Substitute.For<IImageDownloader>(),
|
||||
Substitute.For<IImageStore>(),
|
||||
new GenreMatcher(db)
|
||||
)
|
||||
).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<string>(), Arg.Any<ShowKind>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using TeleWave.Application.Library.GetShowUsage;
|
||||
using TeleWave.Application.Programming.Groups;
|
||||
using TeleWave.Application.Tests.Support;
|
||||
using TeleWave.Domain.Broadcast;
|
||||
using TeleWave.Domain.Library;
|
||||
using TeleWave.Domain.Programming;
|
||||
using Xunit;
|
||||
|
||||
namespace TeleWave.Application.Tests.Library;
|
||||
|
||||
/// <summary>
|
||||
/// «Где используется» для шоу: удаление отбивается ошибкой «используется», и без этого списка
|
||||
/// непонятно, кем именно.
|
||||
/// </summary>
|
||||
public class ShowUsageTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReportsExplicitGroupMembership()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var show = Show.Create("Шоу", ShowKind.Series);
|
||||
var group = Group.Create("Явная");
|
||||
group.AddElement(GroupElementKind.Show, show.Id);
|
||||
|
||||
await using (var seed = fixture.New())
|
||||
{
|
||||
seed.Shows.Add(show);
|
||||
seed.Groups.Add(group);
|
||||
await seed.SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
var usage = await GetAsync(fixture, show.Id);
|
||||
|
||||
var reference = Assert.Single(usage.Groups);
|
||||
Assert.Equal("Явная", reference.Name);
|
||||
Assert.False(reference.ViaRule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsMembershipByRule()
|
||||
{
|
||||
// У динамической группы шоу входит без своей позиции — для «можно ли удалять» это то же самое.
|
||||
var fixture = new TestDb();
|
||||
var genre = Genre.Create("Мультфильм", "animation");
|
||||
var show = Show.Create("Шоу", ShowKind.Series);
|
||||
show.SetGenres([genre.Id]);
|
||||
|
||||
var group = Group.Create("По правилу");
|
||||
group.SetFilter(new GroupFilter(GenreIds: [genre.Id]).ToJson());
|
||||
group.SetMode(GroupMode.Dynamic);
|
||||
|
||||
await using (var seed = fixture.New())
|
||||
{
|
||||
seed.Genres.Add(genre);
|
||||
seed.Shows.Add(show);
|
||||
seed.Groups.Add(group);
|
||||
await seed.SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
var usage = await GetAsync(fixture, show.Id);
|
||||
|
||||
Assert.True(Assert.Single(usage.Groups).ViaRule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsCollectionsAndGroupsReachedThroughThem()
|
||||
{
|
||||
// В группу шоу попадает и через франшизу, целиком — это тоже использование.
|
||||
var fixture = new TestDb();
|
||||
var show = Show.Create("Часть", ShowKind.Single);
|
||||
var collection = Collection.Create("Франшиза");
|
||||
collection.AddShow(show.Id);
|
||||
var group = Group.Create("С франшизой");
|
||||
group.AddElement(GroupElementKind.Collection, collection.Id);
|
||||
|
||||
await using (var seed = fixture.New())
|
||||
{
|
||||
seed.Shows.Add(show);
|
||||
seed.Collections.Add(collection);
|
||||
seed.Groups.Add(group);
|
||||
await seed.SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
var usage = await GetAsync(fixture, show.Id);
|
||||
|
||||
Assert.Equal("Франшиза", Assert.Single(usage.Collections).Name);
|
||||
Assert.Equal("С франшизой", Assert.Single(usage.Groups).Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsChannelsWithScheduledEntries()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
var show = Show.Create("Шоу", ShowKind.Series);
|
||||
var channel = Channel.Create("Первый", "one", DateTimeOffset.UtcNow);
|
||||
var start = new DateTimeOffset(2026, 3, 1, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
await using (var seed = fixture.New())
|
||||
{
|
||||
seed.Shows.Add(show);
|
||||
seed.Channels.Add(channel);
|
||||
seed.ScheduleEntries.Add(
|
||||
ScheduleEntry.Program(
|
||||
channel.Id,
|
||||
Guid.NewGuid(),
|
||||
start,
|
||||
start.AddMinutes(30),
|
||||
show.Id,
|
||||
0
|
||||
)
|
||||
);
|
||||
await seed.SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
var usage = await GetAsync(fixture, show.Id);
|
||||
|
||||
Assert.Equal("Первый", Assert.Single(usage.Channels).Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownShow_ReturnsNotFound()
|
||||
{
|
||||
var fixture = new TestDb();
|
||||
await using var db = fixture.New();
|
||||
|
||||
var result = await new GetShowUsageQueryHandler(db, GroupServices.Dynamic(db)).Handle(
|
||||
new GetShowUsageQuery(Guid.NewGuid()),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.False(result.IsSuccess);
|
||||
}
|
||||
|
||||
private static async Task<ShowUsageDto> GetAsync(TestDb fixture, Guid showId)
|
||||
{
|
||||
await using var db = fixture.New();
|
||||
var result = await new GetShowUsageQueryHandler(db, GroupServices.Dynamic(db)).Handle(
|
||||
new GetShowUsageQuery(showId),
|
||||
CancellationToken.None
|
||||
);
|
||||
Assert.True(result.IsSuccess);
|
||||
return result.Value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user