Files
live_recorder/tests/LiveRecorder.Tests/StalePendingRecoveryTests.cs
T

121 lines
4.8 KiB
C#

using LiveRecorder.Application.Abstractions.Logging;
using LiveRecorder.Application.Models.Logs;
using LiveRecorder.Domain.Entities;
using LiveRecorder.Domain.Enums;
using LiveRecorder.Infrastructure.Persistence;
using LiveRecorder.Infrastructure.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
namespace LiveRecorder.Tests;
public sealed class StalePendingRecoveryTests
{
[Fact]
public async Task StalePendingSessionWithoutMedia_IsFailedAndRetainedForAudit()
{
var services = new ServiceCollection();
var databaseRoot = new InMemoryDatabaseRoot();
var databaseName = $"stale-pending-{Guid.NewGuid():N}";
services.AddDbContext<LiveRecorderDbContext>(options =>
options.UseInMemoryDatabase(databaseName, databaseRoot));
var logService = new CapturingSystemLogService();
services.AddSingleton<ISystemLogService>(logService);
await using var provider = services.BuildServiceProvider();
var createdAt = DateTimeOffset.UtcNow.AddMinutes(-11);
Guid sessionId;
Guid taskId;
await using (var scope = provider.CreateAsyncScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<LiveRecorderDbContext>();
var liveRoom = new LiveRoom(
LivePlatformType.Douyin,
"https://live.example/room",
"room-1",
"https://live.example/room",
createdAt);
var session = new RecordSession(
liveRoom.Id,
"origin",
RecordOutputFormat.Mp4,
RecordSaveMode.Segmented,
createdAt);
var task = new RecordTask(
liveRoom.Id,
session.Id,
1,
"origin",
RecordOutputFormat.Mp4,
createdAt);
sessionId = session.Id;
taskId = task.Id;
dbContext.AddRange(liveRoom, session, task);
await dbContext.SaveChangesAsync();
}
var service = new FfmpegService(
provider.GetRequiredService<IServiceScopeFactory>(),
null!,
null!,
NullLogger<FfmpegService>.Instance);
await using (var preconditionScope = provider.CreateAsyncScope())
{
var preconditionContext = preconditionScope.ServiceProvider.GetRequiredService<LiveRecorderDbContext>();
var persisted = await preconditionContext.RecordSessions.SingleAsync();
Assert.Equal(RecordSessionStatus.Pending, persisted.Status);
Assert.True(
persisted.UpdatedAt <= DateTimeOffset.UtcNow.AddMinutes(-10),
$"updatedAt={persisted.UpdatedAt:O}");
}
var recovered = await service.RecoverStalePendingSessionsAsync();
Assert.Equal(1, recovered);
await using var verificationScope = provider.CreateAsyncScope();
var verificationContext = verificationScope.ServiceProvider.GetRequiredService<LiveRecorderDbContext>();
var persistedSession = await verificationContext.RecordSessions.SingleAsync(item => item.Id == sessionId);
var persistedTask = await verificationContext.RecordTasks
.Include(item => item.Result)
.SingleAsync(item => item.Id == taskId);
Assert.Equal(RecordSessionStatus.Failed, persistedSession.Status);
Assert.Equal(RecordTaskStatus.Failed, persistedTask.Status);
Assert.Contains("interrupted before stream initialization", persistedSession.ErrorMessage);
Assert.Null(persistedTask.Result);
Assert.Single(logService.Entries);
Assert.Equal(SystemLogLevel.Warning, logService.Entries[0].Level);
}
private sealed class CapturingSystemLogService : ISystemLogService
{
public List<(SystemLogLevel Level, string Message)> Entries { get; } = [];
public Task WriteAsync(
SystemLogLevel level,
string category,
string message,
string? detail = null,
Guid? liveRoomId = null,
Guid? recordSessionId = null,
Guid? recordTaskId = null,
CancellationToken cancellationToken = default)
{
Entries.Add((level, message));
return Task.CompletedTask;
}
public Task<IReadOnlyList<SystemLogDto>> ListAsync(
Guid? liveRoomId = null,
Guid? recordSessionId = null,
Guid? recordTaskId = null,
SystemLogLevel? level = null,
string? content = null,
int take = 200,
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlyList<SystemLogDto>>([]);
}
}