package router import ( "bytes" "fmt" "io" "net" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" "github.com/heicode/manager/common" "github.com/heicode/manager/model" "github.com/stretchr/testify/require" "gorm.io/gorm" ) func setupAgentRuntimeHTTPSmokeDB(t *testing.T) *gorm.DB { t.Helper() common.UsingSQLite = true common.UsingMySQL = false common.UsingPostgreSQL = false common.RedisEnabled = false dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_")) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) require.NoError(t, err) model.DB = db model.LOG_DB = db require.NoError(t, db.AutoMigrate( &model.User{}, &model.AgentDeployment{}, &model.AgentAuditEvent{}, )) adminToken := "runtime-smoke-admin-token" require.NoError(t, db.Create(&model.User{ Id: 101, Username: "runtime-smoke-admin", Password: "not-used", Role: common.RoleAdminUser, Status: common.UserStatusEnabled, DisplayName: "Runtime Smoke Admin", AccessToken: &adminToken, Group: "default", }).Error) t.Cleanup(func() { if model.DB == db { model.DB = nil } if model.LOG_DB == db { model.LOG_DB = nil } sqlDB, err := db.DB() if err == nil { _ = sqlDB.Close() } }) return db } func startAgentRuntimeManagerSmokeServer(t *testing.T) string { t.Helper() gin.SetMode(gin.TestMode) engine := gin.New() engine.Use(sessions.Sessions("runtime-smoke", cookie.NewStore([]byte("runtime-smoke-secret")))) SetApiRouter(engine) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) server := &http.Server{Handler: engine} go func() { _ = server.Serve(listener) }() t.Cleanup(func() { _ = server.Close() }) return "http://" + listener.Addr().String() } func agentRuntimeAdminRequest(t *testing.T, method string, url string, body string) *http.Response { t.Helper() req, err := http.NewRequest(method, url, bytes.NewBufferString(body)) require.NoError(t, err) req.Header.Set("Authorization", "Bearer runtime-smoke-admin-token") req.Header.Set("New-Api-User", "101") if body != "" { req.Header.Set("Content-Type", "application/json") } resp, err := (&http.Client{Timeout: 5 * time.Second}).Do(req) require.NoError(t, err) return resp } func readAgentRuntimeSmokeBody(t *testing.T, resp *http.Response) string { t.Helper() defer resp.Body.Close() body, err := io.ReadAll(resp.Body) require.NoError(t, err) return string(body) } func TestAgentRuntimeRealHTTPHealthAndShadowCreateSmoke(t *testing.T) { db := setupAgentRuntimeHTTPSmokeDB(t) runtimeCreateCalled := false runtime := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodGet && r.URL.Path == "/api/agent/health": require.Equal(t, "Bearer runtime-service-token", r.Header.Get("Authorization")) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"status":"healthy"}}`)) case r.Method == http.MethodPost && r.URL.Path == "/api/agent/sub-agile/deployments": runtimeCreateCalled = true require.Equal(t, "Bearer runtime-service-token", r.Header.Get("Authorization")) require.Equal(t, "corr-http-smoke", r.Header.Get("X-Correlation-ID")) body, err := io.ReadAll(r.Body) require.NoError(t, err) require.Contains(t, string(body), `"sub_mode":"agile"`) require.Contains(t, string(body), `"agile_context"`) require.Contains(t, string(body), `"checkpoint":"ready_for_test"`) require.Contains(t, string(body), `"manager_deployment_id"`) require.Contains(t, string(body), `"signing_secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/callback-signing"`) require.Contains(t, string(body), `"subscribed_events"`) var runtimeBody map[string]any require.NoError(t, common.Unmarshal(body, &runtimeBody)) agents := runtimeBody["agents"].([]any) require.Equal(t, "builder", agents[0].(map[string]any)["role"]) grants := runtimeBody["resource_grants"].([]any) require.NotEmpty(t, grants) require.NotEmpty(t, grants[0].(map[string]any)["ref"]) metadata := runtimeBody["metadata"].(map[string]any) require.NotEmpty(t, metadata["manager_deployment_id"]) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-http-dep","swarm_id":"runtime-http-swarm","runtime_status":"accepted"}}`)) default: http.NotFound(w, r) } })) defer runtime.Close() t.Setenv("AGENT_RUNTIME_ENABLED", "true") t.Setenv("AGENT_RUNTIME_ASYNC", "false") t.Setenv("AGENT_RUNTIME_BASE_URL", runtime.URL) t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "runtime-service-token") t.Setenv("AGENT_RUNTIME_CALLBACK_SIGNING_SECRET_REF", "azkv://heicode-kv.vault.azure.net/secrets/callback-signing") managerURL := startAgentRuntimeManagerSmokeServer(t) healthResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/runtime/health", "") healthBody := readAgentRuntimeSmokeBody(t, healthResp) require.Equal(t, http.StatusOK, healthResp.StatusCode) require.Contains(t, healthBody, `"success":true`) require.Contains(t, healthBody, `"status":"healthy"`) schemaResp, err := http.Get(managerURL + "/api/agent/callbacks/runtime-events/schema") require.NoError(t, err) schemaBody := readAgentRuntimeSmokeBody(t, schemaResp) require.Equal(t, http.StatusOK, schemaResp.StatusCode) require.Contains(t, schemaBody, `"event_type":"task.claimed"`) require.Contains(t, schemaBody, `"event_type":"approval.requested"`) var schemaEnvelope map[string]any require.NoError(t, common.Unmarshal([]byte(schemaBody), &schemaEnvelope)) schemaData := schemaEnvelope["data"].(map[string]any) schemaSecurity := schemaData["security"].(map[string]any) require.Equal(t, "azkv:///secrets/", schemaSecurity["secret_ref_scheme"]) createBody := `{ "orchestration_plan":{ "intent_id":"intent-http-smoke", "template_hint":"heicode-task", "objective":"real HTTP runtime smoke", "sub_mode":"agile", "risk_level":"low", "budget":{"max_tokens":10000,"max_cost_usd":1,"max_duration_sec":600}, "user_context":{"user_id":"101","channel_id":"default"}, "billing_context":{"provider":"newapi","newapi_user_ref":"newapi-http-smoke"}, "agile_context":{"iteration":"2026-05-27~2026-05-28","stage":"development","checkpoint":"ready_for_test","acceptance_criteria":["接口返回成功"],"next_action":"submit_test_result","requires_user_approval":false}, "agent_runtime":{"platform":"agent","agents":[{"role":"builder","model_ref":"model-http-smoke","instance_count":1}]}, "agents":[{"role_template":"builder","goal":"smoke","default_model_id":"model-http-smoke","resource_grants":[{"grant_id":"grant-http-git","resource_id":"git-http","resource_type":"git","user_id":"101","binding_scope":"https://example.invalid/heicode/smoke.git#main","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["repo:read"],"metadata":{"repo_url":"https://example.invalid/heicode/smoke.git"},"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/http-smoke-git","status":"active"}]}], "constraints":{"allowed_model_ids":["model-http-smoke"]}, "metadata":{"correlation_id":"corr-http-smoke"} } }` createResp := agentRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/agent/deployments", createBody) responseBody := readAgentRuntimeSmokeBody(t, createResp) require.Equal(t, http.StatusOK, createResp.StatusCode) require.Contains(t, responseBody, `"success":true`) require.Contains(t, responseBody, `"runtime_deployment_id":"runtime-http-dep"`) require.Contains(t, responseBody, `"runtime_swarm_id":"runtime-http-swarm"`) require.True(t, runtimeCreateCalled) var stored model.AgentDeployment require.NoError(t, db.Where("runtime_swarm_id = ?", "runtime-http-swarm").First(&stored).Error) require.Equal(t, "accepted", stored.RuntimeState) logsResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/deployments/"+stored.DeploymentID+"/logs", "") logsBody := readAgentRuntimeSmokeBody(t, logsResp) require.Equal(t, http.StatusOK, logsResp.StatusCode) require.Contains(t, logsBody, `"data_source":"manager_control_plane"`) require.Contains(t, logsBody, `"runtime_source":"not_connected"`) metricsResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/deployments/"+stored.DeploymentID+"/metrics", "") metricsBody := readAgentRuntimeSmokeBody(t, metricsResp) require.Equal(t, http.StatusOK, metricsResp.StatusCode) require.Contains(t, metricsBody, `"data_source":"manager_control_plane"`) require.Contains(t, metricsBody, `"platform_estimated":true`) } func TestSwarmRuntimeHTTPCreateUsesSwarmConfigAndPayload(t *testing.T) { db := setupAgentRuntimeHTTPSmokeDB(t) swarmCreateCalled := false swarmStopCalled := false swarm := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodGet && r.URL.Path == "/api/agent/health": require.Equal(t, "Bearer swarm-runtime-token", r.Header.Get("Authorization")) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"status":"healthy","service":"heicode-swarm-runtime"}}`)) case r.Method == http.MethodPost && r.URL.Path == "/api/swarms": swarmCreateCalled = true require.Equal(t, "Bearer swarm-runtime-token", r.Header.Get("Authorization")) require.Equal(t, "corr-swarm-smoke", r.Header.Get("X-Correlation-ID")) body, err := io.ReadAll(r.Body) require.NoError(t, err) require.Contains(t, string(body), `"runtime_mode":"swarm"`) require.Contains(t, string(body), `"manager_deployment_id"`) var runtimeBody map[string]any require.NoError(t, common.Unmarshal(body, &runtimeBody)) plan := runtimeBody["orchestration_plan"].(map[string]any) agents := plan["agents"].([]any) require.Len(t, agents, 3) require.Equal(t, "planner", agents[0].(map[string]any)["role"]) require.Equal(t, "builder", agents[1].(map[string]any)["role"]) require.Equal(t, "reviewer", agents[2].(map[string]any)["role"]) require.NotContains(t, agents[0].(map[string]any), "role_template") metadata := runtimeBody["metadata"].(map[string]any) require.Equal(t, "swarm", metadata["runtime_mode"]) require.Equal(t, "api_swarms_adapter", metadata["source"]) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"swarm-runtime-dep","swarm_id":"swarm-runtime-id","status":"running"}}`)) case r.Method == http.MethodPost && r.URL.Path == "/api/swarms/swarm-runtime-id/stop": swarmStopCalled = true require.Equal(t, "Bearer swarm-runtime-token", r.Header.Get("Authorization")) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"swarm-runtime-dep","swarm_id":"swarm-runtime-id","status":"stopped"}}`)) default: http.NotFound(w, r) } })) defer swarm.Close() t.Setenv("AGENT_RUNTIME_ENABLED", "false") t.Setenv("SWARM_RUNTIME_ENABLED", "true") t.Setenv("SWARM_RUNTIME_ASYNC", "false") t.Setenv("SWARM_RUNTIME_BASE_URL", swarm.URL) t.Setenv("SWARM_RUNTIME_SERVICE_TOKEN", "swarm-runtime-token") managerURL := startAgentRuntimeManagerSmokeServer(t) healthResp := agentRuntimeAdminRequest(t, http.MethodGet, managerURL+"/api/agent/runtime/health?mode=swarm", "") healthBody := readAgentRuntimeSmokeBody(t, healthResp) require.Equal(t, http.StatusOK, healthResp.StatusCode) require.Contains(t, healthBody, `"mode":"swarm"`) require.Contains(t, healthBody, `"status":"healthy"`) require.Contains(t, healthBody, `"heicode-swarm-runtime"`) createBody := `{ "orchestration_plan":{ "intent_id":"intent-swarm-smoke", "template_hint":"heicode-swarm", "objective":"swarm runtime smoke", "sub_mode":"agile", "risk_level":"low", "budget":{"max_tokens":10000,"max_cost_usd":1,"max_duration_sec":600}, "user_context":{"user_id":"101","channel_id":"default"}, "billing_context":{"provider":"newapi","newapi_user_ref":"newapi-swarm-smoke"}, "agile_context":{"iteration":"2026-05-29","stage":"testing","checkpoint":"runtime_accepted","acceptance_criteria":["Runtime creates a swarm run"],"next_action":"submit_test_result","requires_user_approval":false}, "agent_runtime":{"platform":"agent","agents":[{"role":"planner","model_ref":"model-swarm","instance_count":1},{"role":"builder","model_ref":"model-swarm","instance_count":1},{"role":"reviewer","model_ref":"model-swarm","instance_count":1}]}, "agents":[ {"role_template":"planner","goal":"plan the swarm task","default_model_id":"model-swarm","resource_grants":[{"grant_id":"grant-swarm-plan","resource_id":"doc-swarm","resource_type":"project_doc","user_id":"101","binding_scope":"task-swarm-smoke","target_role":"planner","target_agent_ref":"agent-planner-1","permission_scope":["doc:read"],"metadata":{"resource_ref":"task-swarm-smoke"},"status":"active"}]}, {"role_template":"builder","goal":"build the swarm output","default_model_id":"model-swarm","resource_grants":[{"grant_id":"grant-swarm-build","resource_id":"git-swarm","resource_type":"git","user_id":"101","binding_scope":"task-swarm-smoke","target_role":"builder","target_agent_ref":"agent-builder-1","permission_scope":["repo:read"],"metadata":{"repo_url":"https://example.invalid/heicode/swarm.git"},"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/swarm-git","status":"active"}]}, {"role_template":"reviewer","goal":"verify the swarm output","default_model_id":"model-swarm","resource_grants":[{"grant_id":"grant-swarm-review","resource_id":"doc-swarm","resource_type":"project_doc","user_id":"101","binding_scope":"task-swarm-smoke","target_role":"reviewer","target_agent_ref":"agent-reviewer-1","permission_scope":["doc:read"],"metadata":{"resource_ref":"task-swarm-smoke"},"status":"active"}]} ], "constraints":{"allowed_model_ids":["model-swarm"]}, "metadata":{"correlation_id":"corr-swarm-smoke"} } }` createResp := agentRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/swarms", createBody) responseBody := readAgentRuntimeSmokeBody(t, createResp) require.Equal(t, http.StatusOK, createResp.StatusCode) require.Contains(t, responseBody, `"success":true`) require.Contains(t, responseBody, `"runtime_deployment_id":"swarm-runtime-dep"`) require.Contains(t, responseBody, `"runtime_swarm_id":"swarm-runtime-id"`) require.True(t, swarmCreateCalled) var stored model.AgentDeployment require.NoError(t, db.Where("runtime_swarm_id = ?", "swarm-runtime-id").First(&stored).Error) require.Equal(t, "agile", stored.SubMode) require.Contains(t, stored.PlanJSON, `"runtime_mode":"swarm"`) stopResp := agentRuntimeAdminRequest(t, http.MethodPost, managerURL+"/api/agent/user/deployments/"+stored.DeploymentID+"/stop", `{"reason":"smoke done"}`) stopBody := readAgentRuntimeSmokeBody(t, stopResp) require.Equal(t, http.StatusOK, stopResp.StatusCode) require.Contains(t, stopBody, `"success":true`) require.Contains(t, stopBody, `"runtime_state":"stopped"`) require.True(t, swarmStopCalled) }