From 32433f85d2bef148874d824b838a02a01687688c Mon Sep 17 00:00:00 2001 From: sushi30 Date: Thu, 26 Mar 2026 08:06:09 +0100 Subject: [PATCH] feat(main): extract testable runCommand and add debug log for blocked WhatsApp senders - Extract runCommand(w, args) from main() so the command tree can be tested without spawning a subprocess; fatal errors are printed to w so they surface even when stderr is redirected (e.g. docker). - Add TestRunCommandFatalPrintsToStdout to verify the FATAL message reaches stdout on cobra errors. - Log a DebugCF message when an incoming WhatsApp message is silently dropped because the sender is not in allow_from. Co-Authored-By: Claude Sonnet 4.6 --- cmd/picoclaw/main.go | 19 +++++++++++++++++-- cmd/picoclaw/main_test.go | 14 ++++++++++++++ .../whatsapp_native/whatsapp_native.go | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 543577e68..8325f7df1 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "io" "os" "time" @@ -67,6 +68,21 @@ const ( "\033[0m\r\n" ) +// runCommand executes picoclaw with the given args, writing output to w. +// On failure it prints a prominent error to w so the message is visible +// even when os.Stderr has been redirected (e.g. to a panic-log file). +func runCommand(w io.Writer, args []string) error { + cmd := NewPicoclawCommand() + cmd.SetOut(w) + cmd.SetErr(w) + cmd.SetArgs(args) + if err := cmd.Execute(); err != nil { + fmt.Fprintf(w, "\n\033[1;31m✗ FATAL: %v\033[0m\n", err) + return err + } + return nil +} + func main() { fmt.Printf("%s", banner) @@ -84,8 +100,7 @@ func main() { } } - cmd := NewPicoclawCommand() - if err := cmd.Execute(); err != nil { + if err := runCommand(os.Stdout, os.Args[1:]); err != nil { os.Exit(1) } } diff --git a/cmd/picoclaw/main_test.go b/cmd/picoclaw/main_test.go index 3e147cbfe..18f7b65cc 100644 --- a/cmd/picoclaw/main_test.go +++ b/cmd/picoclaw/main_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "slices" "testing" @@ -12,6 +13,19 @@ import ( "github.com/sipeed/picoclaw/pkg/config" ) +// TestRunCommandFatalPrintsToStdout verifies that when a subcommand fails, +// runCommand prints a prominent error to the provided writer (stdout in production), +// so the error is visible in environments where stderr is redirected (e.g. docker). +func TestRunCommandFatalPrintsToStdout(t *testing.T) { + var buf bytes.Buffer + // Pass an unknown flag to trigger a cobra error without any real I/O. + err := runCommand(&buf, []string{"gateway", "--no-such-flag"}) + require.Error(t, err) + out := buf.String() + assert.Contains(t, out, "FATAL", "fatal error must be printed to stdout") + assert.Contains(t, out, err.Error(), "error message must appear in stdout output") +} + func TestNewPicoclawCommand(t *testing.T) { cmd := NewPicoclawCommand() diff --git a/pkg/channels/whatsapp_native/whatsapp_native.go b/pkg/channels/whatsapp_native/whatsapp_native.go index d0a74a405..64438c30c 100644 --- a/pkg/channels/whatsapp_native/whatsapp_native.go +++ b/pkg/channels/whatsapp_native/whatsapp_native.go @@ -385,6 +385,7 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) { } if !c.IsAllowedSender(sender) { + logger.DebugCF("whatsapp", "WhatsApp message blocked (not in allow_from)", map[string]any{"sender_id": senderID}) return }