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); } }