fix(commands): guard nil Reply and update tests for unknown command behavior

- Add nil guard for req.Reply in Executor.Execute before calling it
- Update TestShowListHandlers_ChannelPolicy to expect OutcomeHandled for unknown commands
- Update TestProcessMessage_CommandOutcomes to verify unknown /foo and /new return error messages without LLM calls
- Fix leftover double blank lines in whatsapp_native.go (fmt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
github-actions[bot] 2026-04-11 16:42:34 +02:00
parent f092429d15
commit 73df8f0848
5 changed files with 30 additions and 17 deletions

View file

@ -1513,11 +1513,11 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) {
Content: "/foo",
Peer: baseMsg.Peer,
})
if fooResp != "LLM reply" {
if fooResp != "Unknown command: foo" {
t.Fatalf("unexpected /foo reply: %q", fooResp)
}
if provider.calls != 1 {
t.Fatalf("LLM should be called exactly once after /foo passthrough, calls=%d", provider.calls)
if provider.calls != 0 {
t.Fatalf("LLM should not be called for unknown command /foo, calls=%d", provider.calls)
}
newResp := helper.executeAndGetResponse(t, context.Background(), bus.InboundMessage{
@ -1527,11 +1527,11 @@ func TestProcessMessage_CommandOutcomes(t *testing.T) {
Content: "/new",
Peer: baseMsg.Peer,
})
if newResp != "LLM reply" {
if newResp != "Unknown command: new" {
t.Fatalf("unexpected /new reply: %q", newResp)
}
if provider.calls != 2 {
t.Fatalf("LLM should be called for passthrough /new command, calls=%d", provider.calls)
if provider.calls != 0 {
t.Fatalf("LLM should not be called for unknown command /new, calls=%d", provider.calls)
}
}

View file

@ -454,7 +454,10 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) {
"WhatsApp message blocked (not in allow_from)",
map[string]any{"sender_id": senderID},
)
_, _ = c.Send(c.runCtx, bus.OutboundMessage{Channel: "whatsapp", ChatID: chatID, Content: channels.ForbiddenReplyText})
_, _ = c.Send(
c.runCtx,
bus.OutboundMessage{Channel: "whatsapp", ChatID: chatID, Content: channels.ForbiddenReplyText},
)
return
}
@ -527,7 +530,6 @@ func isMentionedInGroup(msg *waE2E.Message, content string, botUsers []string) b
return false
}
// VoiceCapabilities reports that this channel supports ASR (speech-to-text).
func (c *WhatsAppNativeChannel) VoiceCapabilities() channels.VoiceCapabilities {
return channels.VoiceCapabilities{ASR: true, TTS: false}

View file

@ -45,7 +45,10 @@ func (e *Executor) Execute(ctx context.Context, req Request) ExecuteResult {
def, found := e.reg.Lookup(cmdName)
if !found {
// Return an error for unrecognized commands instead of forwarding to LLM
err := req.Reply(fmt.Sprintf("Unknown command: %s", cmdName))
var err error
if req.Reply != nil {
err = req.Reply(fmt.Sprintf("Unknown command: %s", cmdName))
}
return ExecuteResult{Outcome: OutcomeHandled, Command: cmdName, Err: err}
}
@ -88,4 +91,4 @@ func (e *Executor) executeDefinition(ctx context.Context, req Request, def Defin
// Unknown sub-command
err := req.Reply(fmt.Sprintf("Unknown option: %s. Usage: %s", subName, def.EffectiveUsage()))
return ExecuteResult{Outcome: OutcomeHandled, Command: def.Name, Err: err}
}
}

View file

@ -22,7 +22,10 @@ func TestExecutor_UnknownSlashCommand_ReturnsError(t *testing.T) {
ex := NewExecutor(NewRegistry(defs), nil)
var reply string
res := ex.Execute(context.Background(), Request{Channel: "telegram", Text: "/unknown", Reply: func(text string) error { reply = text; return nil }})
res := ex.Execute(
context.Background(),
Request{Channel: "telegram", Text: "/unknown", Reply: func(text string) error { reply = text; return nil }},
)
if res.Outcome != OutcomeHandled {
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeHandled)
}
@ -267,4 +270,4 @@ func TestExecutor_SubCommand_NilHandler_ReturnsPassthrough(t *testing.T) {
if res.Outcome != OutcomePassthrough {
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomePassthrough)
}
}
}

View file

@ -44,15 +44,20 @@ func TestShowListHandlers_ChannelPolicy(t *testing.T) {
t.Fatalf("whatsapp /show reply=%q, want=%q", whatsappReply, "Current Channel: whatsapp")
}
passthrough := ex.Execute(context.Background(), Request{
var fooReply string
unknown := ex.Execute(context.Background(), Request{
Channel: "whatsapp",
Text: "/foo",
Reply: func(text string) error { fooReply = text; return nil },
})
if passthrough.Outcome != OutcomePassthrough {
t.Fatalf("whatsapp /foo outcome=%v, want=%v", passthrough.Outcome, OutcomePassthrough)
if unknown.Outcome != OutcomeHandled {
t.Fatalf("whatsapp /foo outcome=%v, want=%v", unknown.Outcome, OutcomeHandled)
}
if passthrough.Command != "foo" {
t.Fatalf("whatsapp /foo command=%q, want=%q", passthrough.Command, "foo")
if unknown.Command != "foo" {
t.Fatalf("whatsapp /foo command=%q, want=%q", unknown.Command, "foo")
}
if fooReply != "Unknown command: foo" {
t.Fatalf("whatsapp /foo reply=%q, want=%q", fooReply, "Unknown command: foo")
}
}