yao/agent/robot/executor/standard/ui_fields_test.go
Max d590bd7557 Enhance Execution Management with UI Display Fields and Localization Support
- Added `Name` and `CurrentTaskName` fields to the `Execution` struct for improved UI display during execution phases.
- Implemented localization support for UI messages, allowing dynamic updates based on the execution context and user locale.
- Updated the executor to manage these fields at various phases, ensuring accurate representation of execution status.
- Enhanced OpenAPI documentation to reflect the new fields and their usage in execution responses.
- Added unit tests to validate the functionality of UI fields and localization handling.
2026-01-24 10:10:48 +08:00

356 lines
11 KiB
Go

package standard
import (
"testing"
"github.com/stretchr/testify/assert"
agentcontext "github.com/yaoapp/yao/agent/context"
robottypes "github.com/yaoapp/yao/agent/robot/types"
)
// ============================================================================
// getEffectiveLocale Tests
// ============================================================================
func TestGetEffectiveLocale(t *testing.T) {
t.Run("returns_input_locale_when_provided", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{
DefaultLocale: "en",
},
}
input := &robottypes.TriggerInput{
Locale: "zh",
}
locale := getEffectiveLocale(robot, input)
assert.Equal(t, "zh", locale)
})
t.Run("returns_robot_default_locale_when_input_locale_empty", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{
DefaultLocale: "zh",
},
}
input := &robottypes.TriggerInput{
Locale: "",
}
locale := getEffectiveLocale(robot, input)
assert.Equal(t, "zh", locale)
})
t.Run("returns_system_default_when_no_locale_configured", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{},
}
input := &robottypes.TriggerInput{}
locale := getEffectiveLocale(robot, input)
assert.Equal(t, "en", locale)
})
t.Run("returns_system_default_when_robot_config_nil", func(t *testing.T) {
robot := &robottypes.Robot{}
input := &robottypes.TriggerInput{}
locale := getEffectiveLocale(robot, input)
assert.Equal(t, "en", locale)
})
t.Run("returns_system_default_when_robot_nil", func(t *testing.T) {
input := &robottypes.TriggerInput{}
locale := getEffectiveLocale(nil, input)
assert.Equal(t, "en", locale)
})
t.Run("returns_system_default_when_input_nil", func(t *testing.T) {
robot := &robottypes.Robot{}
locale := getEffectiveLocale(robot, nil)
assert.Equal(t, "en", locale)
})
}
// ============================================================================
// getLocalizedMessage Tests
// ============================================================================
func TestGetLocalizedMessage(t *testing.T) {
t.Run("returns_english_message_for_en_locale", func(t *testing.T) {
msg := getLocalizedMessage("en", "preparing")
assert.Equal(t, "Preparing...", msg)
})
t.Run("returns_chinese_message_for_zh_locale", func(t *testing.T) {
msg := getLocalizedMessage("zh", "preparing")
assert.Equal(t, "准备中...", msg)
})
t.Run("returns_english_fallback_for_unknown_locale", func(t *testing.T) {
msg := getLocalizedMessage("fr", "preparing")
assert.Equal(t, "Preparing...", msg)
})
t.Run("returns_key_for_unknown_message", func(t *testing.T) {
msg := getLocalizedMessage("en", "unknown_key")
assert.Equal(t, "unknown_key", msg)
})
t.Run("all_english_messages_exist", func(t *testing.T) {
keys := []string{
"preparing", "starting", "scheduled_execution",
"event_prefix", "event_triggered", "analyzing_context",
"planning_goals", "breaking_down_tasks", "completed",
"failed_prefix", "task_prefix",
}
for _, key := range keys {
msg := getLocalizedMessage("en", key)
assert.NotEqual(t, key, msg, "English message should exist for key: %s", key)
}
})
t.Run("all_chinese_messages_exist", func(t *testing.T) {
keys := []string{
"preparing", "starting", "scheduled_execution",
"event_prefix", "event_triggered", "analyzing_context",
"planning_goals", "breaking_down_tasks", "completed",
"failed_prefix", "task_prefix",
}
for _, key := range keys {
msg := getLocalizedMessage("zh", key)
assert.NotEqual(t, key, msg, "Chinese message should exist for key: %s", key)
}
})
}
// ============================================================================
// initUIFields Tests
// ============================================================================
func TestInitUIFields(t *testing.T) {
executor := New()
t.Run("human_trigger_extracts_name_from_message", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "en"},
}
input := &robottypes.TriggerInput{
Messages: []agentcontext.Message{
{Role: agentcontext.RoleUser, Content: "Please analyze the sales data"},
},
}
name, currentTaskName := executor.initUIFields(robottypes.TriggerHuman, input, robot)
assert.Equal(t, "Please analyze the sales data", name)
assert.Equal(t, "Starting...", currentTaskName)
})
t.Run("human_trigger_truncates_long_message", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "en"},
}
longMessage := "This is a very long message that exceeds one hundred characters and should be truncated with an ellipsis at the end"
input := &robottypes.TriggerInput{
Messages: []agentcontext.Message{
{Role: agentcontext.RoleUser, Content: longMessage},
},
}
name, _ := executor.initUIFields(robottypes.TriggerHuman, input, robot)
assert.LessOrEqual(t, len(name), 103) // 100 chars + "..."
assert.True(t, len(name) > 100 || name == longMessage[:100]+"...")
})
t.Run("clock_trigger_uses_scheduled_execution", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "en"},
}
input := &robottypes.TriggerInput{}
name, currentTaskName := executor.initUIFields(robottypes.TriggerClock, input, robot)
assert.Equal(t, "Scheduled execution", name)
assert.Equal(t, "Starting...", currentTaskName)
})
t.Run("clock_trigger_chinese_locale", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "zh"},
}
input := &robottypes.TriggerInput{}
name, currentTaskName := executor.initUIFields(robottypes.TriggerClock, input, robot)
assert.Equal(t, "定时执行", name)
assert.Equal(t, "启动中...", currentTaskName)
})
t.Run("event_trigger_with_event_type", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "en"},
}
input := &robottypes.TriggerInput{
EventType: "lead.created",
}
name, currentTaskName := executor.initUIFields(robottypes.TriggerEvent, input, robot)
assert.Equal(t, "Event: lead.created", name)
assert.Equal(t, "Starting...", currentTaskName)
})
t.Run("event_trigger_without_event_type", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "en"},
}
input := &robottypes.TriggerInput{}
name, _ := executor.initUIFields(robottypes.TriggerEvent, input, robot)
assert.Equal(t, "Event triggered", name)
})
t.Run("event_trigger_chinese_locale", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "zh"},
}
input := &robottypes.TriggerInput{
EventType: "order.placed",
}
name, _ := executor.initUIFields(robottypes.TriggerEvent, input, robot)
assert.Equal(t, "事件: order.placed", name)
})
t.Run("input_locale_overrides_robot_default", func(t *testing.T) {
robot := &robottypes.Robot{
Config: &robottypes.Config{DefaultLocale: "en"},
}
input := &robottypes.TriggerInput{
Locale: "zh",
}
name, currentTaskName := executor.initUIFields(robottypes.TriggerClock, input, robot)
assert.Equal(t, "定时执行", name)
assert.Equal(t, "启动中...", currentTaskName)
})
}
// ============================================================================
// extractGoalName Tests
// ============================================================================
func TestExtractGoalName(t *testing.T) {
t.Run("extracts_first_line_from_content", func(t *testing.T) {
goals := &robottypes.Goals{
Content: "Generate monthly sales report\nAnalyze trends\nSend to stakeholders",
}
name := extractGoalName(goals)
assert.Equal(t, "Generate monthly sales report", name)
})
t.Run("returns_empty_for_nil_goals", func(t *testing.T) {
name := extractGoalName(nil)
assert.Equal(t, "", name)
})
t.Run("returns_empty_for_empty_content", func(t *testing.T) {
goals := &robottypes.Goals{
Content: "",
}
name := extractGoalName(goals)
assert.Equal(t, "", name)
})
t.Run("truncates_long_first_line", func(t *testing.T) {
longLine := "This is an extremely long goal description that exceeds one hundred and fifty characters and should be truncated with an ellipsis at the end to keep the display manageable"
goals := &robottypes.Goals{
Content: longLine,
}
name := extractGoalName(goals)
assert.LessOrEqual(t, len(name), 153) // 150 chars + "..."
})
t.Run("handles_single_line_content", func(t *testing.T) {
goals := &robottypes.Goals{
Content: "Single line goal",
}
name := extractGoalName(goals)
assert.Equal(t, "Single line goal", name)
})
t.Run("handles_carriage_return", func(t *testing.T) {
goals := &robottypes.Goals{
Content: "First goal\r\nSecond goal",
}
name := extractGoalName(goals)
assert.Equal(t, "First goal", name)
})
}
// ============================================================================
// formatTaskProgressName Tests
// ============================================================================
func TestFormatTaskProgressName(t *testing.T) {
t.Run("formats_with_task_description", func(t *testing.T) {
task := &robottypes.Task{
ID: "task-001",
ExecutorType: robottypes.ExecutorAssistant,
ExecutorID: "analyst",
Messages: []agentcontext.Message{
{Role: agentcontext.RoleUser, Content: "Analyze sales data"},
},
}
name := formatTaskProgressName(task, 0, 3, "en")
assert.Equal(t, "Task 1/3: Analyze sales data", name)
})
t.Run("formats_with_chinese_locale", func(t *testing.T) {
task := &robottypes.Task{
ID: "task-001",
ExecutorType: robottypes.ExecutorAssistant,
ExecutorID: "analyst",
Messages: []agentcontext.Message{
{Role: agentcontext.RoleUser, Content: "分析销售数据"},
},
}
name := formatTaskProgressName(task, 1, 5, "zh")
assert.Equal(t, "任务 2/5: 分析销售数据", name)
})
t.Run("truncates_long_description", func(t *testing.T) {
longDesc := "This is a very long task description that should be truncated because it exceeds 80 characters which is the maximum length allowed"
task := &robottypes.Task{
ID: "task-001",
ExecutorType: robottypes.ExecutorAssistant,
ExecutorID: "analyst",
Messages: []agentcontext.Message{
{Role: agentcontext.RoleUser, Content: longDesc},
},
}
name := formatTaskProgressName(task, 0, 1, "en")
// Should be "Task 1/1: " (11 chars) + truncated content (83 chars max with "...")
assert.Contains(t, name, "...")
assert.LessOrEqual(t, len(name), 100)
})
t.Run("fallback_to_executor_info_when_no_messages", func(t *testing.T) {
task := &robottypes.Task{
ID: "task-001",
ExecutorType: robottypes.ExecutorMCP,
ExecutorID: "calculator",
Messages: []agentcontext.Message{},
}
name := formatTaskProgressName(task, 2, 4, "en")
assert.Equal(t, "Task 3/4: mcp:calculator", name)
})
}