using Microsoft.EntityFrameworkCore; using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Maintenance.DeleteShowMedia; using TeleWave.Application.Media.Register; using TeleWave.Domain.Broadcast; using TeleWave.Domain.Library; using TeleWave.Domain.Media; using Xunit; namespace TeleWave.Integration.Tests; [Collection("postgres")] public sealed class TransactionIntegrationTests(PostgresFixture fixture) { [SkippableFact] public async Task RegisterMediaAsset_RollsBack_WhenFilePromoteFails() { Skip.IfNot(fixture.Available, "Docker недоступен"); var fileName = $"clip-{Guid.NewGuid():N}.mkv"; var storage = Substitute.For(); storage .PromoteToOriginalAsync( Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any() ) .Returns(Task.FromException(new IOException("disk full"))); await using var db = fixture.CreateContext(); var handler = new RegisterMediaAssetCommandHandler(db, storage); await Assert.ThrowsAsync(() => handler.Handle( new RegisterMediaAssetCommand(fileName, MediaSource.Upload, fileName), CancellationToken.None ) ); // Перенос файла упал — регистрация должна откатиться, чтобы не осталось orphan-строки. await using var verify = fixture.CreateContext(); var exists = await verify.MediaAssets.AnyAsync(a => a.OriginalFileName == fileName); Assert.False(exists); } [SkippableFact] public async Task DeleteShowMedia_AtomicallyRemovesEntriesAssetsAndEpisodes() { Skip.IfNot(fixture.Available, "Docker недоступен"); var channel = Channel.Create("c", $"c-{Guid.NewGuid():N}", DateTimeOffset.UnixEpoch); var show = Show.Create("Show", ShowKind.Series); var asset = MediaAsset.Register("ep.mkv", ".mkv", MediaSource.Upload); asset.MarkReady( new MediaReadyInfo(TimeSpan.FromMinutes(20), 2, 600, 1920, 1080, "h264", "aac", "assets/x") ); show.AddEpisode(asset.Id); var entry = ScheduleEntry.Program( channel.Id, asset.Id, DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch.AddMinutes(20), show.Id, 0 ); await using (var seed = fixture.CreateContext()) { seed.Channels.Add(channel); seed.Shows.Add(show); seed.MediaAssets.Add(asset); seed.ScheduleEntries.Add(entry); await seed.SaveChangesAsync(CancellationToken.None); } var storage = Substitute.For(); storage .DeleteAssetArtifactsAsync( Arg.Any(), Arg.Any(), Arg.Any() ) .Returns(Task.CompletedTask); await using (var db = fixture.CreateContext()) { var result = await new DeleteShowMediaCommandHandler(db, storage).Handle( new DeleteShowMediaCommand(show.Id), CancellationToken.None ); Assert.True(result.IsSuccess); } await using var verify = fixture.CreateContext(); Assert.False(await verify.MediaAssets.AnyAsync(a => a.Id == asset.Id)); Assert.False(await verify.ScheduleEntries.AnyAsync(e => e.MediaAssetId == asset.Id)); var reloaded = await verify.Shows.Include(s => s.Episodes).FirstAsync(s => s.Id == show.Id); Assert.Empty(reloaded.Episodes); await storage .Received() .DeleteAssetArtifactsAsync(asset.Id, ".mkv", Arg.Any()); } }