// monitor_tool_test.go 测试 MonitorTool 的核心路径. package builtin import ( "context" "encoding/json" "testing" "time" "git.flytoex.net/yuanwei/flyto-agent/pkg/inbox" ) // TestMonitorTool_NilServer 测试 srv=nil 时静默成功(UDS 未启用降级场景). func TestMonitorTool_NilServer(t *testing.T) { tool := NewMonitorTool(nil) input, _ := json.Marshal(map[string]any{ "message": "processing...", }) result, err := tool.Execute(context.Background(), input, nil) if err != nil { t.Fatalf("Execute with nil server should not return error: %v", err) } if result.IsError { t.Errorf("Execute with nil server should not return IsError=true, got output=%q", result.Output) } if result.Output != `{"ok": true}` { t.Errorf("output=%q, want %q", result.Output, `{"ok": true}`) } } // TestMonitorTool_WithServer 测试带真实 UDSServer 时,消息被推送到 channel. func TestMonitorTool_WithServer(t *testing.T) { srv, err := inbox.NewUDSServer("test-monitor-tool-with-server") if err != nil { t.Fatalf("NewUDSServer: %v", err) } if err := srv.Start(); err != nil { t.Fatalf("Start: %v", err) } defer srv.Close() tool := NewMonitorTool(srv) pct := 75.0 input, _ := json.Marshal(map[string]any{ "tool_use_id": "call_xyz", "message": "导入进度", "percent": pct, }) result, err := tool.Execute(context.Background(), input, nil) if err != nil { t.Fatalf("Execute: %v", err) } if result.IsError { t.Errorf("Execute should succeed, got IsError=true, output=%q", result.Output) } select { case msg := <-srv.Messages(): if msg.Type != "progress" { t.Errorf("msg.Type=%q, want %q", msg.Type, "progress") } if msg.ToolUseID != "call_xyz" { t.Errorf("msg.ToolUseID=%q, want %q", msg.ToolUseID, "call_xyz") } // Data 应该包含 percent if msg.Data == "" { t.Errorf("msg.Data should not be empty") } case <-time.After(2 * time.Second): t.Fatal("timeout waiting for inbox message") } } // TestMonitorTool_WithMeta 测试 meta 字段被正确透传. func TestMonitorTool_WithMeta(t *testing.T) { srv, err := inbox.NewUDSServer("test-monitor-tool-meta") if err != nil { t.Fatalf("NewUDSServer: %v", err) } if err := srv.Start(); err != nil { t.Fatalf("Start: %v", err) } defer srv.Close() tool := NewMonitorTool(srv) meta := map[string]any{"batch": 3, "total": 10} metaBytes, _ := json.Marshal(meta) input, _ := json.Marshal(map[string]any{ "message": "batch complete", "meta": json.RawMessage(metaBytes), }) _, err = tool.Execute(context.Background(), input, nil) if err != nil { t.Fatalf("Execute: %v", err) } select { case msg := <-srv.Messages(): var gotMeta map[string]any if err := json.Unmarshal(msg.Meta, &gotMeta); err != nil { t.Fatalf("unmarshal meta: %v", err) } if gotMeta["batch"] != float64(3) { t.Errorf("meta.batch=%v, want 3", gotMeta["batch"]) } case <-time.After(2 * time.Second): t.Fatal("timeout") } } // TestMonitorTool_EmptyMessage 测试 message 为空时返回错误. func TestMonitorTool_EmptyMessage(t *testing.T) { tool := NewMonitorTool(nil) input, _ := json.Marshal(map[string]any{ "message": "", }) result, err := tool.Execute(context.Background(), input, nil) if err != nil { t.Fatalf("Execute: %v", err) } if !result.IsError { t.Errorf("Execute with empty message should return IsError=true") } } // TestMonitorTool_InvalidInput 测试无效 JSON 输入. func TestMonitorTool_InvalidInput(t *testing.T) { tool := NewMonitorTool(nil) _, err := tool.Execute(context.Background(), json.RawMessage(`{invalid}`), nil) if err == nil { t.Error("Execute with invalid JSON should return error") } } // TestMonitorTool_Metadata 测试工具元数据. func TestMonitorTool_Metadata(t *testing.T) { tool := NewMonitorTool(nil) if tool.Name() != "monitor_progress" { t.Errorf("Name=%q, want %q", tool.Name(), "monitor_progress") } meta := tool.Metadata() if !meta.ConcurrencySafe { t.Error("MonitorTool should be ConcurrencySafe") } if !meta.ReadOnly { t.Error("MonitorTool should be ReadOnly") } if meta.Destructive { t.Error("MonitorTool should not be Destructive") } schema := tool.InputSchema() if len(schema) == 0 { t.Error("InputSchema should not be empty") } // schema 应包含 "message" 字段 var schemaObj map[string]any if err := json.Unmarshal(schema, &schemaObj); err != nil { t.Fatalf("unmarshal schema: %v", err) } } // TestMonitorTool_PercentFormat 测试 percent 格式化(含 1 位小数). func TestMonitorTool_PercentFormat(t *testing.T) { srv, err := inbox.NewUDSServer("test-monitor-tool-percent") if err != nil { t.Fatalf("NewUDSServer: %v", err) } if err := srv.Start(); err != nil { t.Fatalf("Start: %v", err) } defer srv.Close() tool := NewMonitorTool(srv) pct := 33.33 input, _ := json.Marshal(map[string]any{ "message": "处理中", "percent": pct, }) _, err = tool.Execute(context.Background(), input, nil) if err != nil { t.Fatalf("Execute: %v", err) } select { case msg := <-srv.Messages(): // Data 应该是 "处理中 (33.3%)" expected := "处理中 (33.3%)" if msg.Data != expected { t.Errorf("msg.Data=%q, want %q", msg.Data, expected) } case <-time.After(2 * time.Second): t.Fatal("timeout") } } // TestMonitorTool_Description 测试工具描述非空. func TestMonitorTool_Description(t *testing.T) { tool := NewMonitorTool(nil) desc := tool.Description(context.Background()) if desc == "" { t.Error("Description should not be empty") } }