namespace AcDream.UI.Abstractions.Tests; public sealed class LiveCommandBusTests { private sealed record FakeCmd(int Value); private sealed record OtherCmd(string Tag); [Fact] public void Publish_InvokesRegisteredHandler_ForMatchingType() { var bus = new LiveCommandBus(); FakeCmd? captured = null; bus.Register(c => captured = c); bus.Publish(new FakeCmd(42)); Assert.NotNull(captured); Assert.Equal(42, captured!.Value); } [Fact] public void Publish_DoesNotThrow_WhenNoHandlerRegistered() { var bus = new LiveCommandBus(); // No handler for OtherCmd — must not throw. bus.Publish(new OtherCmd("x")); } [Fact] public void Register_Twice_ForSameType_Throws() { var bus = new LiveCommandBus(); bus.Register(_ => { }); Assert.Throws(() => bus.Register(_ => { })); } [Fact] public void Clear_ReleasesHandlersAndMakesExistingBusInert() { var bus = new LiveCommandBus(); int calls = 0; bus.Register(_ => calls++); bus.Publish(new FakeCmd(1)); bus.Clear(); bus.Publish(new FakeCmd(2)); Assert.Equal(1, calls); } } public sealed class ChannelResolverTests { [Fact] public void Resolve_LegacyChannels_MatchesHoltburgerIds() { // Holtburger references/holtburger/crates/holtburger-protocol/src/messages/chat/types.rs:8-24 // and holtburger-core/src/client/commands.rs:50-62. Six legacy channels // map to known ChatChannel ids; the rest fall through to TurbineChat. Assert.Equal((0x00000800u, "Fellowship"), AsTuple(ChannelResolver.Resolve(ChatChannelKind.Fellowship))); // CH3 (2026-08-09): 0x02000000 moved from ChatChannelKind.Allegiance // (now ALWAYS Turbine — retail's @a binds unconditionally to // DoTurbineChat_Allegiance) to the dedicated AllegianceBroadcast // kind (retail's @ab verb). Assert.Equal((0x02000000u, "Allegiance"), AsTuple(ChannelResolver.Resolve(ChatChannelKind.AllegianceBroadcast))); Assert.Equal((0x00001000u, "Vassals"), AsTuple(ChannelResolver.Resolve(ChatChannelKind.Vassals))); Assert.Equal((0x00002000u, "Patron"), AsTuple(ChannelResolver.Resolve(ChatChannelKind.Patron))); Assert.Equal((0x00004000u, "Monarch"), AsTuple(ChannelResolver.Resolve(ChatChannelKind.Monarch))); Assert.Equal((0x01000000u, "CoVassals"), AsTuple(ChannelResolver.Resolve(ChatChannelKind.CoVassals))); } [Fact] public void Resolve_NonLegacyChannels_ReturnsNull() { // Say + Tell are handled separately by the SendChatCmd dispatcher. // General/Trade/etc. require a TurbineChat channel id we don't yet wire. // Allegiance (CH3, 2026-08-09) is ALWAYS Turbine now — the legacy // resolver no longer has an entry for it. Assert.Null(ChannelResolver.Resolve(ChatChannelKind.Say)); Assert.Null(ChannelResolver.Resolve(ChatChannelKind.Tell)); Assert.Null(ChannelResolver.Resolve(ChatChannelKind.General)); Assert.Null(ChannelResolver.Resolve(ChatChannelKind.Trade)); Assert.Null(ChannelResolver.Resolve(ChatChannelKind.Allegiance)); Assert.Null(ChannelResolver.Resolve(ChatChannelKind.Unknown)); } private static (uint ChannelId, string DisplayName) AsTuple(ChannelResolver.Resolved? r) { Assert.NotNull(r); return (r!.Value.ChannelId, r.Value.DisplayName); } }