Update scheduling parameters and refactor channel endpoints: extend HorizonDays to 7 and RetentionDays to 90 in appsettings.json. Consolidate channel-related endpoint logic by removing obsolete files and enhancing the ShowEndpoints with audience and genre management capabilities. Improve error handling and streamline command handlers for channel operations.
build / backend (push) Successful in 7m40s
build / frontend (push) Failing after 39s
tests / backend-tests (push) Successful in 6m9s

This commit is contained in:
Leonid Pershin
2026-07-26 13:32:13 +03:00
parent c4ef954dea
commit 66040a8841
272 changed files with 27944 additions and 8699 deletions
@@ -1,324 +1,82 @@
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.AddChannelAd;
using TeleWave.Application.Broadcast.AddChannelShow;
using TeleWave.Application.Broadcast.CreateChannel;
using TeleWave.Application.Broadcast.CreateOverride;
using TeleWave.Application.Broadcast.DeleteOverride;
using TeleWave.Application.Broadcast.RemoveChannelShow;
using TeleWave.Application.Broadcast.UpdateChannelSettings;
using TeleWave.Application.Broadcast.UpdateChannelShow;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
public class ChannelHandlersTests
{
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
[Fact]
public async Task CreateChannel_Succeeds_AndRejectsDuplicateSlug()
{
var fixture = new TestDb();
await using var db = fixture.New();
var ok = await new CreateChannelCommandHandler(db).Handle(
new CreateChannelCommand("News", "news"),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
var dup = await new CreateChannelCommandHandler(db).Handle(
new CreateChannelCommand("Other", "news"),
CancellationToken.None
);
Assert.False(dup.IsSuccess);
Assert.Equal(ChannelErrors.DuplicateSlug, dup.Error);
}
[Fact]
public async Task AddChannelShow_HappyPath_AndErrors()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var show = Show.Create("S", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new AddChannelShowCommandHandler(db);
// неизвестный канал
var noChannel = await handler.Handle(
new AddChannelShowCommand(Guid.NewGuid(), show.Id, 1, BlockMode.Count, 1),
CancellationToken.None
);
Assert.Equal(ChannelErrors.NotFound, noChannel.Error);
// неизвестное шоу
var noShow = await handler.Handle(
new AddChannelShowCommand(channel.Id, Guid.NewGuid(), 1, BlockMode.Count, 1),
CancellationToken.None
);
Assert.Equal(ChannelErrors.ShowNotFound, noShow.Error);
// ок
var ok = await handler.Handle(
new AddChannelShowCommand(channel.Id, show.Id, 2, BlockMode.Count, 3),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
// повторно — уже добавлено
await using var db2 = fixture.New();
var dup = await new AddChannelShowCommandHandler(db2).Handle(
new AddChannelShowCommand(channel.Id, show.Id, 1, BlockMode.Count, 1),
CancellationToken.None
);
Assert.Equal(ChannelErrors.ShowAlreadyAdded, dup.Error);
}
[Fact]
public async Task RemoveChannelShow_RemovesOrReportsNotFound()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var cs = channel.AddShow(Guid.NewGuid(), 1, BlockMode.Count, 1);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new RemoveChannelShowCommandHandler(db);
var missing = await handler.Handle(
new RemoveChannelShowCommand(channel.Id, Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(ChannelErrors.ChannelShowNotFound, missing.Error);
var ok = await handler.Handle(
new RemoveChannelShowCommand(channel.Id, cs.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
}
[Fact]
public async Task AddChannelAd_HappyPath_AndAssetNotFound()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var asset = MediaAsset.Register("clip.mp4", ".mp4", MediaSource.Upload);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.MediaAssets.Add(asset);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new AddChannelAdCommandHandler(db);
var noAsset = await handler.Handle(
new AddChannelAdCommand(channel.Id, Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(ChannelErrors.AssetNotFound, noAsset.Error);
var ok = await handler.Handle(
new AddChannelAdCommand(channel.Id, asset.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
}
[Fact]
public async Task UpdateChannelSettings_UpdatesBumperChances()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new UpdateChannelSettingsCommandHandler(db).Handle(
new UpdateChannelSettingsCommand(
channel.Id,
"c",
true,
AdInsertion.BetweenBlocks,
1,
true,
new BumperSettingsInput(
BumperFont.Sans,
5,
BumperSelection.WeightedRandom,
0.5,
0.2
),
null
),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
await using var verify = fixture.New();
var stored = await verify.Channels.FindAsync(channel.Id);
Assert.Equal(0.5, stored!.BumperShowChangeChance);
Assert.Equal(0.2, stored.BumperEpisodeChangeChance);
Assert.Equal(BumperSelection.WeightedRandom, stored.BumperSelection);
}
[Fact]
public async Task UpdateChannelShow_SetsPreferredHours()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var cs = channel.AddShow(Guid.NewGuid(), 1, BlockMode.Count, 1);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new UpdateChannelShowCommandHandler(db).Handle(
new UpdateChannelShowCommand(
channel.Id,
cs.Id,
3,
BlockMode.Count,
2,
true,
4,
[new HourWindowInput(18, 23)]
),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
await using var verify = fixture.New();
var stored = await verify
.Channels.Include(c => c.Shows)
.ThenInclude(s => s.PreferredHours)
.FirstAsync(c => c.Id == channel.Id);
var show = Assert.Single(stored.Shows);
Assert.Equal(4, show.PreferredWeightMultiplier);
Assert.Single(show.PreferredHours);
}
[Fact]
public async Task CreateOverride_OneTime_Weekly_AndValidation()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var show = Show.Create("S", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new CreateProgrammingOverrideCommandHandler(db);
OverrideShowInput[] shows = [new OverrideShowInput(show.Id, 1)];
// Разовое окно — ок.
var oneTime = await handler.Handle(
new CreateProgrammingOverrideCommand(
channel.Id,
OverrideMode.Exclusive,
OverrideRecurrence.OneTime,
T0,
T0.AddHours(2),
null,
null,
null,
shows
),
CancellationToken.None
);
Assert.True(oneTime.IsSuccess);
// Еженедельное — ок.
var weekly = await handler.Handle(
new CreateProgrammingOverrideCommand(
channel.Id,
OverrideMode.Boost,
OverrideRecurrence.Weekly,
null,
null,
6,
600,
720,
shows
),
CancellationToken.None
);
Assert.True(weekly.IsSuccess);
// Невалидное окно (end <= start).
var bad = await handler.Handle(
new CreateProgrammingOverrideCommand(
channel.Id,
OverrideMode.Exclusive,
OverrideRecurrence.OneTime,
T0.AddHours(2),
T0,
null,
null,
null,
shows
),
CancellationToken.None
);
Assert.Equal(ChannelErrors.InvalidOverrideWindow, bad.Error);
}
[Fact]
public async Task DeleteOverride_RemovesOrReportsNotFound()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var ovr = channel.AddOverride(OverrideMode.Exclusive, T0, T0.AddHours(1));
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new DeleteProgrammingOverrideCommandHandler(db);
var missing = await handler.Handle(
new DeleteProgrammingOverrideCommand(channel.Id, Guid.NewGuid()),
CancellationToken.None
);
Assert.Equal(ChannelErrors.OverrideNotFound, missing.Error);
var ok = await handler.Handle(
new DeleteProgrammingOverrideCommand(channel.Id, ovr.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
}
}
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.CreateChannel;
using TeleWave.Application.Broadcast.UpdateChannelSettings;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
public class ChannelHandlersTests
{
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
[Fact]
public async Task CreateChannel_Succeeds_AndRejectsDuplicateSlug()
{
var fixture = new TestDb();
await using var db = fixture.New();
var ok = await new CreateChannelCommandHandler(db).Handle(
new CreateChannelCommand("News", "news"),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
var dup = await new CreateChannelCommandHandler(db).Handle(
new CreateChannelCommand("Other", "news"),
CancellationToken.None
);
Assert.False(dup.IsSuccess);
Assert.Equal(ChannelErrors.DuplicateSlug, dup.Error);
}
[Fact]
public async Task UpdateChannelSettings_UpdatesBumperChances()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new UpdateChannelSettingsCommandHandler(db).Handle(
new UpdateChannelSettingsCommand(
channel.Id,
"c",
true,
true,
new BumperSettingsInput(
BumperFont.Sans,
5,
BumperSelection.WeightedRandom,
0.5,
0.2
),
null
),
CancellationToken.None
);
Assert.True(result.IsSuccess);
await db.SaveChangesAsync(CancellationToken.None);
await using var verify = fixture.New();
var stored = await verify.Channels.FindAsync(channel.Id);
Assert.Equal(0.5, stored!.BumperShowChangeChance);
Assert.Equal(0.2, stored.BumperEpisodeChangeChance);
Assert.Equal(BumperSelection.WeightedRandom, stored.BumperSelection);
}
}
@@ -1,260 +1,203 @@
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Broadcast.GetChannel;
using TeleWave.Application.Broadcast.GetSchedule;
using TeleWave.Application.Broadcast.ListChannels;
using TeleWave.Application.Broadcast.RemoveChannelAd;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.DeleteShow;
using TeleWave.Application.Library.GetShow;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
public class QueryHandlersTests
{
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
[Fact]
public async Task GetChannel_ReturnsDto_WithBumperAndOverride()
{
var fixture = new TestDb();
var channel = Channel.Create("News", "news", T0);
var show = Show.Create("S", ShowKind.Series);
channel.AddShow(show.Id, 1, BlockMode.Count, 1);
channel.AddWeeklyOverride(OverrideMode.Exclusive, 6, 600, 720).AddShow(show.Id, 1);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new GetChannelQueryHandler(db).Handle(
new GetChannelQuery(channel.Id),
CancellationToken.None
);
Assert.True(result.IsSuccess);
Assert.Equal("News", result.Value.Name);
Assert.Single(result.Value.BumperTemplates);
Assert.Single(result.Value.Overrides);
Assert.Equal(OverrideRecurrence.Weekly, result.Value.Overrides[0].Recurrence);
}
[Fact]
public async Task GetChannel_UnknownId_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await new GetChannelQueryHandler(db).Handle(
new GetChannelQuery(Guid.NewGuid()),
CancellationToken.None
);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task ListChannels_ReturnsSummaries()
{
var fixture = new TestDb();
await using (var seed = fixture.New())
{
seed.Channels.Add(Channel.Create("A", "a", T0));
seed.Channels.Add(Channel.Create("B", "b", T0));
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new ListChannelsQueryHandler(db).Handle(
new ListChannelsQuery(),
CancellationToken.None
);
Assert.Equal(2, result.Count);
}
[Fact]
public async Task GetChannelSchedule_ReturnsEntriesWithBumperLabel()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var show = Show.Create("Show A", ShowKind.Series);
var variant = channel.BumperTemplates[0].Variants[0];
var asset = MediaAsset.Register("Show.A.S01E01.mkv", ".mkv", MediaSource.Upload);
var program = ScheduleEntry.Program(
channel.Id,
asset.Id,
T0,
T0.AddMinutes(20),
show.Id,
0
);
var bumper = ScheduleEntry.Bumper(
channel.Id,
Guid.NewGuid(),
T0.AddMinutes(20),
T0.AddMinutes(20).AddSeconds(8),
show.Id,
variant.Id
);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
seed.Channels.Add(channel);
seed.MediaAssets.Add(asset);
seed.ScheduleEntries.Add(program);
seed.ScheduleEntries.Add(bumper);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new GetChannelScheduleQueryHandler(db).Handle(
new GetChannelScheduleQuery(channel.Id, T0.AddMinutes(-5), T0.AddHours(1)),
CancellationToken.None
);
Assert.True(result.IsSuccess);
Assert.Equal(2, result.Value.Count);
var bumperDto = result.Value.Single(e => e.Kind == ScheduleEntryKind.Bumper);
Assert.NotNull(bumperDto.BumperName);
}
[Fact]
public async Task GetShow_ReturnsDtoOrNotFound()
{
var fixture = new TestDb();
var show = Show.Create("A", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var ok = await new GetShowQueryHandler(db).Handle(
new GetShowQuery(show.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
var missing = await new GetShowQueryHandler(db).Handle(
new GetShowQuery(Guid.NewGuid()),
CancellationToken.None
);
Assert.False(missing.IsSuccess);
}
[Fact]
public async Task DeleteShow_RemovesOrNotFound()
{
var fixture = new TestDb();
var show = Show.Create("A", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var missing = await new DeleteShowCommandHandler(db).Handle(
new DeleteShowCommand(Guid.NewGuid()),
CancellationToken.None
);
Assert.False(missing.IsSuccess);
var ok = await new DeleteShowCommandHandler(db).Handle(
new DeleteShowCommand(show.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
}
[Fact]
public async Task RemoveChannelAd_RemovesOrNotFound()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var ad = channel.AddAd(Guid.NewGuid());
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var handler = new RemoveChannelAdCommandHandler(db);
Assert.Equal(
ChannelErrors.AdNotFound,
(
await handler.Handle(
new RemoveChannelAdCommand(channel.Id, Guid.NewGuid()),
CancellationToken.None
)
).Error
);
Assert.True(
(
await handler.Handle(
new RemoveChannelAdCommand(channel.Id, ad.Id),
CancellationToken.None
)
).IsSuccess
);
}
[Fact]
public async Task AddAndRemoveBumperTemplate_WorkThroughStorage()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
Guid templateId;
await using (var db = fixture.New())
{
var added = await new AddBumperTemplateCommandHandler(db).Handle(
new AddBumperTemplateCommand(channel.Id, ""),
CancellationToken.None
);
Assert.True(added.IsSuccess);
templateId = added.Value;
await db.SaveChangesAsync(CancellationToken.None);
}
var storage = Substitute.For<IBumperTemplateStorage>();
await using (var db = fixture.New())
{
var removed = await new RemoveBumperTemplateCommandHandler(db, storage).Handle(
new RemoveBumperTemplateCommand(channel.Id, templateId),
CancellationToken.None
);
Assert.True(removed.IsSuccess);
}
await storage.Received(1).DeleteTemplateAsync(templateId, Arg.Any<CancellationToken>());
// дефолтный блок удалить нельзя
await using (var db = fixture.New())
{
var def = await db
.Channels.Include(c => c.BumperTemplates)
.FirstAsync(c => c.Id == channel.Id);
var result = await new RemoveBumperTemplateCommandHandler(db, storage).Handle(
new RemoveBumperTemplateCommand(channel.Id, def.BumperTemplates[0].Id),
CancellationToken.None
);
Assert.Equal(ChannelErrors.CannotRemoveDefaultBumperTemplate, result.Error);
}
}
}
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Broadcast.GetChannel;
using TeleWave.Application.Broadcast.GetSchedule;
using TeleWave.Application.Broadcast.ListChannels;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.DeleteShow;
using TeleWave.Application.Library.GetShow;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;
namespace TeleWave.Application.Tests.Broadcast;
public class QueryHandlersTests
{
private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
private static GroupMembershipCleaner GroupCleaner(IAppDbContext db) =>
new(db, new GroupStatsService(db, new GroupElementResolver(db)));
[Fact]
public async Task GetChannel_UnknownId_ReturnsNotFound()
{
var fixture = new TestDb();
await using var db = fixture.New();
var result = await new GetChannelQueryHandler(db).Handle(
new GetChannelQuery(Guid.NewGuid()),
CancellationToken.None
);
Assert.False(result.IsSuccess);
}
[Fact]
public async Task ListChannels_ReturnsSummaries()
{
var fixture = new TestDb();
await using (var seed = fixture.New())
{
seed.Channels.Add(Channel.Create("A", "a", T0));
seed.Channels.Add(Channel.Create("B", "b", T0));
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new ListChannelsQueryHandler(db).Handle(
new ListChannelsQuery(),
CancellationToken.None
);
Assert.Equal(2, result.Count);
}
[Fact]
public async Task GetChannelSchedule_ReturnsEntriesWithBumperLabel()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
var show = Show.Create("Show A", ShowKind.Series);
var variant = channel.BumperTemplates[0].Variants[0];
var asset = MediaAsset.Register("Show.A.S01E01.mkv", ".mkv", MediaSource.Upload);
var program = ScheduleEntry.Program(
channel.Id,
asset.Id,
T0,
T0.AddMinutes(20),
show.Id,
0
);
var bumper = ScheduleEntry.Bumper(
channel.Id,
Guid.NewGuid(),
T0.AddMinutes(20),
T0.AddMinutes(20).AddSeconds(8),
show.Id,
variant.Id
);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
seed.Channels.Add(channel);
seed.MediaAssets.Add(asset);
seed.ScheduleEntries.Add(program);
seed.ScheduleEntries.Add(bumper);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var result = await new GetChannelScheduleQueryHandler(db).Handle(
new GetChannelScheduleQuery(channel.Id, T0.AddMinutes(-5), T0.AddHours(1)),
CancellationToken.None
);
Assert.True(result.IsSuccess);
Assert.Equal(2, result.Value.Count);
var bumperDto = result.Value.Single(e => e.Kind == ScheduleEntryKind.Bumper);
Assert.NotNull(bumperDto.BumperName);
}
[Fact]
public async Task GetShow_ReturnsDtoOrNotFound()
{
var fixture = new TestDb();
var show = Show.Create("A", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var ok = await new GetShowQueryHandler(db).Handle(
new GetShowQuery(show.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
var missing = await new GetShowQueryHandler(db).Handle(
new GetShowQuery(Guid.NewGuid()),
CancellationToken.None
);
Assert.False(missing.IsSuccess);
}
[Fact]
public async Task DeleteShow_RemovesOrNotFound()
{
var fixture = new TestDb();
var show = Show.Create("A", ShowKind.Series);
await using (var seed = fixture.New())
{
seed.Shows.Add(show);
await seed.SaveChangesAsync(CancellationToken.None);
}
await using var db = fixture.New();
var missing = await new DeleteShowCommandHandler(db, GroupCleaner(db)).Handle(
new DeleteShowCommand(Guid.NewGuid()),
CancellationToken.None
);
Assert.False(missing.IsSuccess);
var ok = await new DeleteShowCommandHandler(db, GroupCleaner(db)).Handle(
new DeleteShowCommand(show.Id),
CancellationToken.None
);
Assert.True(ok.IsSuccess);
}
[Fact]
public async Task AddAndRemoveBumperTemplate_WorkThroughStorage()
{
var fixture = new TestDb();
var channel = Channel.Create("c", "c", T0);
await using (var seed = fixture.New())
{
seed.Channels.Add(channel);
await seed.SaveChangesAsync(CancellationToken.None);
}
Guid templateId;
await using (var db = fixture.New())
{
var added = await new AddBumperTemplateCommandHandler(db).Handle(
new AddBumperTemplateCommand(channel.Id, ""),
CancellationToken.None
);
Assert.True(added.IsSuccess);
templateId = added.Value;
await db.SaveChangesAsync(CancellationToken.None);
}
var storage = Substitute.For<IBumperTemplateStorage>();
await using (var db = fixture.New())
{
var removed = await new RemoveBumperTemplateCommandHandler(db, storage).Handle(
new RemoveBumperTemplateCommand(channel.Id, templateId),
CancellationToken.None
);
Assert.True(removed.IsSuccess);
}
await storage.Received(1).DeleteTemplateAsync(templateId, Arg.Any<CancellationToken>());
// дефолтный блок удалить нельзя
await using (var db = fixture.New())
{
var def = await db
.Channels.Include(c => c.BumperTemplates)
.FirstAsync(c => c.Id == channel.Id);
var result = await new RemoveBumperTemplateCommandHandler(db, storage).Handle(
new RemoveBumperTemplateCommand(channel.Id, def.BumperTemplates[0].Id),
CancellationToken.None
);
Assert.Equal(ChannelErrors.CannotRemoveDefaultBumperTemplate, result.Error);
}
}
}
@@ -1,142 +1,88 @@
using TeleWave.Application.Admin.Users.CreateUser;
using TeleWave.Application.Admin.Users.ResetPassword;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Broadcast.CreateOverride;
using TeleWave.Application.Broadcast.UpdateChannelSettings;
using TeleWave.Application.Broadcast.UpdateChannelShow;
using TeleWave.Domain.Broadcast;
using Xunit;
namespace TeleWave.Application.Tests.Validators;
public class ValidatorTests
{
[Fact]
public void UpdateChannelShow_RejectsBadWeightMultiplierAndWindows()
{
var v = new UpdateChannelShowCommandValidator();
var good = new UpdateChannelShowCommand(
Guid.NewGuid(),
Guid.NewGuid(),
5,
BlockMode.Count,
3,
true,
4,
[new HourWindowInput(18, 23)]
);
Assert.True(v.Validate(good).IsValid);
var badMultiplier = good with { PreferredWeightMultiplier = 0 };
Assert.False(v.Validate(badMultiplier).IsValid);
var badWindow = good with { PreferredHours = [new HourWindowInput(23, 20)] };
Assert.False(v.Validate(badWindow).IsValid);
var badWeight = good with { Weight = 0 };
Assert.False(v.Validate(badWeight).IsValid);
}
[Fact]
public void UpdateBumperTextVariant_ChecksLengthsAndWeight()
{
var v = new UpdateBumperTextVariantCommandValidator();
var good = new UpdateBumperTextVariantCommand(
Guid.NewGuid(),
Guid.NewGuid(),
Guid.NewGuid(),
"Name",
BumperTextKind.NowNext,
"NOW",
"NEXT",
"l1",
"l2",
BumperTrigger.Both,
3
);
Assert.True(v.Validate(good).IsValid);
Assert.False(v.Validate(good with { Name = "" }).IsValid);
Assert.False(v.Validate(good with { Weight = -1 }).IsValid);
Assert.False(v.Validate(good with { Line1 = new string('x', 121) }).IsValid);
}
[Fact]
public void UpdateChannelSettings_ChecksRanges()
{
var v = new UpdateChannelSettingsCommandValidator();
var bumper = new BumperSettingsInput(
BumperFont.Sans,
10,
BumperSelection.Rotation,
0.5,
0.5
);
var good = new UpdateChannelSettingsCommand(
Guid.NewGuid(),
"Name",
true,
AdInsertion.BetweenBlocks,
2,
true,
bumper,
null
);
Assert.True(v.Validate(good).IsValid);
Assert.False(v.Validate(good with { Name = "" }).IsValid);
Assert.False(v.Validate(good with { AdsPerBreak = 99 }).IsValid);
Assert.False(
v.Validate(good with { Bumper = bumper with { ShowChangeChance = 2 } }).IsValid
);
}
[Fact]
public void CreateOverride_RequiresShows_WithValidWeights()
{
var v = new CreateProgrammingOverrideCommandValidator();
var good = new CreateProgrammingOverrideCommand(
Guid.NewGuid(),
OverrideMode.Boost,
OverrideRecurrence.OneTime,
DateTimeOffset.UtcNow,
DateTimeOffset.UtcNow.AddHours(1),
null,
null,
null,
[new OverrideShowInput(Guid.NewGuid(), 3)]
);
Assert.True(v.Validate(good).IsValid);
Assert.False(v.Validate(good with { Shows = [] }).IsValid);
Assert.False(
v.Validate(good with { Shows = [new OverrideShowInput(Guid.NewGuid(), 0)] }).IsValid
);
}
[Fact]
public void ResetUserPassword_RequiresMinLength()
{
var v = new ResetUserPasswordCommandValidator();
Assert.True(v.Validate(new ResetUserPasswordCommand(Guid.NewGuid(), "Password1")).IsValid);
Assert.False(v.Validate(new ResetUserPasswordCommand(Guid.NewGuid(), "short")).IsValid);
Assert.False(v.Validate(new ResetUserPasswordCommand(Guid.Empty, "Password1")).IsValid);
}
[Fact]
public void CreateUser_ChecksNameAndPassword()
{
var v = new CreateUserCommandValidator();
Assert.True(v.Validate(new CreateUserCommand("bob", "Password1", Guid.NewGuid())).IsValid);
Assert.False(v.Validate(new CreateUserCommand("ab", "Password1", Guid.NewGuid())).IsValid);
Assert.False(v.Validate(new CreateUserCommand("bob", "short", Guid.NewGuid())).IsValid);
Assert.False(v.Validate(new CreateUserCommand("bob", "Password1", Guid.Empty)).IsValid);
}
}
using TeleWave.Application.Admin.Users.CreateUser;
using TeleWave.Application.Admin.Users.ResetPassword;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Broadcast.UpdateChannelSettings;
using TeleWave.Domain.Broadcast;
using Xunit;
namespace TeleWave.Application.Tests.Validators;
public class ValidatorTests
{
[Fact]
public void UpdateBumperTextVariant_ChecksLengthsAndWeight()
{
var v = new UpdateBumperTextVariantCommandValidator();
var good = new UpdateBumperTextVariantCommand(
Guid.NewGuid(),
Guid.NewGuid(),
Guid.NewGuid(),
"Name",
BumperTextKind.NowNext,
"NOW",
"NEXT",
"l1",
"l2",
BumperTrigger.Both,
3
);
Assert.True(v.Validate(good).IsValid);
Assert.False(v.Validate(good with { Name = "" }).IsValid);
Assert.False(v.Validate(good with { Weight = -1 }).IsValid);
Assert.False(v.Validate(good with { Line1 = new string('x', 121) }).IsValid);
}
[Fact]
public void UpdateChannelSettings_ChecksRanges()
{
var v = new UpdateChannelSettingsCommandValidator();
var bumper = new BumperSettingsInput(
BumperFont.Sans,
10,
BumperSelection.Rotation,
0.5,
0.5
);
var good = new UpdateChannelSettingsCommand(
Guid.NewGuid(),
"Name",
true,
true,
bumper,
null
);
Assert.True(v.Validate(good).IsValid);
Assert.False(v.Validate(good with { Name = "" }).IsValid);
Assert.False(
v.Validate(good with { Bumper = bumper with { ShowChangeChance = 2 } }).IsValid
);
}
[Fact]
public void ResetUserPassword_RequiresMinLength()
{
var v = new ResetUserPasswordCommandValidator();
Assert.True(v.Validate(new ResetUserPasswordCommand(Guid.NewGuid(), "Password1")).IsValid);
Assert.False(v.Validate(new ResetUserPasswordCommand(Guid.NewGuid(), "short")).IsValid);
Assert.False(v.Validate(new ResetUserPasswordCommand(Guid.Empty, "Password1")).IsValid);
}
[Fact]
public void CreateUser_ChecksNameAndPassword()
{
var v = new CreateUserCommandValidator();
Assert.True(v.Validate(new CreateUserCommand("bob", "Password1", Guid.NewGuid())).IsValid);
Assert.False(v.Validate(new CreateUserCommand("ab", "Password1", Guid.NewGuid())).IsValid);
Assert.False(v.Validate(new CreateUserCommand("bob", "short", Guid.NewGuid())).IsValid);
Assert.False(v.Validate(new CreateUserCommand("bob", "Password1", Guid.Empty)).IsValid);
}
}