feat(plugins): complete Campaign LA5 cross-host hosting

This commit is contained in:
Erik 2026-08-14 18:12:59 +02:00
parent 6c4cd2bbc6
commit 95f4be94db
26 changed files with 1630 additions and 99 deletions

View file

@ -30,11 +30,13 @@ public sealed class SessionStatusWriterTests
new LiveSessionRosterEntry(0x50000002u, "Grey", 10u),
]));
writer.EnteredWorld("s1", 0x50000001u, "Ready");
writer.PluginLoaded("s1", "acdream.good");
writer.PluginFailed("s1", "acdream.bad", "enable failed");
writer.Disconnected("s1", "stopped");
writer.Exited("s1", 0, "disposed");
string[] lines = File.ReadAllLines(file.Path);
Assert.Equal(6, lines.Length);
Assert.Equal(8, lines.Length);
JsonElement started = Parse(lines[0]);
Assert.Equal(1, started.GetProperty("v").GetInt32());
@ -62,11 +64,20 @@ public sealed class SessionStatusWriterTests
Assert.Equal(0x50000001u, enteredWorld.GetProperty("characterId").GetUInt32());
Assert.Equal("Ready", enteredWorld.GetProperty("characterName").GetString());
JsonElement disconnected = Parse(lines[4]);
JsonElement pluginLoaded = Parse(lines[4]);
Assert.Equal("pluginLoaded", pluginLoaded.GetProperty("e").GetString());
Assert.Equal("acdream.good", pluginLoaded.GetProperty("plugin").GetString());
JsonElement pluginFailed = Parse(lines[5]);
Assert.Equal("pluginFailed", pluginFailed.GetProperty("e").GetString());
Assert.Equal("acdream.bad", pluginFailed.GetProperty("plugin").GetString());
Assert.Equal("enable failed", pluginFailed.GetProperty("error").GetString());
JsonElement disconnected = Parse(lines[6]);
Assert.Equal("disconnected", disconnected.GetProperty("e").GetString());
Assert.Equal("stopped", disconnected.GetProperty("reason").GetString());
JsonElement exited = Parse(lines[5]);
JsonElement exited = Parse(lines[7]);
Assert.Equal("exited", exited.GetProperty("e").GetString());
Assert.Equal(0, exited.GetProperty("code").GetInt32());
Assert.Equal("disposed", exited.GetProperty("reason").GetString());
@ -80,6 +91,8 @@ public sealed class SessionStatusWriterTests
writer.Started("s1");
writer.Connected("s1");
writer.PluginLoaded("s1", "acdream.good");
writer.PluginFailed("s1", "acdream.bad", "failed");
writer.Disconnected("s1", "stopped");
writer.Exited("s1", 0, "disposed");
@ -164,9 +177,10 @@ public sealed class SessionStatusWriterTests
/// credential material into this stream" contract: each event kind
/// serializes EXACTLY its pinned property set — the shared envelope
/// (<c>v</c>/<c>e</c>/<c>t</c>/<c>sessionId</c>) plus that event's own
/// named fields, nothing else. An extra property (a smuggled password,
/// or any other accidental field) fails this test by construction,
/// regardless of what value it carries.
/// named fields, nothing else. An extra credential-shaped or otherwise
/// accidental property fails this test by construction. LA5's documented
/// <c>pluginFailed.error</c> diagnostic is the one free-text value and its
/// caller remains responsible for never appending session secrets.
/// </summary>
[Fact]
public void EachEventSerializesExactlyItsPinnedPropertySetAndNothingElse()
@ -183,11 +197,13 @@ public sealed class SessionStatusWriterTests
11,
[new LiveSessionRosterEntry(0x50000001u, "Ready", 0u)]));
writer.EnteredWorld("bot", 0x50000001u, "Ready");
writer.PluginLoaded("bot", "acdream.good");
writer.PluginFailed("bot", "acdream.bad", "enable failed");
writer.Disconnected("bot", "stopped");
writer.Exited("bot", 0, "disposed");
string[] lines = File.ReadAllLines(file.Path);
Assert.Equal(6, lines.Length);
Assert.Equal(8, lines.Length);
AssertExactProperties(lines[0], "v", "e", "t", "sessionId");
AssertExactProperties(lines[1], "v", "e", "t", "sessionId");
@ -196,8 +212,11 @@ public sealed class SessionStatusWriterTests
"v", "e", "t", "sessionId", "accountName", "slotCount", "characters");
AssertExactProperties(
lines[3], "v", "e", "t", "sessionId", "characterId", "characterName");
AssertExactProperties(lines[4], "v", "e", "t", "sessionId", "reason");
AssertExactProperties(lines[5], "v", "e", "t", "sessionId", "code", "reason");
AssertExactProperties(lines[4], "v", "e", "t", "sessionId", "plugin");
AssertExactProperties(
lines[5], "v", "e", "t", "sessionId", "plugin", "error");
AssertExactProperties(lines[6], "v", "e", "t", "sessionId", "reason");
AssertExactProperties(lines[7], "v", "e", "t", "sessionId", "code", "reason");
// The nested characters[] entries are exact too — the exact shape a
// password could otherwise be smuggled through.