Server 2.10.3: browse the MSCO mod list in the web panel
Mods -> MSCO mod list: search the approved mods from msco.mbm.wtf/mods and add or update one with a click. The server lists and downloads only from the official keymaster (never from a URL in the answer), checks the SHA-256 and stores it in data/mods like an uploaded mod. Admins only. 6 new tests (154 pass). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -431,6 +431,65 @@ async Task PanelTests()
|
||||
|
||||
var cmd = await Call(owner, "POST", "api/console", new { command = "status" });
|
||||
Check(cmd.status == 200 && cmd.json.GetProperty("message").GetString().Contains("players"), "Console command runs and returns its output");
|
||||
// Mod-Liste des Keymasters: kleiner Ersatz-Keymaster auf localhost
|
||||
{
|
||||
var modBytes = new byte[3000];
|
||||
Random.Shared.NextBytes(modBytes);
|
||||
modBytes[0] = 0x4d; modBytes[1] = 0x5a;
|
||||
string modSha = Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(modBytes)).ToLowerInvariant();
|
||||
bool serveBroken = false;
|
||||
int catalogPort = 39000 + Random.Shared.Next(900);
|
||||
var listener = new System.Net.HttpListener();
|
||||
listener.Prefixes.Add($"http://127.0.0.1:{catalogPort}/");
|
||||
listener.Start();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (listener.IsListening)
|
||||
{
|
||||
System.Net.HttpListenerContext c;
|
||||
try { c = await listener.GetContextAsync(); } catch { break; }
|
||||
byte[] body;
|
||||
if (c.Request.Url.AbsolutePath == "/api/mods")
|
||||
{
|
||||
body = System.Text.Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(new
|
||||
{
|
||||
mods = new[] { new { id = "catalogmod", name = "Catalog Mod", description = "From the list", author = "Tester", version = "1.0.0",
|
||||
sha256 = modSha, size = modBytes.Length, changelog = "", updatedUtc = "2026-10-03", downloads = 3,
|
||||
url = "http://evil.example/steal.dll" } }
|
||||
}));
|
||||
c.Response.ContentType = "application/json";
|
||||
}
|
||||
else if (c.Request.Url.AbsolutePath == "/mods/download/catalogmod/1.0.0")
|
||||
{
|
||||
body = serveBroken ? new byte[] { 0x4d, 0x5a, 1, 2, 3 } : modBytes;
|
||||
}
|
||||
else { c.Response.StatusCode = 404; body = Array.Empty<byte>(); }
|
||||
await c.Response.OutputStream.WriteAsync(body);
|
||||
c.Response.Close();
|
||||
}
|
||||
});
|
||||
ModCatalog.BaseUrl = $"http://127.0.0.1:{catalogPort}";
|
||||
var catalog = await Call(owner, "GET", "api/mods/catalog");
|
||||
Check(catalog.status == 200 && catalog.json.EnumerateArray().Any(m => m.GetProperty("id").GetString() == "catalogmod" && !m.GetProperty("installed").GetBoolean()),
|
||||
"Panel lists the MSCO mod list (not yet added)");
|
||||
serveBroken = true;
|
||||
var broken = await Call(owner, "POST", "api/mods/catalog/catalogmod/install");
|
||||
Check(broken.status == 400 && !File.Exists(Path.Combine(dir, "mods", "catalogmod.dll")), "A download with the wrong checksum is not added");
|
||||
serveBroken = false;
|
||||
Check((await Call(mod1Placeholder(), "POST", "api/mods/catalog/catalogmod/install")).status is 401 or 403, "Without admin rights nothing can be added");
|
||||
var installMod = await Call(owner, "POST", "api/mods/catalog/catalogmod/install");
|
||||
Check(installMod.status == 200 && File.Exists(Path.Combine(dir, "mods", "catalogmod.dll"))
|
||||
&& File.ReadAllBytes(Path.Combine(dir, "mods", "catalogmod.dll")).SequenceEqual(modBytes),
|
||||
"Admin adds a mod from the list (downloaded from the keymaster, checksum checked)");
|
||||
catalog = await Call(owner, "GET", "api/mods/catalog");
|
||||
Check(catalog.json.EnumerateArray().Any(m => m.GetProperty("id").GetString() == "catalogmod" && m.GetProperty("upToDate").GetBoolean()),
|
||||
"The list shows the mod as added");
|
||||
Check((await Call(owner, "POST", "api/mods/catalog/doesnotexist/install")).status == 400, "Unknown mods cannot be added");
|
||||
await Call(owner, "POST", "api/mods/catalogmod/remove");
|
||||
listener.Stop();
|
||||
ModCatalog.BaseUrl = MasterServer.Url;
|
||||
}
|
||||
HttpClient mod1Placeholder() => Browser();
|
||||
var log = await Call(owner, "GET", "api/console?after=0");
|
||||
Check(log.status == 200 && log.json.GetProperty("lines").GetArrayLength() > 0, "Live console log is readable");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user