183 lines
6.1 KiB
C#
183 lines
6.1 KiB
C#
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; }
|
|
}
|