yao/agent/robot/manager/integration_event_test.go
Max 00851c442e Enhance Integration Tests with Cache Verification
- Added assertions in multiple integration tests to verify that robots are correctly loaded into the cache during various execution scenarios.
- Updated tests for clock triggers, concurrent executions, control tests, event triggers, and human interventions to ensure comprehensive coverage of cache functionality.
- Improved test reliability by confirming the presence of expected robots in the cache, enhancing overall test robustness.
2026-01-15 17:39:03 +08:00

571 lines
15 KiB
Go

package manager_test
// Integration tests for Event triggers
// Tests Manager.HandleEvent() with various event types and scenarios
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yaoapp/gou/model"
"github.com/yaoapp/xun/capsule"
"github.com/yaoapp/yao/agent/robot/manager"
"github.com/yaoapp/yao/agent/robot/pool"
"github.com/yaoapp/yao/agent/robot/types"
"github.com/yaoapp/yao/agent/testutils"
)
// ==================== Event Trigger Tests ====================
// TestIntegrationEventTrigger tests event trigger flow
func TestIntegrationEventTrigger(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
testutils.Prepare(t)
defer testutils.Clean(t)
cleanupIntegrationRobots(t)
defer cleanupIntegrationRobots(t)
t.Run("webhook event success", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_webhook", "team_integ_event")
config := &manager.Config{
TickInterval: 100 * time.Millisecond,
PoolConfig: &pool.Config{WorkerSize: 3, QueueSize: 20},
}
m := manager.NewWithConfig(config)
err := m.Start()
require.NoError(t, err)
defer m.Stop()
// Verify robot is loaded into cache
robot := m.Cache().Get("robot_integ_event_webhook")
require.NotNil(t, robot, "Robot should be loaded into cache")
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_webhook",
Source: "webhook",
EventType: "lead.created",
Data: map[string]interface{}{
"name": "John Doe",
"email": "john@example.com",
"company": "Acme Corp",
},
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
assert.Equal(t, types.ExecPending, result.Status)
assert.Contains(t, result.Message, "webhook")
assert.Contains(t, result.Message, "lead.created")
// Wait for execution
time.Sleep(500 * time.Millisecond)
// Verify execution completed
assert.GreaterOrEqual(t, m.Executor().ExecCount(), 1)
})
t.Run("database event success", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_db", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_db",
Source: "database",
EventType: "order.paid",
Data: map[string]interface{}{
"order_id": "ORD-12345",
"amount": 1500.00,
"customer": "customer_001",
},
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
})
t.Run("event with complex data", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_complex", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_complex",
Source: "webhook",
EventType: "crm.contact.updated",
Data: map[string]interface{}{
"contact": map[string]interface{}{
"id": "contact_001",
"name": "Jane Smith",
"email": "jane@example.com",
"tags": []string{"vip", "enterprise"},
},
"changes": map[string]interface{}{
"old_status": "active",
"new_status": "premium",
},
"timestamp": time.Now().Unix(),
},
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
})
}
// TestIntegrationEventTriggerErrors tests error cases for event triggers
func TestIntegrationEventTriggerErrors(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
testutils.Prepare(t)
defer testutils.Clean(t)
cleanupIntegrationRobots(t)
defer cleanupIntegrationRobots(t)
t.Run("robot not found", func(t *testing.T) {
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_nonexistent",
Source: "webhook",
EventType: "test.event",
}
_, err = m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Equal(t, types.ErrRobotNotFound, err)
})
t.Run("robot paused", func(t *testing.T) {
setupEventTestRobotPaused(t, "robot_integ_event_paused", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_paused",
Source: "webhook",
EventType: "test.event",
}
_, err = m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Equal(t, types.ErrRobotPaused, err)
})
t.Run("event trigger disabled", func(t *testing.T) {
setupEventTestRobotDisabled(t, "robot_integ_event_disabled", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_disabled",
Source: "webhook",
EventType: "test.event",
}
_, err = m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Equal(t, types.ErrTriggerDisabled, err)
})
t.Run("invalid request - empty member_id", func(t *testing.T) {
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "", // Empty
Source: "webhook",
EventType: "test.event",
}
_, err = m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member_id")
})
t.Run("invalid request - empty source", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_nosource", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_nosource",
Source: "", // Empty
EventType: "test.event",
}
_, err = m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "source")
})
t.Run("invalid request - empty event_type", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_notype", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_notype",
Source: "webhook",
EventType: "", // Empty
}
_, err = m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "event_type")
})
t.Run("manager not started", func(t *testing.T) {
m := manager.New()
// Don't start
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_test",
Source: "webhook",
EventType: "test.event",
}
_, err := m.HandleEvent(ctx, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not started")
})
}
// TestIntegrationEventTriggerTypes tests various event types
func TestIntegrationEventTriggerTypes(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
testutils.Prepare(t)
defer testutils.Clean(t)
cleanupIntegrationRobots(t)
defer cleanupIntegrationRobots(t)
// Common event types to test
eventTypes := []struct {
name string
eventType string
data map[string]interface{}
}{
{
name: "lead.created",
eventType: "lead.created",
data: map[string]interface{}{"name": "John", "email": "john@example.com"},
},
{
name: "order.paid",
eventType: "order.paid",
data: map[string]interface{}{"order_id": "ORD-001", "amount": 100.0},
},
{
name: "customer.signup",
eventType: "customer.signup",
data: map[string]interface{}{"customer_id": "cust_001", "plan": "premium"},
},
{
name: "ticket.created",
eventType: "ticket.created",
data: map[string]interface{}{"ticket_id": "TKT-001", "priority": "high"},
},
{
name: "inventory.low",
eventType: "inventory.low",
data: map[string]interface{}{"product_id": "PRD-001", "quantity": 5},
},
}
for _, tc := range eventTypes {
t.Run(tc.name, func(t *testing.T) {
memberID := "robot_integ_event_type_" + tc.name
setupEventTestRobot(t, memberID, "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: memberID,
Source: "webhook",
EventType: tc.eventType,
Data: tc.data,
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err, "Event type %s should succeed", tc.eventType)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
assert.Equal(t, types.ExecPending, result.Status)
})
}
}
// TestIntegrationEventTriggerSources tests different event sources
func TestIntegrationEventTriggerSources(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
testutils.Prepare(t)
defer testutils.Clean(t)
cleanupIntegrationRobots(t)
defer cleanupIntegrationRobots(t)
sources := []string{"webhook", "database", "api", "scheduler", "internal"}
for _, source := range sources {
t.Run("source_"+source, func(t *testing.T) {
memberID := "robot_integ_event_src_" + source
setupEventTestRobot(t, memberID, "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: memberID,
Source: source,
EventType: "test.event",
Data: map[string]interface{}{"source": source},
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err, "Source %s should succeed", source)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
})
}
}
// TestIntegrationEventTriggerWithEmptyData tests event with empty or nil data
func TestIntegrationEventTriggerWithEmptyData(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
testutils.Prepare(t)
defer testutils.Clean(t)
cleanupIntegrationRobots(t)
defer cleanupIntegrationRobots(t)
t.Run("nil data", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_nildata", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_nildata",
Source: "webhook",
EventType: "ping",
Data: nil, // Nil data
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
})
t.Run("empty data map", func(t *testing.T) {
setupEventTestRobot(t, "robot_integ_event_emptydata", "team_integ_event")
m := manager.New()
err := m.Start()
require.NoError(t, err)
defer m.Stop()
ctx := types.NewContext(context.Background(), nil)
req := &types.EventRequest{
MemberID: "robot_integ_event_emptydata",
Source: "webhook",
EventType: "heartbeat",
Data: map[string]interface{}{}, // Empty map
}
result, err := m.HandleEvent(ctx, req)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.NotEmpty(t, result.ExecutionID)
})
}
// ==================== Test Data Setup Helpers ====================
// setupEventTestRobot creates a robot with event trigger enabled
func setupEventTestRobot(t *testing.T, memberID, teamID string) {
m := model.Select("__yao.member")
tableName := m.MetaData.Table.Name
qb := capsule.Query()
robotConfig := map[string]interface{}{
"identity": map[string]interface{}{
"role": "Event Test Robot",
"duties": []string{"Handle event triggers"},
},
"quota": map[string]interface{}{
"max": 5,
"queue": 20,
"priority": 5,
},
"triggers": map[string]interface{}{
"clock": map[string]interface{}{"enabled": false},
"intervene": map[string]interface{}{"enabled": false},
"event": map[string]interface{}{"enabled": true},
},
"events": []map[string]interface{}{
{
"type": "webhook",
"source": "/webhook/events",
},
{
"type": "database",
"source": "orders",
},
},
}
configJSON, _ := json.Marshal(robotConfig)
err := qb.Table(tableName).Insert([]map[string]interface{}{
{
"member_id": memberID,
"team_id": teamID,
"member_type": "robot",
"display_name": "Event Test Robot " + memberID,
"status": "active",
"role_id": "member",
"autonomous_mode": true,
"robot_status": "idle",
"robot_config": string(configJSON),
},
})
if err != nil {
t.Fatalf("Failed to insert %s: %v", memberID, err)
}
}
// setupEventTestRobotPaused creates a paused robot
func setupEventTestRobotPaused(t *testing.T, memberID, teamID string) {
m := model.Select("__yao.member")
tableName := m.MetaData.Table.Name
qb := capsule.Query()
robotConfig := map[string]interface{}{
"identity": map[string]interface{}{"role": "Paused Event Robot"},
"triggers": map[string]interface{}{
"event": map[string]interface{}{"enabled": true},
},
}
configJSON, _ := json.Marshal(robotConfig)
err := qb.Table(tableName).Insert([]map[string]interface{}{
{
"member_id": memberID,
"team_id": teamID,
"member_type": "robot",
"display_name": "Paused Event Robot " + memberID,
"status": "active",
"role_id": "member",
"autonomous_mode": true,
"robot_status": "paused", // Paused
"robot_config": string(configJSON),
},
})
if err != nil {
t.Fatalf("Failed to insert %s: %v", memberID, err)
}
}
// setupEventTestRobotDisabled creates a robot with event trigger disabled
func setupEventTestRobotDisabled(t *testing.T, memberID, teamID string) {
m := model.Select("__yao.member")
tableName := m.MetaData.Table.Name
qb := capsule.Query()
robotConfig := map[string]interface{}{
"identity": map[string]interface{}{"role": "Event Disabled Robot"},
"triggers": map[string]interface{}{
"event": map[string]interface{}{"enabled": false}, // Disabled
},
}
configJSON, _ := json.Marshal(robotConfig)
err := qb.Table(tableName).Insert([]map[string]interface{}{
{
"member_id": memberID,
"team_id": teamID,
"member_type": "robot",
"display_name": "Event Disabled Robot " + memberID,
"status": "active",
"role_id": "member",
"autonomous_mode": true,
"robot_status": "idle",
"robot_config": string(configJSON),
},
})
if err != nil {
t.Fatalf("Failed to insert %s: %v", memberID, err)
}
}