using Microsoft.EntityFrameworkCore; using NSubstitute; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Media; using TeleWave.Application.Media.Delete; using TeleWave.Application.Media.Retry; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Library; using TeleWave.Domain.Media; using Xunit; namespace TeleWave.Application.Tests.Media; /// /// Перезапуск упавшей обработки. Файл при этом на месте — упала только нарезка, и повторить её /// обычно и есть всё лечение; до этого оставалось удалить ассет и залить заново. /// public class RetryMediaTests { private static MediaAsset Failed(string name) { var asset = MediaAsset.Register(name, ".mkv", MediaSource.Upload); asset.MarkProcessing(); asset.MarkFailed("Процесс /usr/bin/ffmpeg превысил таймаут"); return asset; } private static MediaAsset Ready(string name) { var asset = MediaAsset.Register(name, ".mkv", MediaSource.Upload); asset.MarkProcessing(); asset.MarkReady( new MediaReadyInfo(TimeSpan.FromMinutes(30), 2, 900, 1920, 1080, "h264", "aac", "x") ); return asset; } [Fact] public async Task Retry_ReturnsFailedAssetToTheQueue() { var fixture = new TestDb(); var failed = Failed("Гладиатор.mkv"); await using (var seed = fixture.New()) { seed.MediaAssets.Add(failed); await seed.SaveChangesAsync(CancellationToken.None); } var queue = Substitute.For(); await using var db = fixture.New(); var result = await new RetryMediaCommandHandler(db, queue).Handle( new RetryMediaCommand(failed.Id), CancellationToken.None ); Assert.True(result.IsSuccess); Assert.Equal(1, result.Value.Requeued); // Обработчик будят после сохранения: до коммита он прочитал бы из базы ещё Failed. queue.Received(1).Enqueue(failed.Id); await using var verify = fixture.New(); var stored = verify.MediaAssets.Single(a => a.Id == failed.Id); Assert.Equal(MediaAssetStatus.Pending, stored.Status); Assert.Null(stored.ErrorMessage); } [Fact] public async Task Retry_WithoutId_TakesEveryFailedButLeavesTheRest() { // Падают пачкой и по одной причине, чинят тоже пачкой — но готовое трогать нельзя: // оно уже стоит в эфире. var fixture = new TestDb(); var first = Failed("Терминатор.mkv"); var second = Failed("Криминальное чтиво.mkv"); var ready = Ready("Симпсоны s01e01.mkv"); await using (var seed = fixture.New()) { seed.MediaAssets.AddRange(first, second, ready); await seed.SaveChangesAsync(CancellationToken.None); } var queue = Substitute.For(); await using var db = fixture.New(); var result = await new RetryMediaCommandHandler(db, queue).Handle( new RetryMediaCommand(), CancellationToken.None ); Assert.Equal(2, result.Value.Requeued); await using var verify = fixture.New(); Assert.Equal( MediaAssetStatus.Ready, verify.MediaAssets.Single(a => a.Id == ready.Id).Status ); Assert.All( verify.MediaAssets.Where(a => a.Id != ready.Id).ToList(), a => Assert.Equal(MediaAssetStatus.Pending, a.Status) ); } [Fact] public async Task Delete_AlsoRemovesTheEpisodeFromTheShow() { // Серия ссылается на ассет без внешнего ключа: без этого шага в шоу остаётся строка, // за которой нет файла, — в эфир она не идёт и ничем себя не выдаёт. var fixture = new TestDb(); var asset = Ready("Металлопокалипсис s01e38.mkv"); var show = Show.Create("Металлопокалипсис", ShowKind.Series); show.AddEpisode(asset.Id); await using (var seed = fixture.New()) { seed.MediaAssets.Add(asset); seed.Shows.Add(show); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); var result = await new DeleteMediaAssetCommandHandler( db, Substitute.For() ).Handle(new DeleteMediaAssetCommand(asset.Id), CancellationToken.None); Assert.True(result.IsSuccess); await using var verify = fixture.New(); var stored = verify.Shows.Include(s => s.Episodes).Single(s => s.Id == show.Id); Assert.Empty(stored.Episodes); } [Fact] public async Task Retry_OfHealthyAsset_IsRefused() { var fixture = new TestDb(); var ready = Ready("Симпсоны s01e02.mkv"); await using (var seed = fixture.New()) { seed.MediaAssets.Add(ready); await seed.SaveChangesAsync(CancellationToken.None); } await using var db = fixture.New(); var result = await new RetryMediaCommandHandler( db, Substitute.For() ).Handle(new RetryMediaCommand(ready.Id), CancellationToken.None); Assert.False(result.IsSuccess); Assert.Equal(MediaErrors.NotFailed, result.Error); } }