121 lines
4.8 KiB
C#
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>>([]);
|
|
}
|
|
}
|