Updates
This commit is contained in:
@@ -47,6 +47,42 @@ catch (Exception exception)
|
||||
failures.Add($"Path resolution test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RunRenderJobContractDefaultsTest();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failures.Add($"Render contract test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RunRescoreReplacementTestAsync();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failures.Add($"Rescore replacement test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RunParserFailureTestAsync();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failures.Add($"Parser failure test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RunInvalidClipUploadTestAsync();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
failures.Add($"Invalid clip upload test failed: {exception.Message}");
|
||||
}
|
||||
|
||||
if (failures.Count > 0)
|
||||
{
|
||||
foreach (var failure in failures)
|
||||
@@ -94,7 +130,7 @@ static void RunScoringTest()
|
||||
[7] = new() { Id = 7, Name = "Alpha", Side = "CT" }
|
||||
};
|
||||
|
||||
var first = new EventRecord { Id = 1, RoundId = 1, Tick = 100, ActorPlayerId = 7, EventType = "kill" };
|
||||
var first = new EventRecord { Id = 1, RoundId = 1, Tick = 100, ActorPlayerId = 7, EventType = EventTypes.Kill };
|
||||
var target = new EventRecord
|
||||
{
|
||||
Id = 2,
|
||||
@@ -102,7 +138,7 @@ static void RunScoringTest()
|
||||
Tick = 450,
|
||||
Second = 38,
|
||||
ActorPlayerId = 7,
|
||||
EventType = "kill",
|
||||
EventType = EventTypes.Kill,
|
||||
IsHeadshot = true,
|
||||
KillerAliveCount = 1,
|
||||
EnemyAliveCount = 3
|
||||
@@ -115,6 +151,10 @@ static void RunScoringTest()
|
||||
Assert(result.Tags.Contains("multikill"), "Expected multikill tag.");
|
||||
Assert(result.Tags.Contains("clutch"), "Expected clutch tag.");
|
||||
Assert(result.Tags.Contains("1vX"), "Expected 1vX tag.");
|
||||
Assert(result.StartTick == 290, "Expected clip window pre-roll to be applied.");
|
||||
Assert(result.EndTick == 546, "Expected clip window post-roll to be applied.");
|
||||
Assert(result.Explanation.Any(item => item.Reason == "headshot"), "Expected score explanation to include headshot.");
|
||||
Assert(result.Explanation.Any(item => item.Reason == "clutch_1v3"), "Expected score explanation to include clutch weight.");
|
||||
}
|
||||
|
||||
static async Task RunFlowTestAsync()
|
||||
@@ -146,7 +186,7 @@ static async Task RunFlowTestAsync()
|
||||
|
||||
var store = new JsonAppStore(options);
|
||||
var scoringProvider = new ScoringRulesProvider(environment);
|
||||
var renderWorker = new NoOpRenderWorkerAdapter();
|
||||
var renderWorker = new RecordingRenderWorkerAdapter();
|
||||
var scoring = new MatchScoringService(scoringProvider, renderWorker, options);
|
||||
var ingest = new DemoIngestService(store, options, new SampleDemoParserAdapter(), scoring);
|
||||
var clipMedia = new ClipMediaService(store, options);
|
||||
@@ -159,17 +199,18 @@ static async Task RunFlowTestAsync()
|
||||
|
||||
var state = await store.GetSnapshotAsync();
|
||||
Assert(state.Demos.Count == 1, "Expected one demo.");
|
||||
Assert(state.Demos[0].Status == "parsed", "Expected parsed demo status.");
|
||||
Assert(state.Demos[0].Status == DemoStatuses.Parsed, "Expected parsed demo status.");
|
||||
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.");
|
||||
Assert(renderWorker.EnqueuedClipJobIds.Count == state.ClipJobs.Count, "Expected every created clip job to be enqueued.");
|
||||
|
||||
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);
|
||||
await clipMedia.AttachClipAssetAsync(state.ClipJobs[0].Id, $"sample{ClipMediaFormats.Mp4Extension}", ClipMediaFormats.Mp4ContentType, 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].Status == ClipJobStatuses.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.");
|
||||
@@ -198,18 +239,18 @@ static void RunRetryFlowTest()
|
||||
{
|
||||
Id = 1,
|
||||
HighlightId = 7,
|
||||
Renderer = "future-renderer",
|
||||
Status = "pending",
|
||||
Renderer = RenderBackendNames.FutureRenderer,
|
||||
Status = ClipJobStatuses.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.MarkAttemptStarted(firstJob, RenderBackendNames.SampleCopy, 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.Status == ClipJobStatuses.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.");
|
||||
@@ -218,13 +259,13 @@ static void RunRetryFlowTest()
|
||||
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";
|
||||
ClipJobRetryPolicy.MarkAttemptStarted(firstJob, RenderBackendNames.SampleCopy, DateTime.UtcNow);
|
||||
firstJob.Status = ClipJobStatuses.Rendered;
|
||||
firstJob.ErrorMessage = null;
|
||||
firstJob.RetryAfterUtc = null;
|
||||
firstJob.CompletedAtUtc = DateTime.UtcNow;
|
||||
|
||||
Assert(firstJob.Status == "rendered", "Expected retry success to render the clip.");
|
||||
Assert(firstJob.Status == ClipJobStatuses.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.");
|
||||
|
||||
@@ -232,13 +273,13 @@ static void RunRetryFlowTest()
|
||||
{
|
||||
Id = 2,
|
||||
HighlightId = 8,
|
||||
Status = "pending",
|
||||
Status = ClipJobStatuses.Pending,
|
||||
MaxAttempts = 1
|
||||
};
|
||||
|
||||
ClipJobRetryPolicy.MarkAttemptStarted(terminalJob, "sample-copy", DateTime.UtcNow);
|
||||
ClipJobRetryPolicy.MarkAttemptStarted(terminalJob, RenderBackendNames.SampleCopy, 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.Status == ClipJobStatuses.Failed, "Expected job with no retries remaining to become failed.");
|
||||
Assert(terminalJob.RetryAfterUtc is null, "Expected terminal failure to avoid scheduling another retry.");
|
||||
}
|
||||
|
||||
@@ -257,6 +298,258 @@ static void RunPathResolutionTest()
|
||||
Assert(resolvedFromBaseDirectory == root, "Expected app base directory traversal to find the project root.");
|
||||
}
|
||||
|
||||
static void RunRenderJobContractDefaultsTest()
|
||||
{
|
||||
var minimalPayload = new Dictionary<string, string>
|
||||
{
|
||||
["demoId"] = "9",
|
||||
["matchId"] = "17",
|
||||
["highlightId"] = "23",
|
||||
["sourceEventId"] = "42",
|
||||
["startTick"] = "1500",
|
||||
["endTick"] = "1800"
|
||||
};
|
||||
|
||||
var contract = RenderJobContract.FromPayload(minimalPayload);
|
||||
|
||||
Assert(contract.Version == RenderJobContract.CurrentVersion, "Expected missing version to fall back to current contract version.");
|
||||
Assert(contract.CameraMode == RenderJobContract.DefaultCameraMode, "Expected default camera mode.");
|
||||
Assert(contract.OutputFormat == RenderJobContract.DefaultOutputFormat, "Expected default output format.");
|
||||
Assert(contract.Resolution == RenderJobContract.DefaultResolution, "Expected default resolution.");
|
||||
Assert(contract.Fps == RenderJobContract.DefaultFps, "Expected default fps.");
|
||||
Assert(contract.GetSuggestedFileExtension() == ClipMediaFormats.Mp4Extension, "Expected mp4 extension for default output format.");
|
||||
|
||||
contract.OutputFormat = "webm";
|
||||
Assert(contract.GetSuggestedFileExtension() == ClipMediaFormats.WebMExtension, "Expected webm extension override.");
|
||||
}
|
||||
|
||||
static async Task RunRescoreReplacementTestAsync()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "fragalytics-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
Directory.CreateDirectory(Path.Combine(root, "data"));
|
||||
Directory.CreateDirectory(Path.Combine(root, "uploads"));
|
||||
|
||||
var environment = new FakeEnvironment(root);
|
||||
var options = Options.Create(new FragalyticsOptions
|
||||
{
|
||||
DataDirectory = Path.Combine(root, "data"),
|
||||
UploadDirectory = Path.Combine(root, "uploads"),
|
||||
AutoStartWorker = false,
|
||||
RenderWorker = new RenderWorkerOptions
|
||||
{
|
||||
MaxAttempts = 3,
|
||||
RetryDelaySeconds = 15
|
||||
}
|
||||
});
|
||||
|
||||
var scoringRules = """
|
||||
{
|
||||
"name": "test-rules",
|
||||
"weights": {
|
||||
"base_kill": 10,
|
||||
"headshot": 8,
|
||||
"opening_kill": 12,
|
||||
"wallbang": 10,
|
||||
"smoke_kill": 10,
|
||||
"round_win": 6,
|
||||
"multikill_2": 8,
|
||||
"multikill_3": 18,
|
||||
"multikill_4": 30,
|
||||
"multikill_5": 45,
|
||||
"clutch_1v2": 18,
|
||||
"clutch_1v3": 28,
|
||||
"clutch_1v4": 40,
|
||||
"clutch_1v5": 55,
|
||||
"impact_last_enemy": 12,
|
||||
"impact_low_time": 4
|
||||
},
|
||||
"clip": {
|
||||
"preTicks": 160,
|
||||
"postTicks": 96
|
||||
},
|
||||
"thresholds": {
|
||||
"highlightScoreMin": 18
|
||||
}
|
||||
}
|
||||
""";
|
||||
File.WriteAllText(Path.Combine(root, "scoring-rules.json"), scoringRules);
|
||||
|
||||
var store = new JsonAppStore(options);
|
||||
var scoringProvider = new ScoringRulesProvider(environment);
|
||||
var renderWorker = new RecordingRenderWorkerAdapter();
|
||||
var parser = new StaticParserAdapter(BuildRescoreParsedDemo());
|
||||
var scoring = new MatchScoringService(scoringProvider, renderWorker, options);
|
||||
var ingest = new DemoIngestService(store, options, parser, scoring);
|
||||
var clipMedia = new ClipMediaService(store, options);
|
||||
|
||||
await using var payload = new MemoryStream("rescore demo"u8.ToArray());
|
||||
var demoId = await ingest.SaveUploadAsync("rescore.dem", payload, CancellationToken.None);
|
||||
await ingest.ProcessNextAsync(CancellationToken.None);
|
||||
|
||||
var state = await store.GetSnapshotAsync();
|
||||
Assert(state.Highlights.Count == 1, "Expected only one above-threshold highlight from the initial parse.");
|
||||
Assert(state.ClipJobs.Count == 1, "Expected one clip job for one highlight.");
|
||||
Assert(renderWorker.EnqueuedClipJobIds.Count == 1, "Expected initial highlight to be enqueued once.");
|
||||
|
||||
var initialHighlightId = state.Highlights[0].Id;
|
||||
var initialClipJobId = state.ClipJobs[0].Id;
|
||||
|
||||
await using var clipPayload = new MemoryStream("first clip bytes"u8.ToArray());
|
||||
await clipMedia.AttachClipAssetAsync(initialClipJobId, $"initial{ClipMediaFormats.Mp4Extension}", ClipMediaFormats.Mp4ContentType, clipPayload, CancellationToken.None);
|
||||
|
||||
state = await store.GetSnapshotAsync();
|
||||
Assert(state.ClipAssets.Count == 1, "Expected one attached clip asset before rescoring.");
|
||||
|
||||
File.WriteAllText(
|
||||
Path.Combine(root, "scoring-rules.json"),
|
||||
scoringRules.Replace("\"highlightScoreMin\": 18", "\"highlightScoreMin\": 8", StringComparison.Ordinal));
|
||||
|
||||
var rescoredCount = await ingest.RescoreAllAsync(CancellationToken.None);
|
||||
Assert(rescoredCount == 2, "Expected rescoring to create two highlights after lowering the threshold.");
|
||||
|
||||
state = await store.GetSnapshotAsync();
|
||||
Assert(state.Highlights.Count == 2, "Expected rescoring to replace old highlights with the new set.");
|
||||
Assert(state.Highlights.All(item => item.Id != initialHighlightId), "Expected rescoring to replace prior highlight records.");
|
||||
Assert(state.ClipJobs.Count == 2, "Expected clip jobs to be regenerated on rescoring.");
|
||||
Assert(state.ClipJobs.All(item => item.Id != initialClipJobId), "Expected prior clip jobs to be discarded on rescoring.");
|
||||
Assert(state.ClipAssets.Count == 0, "Expected clip assets tied to old clip jobs to be removed during rescoring.");
|
||||
Assert(renderWorker.EnqueuedClipJobIds.Count == 3, "Expected rescoring to enqueue the replacement clip jobs.");
|
||||
Assert(state.ClipJobs.All(item => item.MaxAttempts == 3), "Expected regenerated clip jobs to preserve configured retry policy.");
|
||||
Assert(state.ScoringProfiles.Count == 2, "Expected a new scoring profile version after rules changed.");
|
||||
Assert(state.ScoringProfiles.Count(item => item.IsActive) == 1, "Expected only one active scoring profile.");
|
||||
|
||||
var rescoredHighlightsByEvent = state.Highlights.ToDictionary(item => item.SourceEventId);
|
||||
Assert(rescoredHighlightsByEvent.Count == 2, "Expected each source event to map to one highlight after rescoring.");
|
||||
Assert(rescoredHighlightsByEvent.Values.Any(item => item.Score < 18), "Expected rescoring to include a previously filtered low-score highlight.");
|
||||
|
||||
var regeneratedPayload = state.ClipJobs
|
||||
.Select(item => RenderJobContract.FromPayload(item.Payload))
|
||||
.ToList();
|
||||
Assert(regeneratedPayload.All(item => item.DemoId == demoId), "Expected regenerated payloads to retain the demo id.");
|
||||
Assert(regeneratedPayload.All(item => item.OutputFormat == RenderJobContract.DefaultOutputFormat), "Expected regenerated payloads to keep default render settings.");
|
||||
}
|
||||
|
||||
static async Task RunParserFailureTestAsync()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "fragalytics-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
Directory.CreateDirectory(Path.Combine(root, "data"));
|
||||
Directory.CreateDirectory(Path.Combine(root, "uploads"));
|
||||
|
||||
var environment = new FakeEnvironment(root);
|
||||
var options = Options.Create(new FragalyticsOptions
|
||||
{
|
||||
DataDirectory = Path.Combine(root, "data"),
|
||||
UploadDirectory = Path.Combine(root, "uploads"),
|
||||
AutoStartWorker = false
|
||||
});
|
||||
|
||||
var repoRoot = FindRepoRoot();
|
||||
File.Copy(
|
||||
Path.Combine(repoRoot, "src", "Fragalytics.Web", "scoring-rules.json"),
|
||||
Path.Combine(root, "scoring-rules.json"),
|
||||
overwrite: true);
|
||||
|
||||
var store = new JsonAppStore(options);
|
||||
var scoringProvider = new ScoringRulesProvider(environment);
|
||||
var scoring = new MatchScoringService(scoringProvider, new RecordingRenderWorkerAdapter(), options);
|
||||
var ingest = new DemoIngestService(store, options, new ThrowingParserAdapter("Simulated parser failure."), scoring);
|
||||
|
||||
await using var payload = new MemoryStream("broken demo"u8.ToArray());
|
||||
var demoId = await ingest.SaveUploadAsync("broken.dem", payload, CancellationToken.None);
|
||||
|
||||
var processed = await ingest.ProcessNextAsync(CancellationToken.None);
|
||||
Assert(processed == 0, "Expected failed parse to report no processed demo.");
|
||||
|
||||
var state = await store.GetSnapshotAsync();
|
||||
Assert(state.Demos.Count == 1, "Expected failed parse to retain the demo record.");
|
||||
Assert(state.Demos[0].Id == demoId, "Expected the failed demo to remain addressable.");
|
||||
Assert(state.Demos[0].Status == DemoStatuses.Failed, "Expected parser failure to mark the demo as failed.");
|
||||
Assert(state.Demos[0].ErrorMessage == "Simulated parser failure.", "Expected parser failure message to be recorded.");
|
||||
Assert(state.Demos[0].ProcessedAtUtc.HasValue, "Expected failed parser attempt to stamp processed time.");
|
||||
Assert(state.Matches.Count == 0, "Expected failed parser attempt to avoid creating a match.");
|
||||
Assert(state.Events.Count == 0, "Expected failed parser attempt to avoid creating events.");
|
||||
Assert(state.Highlights.Count == 0, "Expected failed parser attempt to avoid creating highlights.");
|
||||
Assert(state.ClipJobs.Count == 0, "Expected failed parser attempt to avoid creating clip jobs.");
|
||||
}
|
||||
|
||||
static async Task RunInvalidClipUploadTestAsync()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "fragalytics-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
Directory.CreateDirectory(Path.Combine(root, "data"));
|
||||
Directory.CreateDirectory(Path.Combine(root, "uploads"));
|
||||
|
||||
var environment = new FakeEnvironment(root);
|
||||
var options = Options.Create(new FragalyticsOptions
|
||||
{
|
||||
DataDirectory = Path.Combine(root, "data"),
|
||||
UploadDirectory = Path.Combine(root, "uploads"),
|
||||
AutoStartWorker = false
|
||||
});
|
||||
|
||||
var repoRoot = FindRepoRoot();
|
||||
File.Copy(
|
||||
Path.Combine(repoRoot, "src", "Fragalytics.Web", "scoring-rules.json"),
|
||||
Path.Combine(root, "scoring-rules.json"),
|
||||
overwrite: true);
|
||||
|
||||
var store = new JsonAppStore(options);
|
||||
var scoringProvider = new ScoringRulesProvider(environment);
|
||||
var scoring = new MatchScoringService(scoringProvider, new RecordingRenderWorkerAdapter(), options);
|
||||
var ingest = new DemoIngestService(store, options, new SampleDemoParserAdapter(), scoring);
|
||||
var clipMedia = new ClipMediaService(store, options);
|
||||
|
||||
await using var payload = new MemoryStream("valid demo"u8.ToArray());
|
||||
await ingest.SaveUploadAsync("valid.dem", payload, CancellationToken.None);
|
||||
await ingest.ProcessNextAsync(CancellationToken.None);
|
||||
|
||||
var state = await store.GetSnapshotAsync();
|
||||
Assert(state.ClipJobs.Count > 0, "Expected parsed demo to create a clip job for invalid upload test.");
|
||||
var clipJobId = state.ClipJobs[0].Id;
|
||||
|
||||
await using var invalidClip = new MemoryStream("not a video"u8.ToArray());
|
||||
var exception = await AssertThrowsAsync<InvalidOperationException>(
|
||||
() => clipMedia.AttachClipAssetAsync(clipJobId, "invalid.avi", "video/x-msvideo", invalidClip, CancellationToken.None),
|
||||
"Expected unsupported clip extension to be rejected.");
|
||||
Assert(exception.Message.Contains(ClipMediaFormats.Mp4Extension, StringComparison.Ordinal), "Expected invalid clip upload message to mention mp4 support.");
|
||||
Assert(exception.Message.Contains(ClipMediaFormats.WebMExtension, StringComparison.Ordinal), "Expected invalid clip upload message to mention webm support.");
|
||||
|
||||
state = await store.GetSnapshotAsync();
|
||||
Assert(state.ClipAssets.Count == 0, "Expected invalid clip upload to avoid creating clip assets.");
|
||||
Assert(state.ClipJobs[0].Id == clipJobId, "Expected invalid clip upload to leave the same clip job in place.");
|
||||
Assert(state.ClipJobs[0].Status == ClipJobStatuses.Pending, "Expected invalid clip upload to avoid changing clip job status.");
|
||||
Assert(state.ClipJobs[0].ClipAssetId is null, "Expected invalid clip upload to avoid linking a clip asset.");
|
||||
}
|
||||
|
||||
static ParsedDemo BuildRescoreParsedDemo()
|
||||
{
|
||||
return new ParsedDemo
|
||||
{
|
||||
MapName = "test_rescore_map",
|
||||
ServerName = "Deterministic Test Parser",
|
||||
TickRate = 64,
|
||||
TeamCtName = "Counter-Terrorists",
|
||||
TeamTName = "Terrorists",
|
||||
Players =
|
||||
[
|
||||
new() { SteamId = "1001", Name = "Alpha", TeamName = "Counter-Terrorists", Side = "CT" },
|
||||
new() { SteamId = "1002", Name = "Bravo", TeamName = "Counter-Terrorists", Side = "CT" },
|
||||
new() { SteamId = "1003", Name = "Charlie", TeamName = "Terrorists", Side = "T" }
|
||||
],
|
||||
Rounds =
|
||||
[
|
||||
new() { Number = 1, StartTick = 0, EndTick = 600, WinnerSide = "CT", WinnerTeamName = "Counter-Terrorists", Reason = "elimination" }
|
||||
],
|
||||
KillEvents =
|
||||
[
|
||||
new() { Tick = 120, Second = 5.0, RoundNumber = 1, KillerSteamId = "1001", KillerName = "Alpha", VictimSteamId = "1003", VictimName = "Charlie", Weapon = "m4a1", IsHeadshot = true, KillerAliveCount = 2, EnemyAliveCount = 2 },
|
||||
new() { Tick = 280, Second = 18.0, RoundNumber = 1, KillerSteamId = "1002", KillerName = "Bravo", VictimSteamId = "1003", VictimName = "Charlie", Weapon = "famas", KillerAliveCount = 2, EnemyAliveCount = 2 }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
static string FindRepoRoot()
|
||||
{
|
||||
var current = AppContext.BaseDirectory;
|
||||
@@ -288,6 +581,21 @@ static void Assert(bool condition, string message)
|
||||
}
|
||||
}
|
||||
|
||||
static async Task<TException> AssertThrowsAsync<TException>(Func<Task> action, string message)
|
||||
where TException : Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
await action();
|
||||
}
|
||||
catch (TException exception)
|
||||
{
|
||||
return exception;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
internal sealed class FakeEnvironment : IWebHostEnvironment
|
||||
{
|
||||
public FakeEnvironment(string rootPath)
|
||||
@@ -307,3 +615,48 @@ internal sealed class FakeEnvironment : IWebHostEnvironment
|
||||
public string ContentRootPath { get; set; }
|
||||
public IFileProvider ContentRootFileProvider { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class RecordingRenderWorkerAdapter : IRenderWorkerAdapter
|
||||
{
|
||||
public string Name => "recording-renderer";
|
||||
public List<int> EnqueuedClipJobIds { get; } = [];
|
||||
|
||||
public void Enqueue(ClipJobRecord clipJob)
|
||||
{
|
||||
EnqueuedClipJobIds.Add(clipJob.Id);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StaticParserAdapter : IDemoParserAdapter
|
||||
{
|
||||
private readonly ParsedDemo _parsedDemo;
|
||||
|
||||
public StaticParserAdapter(ParsedDemo parsedDemo)
|
||||
{
|
||||
_parsedDemo = parsedDemo;
|
||||
}
|
||||
|
||||
public string Name => "static-test";
|
||||
|
||||
public Task<ParsedDemo> ParseAsync(string demoPath, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(_parsedDemo);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ThrowingParserAdapter : IDemoParserAdapter
|
||||
{
|
||||
private readonly string _message;
|
||||
|
||||
public ThrowingParserAdapter(string message)
|
||||
{
|
||||
_message = message;
|
||||
}
|
||||
|
||||
public string Name => "throwing-test";
|
||||
|
||||
public Task<ParsedDemo> ParseAsync(string demoPath, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new InvalidOperationException(_message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user