first commit

This commit is contained in:
Zeeshaun
2026-04-20 18:15:08 +00:00
commit c98e158e26
111 changed files with 77565 additions and 0 deletions
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Fragalytics.Web\Fragalytics.Web.csproj" />
</ItemGroup>
</Project>
+182
View File
@@ -0,0 +1,182 @@
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<string>();
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<string, double>
{
["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<int, PlayerRecord>
{
[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; }
}