fix: harden auto uploads and table layouts
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using LiveRecorder.Application.Abstractions.Logging;
|
||||
using LiveRecorder.Application.Abstractions.Recording;
|
||||
using LiveRecorder.Application.Abstractions.Scripting;
|
||||
using LiveRecorder.Application.Abstractions.Settings;
|
||||
using LiveRecorder.Application.Models.Logs;
|
||||
using LiveRecorder.Application.Models.Settings;
|
||||
@@ -14,11 +15,83 @@ using LiveRecorder.Infrastructure.Persistence.Repositories;
|
||||
using LiveRecorder.Infrastructure.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace LiveRecorder.Tests;
|
||||
|
||||
public sealed class OpenListUploadTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TestConnection_ReportsDestinationMountFailureAfterSuccessfulLogin()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(async request =>
|
||||
{
|
||||
var path = request.RequestUri!.AbsolutePath;
|
||||
if (path.EndsWith("/api/auth/login", StringComparison.Ordinal))
|
||||
{
|
||||
return JsonResponse("""{"code":200,"message":"success","data":{"token":"test-token"}}""");
|
||||
}
|
||||
|
||||
if (path.EndsWith("/api/public/settings", StringComparison.Ordinal))
|
||||
{
|
||||
return JsonResponse("""{"code":200,"message":"success","data":{"version":"4.1.10"}}""");
|
||||
}
|
||||
|
||||
var body = JsonDocument.Parse(await request.Content!.ReadAsStringAsync()).RootElement;
|
||||
return body.GetProperty("path").GetString() == "/source"
|
||||
? JsonResponse("""{"code":200,"message":"success","data":{"content":[],"write":false}}""")
|
||||
: JsonResponse("""{"code":500,"message":"provider timeout","data":null}""");
|
||||
});
|
||||
var client = CreateClient(handler);
|
||||
var connection = Connection();
|
||||
connection.SourcePath = "/source";
|
||||
connection.DestinationPath = "/destination";
|
||||
|
||||
var result = await client.TestConnectionAsync(connection);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("4.1.10", result.Version);
|
||||
Assert.True(result.SourcePath?.Success);
|
||||
Assert.False(result.DestinationPath?.Success);
|
||||
Assert.Contains("provider timeout", result.DestinationPath?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompletionDispatch_ScriptFailureDoesNotBlockAutomaticUpload()
|
||||
{
|
||||
await using var fixture = await QueueFixture.CreateAsync();
|
||||
await fixture.PrepareCompletionDispatchAsync();
|
||||
var service = fixture.CreateCompletionDispatchService(new FixedEventScriptService(
|
||||
new EventScriptExecutionResultDto { Success = false, Message = "script failed" }));
|
||||
|
||||
await service.TryDispatchTaskAsync(fixture.RecordTaskId);
|
||||
|
||||
var dispatch = await fixture.Context.RecordCompletionDispatches.SingleAsync();
|
||||
Assert.False(dispatch.ScriptDispatched);
|
||||
Assert.True(dispatch.UploadDispatched);
|
||||
Assert.Null(dispatch.CompletedAt);
|
||||
Assert.Contains("script failed", dispatch.LastError);
|
||||
Assert.NotNull(await fixture.Context.RecordUploadJobs.SingleOrDefaultAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompletionDispatch_UploadRejectionRemainsRetryable()
|
||||
{
|
||||
await using var fixture = await QueueFixture.CreateAsync(
|
||||
new VideoMetadata(0.18, 1920, 1080, "h264", "aac", 30, 4_000_000));
|
||||
await fixture.PrepareCompletionDispatchAsync();
|
||||
var service = fixture.CreateCompletionDispatchService(new FixedEventScriptService(null));
|
||||
|
||||
await service.TryDispatchTaskAsync(fixture.RecordTaskId);
|
||||
|
||||
var dispatch = await fixture.Context.RecordCompletionDispatches.SingleAsync();
|
||||
Assert.True(dispatch.ScriptDispatched);
|
||||
Assert.False(dispatch.UploadDispatched);
|
||||
Assert.Null(dispatch.CompletedAt);
|
||||
Assert.Contains("自动上传", dispatch.LastError);
|
||||
Assert.True(dispatch.NextAttemptAt > DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Enqueue_RejectsShortMediaAndKeepsItNotUploaded()
|
||||
{
|
||||
@@ -763,6 +836,30 @@ public sealed class OpenListUploadTests
|
||||
|
||||
public Guid RecordTaskId { get; }
|
||||
|
||||
public async Task PrepareCompletionDispatchAsync()
|
||||
{
|
||||
var session = await Context.RecordSessions.SingleAsync();
|
||||
session.MarkCompleted(DateTimeOffset.UtcNow);
|
||||
if (!await Context.RecordCompletionDispatches.AnyAsync())
|
||||
{
|
||||
Context.RecordCompletionDispatches.Add(
|
||||
new RecordCompletionDispatch(RecordTaskId, DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public CompletionDispatchService CreateCompletionDispatchService(IEventScriptService eventScriptService)
|
||||
{
|
||||
var uploadService = new RecordUploadService(Context, _settingsService, new NullSystemLogService(), Queue);
|
||||
var consolidation = new ShortFragmentConsolidationService(
|
||||
Context,
|
||||
_settingsService,
|
||||
_videoMetadataService,
|
||||
NullLogger<ShortFragmentConsolidationService>.Instance);
|
||||
return new CompletionDispatchService(Context, eventScriptService, uploadService, consolidation);
|
||||
}
|
||||
|
||||
public static async Task<QueueFixture> CreateAsync(VideoMetadata? metadata = null)
|
||||
{
|
||||
var temporaryRoot = Path.Combine(Path.GetTempPath(), $"live-recorder-openlist-{Guid.NewGuid():N}");
|
||||
@@ -886,6 +983,42 @@ public sealed class OpenListUploadTests
|
||||
CancellationToken cancellationToken = default) => Task.FromResult<string?>(null);
|
||||
}
|
||||
|
||||
private sealed class FixedEventScriptService : IEventScriptService
|
||||
{
|
||||
private readonly EventScriptExecutionResultDto? _segmentResult;
|
||||
|
||||
public FixedEventScriptService(EventScriptExecutionResultDto? segmentResult)
|
||||
{
|
||||
_segmentResult = segmentResult;
|
||||
}
|
||||
|
||||
public Task<EventScriptExecutionResultDto?> RunLiveStartedAsync(
|
||||
LiveRoom liveRoom,
|
||||
DateTimeOffset occurredAt,
|
||||
CancellationToken cancellationToken = default) => Task.FromResult<EventScriptExecutionResultDto?>(null);
|
||||
|
||||
public Task<EventScriptExecutionResultDto?> RunLiveEndedAsync(
|
||||
LiveRoom liveRoom,
|
||||
DateTimeOffset occurredAt,
|
||||
CancellationToken cancellationToken = default) => Task.FromResult<EventScriptExecutionResultDto?>(null);
|
||||
|
||||
public Task<EventScriptExecutionResultDto?> RunSegmentCompletedAsync(
|
||||
LiveRoom? liveRoom,
|
||||
RecordSession recordSession,
|
||||
RecordTask recordTask,
|
||||
RecordResult? recordResult,
|
||||
string segmentFilePath,
|
||||
DateTimeOffset occurredAt,
|
||||
bool forceRun = false,
|
||||
Guid? eventId = null,
|
||||
CancellationToken cancellationToken = default) => Task.FromResult(_segmentResult);
|
||||
|
||||
public Task<EventScriptTestResultDto> TestAsync(
|
||||
TestEventScriptRequest request,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(new EventScriptTestResultDto { Success = true, Message = "ok" });
|
||||
}
|
||||
|
||||
private sealed class FixedSettingsService : ISystemSettingsService
|
||||
{
|
||||
private readonly SystemSettingsDto _settings;
|
||||
|
||||
Reference in New Issue
Block a user