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