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>
132 lines
4.4 KiB
C#
132 lines
4.4 KiB
C#
using Metin2Launcher.Formats;
|
|
using Metin2Launcher.Manifest;
|
|
using Metin2Launcher.Runtime;
|
|
using Xunit;
|
|
using ManifestDto = Metin2Launcher.Manifest.Manifest;
|
|
|
|
namespace Metin2Launcher.Tests;
|
|
|
|
public class M2PackFormatTests
|
|
{
|
|
private const string ValidHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
|
|
|
private static ManifestDto SampleManifest() => new()
|
|
{
|
|
Version = "2026.04.14-1",
|
|
Format = "m2pack",
|
|
Launcher = new ManifestLauncherEntry { Path = "Metin2Launcher.exe", Sha256 = "x", Size = 1 },
|
|
Files =
|
|
{
|
|
new ManifestFile { Path = "pack/item.m2p", Sha256 = "h1", Size = 1000 },
|
|
new ManifestFile { Path = "runtime-key.json", Sha256 = "h2", Size = 200 },
|
|
},
|
|
};
|
|
|
|
private static string WriteRuntimeKey(string dir, string keyId = "2026.04.14-1")
|
|
{
|
|
var path = Path.Combine(dir, M2PackFormat.RuntimeKeyFileName);
|
|
File.WriteAllText(path, $$"""
|
|
{
|
|
"key_id": "{{keyId}}",
|
|
"master_key_hex": "{{ValidHex}}",
|
|
"sign_pubkey_hex": "{{ValidHex}}"
|
|
}
|
|
""");
|
|
return path;
|
|
}
|
|
|
|
[Fact]
|
|
public void Name_is_m2pack()
|
|
{
|
|
Assert.Equal("m2pack", new M2PackFormat().Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void FilterApplicable_includes_m2p_and_runtime_key()
|
|
{
|
|
var filtered = new M2PackFormat().FilterApplicable(SampleManifest(), "windows");
|
|
Assert.Equal(2, filtered.Count);
|
|
Assert.Contains(filtered, f => f.Path.EndsWith(".m2p"));
|
|
Assert.Contains(filtered, f => f.Path == "runtime-key.json");
|
|
}
|
|
|
|
[Fact]
|
|
public void OnApplied_loads_runtime_key_when_present()
|
|
{
|
|
var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
|
|
Directory.CreateDirectory(tmp);
|
|
try
|
|
{
|
|
WriteRuntimeKey(tmp);
|
|
var outcome = new ReleaseOutcome();
|
|
new M2PackFormat().OnApplied(
|
|
tmp,
|
|
new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
|
|
outcome);
|
|
|
|
Assert.NotNull(outcome.RuntimeKey);
|
|
Assert.Equal("2026.04.14-1", outcome.RuntimeKey!.KeyId);
|
|
Assert.Equal(ValidHex, outcome.RuntimeKey.MasterKeyHex);
|
|
}
|
|
finally { Directory.Delete(tmp, true); }
|
|
}
|
|
|
|
[Fact]
|
|
public void OnApplied_tolerates_missing_runtime_key()
|
|
{
|
|
var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
|
|
Directory.CreateDirectory(tmp);
|
|
try
|
|
{
|
|
var outcome = new ReleaseOutcome();
|
|
new M2PackFormat().OnApplied(
|
|
tmp,
|
|
new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
|
|
outcome);
|
|
Assert.Null(outcome.RuntimeKey); // warn-logged, not thrown
|
|
}
|
|
finally { Directory.Delete(tmp, true); }
|
|
}
|
|
|
|
[Fact]
|
|
public void OnApplied_throws_on_malformed_runtime_key()
|
|
{
|
|
var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
|
|
Directory.CreateDirectory(tmp);
|
|
try
|
|
{
|
|
File.WriteAllText(Path.Combine(tmp, M2PackFormat.RuntimeKeyFileName), "{ not valid }");
|
|
var outcome = new ReleaseOutcome();
|
|
Assert.ThrowsAny<Exception>(() =>
|
|
new M2PackFormat().OnApplied(
|
|
tmp,
|
|
new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
|
|
outcome));
|
|
}
|
|
finally { Directory.Delete(tmp, true); }
|
|
}
|
|
|
|
[Fact]
|
|
public void Launcher_never_opens_m2p_archive()
|
|
{
|
|
// Guard test: the format must not touch the .m2p file itself. We assert
|
|
// by constructing a manifest whose .m2p entry points at a file that
|
|
// does not exist on disk and would throw if opened — OnApplied must
|
|
// ignore it entirely and only touch runtime-key.json.
|
|
var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
|
|
Directory.CreateDirectory(tmp);
|
|
try
|
|
{
|
|
WriteRuntimeKey(tmp);
|
|
// Intentionally no pack/item.m2p on disk.
|
|
var outcome = new ReleaseOutcome();
|
|
new M2PackFormat().OnApplied(
|
|
tmp,
|
|
new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
|
|
outcome);
|
|
Assert.NotNull(outcome.RuntimeKey);
|
|
}
|
|
finally { Directory.Delete(tmp, true); }
|
|
}
|
|
}
|