License grace period: keep running when the master is down or rejects the key (LicenseGraceHours, default 72 h), start with the last confirmed license, auto-retry; panel restart button (port/panel settings without program restart, login kept); tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 20:02:05 +02:00
co-authored by Claude Opus 5.5
parent d4e5897b7f
commit 14db3ef4f4
10 changed files with 325 additions and 24 deletions
+43
View File
@@ -486,6 +486,49 @@ async Task PanelTests()
using (var da = new System.IO.Compression.ZipArchive(new MemoryStream(downZip)))
Check(down.IsSuccessStatusCode && da.GetEntry("defaultES2File.txt") != null, "The current save can be downloaded as ZIP");
// Lizenz: Master fällt aus -> der Server läuft weiter, erst nach der Kulanzzeit wird er gestoppt
LicenseCache.Save(dir, "test", "Tester", "Test", DateTime.UtcNow.AddHours(-5));
var cachedLicense = LicenseCache.Load(dir, "test");
Check(cachedLicense != null && cachedLicense.Owner == "Tester" && LicenseCache.Load(dir, "other-key") == null
&& !File.ReadAllText(Path.Combine(dir, "license.json")).Contains("\"test\""),
"The last license check is remembered (only a hash of the key)");
Srv().LicenseConfirmedUtc = DateTime.UtcNow.AddHours(-10); // innerhalb der 72 h: läuft weiter
await Task.Delay(500);
Check(host.State == HostState.Running, "Server keeps running while the license is unconfirmed but within the grace period");
Srv().LicenseConfirmedUtc = DateTime.UtcNow.AddHours(-100); // Kulanzzeit abgelaufen
await WaitForState(HostState.Error);
Check(host.State == HostState.Error && (host.LastError ?? "").Contains("license"), "After the grace period the game server stops: " + host.LastError);
Check((await Call(owner, "GET", "api/session")).status == 200, "The panel stays reachable after the license stop");
Check((await Call(owner, "POST", "api/server/start")).status == 200, "It can be started again");
await WaitForState(HostState.Running);
Check(host.State == HostState.Running, "Running again after the start");
// Panel neu starten: eigener Port, danach wieder zurück auf den Spiel-Port - angemeldet bleiben
Check((await Call(owner, "POST", "api/config", new { panelPort = 17791 })).status == 200, "Panel port can be changed");
var pr = await Call(owner, "POST", "api/server/restart-panel");
Check(pr.status == 200 && pr.json.GetProperty("url").GetString().Contains(":17791/"), "Restart panel returns the new address: " + pr.json);
int code = 0;
for (int i = 0; i < 30 && code != 200; i++)
{
await Task.Delay(300);
try { code = (int)(await owner.GetAsync("http://127.0.0.1:17791/api/status")).StatusCode; } catch { }
}
Check(code == 200, "Panel answers on the new port and the login is kept");
var back = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:17791/api/config")
{ Content = new StringContent("{\"panelPort\":0}", System.Text.Encoding.UTF8, "application/json") };
back.Headers.Add("X-MSCO-Panel", "1");
await owner.SendAsync(back);
var back2 = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:17791/api/server/restart-panel");
back2.Headers.Add("X-MSCO-Panel", "1");
await owner.SendAsync(back2);
code = 0;
for (int i = 0; i < 30 && code != 200; i++)
{
await Task.Delay(300);
try { code = (await Call(owner, "GET", "api/status")).status; } catch { }
}
Check(code == 200 && host.Panel.SharesGamePort, "Panel is back on the game port");
await Call(owner, "POST", "api/logout");
Check((await Call(owner, "GET", "api/status")).status == 401, "Logout ends the session");
}