using Fragalytics.Web; using Fragalytics.Web.Infrastructure; using Fragalytics.Web.Models; using Fragalytics.Web.Services; using Fragalytics.Web.Services.Parser; using Fragalytics.Web.Services.Render; using Fragalytics.Web.Services.Scoring; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; var failures = new List(); try { RunScoringTest(); } catch (Exception exception) { failures.Add($"Scoring test failed: {exception.Message}"); } try { await RunFlowTestAsync(); } catch (Exception exception) { failures.Add($"Flow test failed: {exception.Message}"); } if (failures.Count > 0) { foreach (var failure in failures) { Console.Error.WriteLine(failure); } return 1; } Console.WriteLine("All tests passed."); return 0; static void RunScoringTest() { var rules = new HighlightScoringRules { Weights = new Dictionary { ["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 = new ClipWindowRules { PreTicks = 160, PostTicks = 96 }, Thresholds = new ScoreThresholdRules { HighlightScoreMin = 18 } }; var scorer = new HighlightScorer(rules); var round = new RoundRecord { Id = 1, Number = 3, WinnerSide = "CT" }; var players = new Dictionary { [7] = new() { Id = 7, Name = "Alpha", Side = "CT" } }; var first = new EventRecord { Id = 1, RoundId = 1, Tick = 100, ActorPlayerId = 7, EventType = "kill" }; var target = new EventRecord { Id = 2, RoundId = 1, Tick = 450, Second = 38, ActorPlayerId = 7, EventType = "kill", IsHeadshot = true, KillerAliveCount = 1, EnemyAliveCount = 3 }; var result = scorer.ScoreKill(target, round, [first, target], players, 1); Assert(result.Score >= 18, "Expected score above threshold."); Assert(result.Tags.Contains("headshot"), "Expected headshot tag."); Assert(result.Tags.Contains("multikill"), "Expected multikill tag."); Assert(result.Tags.Contains("clutch"), "Expected clutch tag."); Assert(result.Tags.Contains("1vX"), "Expected 1vX tag."); } static async Task RunFlowTestAsync() { 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 scoringRulesSource = Path.GetFullPath( Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "src", "Fragalytics.Web", "scoring-rules.json")); File.Copy( scoringRulesSource, Path.Combine(root, "scoring-rules.json"), overwrite: true); var store = new JsonAppStore(options); var scoringProvider = new ScoringRulesProvider(environment); var renderWorker = new NoOpRenderWorkerAdapter(); var scoring = new MatchScoringService(scoringProvider, renderWorker); var ingest = new DemoIngestService(store, options, new SampleDemoParserAdapter(), scoring); var clipMedia = new ClipMediaService(store, options); await using var payload = new MemoryStream("fake demo bytes"u8.ToArray()); var demoId = await ingest.SaveUploadAsync("sample.dem", payload, CancellationToken.None); Assert(demoId == 1, "Expected first demo id to be 1."); await ingest.ProcessNextAsync(CancellationToken.None); 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.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."); 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.ContainsKey("cameraMode"), "Expected render payload defaults on clip job."); await ingest.RescoreAllAsync(CancellationToken.None); state = await store.GetSnapshotAsync(); Assert(state.Highlights.Count > 0, "Expected highlights after rescoring."); } static void Assert(bool condition, string message) { if (!condition) { throw new InvalidOperationException(message); } } internal sealed class FakeEnvironment : IWebHostEnvironment { public FakeEnvironment(string rootPath) { ContentRootPath = rootPath; ContentRootFileProvider = new PhysicalFileProvider(rootPath); WebRootPath = rootPath; WebRootFileProvider = new PhysicalFileProvider(rootPath); ApplicationName = "Fragalytics.Web.Tests"; EnvironmentName = "Development"; } public string ApplicationName { get; set; } public IFileProvider WebRootFileProvider { get; set; } public string WebRootPath { get; set; } public string EnvironmentName { get; set; } public string ContentRootPath { get; set; } public IFileProvider ContentRootFileProvider { get; set; } }