Adding features
This commit is contained in:
@@ -29,6 +29,24 @@ catch (Exception exception)
|
||||
failures.Add($"Flow test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RunRetryFlowTest();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failures.Add($"Retry flow test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RunPathResolutionTest();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failures.Add($"Path resolution test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
if (failures.Count > 0)
|
||||
{
|
||||
foreach (var failure in failures)
|
||||
@@ -111,11 +129,16 @@ static async Task RunFlowTestAsync()
|
||||
{
|
||||
DataDirectory = Path.Combine(root, "data"),
|
||||
UploadDirectory = Path.Combine(root, "uploads"),
|
||||
AutoStartWorker = false
|
||||
AutoStartWorker = false,
|
||||
RenderWorker = new RenderWorkerOptions
|
||||
{
|
||||
MaxAttempts = 2,
|
||||
RetryDelaySeconds = 30
|
||||
}
|
||||
});
|
||||
|
||||
var scoringRulesSource = Path.GetFullPath(
|
||||
Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "src", "Fragalytics.Web", "scoring-rules.json"));
|
||||
var repoRoot = FindRepoRoot();
|
||||
var scoringRulesSource = Path.Combine(repoRoot, "src", "Fragalytics.Web", "scoring-rules.json");
|
||||
File.Copy(
|
||||
scoringRulesSource,
|
||||
Path.Combine(root, "scoring-rules.json"),
|
||||
@@ -124,7 +147,7 @@ static async Task RunFlowTestAsync()
|
||||
var store = new JsonAppStore(options);
|
||||
var scoringProvider = new ScoringRulesProvider(environment);
|
||||
var renderWorker = new NoOpRenderWorkerAdapter();
|
||||
var scoring = new MatchScoringService(scoringProvider, renderWorker);
|
||||
var scoring = new MatchScoringService(scoringProvider, renderWorker, options);
|
||||
var ingest = new DemoIngestService(store, options, new SampleDemoParserAdapter(), scoring);
|
||||
var clipMedia = new ClipMediaService(store, options);
|
||||
|
||||
@@ -140,17 +163,121 @@ static async Task RunFlowTestAsync()
|
||||
Assert(state.Matches.Count == 1, "Expected one match.");
|
||||
Assert(state.Highlights.Count > 0, "Expected highlights.");
|
||||
Assert(state.ClipJobs.Count == state.Highlights.Count, "Expected one clip job per highlight.");
|
||||
Assert(state.ClipJobs[0].AttemptCount == 0, "Expected new clip job to have no worker attempts yet.");
|
||||
|
||||
await using var clipPayload = new MemoryStream("fake rendered mp4 bytes"u8.ToArray());
|
||||
await clipMedia.AttachClipAssetAsync(state.ClipJobs[0].Id, "sample.mp4", "video/mp4", clipPayload, CancellationToken.None);
|
||||
state = await store.GetSnapshotAsync();
|
||||
Assert(state.ClipAssets.Count == 1, "Expected one clip asset.");
|
||||
Assert(state.ClipJobs[0].Status == "rendered", "Expected rendered clip status after attach.");
|
||||
Assert(state.ClipJobs[0].Payload.TryGetValue("version", out var version) && version == RenderJobContract.CurrentVersion, "Expected versioned render payload.");
|
||||
Assert(state.ClipJobs[0].Payload.TryGetValue("demoId", out var demoIdPayload) && demoIdPayload == "1", "Expected demo id in render payload.");
|
||||
Assert(state.ClipJobs[0].Payload.ContainsKey("cameraMode"), "Expected render payload defaults on clip job.");
|
||||
Assert(state.ClipJobs[0].Payload.Keys.All(key => key is "version" or "demoId" or "matchId" or "highlightId" or "sourceEventId" or "startTick" or "endTick" or "cameraMode" or "outputFormat" or "resolution" or "fps"), "Expected payload to contain only render contract fields.");
|
||||
|
||||
var renderJob = RenderJobContract.FromPayload(state.ClipJobs[0].Payload);
|
||||
Assert(renderJob.MatchId == state.Matches[0].Id, "Expected match id in typed render payload.");
|
||||
Assert(renderJob.OutputFormat == RenderJobContract.DefaultOutputFormat, "Expected default output format.");
|
||||
|
||||
await ingest.RescoreAllAsync(CancellationToken.None);
|
||||
state = await store.GetSnapshotAsync();
|
||||
Assert(state.Highlights.Count > 0, "Expected highlights after rescoring.");
|
||||
|
||||
var deleted = await ingest.DeleteDemoAsync(demoId, CancellationToken.None);
|
||||
Assert(deleted, "Expected demo deletion to succeed.");
|
||||
state = await store.GetSnapshotAsync();
|
||||
Assert(state.Demos.Count == 0, "Expected demo to be removed after deletion.");
|
||||
Assert(state.Matches.Count == 0, "Expected match graph to be removed after demo deletion.");
|
||||
Assert(state.Highlights.Count == 0, "Expected highlights to be removed after demo deletion.");
|
||||
Assert(state.ClipJobs.Count == 0, "Expected clip jobs to be removed after demo deletion.");
|
||||
}
|
||||
|
||||
static void RunRetryFlowTest()
|
||||
{
|
||||
var firstJob = new ClipJobRecord
|
||||
{
|
||||
Id = 1,
|
||||
HighlightId = 7,
|
||||
Renderer = "future-renderer",
|
||||
Status = "pending",
|
||||
StartTick = 100,
|
||||
EndTick = 200,
|
||||
MaxAttempts = 2
|
||||
};
|
||||
|
||||
Assert(ClipJobRetryPolicy.CanStartAttempt(firstJob, DateTime.UtcNow), "Expected pending job to be claimable before any attempts.");
|
||||
ClipJobRetryPolicy.MarkAttemptStarted(firstJob, "sample-copy", DateTime.UtcNow);
|
||||
ClipJobRetryPolicy.MarkAttemptFailed(firstJob, "Simulated backend failure.", DateTime.UtcNow, TimeSpan.FromSeconds(30));
|
||||
|
||||
Assert(firstJob.Status == "pending", "Expected failed render with retries remaining to return to pending.");
|
||||
Assert(firstJob.AttemptCount == 1, "Expected first failed attempt to increment attempt count.");
|
||||
Assert(firstJob.RetryAfterUtc.HasValue, "Expected retry to be scheduled after a failure.");
|
||||
Assert(!string.IsNullOrWhiteSpace(firstJob.ErrorMessage), "Expected failure message to be preserved for retry visibility.");
|
||||
Assert(!ClipJobRetryPolicy.CanStartAttempt(firstJob, DateTime.UtcNow), "Expected retry window to block immediate reprocessing.");
|
||||
|
||||
firstJob.RetryAfterUtc = DateTime.UtcNow.AddSeconds(-1);
|
||||
Assert(ClipJobRetryPolicy.CanStartAttempt(firstJob, DateTime.UtcNow), "Expected job to become claimable once retry delay passes.");
|
||||
|
||||
ClipJobRetryPolicy.MarkAttemptStarted(firstJob, "sample-copy", DateTime.UtcNow);
|
||||
firstJob.Status = "rendered";
|
||||
firstJob.ErrorMessage = null;
|
||||
firstJob.RetryAfterUtc = null;
|
||||
firstJob.CompletedAtUtc = DateTime.UtcNow;
|
||||
|
||||
Assert(firstJob.Status == "rendered", "Expected retry success to render the clip.");
|
||||
Assert(firstJob.AttemptCount == 2, "Expected successful retry to record the second attempt.");
|
||||
Assert(firstJob.RetryAfterUtc is null, "Expected successful retry to clear the retry schedule.");
|
||||
|
||||
var terminalJob = new ClipJobRecord
|
||||
{
|
||||
Id = 2,
|
||||
HighlightId = 8,
|
||||
Status = "pending",
|
||||
MaxAttempts = 1
|
||||
};
|
||||
|
||||
ClipJobRetryPolicy.MarkAttemptStarted(terminalJob, "sample-copy", DateTime.UtcNow);
|
||||
ClipJobRetryPolicy.MarkAttemptFailed(terminalJob, "Permanent failure.", DateTime.UtcNow, TimeSpan.FromSeconds(30));
|
||||
Assert(terminalJob.Status == "failed", "Expected job with no retries remaining to become failed.");
|
||||
Assert(terminalJob.RetryAfterUtc is null, "Expected terminal failure to avoid scheduling another retry.");
|
||||
}
|
||||
|
||||
static void RunPathResolutionTest()
|
||||
{
|
||||
var root = Path.Combine(FindRepoRoot(), "src", "Fragalytics.Web");
|
||||
|
||||
if (root.StartsWith("/mnt/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var mixedWindowsPath = @"Z:\mnt\z\" + Path.GetRelativePath("/mnt/z", root).Replace('/', '\\');
|
||||
var resolvedFromMixedWindows = AppPathResolver.ResolveContentRoot(mixedWindowsPath, root);
|
||||
Assert(resolvedFromMixedWindows == root, "Expected mixed Windows path to resolve to the real app root.");
|
||||
}
|
||||
|
||||
var resolvedFromBaseDirectory = AppPathResolver.ResolveContentRoot(Path.Combine(root, "bin", "Debug", "net8.0"), Path.Combine(root, "bin", "Debug", "net8.0"));
|
||||
Assert(resolvedFromBaseDirectory == root, "Expected app base directory traversal to find the project root.");
|
||||
}
|
||||
|
||||
static string FindRepoRoot()
|
||||
{
|
||||
var current = AppContext.BaseDirectory;
|
||||
while (!string.IsNullOrWhiteSpace(current))
|
||||
{
|
||||
if (File.Exists(Path.Combine(current, "Fragalytics.sln"))
|
||||
&& File.Exists(Path.Combine(current, "Directory.Build.props")))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
var parent = Path.GetDirectoryName(current);
|
||||
if (string.IsNullOrWhiteSpace(parent) || string.Equals(parent, current, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Could not locate repository root from test base directory.");
|
||||
}
|
||||
|
||||
static void Assert(bool condition, string message)
|
||||
|
||||
Reference in New Issue
Block a user