acdream/tests/AcDream.Runtime.Tests/Chat/ChatCommandRouterFeedbackRoutingTests.cs
Erik 6b7b4bb213 fix #363/#367: route unknown-command refusals to chat, not SpewBox
Owner direction 2026-09-07 (verbatim): "Unknown commands like /vt or
stuff from plugins shall now go to the SpewBox. They should go to the
chatbox." Retail itself types ChatCommandRouter's "Unknown command"
refusals as ClientLocal (0x1A) -- the bit every ChatInterface window's
default filter excludes, so they only ever reached the transient
SpewBox overlay and left no transcript record.

Three call sites in ChatCommandRouter.Submit/EmitVerbHelp now call
IChatCommandFeedback.ShowSystemMessage (chat scroll, retail
Default/0x00) instead of ShowInterfaceText (SpewBox): the degenerate-
prefix "Unknown command: {verb}." guard, EmitVerbHelp's confirmed-
null-help branch, and EmitVerbHelp's unresolved-verb fallback. Every
OTHER 0x1A refusal in this file (AP-183 bad-argument refusals of REAL
retail commands -- lifestone, marketplace, channel list/on/off,
allegiance, house, the generic HandleFailureEvent(0x26) fallback,
DoStupidChannelHack, DoReply) is unchanged and still SpewBox-only --
the owner named only unknown commands and plugin text.

This is a deliberate deviation from retail's own 0x1A typing, recorded
as register row AD-124 (also covers the sibling plugin-text change in
a follow-up commit). docs/ISSUES.md #363/#367 get a one-line note
under each pointing at the re-route; their CLOSED status is untouched.

Mutation check: temporarily reverted all three ShowSystemMessage call
sites back to ShowInterfaceText and confirmed the 3 new/changed pinned
tests fail (Assert.Single() on an empty chat log) while the AP-183
boundary test (real command, bad args, still SpewBox) continues to
pass -- see ChatCommandRouterFeedbackRoutingTests.cs and the updated
ChatCommandRouterTests.cs/RetailCommandHelpTableTests.cs assertions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:22:06 +02:00

111 lines
5.1 KiB
C#

using AcDream.Core.Chat;
using AcDream.Runtime.Chat;
using AcDream.Runtime.Gameplay;
namespace AcDream.Runtime.Tests.Chat;
/// <summary>
/// Owner direction 2026-09-07 (verbatim): "Unknown commands like /vt or
/// stuff from plugins shall now go to the SpewBox. They should go to the
/// chatbox." Register row AD-124 records the deviation from retail's own
/// ClientLocal (0x1A) typing for exactly these two families. This file pins
/// the <see cref="ChatCommandRouter"/> half of that change at the Runtime
/// layer — <see cref="RuntimeChatCommandFeedback"/> bound to a real
/// <see cref="RuntimeCommunicationState"/> — since the existing router
/// coverage in <c>AcDream.UI.Abstractions.Tests</c> only exercises the
/// <c>ChatVM</c> feedback implementation. The plugin-text half is pinned at
/// the App layer (<c>AppAutomationSurfaceTests.PostSystemMessage_RoutesToChatLog_NeverSpewBox</c>),
/// since <c>AppAutomationSurface</c> is the App-layer production
/// implementation of <c>IPluginChat</c>.
/// </summary>
public sealed class ChatCommandRouterFeedbackRoutingTests
{
[Fact]
public void DegeneratePrefix_UnknownCommandRefusal_RoutesToChatLog_NeverSpewBox()
{
// "/" alone (no letter verb) is the degenerate-prefix guard's
// "Unknown command: {verb}." refusal — retail itself types this
// 0x1A (ClientLocal / SpewBox-only); the owner override moves it to
// the chat scroll (Default/0x00) instead.
using var communication = new RuntimeCommunicationState();
var feedback = new RuntimeChatCommandFeedback(communication);
SubmitOutcome outcome = ChatCommandRouter.Submit(
"/", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
Assert.Equal(SubmitOutcome.UnknownCommand, outcome);
ChatEntry entry = Assert.Single(communication.Chat.Snapshot());
Assert.Contains("Unknown command:", entry.Text);
Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType);
communication.SpewBox.Tick(0d);
Assert.Equal(0, communication.SpewBox.Count);
}
[Fact]
public void HelpUnresolvedVerb_UnknownCommandText_RoutesToChatLog_NeverSpewBox()
{
// "/help nonsenseverb" hits EmitVerbHelp's final unresolved-verb
// fallback (RetailCommandHelpTable.UnknownCommand), the exact
// existing retail-swept text — only the destination changes.
using var communication = new RuntimeCommunicationState();
var feedback = new RuntimeChatCommandFeedback(communication);
SubmitOutcome outcome = ChatCommandRouter.Submit(
"/help nonsenseverb", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
ChatEntry entry = Assert.Single(communication.Chat.Snapshot());
Assert.Equal(RetailCommandHelpTable.UnknownCommand, entry.Text);
Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType);
communication.SpewBox.Tick(0d);
Assert.Equal(0, communication.SpewBox.Count);
}
[Fact]
public void HelpConfirmedNullVerb_UnknownCommandText_RoutesToChatLog_NeverSpewBox()
{
// "index" is one of the four catalog verbs retail registers with a
// genuinely NULL help pointer (RetailCommandHelpTable.
// CatalogVerbsWithNoRetailHelp) — EmitVerbHelp's OTHER "Unknown
// command" call site, distinct from the unresolved-verb fallback
// above.
using var communication = new RuntimeCommunicationState();
var feedback = new RuntimeChatCommandFeedback(communication);
SubmitOutcome outcome = ChatCommandRouter.Submit(
"/help index", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
ChatEntry entry = Assert.Single(communication.Chat.Snapshot());
Assert.Equal(RetailCommandHelpTable.UnknownCommand, entry.Text);
Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType);
communication.SpewBox.Tick(0d);
Assert.Equal(0, communication.SpewBox.Count);
}
[Fact]
public void RealCommandBadArguments_StillRoutesToSpewBox_NeverChatLog()
{
// Boundary pin: AP-183's bad-argument refusals of REAL retail
// commands are UNCHANGED by the owner's 2026-09-07 direction, which
// named only unknown commands and plugin text. "/ls now" (Lifestone
// with bad args) must still land in the SpewBox exclusively.
using var communication = new RuntimeCommunicationState();
var feedback = new RuntimeChatCommandFeedback(communication);
SubmitOutcome outcome = ChatCommandRouter.Submit(
"/ls now", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
Assert.Empty(communication.Chat.Snapshot());
communication.SpewBox.Tick(0d);
Assert.Equal(1, communication.SpewBox.Count);
Assert.Equal(
"Please see @help lifestone for more information on how to use this command.",
communication.SpewBox.Snapshot()[0].Text);
}
}