package service import ( "context" "net/http" "net/http/httptest" "testing" "time" "ai-agent-scaffold-go/internal/model" "github.com/stretchr/testify/assert" ) // ============================================================ // 辅助函数 // ============================================================ func newTestTable() model.AiAgentConfigTable { return model.AiAgentConfigTable{ AppName: "test-app", Agent: model.AgentSummary{AgentID: "10001", AgentName: "test", AgentDesc: "test agent"}, Module: model.AgentModule{ AiAPI: model.AiAPIConfig{BaseURL: "http://localhost:8080", APIKey: "test-key", CompletionsPath: "v1/chat/completions"}, ChatModel: model.ChatModelConfig{Model: "gpt-4"}, Agents: []model.AgentConfig{{Name: "bot", Instruction: "hello"}}, Runner: model.RunnerConfig{AgentName: "bot"}, }, } } // fakeLLMServer 返回固定回复的模拟 LLM 服务 func fakeLLMServer(response string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(response)) })) } // ============================================================ // assembleOne 测试 // ============================================================ func TestAssembleOne_SingleAgent_Success(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL reg, err := assembleOne(context.Background(), table, 5*time.Second) assert.NoError(t, err) assert.Equal(t, "10001", reg.AgentID) assert.Equal(t, "test", reg.AgentName) assert.Equal(t, "test-app", reg.AppName) assert.NotNil(t, reg.Runner) } func TestAssembleOne_WithWorkflow_Success(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL table.Module.Agents = []model.AgentConfig{ {Name: "agent1", Instruction: "inst1"}, {Name: "agent2", Instruction: "inst2"}, } table.Module.AgentWorkflows = []model.AgentWorkflowConfig{ { Type: model.WorkflowTypeSequential, Name: "seq", SubAgents: []string{"agent1", "agent2"}, }, } table.Module.Runner.AgentName = "seq" reg, err := assembleOne(context.Background(), table, 5*time.Second) assert.NoError(t, err) assert.NotNil(t, reg.Runner) } func TestAssembleOne_ParallelWorkflow_Success(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL table.Module.Agents = []model.AgentConfig{ {Name: "a1", Instruction: "inst1"}, {Name: "a2", Instruction: "inst2"}, } table.Module.AgentWorkflows = []model.AgentWorkflowConfig{ { Type: model.WorkflowTypeParallel, Name: "par", SubAgents: []string{"a1", "a2"}, }, } table.Module.Runner.AgentName = "par" reg, err := assembleOne(context.Background(), table, 5*time.Second) assert.NoError(t, err) assert.NotNil(t, reg.Runner) } func TestAssembleOne_LoopWorkflow_Success(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL table.Module.Agents = []model.AgentConfig{ {Name: "a1", Instruction: "inst"}, } table.Module.AgentWorkflows = []model.AgentWorkflowConfig{ { Type: model.WorkflowTypeLoop, Name: "loop", SubAgents: []string{"a1"}, MaxIterations: 3, }, } table.Module.Runner.AgentName = "loop" reg, err := assembleOne(context.Background(), table, 5*time.Second) assert.NoError(t, err) assert.NotNil(t, reg.Runner) } func TestAssembleOne_UnknownSubAgent_ReturnsError(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL table.Module.AgentWorkflows = []model.AgentWorkflowConfig{ { Type: model.WorkflowTypeSequential, Name: "seq", SubAgents: []string{"nonexistent"}, }, } table.Module.Runner.AgentName = "seq" _, err := assembleOne(context.Background(), table, 5*time.Second) assert.Error(t, err) assert.Contains(t, err.Error(), "unknown agent") } func TestAssembleOne_UnknownWorkflowType_ReturnsError(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL table.Module.AgentWorkflows = []model.AgentWorkflowConfig{ { Type: "bad-type", Name: "wf", SubAgents: []string{"bot"}, }, } table.Module.Runner.AgentName = "wf" _, err := assembleOne(context.Background(), table, 5*time.Second) assert.Error(t, err) assert.Contains(t, err.Error(), "unknown workflow type") } func TestAssembleOne_EntryAgentNotFound_ReturnsError(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() table := newTestTable() table.Module.AiAPI.BaseURL = srv.URL table.Module.Runner.AgentName = "nonexistent" _, err := assembleOne(context.Background(), table, 5*time.Second) assert.Error(t, err) assert.Contains(t, err.Error(), "not found") } // ============================================================ // AssembleAll 测试 // ============================================================ func TestAssembleAll_MultipleTables_Success(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() tables := map[string]model.AiAgentConfigTable{ "t1": func() model.AiAgentConfigTable { t := newTestTable() t.Module.AiAPI.BaseURL = srv.URL t.AppName = "app1" t.Agent.AgentID = "1" return t }(), "t2": func() model.AiAgentConfigTable { t := newTestTable() t.Module.AiAPI.BaseURL = srv.URL t.AppName = "app2" t.Agent.AgentID = "2" return t }(), } agents, err := AssembleAll(context.Background(), tables, 5*time.Second) assert.NoError(t, err) assert.Len(t, agents, 2) } func TestAssembleAll_OneFails_ReturnsError(t *testing.T) { srv := fakeLLMServer(`{"choices":[{"message":{"content":"ok"}}]}`) defer srv.Close() tables := map[string]model.AiAgentConfigTable{ "t1": func() model.AiAgentConfigTable { t := newTestTable() t.Module.AiAPI.BaseURL = srv.URL return t }(), "t2": func() model.AiAgentConfigTable { t := newTestTable() t.Module.AiAPI.BaseURL = srv.URL t.Module.Runner.AgentName = "nonexistent" return t }(), } _, err := AssembleAll(context.Background(), tables, 5*time.Second) assert.Error(t, err) } // ============================================================ // LoadAndAssemble 测试 // ============================================================ func TestLoadAndAssemble_NoPaths_ReturnsError(t *testing.T) { _, err := LoadAndAssemble(context.Background(), []string{}, 5*time.Second) assert.Error(t, err) assert.Contains(t, err.Error(), "no agent tables loaded") } func TestLoadAndAssemble_EmptyPaths_ReturnsError(t *testing.T) { _, err := LoadAndAssemble(context.Background(), []string{"", " "}, 5*time.Second) assert.Error(t, err) }