Files
metin-launcher/tests/Metin2Launcher.Tests/ClientAppliedReporterTests.cs
Jan Nedbal 0e95171e50 test: cover runtime key, release formats and telemetry
adds ~60 new tests across RuntimeKey parsing, EnvVarKeyDelivery, the
legacy and m2pack formats, ReleaseFormatFactory dispatch, manifest
loader tolerance of unknown top-level fields, orchestrator wiring and
the ClientAppliedReporter (disabled-by-default, success, 5xx, timeout,
connection refused). The telemetry tests spin up an in-process
HttpListener helper — no new NuGet dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 21:10:48 +02:00

125 lines
4.0 KiB
C#

using System.Net;
using System.Net.Http;
using Metin2Launcher.Telemetry;
using Xunit;
namespace Metin2Launcher.Tests;
public class ClientAppliedReporterTests
{
[Fact]
public void Disabled_when_template_is_empty()
{
using var http = new HttpClient();
var r = new ClientAppliedReporter(http, "");
Assert.False(r.IsEnabled);
}
[Fact]
public void Disabled_when_template_is_null()
{
using var http = new HttpClient();
var r = new ClientAppliedReporter(http, null);
Assert.False(r.IsEnabled);
}
[Fact]
public void Disabled_when_template_is_whitespace()
{
using var http = new HttpClient();
var r = new ClientAppliedReporter(http, " ");
Assert.False(r.IsEnabled);
}
[Fact]
public async Task ReportAsync_noop_when_disabled()
{
using var http = new HttpClient();
var r = new ClientAppliedReporter(http, "");
var ok = await r.ReportAsync("m2pack", "v1", CancellationToken.None);
Assert.False(ok);
}
[Fact]
public void Expand_substitutes_placeholders()
{
var s = ClientAppliedReporter.Expand("https://x/{format}/{version}", "m2pack", "2026.04.14-1");
Assert.Equal("https://x/m2pack/2026.04.14-1", s);
}
[Fact]
public void Expand_uri_escapes_values()
{
var s = ClientAppliedReporter.Expand("https://x/{version}", "m2pack", "v 1+beta");
Assert.Contains("v%201%2Bbeta", s);
}
[Fact]
public void Expand_leaves_unknown_placeholders()
{
var s = ClientAppliedReporter.Expand("https://x/{other}", "m2pack", "v1");
Assert.Equal("https://x/{other}", s);
}
[Fact]
public async Task ReportAsync_posts_json_on_success()
{
using var listener = new TestHttpListener();
var url = listener.Start();
listener.RespondWith(HttpStatusCode.OK);
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
var r = new ClientAppliedReporter(http, url + "?format={format}&version={version}");
var ok = await r.ReportAsync("m2pack", "2026.04.14-1", CancellationToken.None);
Assert.True(ok);
var req = listener.LastRequest!;
Assert.Equal("POST", req.Method);
Assert.Contains("format=m2pack", req.RawUrl);
Assert.Contains("\"Format\":\"m2pack\"", req.Body);
Assert.Contains("\"Version\":\"2026.04.14-1\"", req.Body);
}
[Fact]
public async Task ReportAsync_returns_false_on_500()
{
using var listener = new TestHttpListener();
var url = listener.Start();
listener.RespondWith(HttpStatusCode.InternalServerError);
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
var r = new ClientAppliedReporter(http, url);
var ok = await r.ReportAsync("m2pack", "v1", CancellationToken.None);
Assert.False(ok);
}
[Fact]
public async Task ReportAsync_swallows_connection_refused()
{
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
// pick a port unlikely to be listening
var r = new ClientAppliedReporter(http, "http://127.0.0.1:1/");
var ok = await r.ReportAsync("m2pack", "v1", CancellationToken.None);
Assert.False(ok);
}
[Fact]
public async Task ReportAsync_times_out_after_five_seconds()
{
using var listener = new TestHttpListener();
var url = listener.Start();
listener.RespondAfter(TimeSpan.FromSeconds(10), HttpStatusCode.OK);
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
var r = new ClientAppliedReporter(http, url);
var sw = System.Diagnostics.Stopwatch.StartNew();
var ok = await r.ReportAsync("m2pack", "v1", CancellationToken.None);
sw.Stop();
Assert.False(ok);
// reporter caps at 5s internally — allow slack for CI schedulers
Assert.InRange(sw.Elapsed.TotalSeconds, 3.0, 9.0);
}
}