package standard_test import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" agentcontext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/robot/executor/standard" "github.com/yaoapp/yao/agent/robot/types" ) // ============================================================================ // InputFormatter Tests // ============================================================================ func TestInputFormatterFormatClockContext(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats clock context with all fields", func(t *testing.T) { now := time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC) clock := types.NewClockContext(now, "UTC") result := formatter.FormatClockContext(clock, nil) assert.Contains(t, result, "## Current Time Context") assert.Contains(t, result, "2024-01-15 09:30:00") assert.Contains(t, result, "Monday") assert.Contains(t, result, "UTC") assert.Contains(t, result, "**Hour**: 9") assert.Contains(t, result, "### Time Markers") }) t.Run("shows all time markers with check/cross", func(t *testing.T) { // Regular weekday, not month start/end now := time.Date(2024, 1, 15, 14, 0, 0, 0, time.UTC) clock := types.NewClockContext(now, "UTC") result := formatter.FormatClockContext(clock, nil) // Should show all markers, even when false assert.Contains(t, result, "✗ Weekend") assert.Contains(t, result, "✗ Month Start") assert.Contains(t, result, "✗ Month End") assert.Contains(t, result, "✗ Quarter End") assert.Contains(t, result, "✗ Year End") }) t.Run("includes robot identity when provided", func(t *testing.T) { now := time.Now() clock := types.NewClockContext(now, "UTC") robot := &types.Robot{ MemberID: "test-robot", Config: &types.Config{ Identity: &types.Identity{ Role: "Sales Analyst", Duties: []string{"Analyze sales data", "Generate reports"}, Rules: []string{"Be accurate", "Be concise"}, }, }, } result := formatter.FormatClockContext(clock, robot) assert.Contains(t, result, "## Robot Identity") assert.Contains(t, result, "Sales Analyst") assert.Contains(t, result, "Analyze sales data") assert.Contains(t, result, "Be accurate") }) t.Run("returns empty for nil clock", func(t *testing.T) { result := formatter.FormatClockContext(nil, nil) assert.Empty(t, result) }) t.Run("uses DisplayName/Bio/SystemPrompt when Identity is nil", func(t *testing.T) { now := time.Now() clock := types.NewClockContext(now, "UTC") robot := &types.Robot{ MemberID: "test-robot", DisplayName: "SEO Specialist", Bio: "Focuses on content optimization", SystemPrompt: "You are an SEO assistant.\n\n## Core Duties\n- Analyze keywords", Config: &types.Config{}, // Identity is nil } result := formatter.FormatClockContext(clock, robot) assert.Contains(t, result, "## Robot Identity") assert.Contains(t, result, "SEO Specialist") assert.Contains(t, result, "Focuses on content optimization") assert.Contains(t, result, "SEO assistant") assert.Contains(t, result, "Analyze keywords") }) t.Run("marks weekend correctly", func(t *testing.T) { // Saturday saturday := time.Date(2024, 1, 13, 10, 0, 0, 0, time.UTC) clock := types.NewClockContext(saturday, "UTC") result := formatter.FormatClockContext(clock, nil) assert.Contains(t, result, "✓ Weekend") }) t.Run("marks month start correctly", func(t *testing.T) { // 2nd of month monthStart := time.Date(2024, 1, 2, 10, 0, 0, 0, time.UTC) clock := types.NewClockContext(monthStart, "UTC") result := formatter.FormatClockContext(clock, nil) assert.Contains(t, result, "✓ Month Start") }) t.Run("marks month end correctly", func(t *testing.T) { // 30th of January (last 3 days) monthEnd := time.Date(2024, 1, 30, 10, 0, 0, 0, time.UTC) clock := types.NewClockContext(monthEnd, "UTC") result := formatter.FormatClockContext(clock, nil) assert.Contains(t, result, "✓ Month End") }) } func TestInputFormatterFormatInspirationReport(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats inspiration report with clock", func(t *testing.T) { now := time.Date(2024, 1, 15, 9, 30, 0, 0, time.UTC) clock := types.NewClockContext(now, "UTC") report := &types.InspirationReport{ Clock: clock, Content: "Today is a good day to analyze sales data.", } result := formatter.FormatInspirationReport(report) assert.Contains(t, result, "## Time Context") assert.Contains(t, result, "Monday") assert.Contains(t, result, "## Inspiration Report") assert.Contains(t, result, "analyze sales data") }) t.Run("formats inspiration report without clock", func(t *testing.T) { report := &types.InspirationReport{ Content: "Focus on quarterly review.", } result := formatter.FormatInspirationReport(report) assert.NotContains(t, result, "## Time Context") assert.Contains(t, result, "## Inspiration Report") assert.Contains(t, result, "quarterly review") }) t.Run("returns empty for nil report", func(t *testing.T) { result := formatter.FormatInspirationReport(nil) assert.Empty(t, result) }) } func TestInputFormatterFormatAvailableResources(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats all resource types", func(t *testing.T) { robot := &types.Robot{ MemberID: "test-robot", Config: &types.Config{ Resources: &types.Resources{ Agents: []string{"data-analyst", "chart-gen", "report-writer"}, MCP: []types.MCPConfig{ {ID: "database", Tools: []string{"query", "insert"}}, {ID: "email", Tools: []string{}}, // all tools }, }, KB: &types.KB{ Collections: []string{"sales-policies", "products"}, }, DB: &types.DB{ Models: []string{"sales", "customers", "orders"}, }, }, } result := formatter.FormatAvailableResources(robot) // Check structure assert.Contains(t, result, "## Available Resources") // Check agents assert.Contains(t, result, "### Agents") assert.Contains(t, result, "data-analyst") assert.Contains(t, result, "chart-gen") assert.Contains(t, result, "report-writer") // Check MCP tools assert.Contains(t, result, "### MCP Tools") assert.Contains(t, result, "database") assert.Contains(t, result, "query, insert") assert.Contains(t, result, "email") assert.Contains(t, result, "all tools available") // Check KB assert.Contains(t, result, "### Knowledge Base") assert.Contains(t, result, "sales-policies") assert.Contains(t, result, "products") // Check DB assert.Contains(t, result, "### Database") assert.Contains(t, result, "sales") assert.Contains(t, result, "customers") assert.Contains(t, result, "orders") // Check important note assert.Contains(t, result, "Only plan goals and tasks that can be accomplished") }) t.Run("returns empty for nil robot", func(t *testing.T) { result := formatter.FormatAvailableResources(nil) assert.Empty(t, result) }) t.Run("returns empty for robot without config", func(t *testing.T) { robot := &types.Robot{MemberID: "test"} result := formatter.FormatAvailableResources(robot) assert.Empty(t, result) }) t.Run("returns empty for robot without resources", func(t *testing.T) { robot := &types.Robot{ MemberID: "test", Config: &types.Config{}, } result := formatter.FormatAvailableResources(robot) assert.Empty(t, result) }) t.Run("handles partial resources", func(t *testing.T) { robot := &types.Robot{ MemberID: "test", Config: &types.Config{ Resources: &types.Resources{ Agents: []string{"single-agent"}, }, }, } result := formatter.FormatAvailableResources(robot) assert.Contains(t, result, "## Available Resources") assert.Contains(t, result, "### Agents") assert.Contains(t, result, "single-agent") assert.NotContains(t, result, "### MCP Tools") assert.NotContains(t, result, "### Knowledge Base") assert.NotContains(t, result, "### Database") }) } func TestInputFormatterFormatTriggerInput(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats human intervention", func(t *testing.T) { input := &types.TriggerInput{ Action: "task.add", UserID: "user-123", Messages: []agentcontext.Message{ {Role: agentcontext.RoleUser, Content: "Please add a task to review Q4 sales"}, }, } result := formatter.FormatTriggerInput(input) assert.Contains(t, result, "## Human Intervention") assert.Contains(t, result, "task.add") assert.Contains(t, result, "user-123") assert.Contains(t, result, "### User Input") assert.Contains(t, result, "review Q4 sales") }) t.Run("formats event trigger", func(t *testing.T) { input := &types.TriggerInput{ Source: "webhook", EventType: "order.created", Data: map[string]interface{}{ "order_id": "12345", "amount": 99.99, }, } result := formatter.FormatTriggerInput(input) assert.Contains(t, result, "## Event Trigger") assert.Contains(t, result, "webhook") assert.Contains(t, result, "order.created") assert.Contains(t, result, "### Event Data") assert.Contains(t, result, "order_id") assert.Contains(t, result, "12345") }) t.Run("returns empty for nil input", func(t *testing.T) { result := formatter.FormatTriggerInput(nil) assert.Empty(t, result) }) t.Run("returns empty for empty input", func(t *testing.T) { input := &types.TriggerInput{} result := formatter.FormatTriggerInput(input) assert.Empty(t, result) }) } func TestInputFormatterFormatGoals(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats goals with resources", func(t *testing.T) { goals := &types.Goals{ Content: "1. Analyze sales data\n2. Generate report\n3. Send to stakeholders", } robot := &types.Robot{ MemberID: "test-robot", Config: &types.Config{ Resources: &types.Resources{ Agents: []string{"data-analyzer", "report-generator"}, MCP: []types.MCPConfig{ {ID: "database", Tools: []string{"query", "insert"}}, {ID: "email"}, }, }, }, } result := formatter.FormatGoals(goals, robot) assert.Contains(t, result, "## Goals") assert.Contains(t, result, "Analyze sales data") assert.Contains(t, result, "## Available Resources") assert.Contains(t, result, "### Agents") assert.Contains(t, result, "data-analyzer") assert.Contains(t, result, "### MCP Tools") assert.Contains(t, result, "database") assert.Contains(t, result, "query, insert") assert.Contains(t, result, "email") assert.Contains(t, result, "all tools available") }) t.Run("formats goals without robot", func(t *testing.T) { goals := &types.Goals{ Content: "Complete the task.", } result := formatter.FormatGoals(goals, nil) assert.Contains(t, result, "## Goals") assert.Contains(t, result, "Complete the task") assert.NotContains(t, result, "## Available Resources") }) t.Run("returns empty for nil goals", func(t *testing.T) { result := formatter.FormatGoals(nil, nil) assert.Empty(t, result) }) } func TestInputFormatterFormatTasks(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats multiple tasks", func(t *testing.T) { tasks := []types.Task{ { ID: "task-1", GoalRef: "goal-1", Source: types.TaskSourceAuto, ExecutorType: types.ExecutorMCP, ExecutorID: "database.query", Messages: []agentcontext.Message{ {Role: agentcontext.RoleUser, Content: "Query sales data for Q4"}, }, Args: []any{"sales", "Q4"}, }, { ID: "task-2", GoalRef: "goal-1", Source: types.TaskSourceAuto, ExecutorType: types.ExecutorAssistant, ExecutorID: "report-generator", }, } result := formatter.FormatTasks(tasks) assert.Contains(t, result, "## Tasks to Execute") assert.Contains(t, result, "### Task 1: task-1") assert.Contains(t, result, "goal-1") assert.Contains(t, result, "database.query") assert.Contains(t, result, "**Instructions**") assert.Contains(t, result, "Query sales data") assert.Contains(t, result, "**Arguments**") assert.Contains(t, result, "### Task 2: task-2") assert.Contains(t, result, "report-generator") }) t.Run("returns message for empty tasks", func(t *testing.T) { result := formatter.FormatTasks(nil) assert.Equal(t, "No tasks to execute.", result) result = formatter.FormatTasks([]types.Task{}) assert.Equal(t, "No tasks to execute.", result) }) } func TestInputFormatterFormatTaskResults(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats task results with summary", func(t *testing.T) { results := []types.TaskResult{ { TaskID: "task-1", Success: true, Duration: 150, Validation: &types.ValidationResult{ Passed: true, Score: 0.95, }, Output: map[string]interface{}{"rows": 100}, }, { TaskID: "task-2", Success: false, Duration: 50, Validation: &types.ValidationResult{ Passed: false, Issues: []string{"Connection timeout"}, }, Error: "Connection timeout", }, } result := formatter.FormatTaskResults(results) assert.Contains(t, result, "## Task Results") assert.Contains(t, result, "### Task: task-1") assert.Contains(t, result, "✓ Success") assert.Contains(t, result, "150ms") assert.Contains(t, result, "**Validation**: ✓ Passed") assert.Contains(t, result, "score: 0.95") assert.Contains(t, result, "**Output**") assert.Contains(t, result, "### Task: task-2") assert.Contains(t, result, "✗ Failed") assert.Contains(t, result, "**Validation**: ✗ Failed") assert.Contains(t, result, "Connection timeout") assert.Contains(t, result, "## Summary") assert.Contains(t, result, "Total: 2 tasks") assert.Contains(t, result, "Success: 1") assert.Contains(t, result, "Failed: 1") assert.Contains(t, result, "Validated: 1/2") }) t.Run("returns message for empty results", func(t *testing.T) { result := formatter.FormatTaskResults(nil) assert.Equal(t, "No task results.", result) }) } func TestInputFormatterFormatExecutionSummary(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats complete execution summary", func(t *testing.T) { startTime := time.Date(2024, 1, 15, 9, 0, 0, 0, time.UTC) endTime := time.Date(2024, 1, 15, 9, 5, 0, 0, time.UTC) exec := &types.Execution{ ID: "exec-123", TriggerType: types.TriggerClock, Status: types.ExecCompleted, StartTime: startTime, EndTime: &endTime, Inspiration: &types.InspirationReport{ Content: "Morning analysis suggests high activity.", }, Goals: &types.Goals{ Content: "1. Review data\n2. Generate report", }, Tasks: []types.Task{ {ID: "t1", Status: types.TaskCompleted, ExecutorID: "db.query"}, {ID: "t2", Status: types.TaskCompleted, ExecutorID: "report.gen"}, }, Results: []types.TaskResult{ {TaskID: "t1", Success: true, Duration: 100}, {TaskID: "t2", Success: true, Duration: 200}, }, Delivery: &types.DeliveryResult{ RequestID: "test-delivery-001", Content: &types.DeliveryContent{ Summary: "Test delivery completed", Body: "# Test Delivery\n\nTest delivery body.", }, Success: true, }, } result := formatter.FormatExecutionSummary(exec) assert.Contains(t, result, "## Execution Summary") assert.Contains(t, result, "exec-123") assert.Contains(t, result, "clock") assert.Contains(t, result, "completed") assert.Contains(t, result, "**Duration**:") assert.Contains(t, result, "## Inspiration (P0)") assert.Contains(t, result, "Morning analysis") assert.Contains(t, result, "## Goals (P1)") assert.Contains(t, result, "Review data") assert.Contains(t, result, "## Tasks (P2)") assert.Contains(t, result, "db.query") assert.Contains(t, result, "## Results (P3)") assert.Contains(t, result, "✓ t1") assert.Contains(t, result, "## Delivery (P4)") assert.Contains(t, result, "Test delivery completed") }) t.Run("formats execution with error", func(t *testing.T) { startTime := time.Now() exec := &types.Execution{ ID: "exec-456", TriggerType: types.TriggerHuman, Status: types.ExecFailed, StartTime: startTime, Error: "Task execution failed", } result := formatter.FormatExecutionSummary(exec) assert.Contains(t, result, "exec-456") assert.Contains(t, result, "failed") assert.Contains(t, result, "**Error**: Task execution failed") }) t.Run("returns empty for nil execution", func(t *testing.T) { result := formatter.FormatExecutionSummary(nil) assert.Empty(t, result) }) } func TestInputFormatterBuildMessages(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("builds user message", func(t *testing.T) { msgs := formatter.BuildMessages("Hello, world!") require.Len(t, msgs, 1) assert.Equal(t, agentcontext.RoleUser, msgs[0].Role) assert.Equal(t, "Hello, world!", msgs[0].Content) }) } func TestInputFormatterBuildMessagesWithSystem(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("builds system and user messages", func(t *testing.T) { msgs := formatter.BuildMessagesWithSystem( "You are a helpful assistant.", "What is the weather?", ) require.Len(t, msgs, 2) assert.Equal(t, agentcontext.RoleSystem, msgs[0].Role) assert.Equal(t, "You are a helpful assistant.", msgs[0].Content) assert.Equal(t, agentcontext.RoleUser, msgs[1].Role) assert.Equal(t, "What is the weather?", msgs[1].Content) }) }