package standard_test import ( "context" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yaoapp/yao/agent/robot/executor/standard" "github.com/yaoapp/yao/agent/robot/types" "github.com/yaoapp/yao/agent/testutils" ) // ============================================================================ // P4 Delivery Phase Tests // ============================================================================ func TestRunDeliveryBasic(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test") } testutils.Prepare(t) defer testutils.Clean(t) ctx := types.NewContext(context.Background(), testAuth()) t.Run("generates delivery content from execution results", func(t *testing.T) { robot := createDeliveryTestRobot(t, "robot.delivery") exec := createDeliveryTestExecution(robot) exec.Inspiration = &types.InspirationReport{ Content: "Morning analysis suggests focus on Q4 review.", } exec.Goals = &types.Goals{ Content: "## Goals\n1. Review Q4 data\n2. Generate summary report", } exec.Tasks = []types.Task{ {ID: "task-001", ExecutorID: "experts.data-analyst", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted}, {ID: "task-002", ExecutorID: "experts.summarizer", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted}, } exec.Results = []types.TaskResult{ {TaskID: "task-001", Success: true, Duration: 1500, Output: map[string]interface{}{"total_sales": 1500000}}, {TaskID: "task-002", Success: true, Duration: 800, Output: "Q4 sales exceeded expectations by 15%."}, } e := standard.New() err := e.RunDelivery(ctx, exec, nil) require.NoError(t, err) require.NotNil(t, exec.Delivery) require.NotNil(t, exec.Delivery.Content) assert.NotEmpty(t, exec.Delivery.Content.Summary) assert.NotEmpty(t, exec.Delivery.Content.Body) assert.True(t, exec.Delivery.Success) }) t.Run("handles partial failure in results", func(t *testing.T) { robot := createDeliveryTestRobot(t, "robot.delivery") exec := createDeliveryTestExecution(robot) exec.Goals = &types.Goals{ Content: "## Goals\n1. Analyze data\n2. Generate report", } exec.Tasks = []types.Task{ {ID: "task-001", ExecutorID: "experts.data-analyst", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted}, {ID: "task-002", ExecutorID: "experts.summarizer", ExecutorType: types.ExecutorAssistant, Status: types.TaskFailed}, } exec.Results = []types.TaskResult{ {TaskID: "task-001", Success: true, Duration: 1500, Output: map[string]interface{}{"data": "analyzed"}}, {TaskID: "task-002", Success: false, Duration: 500, Error: "Summarization failed: timeout"}, } e := standard.New() err := e.RunDelivery(ctx, exec, nil) require.NoError(t, err) require.NotNil(t, exec.Delivery) require.NotNil(t, exec.Delivery.Content) body := strings.ToLower(exec.Delivery.Content.Body) hasFailureInfo := strings.Contains(body, "fail") || strings.Contains(body, "error") || strings.Contains(body, "partial") || strings.Contains(body, "✗") assert.True(t, hasFailureInfo || exec.Delivery.Content.Summary != "", "should mention failure or have valid summary") }) } func TestRunDeliveryErrorHandling(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test") } testutils.Prepare(t) defer testutils.Clean(t) ctx := types.NewContext(context.Background(), testAuth()) t.Run("returns error when robot is nil", func(t *testing.T) { exec := &types.Execution{ ID: "test-exec-1", TriggerType: types.TriggerClock, } e := standard.New() err := e.RunDelivery(ctx, exec, nil) assert.Error(t, err) assert.Contains(t, err.Error(), "robot not found") }) t.Run("returns error when agent not found", func(t *testing.T) { robot := &types.Robot{ MemberID: "test-robot-1", TeamID: "test-team-1", Config: &types.Config{ Identity: &types.Identity{Role: "Test"}, Resources: &types.Resources{ Phases: map[types.Phase]string{ types.PhaseDelivery: "non.existent.agent", }, }, }, } exec := createDeliveryTestExecution(robot) exec.Results = []types.TaskResult{ {TaskID: "task-001", Success: true, Duration: 100}, } e := standard.New() err := e.RunDelivery(ctx, exec, nil) assert.Error(t, err) assert.Contains(t, err.Error(), "call failed") }) } // ============================================================================ // Email Channel Config Tests // ============================================================================ func TestDefaultEmailChannel(t *testing.T) { t.Run("returns default email channel", func(t *testing.T) { assert.Equal(t, "default", types.DefaultEmailChannel()) }) t.Run("can set custom email channel", func(t *testing.T) { original := types.DefaultEmailChannel() defer types.SetDefaultEmailChannel(original) types.SetDefaultEmailChannel("custom-email") assert.Equal(t, "custom-email", types.DefaultEmailChannel()) }) t.Run("ignores empty channel", func(t *testing.T) { original := types.DefaultEmailChannel() defer types.SetDefaultEmailChannel(original) types.SetDefaultEmailChannel("") assert.Equal(t, original, types.DefaultEmailChannel()) }) } func TestRobotEmailInDelivery(t *testing.T) { t.Run("robot email field is loaded from map", func(t *testing.T) { data := map[string]interface{}{ "member_id": "robot-001", "team_id": "team-001", "robot_email": "robot@example.com", } robot, err := types.NewRobotFromMap(data) require.NoError(t, err) assert.Equal(t, "robot@example.com", robot.RobotEmail) }) t.Run("robot email can be empty", func(t *testing.T) { data := map[string]interface{}{ "member_id": "robot-001", "team_id": "team-001", } robot, err := types.NewRobotFromMap(data) require.NoError(t, err) assert.Empty(t, robot.RobotEmail) }) } // ============================================================================ // FormatDeliveryInput Tests // ============================================================================ func TestFormatDeliveryInput(t *testing.T) { formatter := standard.NewInputFormatter() t.Run("formats complete execution context", func(t *testing.T) { robot := &types.Robot{ MemberID: "test-robot", Config: &types.Config{ Identity: &types.Identity{ Role: "Sales Analyst", Duties: []string{"Analyze data", "Generate reports"}, }, }, } startTime := time.Now().Add(-5 * time.Minute) endTime := time.Now() exec := &types.Execution{ ID: "exec-123", TriggerType: types.TriggerClock, Status: types.ExecCompleted, StartTime: startTime, EndTime: &endTime, Inspiration: &types.InspirationReport{ Content: "Morning analysis suggests focus on Q4.", }, Goals: &types.Goals{ Content: "## Goals\n1. Review Q4 data", }, Tasks: []types.Task{ {ID: "task-001", ExecutorID: "data-analyst", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted, ExpectedOutput: "JSON with sales data"}, }, Results: []types.TaskResult{ {TaskID: "task-001", Success: true, Duration: 1500, Output: map[string]interface{}{"sales": 1000000}}, }, } result := formatter.FormatDeliveryInput(exec, robot) assert.Contains(t, result, "## Robot Identity") assert.Contains(t, result, "Sales Analyst") assert.Contains(t, result, "## Execution Context") assert.Contains(t, result, "clock") assert.Contains(t, result, "## Inspiration (P0)") assert.Contains(t, result, "Morning analysis") assert.Contains(t, result, "## Goals (P1)") assert.Contains(t, result, "Review Q4 data") assert.Contains(t, result, "## Tasks (P2)") assert.Contains(t, result, "task-001") assert.Contains(t, result, "## Results (P3)") assert.Contains(t, result, "✓ Task: task-001") }) t.Run("handles empty execution", func(t *testing.T) { exec := &types.Execution{ ID: "exec-empty", TriggerType: types.TriggerHuman, Status: types.ExecPending, StartTime: time.Now(), } result := formatter.FormatDeliveryInput(exec, nil) assert.Contains(t, result, "## Execution Context") assert.Contains(t, result, "human") }) } // ============================================================================ // Helper Functions // ============================================================================ func createDeliveryTestRobot(t *testing.T, agentID string) *types.Robot { t.Helper() return &types.Robot{ MemberID: "test-robot-1", TeamID: "test-team-1", DisplayName: "Test Robot", Config: &types.Config{ Identity: &types.Identity{ Role: "Test Assistant", Duties: []string{"Testing", "Validation"}, }, Resources: &types.Resources{ Phases: map[types.Phase]string{ types.PhaseDelivery: agentID, }, }, }, } } func createDeliveryTestExecution(robot *types.Robot) *types.Execution { exec := &types.Execution{ ID: "test-exec-delivery-1", MemberID: robot.MemberID, TeamID: robot.TeamID, TriggerType: types.TriggerClock, StartTime: time.Now(), Status: types.ExecRunning, Phase: types.PhaseDelivery, } exec.SetRobot(robot) return exec }