From a188e7463c9fa856e4863c34dd04aa8fac1e4e3c Mon Sep 17 00:00:00 2001 From: lxowalle Date: Fri, 17 Apr 2026 17:45:57 +0800 Subject: [PATCH] fix ci --- cmd/picoclaw/internal/auth/wecom_test.go | 20 +++- pkg/agent/loop_test.go | 139 ++--------------------- pkg/channels/discord/discord_test.go | 1 + pkg/channels/feishu/feishu_64_test.go | 4 +- pkg/channels/manager_test.go | 5 +- pkg/channels/telegram/telegram_test.go | 7 -- pkg/utils/tool_feedback_test.go | 10 +- web/backend/api/session_test.go | 9 +- 8 files changed, 49 insertions(+), 146 deletions(-) diff --git a/cmd/picoclaw/internal/auth/wecom_test.go b/cmd/picoclaw/internal/auth/wecom_test.go index c152481be..aafd39e69 100644 --- a/cmd/picoclaw/internal/auth/wecom_test.go +++ b/cmd/picoclaw/internal/auth/wecom_test.go @@ -3,6 +3,7 @@ package auth import ( "bytes" "context" + "net" "net/http" "net/http/httptest" "net/url" @@ -19,6 +20,19 @@ import ( "github.com/sipeed/picoclaw/pkg/config" ) +func newIPv4TestServer(t *testing.T, handler http.Handler) *httptest.Server { + t.Helper() + + server := httptest.NewUnstartedServer(handler) + listener, err := net.Listen("tcp4", "127.0.0.1:0") + require.NoError(t, err) + + server.Listener = listener + server.Start() + t.Cleanup(server.Close) + return server +} + func TestNewWeComCommand(t *testing.T) { cmd := newWeComCommand() @@ -53,7 +67,7 @@ func TestBuildWeComQRCodePageURL(t *testing.T) { } func TestFetchWeComQRCode(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + server := newIPv4TestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/generate", r.URL.Path) assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("source")) assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("sourceID")) @@ -61,7 +75,6 @@ func TestFetchWeComQRCode(t *testing.T) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"data":{"scode":"scode-1","auth_url":"https://example.com/qr"}}`)) })) - defer server.Close() opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{ HTTPClient: server.Client(), @@ -78,7 +91,7 @@ func TestFetchWeComQRCode(t *testing.T) { func TestPollWeComQRCodeResult(t *testing.T) { var calls atomic.Int32 - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + server := newIPv4TestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { call := calls.Add(1) assert.Equal(t, "/query", r.URL.Path) assert.Equal(t, "scode-1", r.URL.Query().Get("scode")) @@ -92,7 +105,6 @@ func TestPollWeComQRCodeResult(t *testing.T) { _, _ = w.Write([]byte(`{"data":{"status":"success","bot_info":{"botid":"bot-1","secret":"secret-1"}}}`)) } })) - defer server.Close() var output bytes.Buffer opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{ diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 1ed42b21b..8c516e5b5 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -1794,43 +1794,6 @@ func (m *toolFeedbackReasoningProvider) GetDefaultModel() string { return "tool-feedback-reasoning-model" } -type toolFeedbackExtraContentProvider struct { - filePath string - calls int -} - -func (m *toolFeedbackExtraContentProvider) Chat( - ctx context.Context, - messages []providers.Message, - tools []providers.ToolDefinition, - model string, - opts map[string]any, -) (*providers.LLMResponse, error) { - m.calls++ - if m.calls == 1 { - return &providers.LLMResponse{ - ToolCalls: []providers.ToolCall{{ - ID: "call_explicit_read_file", - Type: "function", - Name: "read_file", - Arguments: map[string]any{"path": m.filePath}, - ExtraContent: &providers.ExtraContent{ - ToolFeedbackExplanation: "Read README.md first to confirm the current project structure.", - }, - }}, - }, nil - } - - return &providers.LLMResponse{ - Content: "DONE", - ToolCalls: []providers.ToolCall{}, - }, nil -} - -func (m *toolFeedbackExtraContentProvider) GetDefaultModel() string { - return "tool-feedback-extra-content-model" -} - func TestToolFeedbackExplanationFromResponse_UsesCurrentContentFirst(t *testing.T) { response := &providers.LLMResponse{ Content: "Read README.md first", @@ -3934,8 +3897,14 @@ func TestProcessMessage_DoesNotLeakReasoningContentInToolFeedback(t *testing.T) } func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *testing.T) { + assertToolFeedbackNotPublishedWhenDisabled(t, "discord") +} + +func assertToolFeedbackNotPublishedWhenDisabled(t *testing.T, channel string) { + t.Helper() + tmpDir := t.TempDir() - heartbeatFile := filepath.Join(tmpDir, "tool-feedback-discord.txt") + heartbeatFile := filepath.Join(tmpDir, "tool-feedback-"+channel+".txt") if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil { t.Fatalf("WriteFile() error = %v", err) } @@ -3961,7 +3930,7 @@ func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *test al := NewAgentLoop(cfg, msgBus, provider) response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{ - Channel: "discord", + Channel: channel, SenderID: "user-1", ChatID: "chat-1", Content: "check tool feedback", @@ -3975,103 +3944,17 @@ func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *test select { case outbound := <-msgBus.OutboundChan(): - t.Fatalf("expected no outbound tool feedback for discord when disabled, got %+v", outbound) + t.Fatalf("expected no outbound tool feedback for %s when disabled, got %+v", channel, outbound) case <-time.After(200 * time.Millisecond): } } func TestProcessMessage_DoesNotPublishToolFeedbackForTelegramWhenDisabled(t *testing.T) { - tmpDir := t.TempDir() - heartbeatFile := filepath.Join(tmpDir, "tool-feedback-telegram-default.txt") - if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil { - t.Fatalf("WriteFile() error = %v", err) - } - - cfg := &config.Config{ - Agents: config.AgentsConfig{ - Defaults: config.AgentDefaults{ - Workspace: tmpDir, - ModelName: "test-model", - MaxTokens: 4096, - MaxToolIterations: 10, - }, - }, - Tools: config.ToolsConfig{ - ReadFile: config.ReadFileToolConfig{ - Enabled: true, - }, - }, - } - - msgBus := bus.NewMessageBus() - provider := &toolFeedbackProvider{filePath: heartbeatFile} - al := NewAgentLoop(cfg, msgBus, provider) - - response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{ - Channel: "telegram", - SenderID: "user-1", - ChatID: "chat-1", - Content: "check tool feedback", - })) - if err != nil { - t.Fatalf("processMessage() error = %v", err) - } - if response != "HEARTBEAT_OK" { - t.Fatalf("processMessage() response = %q, want %q", response, "HEARTBEAT_OK") - } - - select { - case outbound := <-msgBus.OutboundChan(): - t.Fatalf("expected no outbound tool feedback for telegram when disabled, got %+v", outbound) - case <-time.After(200 * time.Millisecond): - } + assertToolFeedbackNotPublishedWhenDisabled(t, "telegram") } func TestProcessMessage_DoesNotPublishToolFeedbackForFeishuWhenDisabled(t *testing.T) { - tmpDir := t.TempDir() - heartbeatFile := filepath.Join(tmpDir, "tool-feedback-feishu-default.txt") - if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil { - t.Fatalf("WriteFile() error = %v", err) - } - - cfg := &config.Config{ - Agents: config.AgentsConfig{ - Defaults: config.AgentDefaults{ - Workspace: tmpDir, - ModelName: "test-model", - MaxTokens: 4096, - MaxToolIterations: 10, - }, - }, - Tools: config.ToolsConfig{ - ReadFile: config.ReadFileToolConfig{ - Enabled: true, - }, - }, - } - - msgBus := bus.NewMessageBus() - provider := &toolFeedbackProvider{filePath: heartbeatFile} - al := NewAgentLoop(cfg, msgBus, provider) - - response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{ - Channel: "feishu", - SenderID: "user-1", - ChatID: "chat-1", - Content: "check tool feedback", - })) - if err != nil { - t.Fatalf("processMessage() error = %v", err) - } - if response != "HEARTBEAT_OK" { - t.Fatalf("processMessage() response = %q, want %q", response, "HEARTBEAT_OK") - } - - select { - case outbound := <-msgBus.OutboundChan(): - t.Fatalf("expected no outbound tool feedback for feishu when disabled, got %+v", outbound) - case <-time.After(200 * time.Millisecond): - } + assertToolFeedbackNotPublishedWhenDisabled(t, "feishu") } func TestProcessMessage_MessageToolPublishesOutboundWithTurnMetadata(t *testing.T) { diff --git a/pkg/channels/discord/discord_test.go b/pkg/channels/discord/discord_test.go index e33ff81fd..77ed5b368 100644 --- a/pkg/channels/discord/discord_test.go +++ b/pkg/channels/discord/discord_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/bwmarrin/discordgo" + "github.com/sipeed/picoclaw/pkg/audio/tts" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/channels" diff --git a/pkg/channels/feishu/feishu_64_test.go b/pkg/channels/feishu/feishu_64_test.go index 236f3ed23..0bdac0352 100644 --- a/pkg/channels/feishu/feishu_64_test.go +++ b/pkg/channels/feishu/feishu_64_test.go @@ -7,9 +7,9 @@ import ( "errors" "testing" - "github.com/sipeed/picoclaw/pkg/channels" - larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1" + + "github.com/sipeed/picoclaw/pkg/channels" ) func TestExtractContent(t *testing.T) { diff --git a/pkg/channels/manager_test.go b/pkg/channels/manager_test.go index a50e7b334..00eb1592a 100644 --- a/pkg/channels/manager_test.go +++ b/pkg/channels/manager_test.go @@ -932,7 +932,10 @@ func TestPreSendMedia_LeavesTrackedMessageForChannelSend(t *testing.T) { }, ch) if ch.dismissedChatID != "" { - t.Fatalf("expected tracked tool feedback cleanup to be deferred to channel media send, got %q", ch.dismissedChatID) + t.Fatalf( + "expected tracked tool feedback cleanup to be deferred to channel media send, got %q", + ch.dismissedChatID, + ) } } diff --git a/pkg/channels/telegram/telegram_test.go b/pkg/channels/telegram/telegram_test.go index 74c56441a..7797839fb 100644 --- a/pkg/channels/telegram/telegram_test.go +++ b/pkg/channels/telegram/telegram_test.go @@ -109,13 +109,6 @@ func successResponseWithMessageID(t *testing.T, messageID int) *ta.Response { return &ta.Response{Ok: true, Result: b} } -func successBoolResponse(t *testing.T) *ta.Response { - t.Helper() - b, err := json.Marshal(true) - require.NoError(t, err) - return &ta.Response{Ok: true, Result: b} -} - func successUserResponse(t *testing.T, user *telego.User) *ta.Response { t.Helper() b, err := json.Marshal(user) diff --git a/pkg/utils/tool_feedback_test.go b/pkg/utils/tool_feedback_test.go index 07cbf3351..316ce2408 100644 --- a/pkg/utils/tool_feedback_test.go +++ b/pkg/utils/tool_feedback_test.go @@ -3,7 +3,10 @@ package utils import "testing" func TestFormatToolFeedbackMessage(t *testing.T) { - got := FormatToolFeedbackMessage("read_file", "I will read README.md first to confirm the current project structure.") + got := FormatToolFeedbackMessage( + "read_file", + "I will read README.md first to confirm the current project structure.", + ) want := "\U0001f527 `read_file`\nI will read README.md first to confirm the current project structure." if got != want { t.Fatalf("FormatToolFeedbackMessage() = %q, want %q", got, want) @@ -27,7 +30,10 @@ func TestFormatToolFeedbackMessage_EmptyToolNameOmitsToolLine(t *testing.T) { } func TestFitToolFeedbackMessage_TruncatesBodyWithinSingleMessage(t *testing.T) { - got := FitToolFeedbackMessage("\U0001f527 `read_file`\nRead README.md first to confirm the current project structure.", 40) + got := FitToolFeedbackMessage( + "\U0001f527 `read_file`\nRead README.md first to confirm the current project structure.", + 40, + ) want := "\U0001f527 `read_file`\nRead README.md first to..." if got != want { t.Fatalf("FitToolFeedbackMessage() = %q, want %q", got, want) diff --git a/web/backend/api/session_test.go b/web/backend/api/session_test.go index fe24de8d3..a67416d4e 100644 --- a/web/backend/api/session_test.go +++ b/web/backend/api/session_test.go @@ -700,7 +700,8 @@ func TestHandleGetSession_DoesNotExposeLegacyToolArgumentsWhenExplanationMissing t.Fatalf("LoadConfig() error = %v", err) } cfg.Agents.Defaults.ToolFeedback.MaxArgsLength = 20 - if err := config.SaveConfig(configPath, cfg); err != nil { + err = config.SaveConfig(configPath, cfg) + if err != nil { t.Fatalf("SaveConfig() error = %v", err) } @@ -712,7 +713,11 @@ func TestHandleGetSession_DoesNotExposeLegacyToolArgumentsWhenExplanationMissing argsJSON := `{"path":"README.md","start_line":1,"end_line":10,"extra":"abcdefghijklmnopqrstuvwxyz"}` sessionKey := picoSessionPrefix + "detail-tool-summary-legacy-args" - if err := store.AddFullMessage(nil, sessionKey, providers.Message{Role: "user", Content: "check file"}); err != nil { + if err := store.AddFullMessage( + nil, + sessionKey, + providers.Message{Role: "user", Content: "check file"}, + ); err != nil { t.Fatalf("AddFullMessage(user) error = %v", err) } if err := store.AddFullMessage(nil, sessionKey, providers.Message{