Files
heicode-mananger/heicode/controller/agent_control_plane_test.go
T
chenchenandClaude Opus 4.8 0fe1d20d67 feat(agent): unify agnet→agent and implement client/runtime unification spec v0.1 core
按桌面客户端统一方案 v0.1 + agent_management Sub Mode Runtime 对接,强制全量统一,不留兼容。

命名统一(强制,无兼容):
- 全仓 agnet/Agnet/AGNET → agent/Agent/AGENT:后端 Go(路由 /api/agent/*、env AGENT_*、
  结构体/函数、19 个文件改名)、前端(agent-console/agent-hub、/api/agent 调用、i18n)、
  DB(表 agent_*、列 agent_id)、compose/.env、文档、脚本。
- DB 加幂等迁移 renameAgnetTablesToAgent():启动时 rename 老 agnet_* 表/列,保住生产数据。

统一方案核心(10 项):
- callback 统一 /api/agent/callbacks/runtime-events(路由/广播URL/函数名)。
- artifact 兜底判定改用 Runtime 权威信号 metadata.synthesized(§7.2)+ 结构化 artifact_type。
- Manager→Runtime 路径对齐 /api/agent/sub-agile/deployments(§2.2),{deployment_id} 回退 swarm_id。
- 状态裁决 display_status:Manager 唯一裁判,completed 无有效产物→needs_codegen/
  completed_without_deliverable(§10.6),接入 detail/timeline/workflow。
- GET /api/heicode/capabilities 能力发现(§6)。
- 模型策略 per_role(role_models)+ 收集 allowed_model_ids(§9)。
- resource_binding_id→secret_ref 服务端解析,客户端不再 inline secret_ref(§17.6)。
- 客户端统一路由层 /api/heicode/sub-agile|swarm/*(task≡deployment,复用控制面)+ workflow 投影。
- 日志分层 user_logs/debug_logs(§13)。

验证:go build ./... + go test(controller/router/model/middleware)全绿;前端 tsc -b + rsbuild build 通过。
待部署:VM .env 的 AGNET_*→AGENT_*;启动迁移自动 rename 表;其他三仓库需同步切到 /api/agent。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 23:45:10 +08:00

1973 lines
80 KiB
Go

package controller
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"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"
)
type agentCreateTestEnvelope struct {
Success bool `json:"success"`
Message string `json:"message"`
Error agentTestError `json:"error"`
}
type agentTestError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func resetAgentControlPlaneState(t *testing.T) {
t.Helper()
agentMu.Lock()
defer agentMu.Unlock()
agentDeployments = make(map[string]agentDeploymentRecord)
agentSnapshots = make(map[string][]agentSKSnapshot)
// Audit rows live in DB now; tests use a fresh SQLite per case
// (setupTokenControllerTestDB style) so there is no global map
// to flush here.
}
func setupAgentControlPlaneTestDB(t *testing.T) *gorm.DB {
t.Helper()
gin.SetMode(gin.TestMode)
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.ResourceBinding{},
&model.ResourceGrant{},
&model.AgentAuditEvent{},
&model.AgentDeployment{},
&model.AgentCallbackEvent{},
&model.AgentArtifact{},
&model.AgentSKSnapshot{},
&model.AgentApprovalRequest{},
))
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 baseAgentResourceGrantPlan() agentOrchestrationPlan {
return agentOrchestrationPlan{
IntentID: "intent-resource-grant",
TemplateHint: "manager-resource-binding",
Objective: "Grant bounded resources to a child Agent role",
RiskLevel: agentRiskLow,
Budget: agentBudget{
MaxTokens: 10000,
MaxCostUSD: 10,
MaxDurationSec: 3600,
},
UserContext: agentUserContext{
UserID: "user-p1",
Email: "builder@example.invalid",
Role: "user",
ChannelID: "channel-p1",
},
BillingContext: agentBillingContext{
Provider: "newapi",
NewAPIUserRef: "newapi-user-p1",
NewAPIGroup: "development",
QuotaRef: "newapi-quota-ref-p1",
},
AgentRuntime: agentAgentRuntime{
Platform: "agent",
Agents: []agentRuntimeAgent{
{
Role: "builder",
ModelRef: "agent-model-profile-builder",
InstanceCount: 1,
},
},
},
Agents: []agentAgentPlan{
{
RoleTemplate: "builder",
Goal: "Use granted resources without receiving plaintext credentials",
DefaultModelID: "gpt-resource-test",
ResourceGrants: []agentResourceGrant{
{
GrantID: "grant-git-builder",
ResourceID: "res-git-main",
ResourceType: agentResourceGit,
UserID: "user-p1",
BindingScope: "https://example.invalid/acme/project.git#main",
TargetRole: "builder",
TargetAgentRef: "agent-builder-1",
PermissionScope: []string{"repo:read", "repo:write:feature-branches"},
Constraints: map[string]string{
"allowed_paths": "src/**,docs/**",
"ref": "main",
},
Metadata: map[string]string{
"provider": "gitee",
"repo_url": "https://example.invalid/acme/project.git",
},
Status: agentGrantStatusActive,
SecretRef: "azkv://heicode-kv.vault.azure.net/secrets/user-p1-project-main-res-git-main",
Audit: map[string]string{
"created_by": "manager-test",
},
},
{
GrantID: "grant-doc-builder",
ResourceID: "res-doc-plan",
ResourceType: agentResourceProjectDoc,
UserID: "user-p1",
BindingScope: "docs/heicode.md",
TargetRole: "builder",
TargetAgentRef: "agent-builder-1",
PermissionScope: []string{"doc:read"},
Metadata: map[string]string{
"doc_ref": "docs/heicode.md",
},
Status: agentGrantStatusActive,
},
},
},
},
Constraints: agentConstraints{AllowedModelIDs: []string{"gpt-resource-test"}},
Metadata: agentMetadata{
CorrelationID: "corr-p1-resource-grant",
},
}
}
func postAgentCreateDeployment(t *testing.T, plan agentOrchestrationPlan) (*httptest.ResponseRecorder, agentCreateTestEnvelope) {
t.Helper()
gin.SetMode(gin.TestMode)
body, err := common.Marshal(agentDeploymentRequest{Plan: plan})
require.NoError(t, err)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/deployments", strings.NewReader(string(body)))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentCreateDeployment(ctx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &envelope))
return recorder, envelope
}
func postAgentCreateUserDeployment(t *testing.T, userID int, plan agentOrchestrationPlan) (*httptest.ResponseRecorder, agentCreateTestEnvelope) {
t.Helper()
gin.SetMode(gin.TestMode)
body, err := common.Marshal(agentDeploymentRequest{Plan: plan})
require.NoError(t, err)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", userID)
ctx.Set("group", "development")
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/deployments", strings.NewReader(string(body)))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentCreateUserDeployment(ctx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &envelope))
return recorder, envelope
}
func TestAgentDeploymentSurvivesInProcessStateReset(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
recorder, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
require.NotEmpty(t, deploymentID)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, "user-p1", stored.UserID)
require.Equal(t, "channel-p1", stored.ChannelID)
require.Contains(t, stored.PayloadJSON, `"orchestration_plan"`)
require.Contains(t, stored.PermissionManifestJSON, `"grant-git-builder"`)
require.Contains(t, stored.PermissionManifestJSON, `"secret_ref"`)
require.NotContains(t, strings.ToLower(stored.PayloadJSON), "password")
require.NotContains(t, strings.ToLower(stored.PayloadJSON), "private_key")
require.NotContains(t, strings.ToLower(stored.PermissionManifestJSON), "access_token")
resetAgentControlPlaneState(t)
getRecorder := httptest.NewRecorder()
getCtx, _ := gin.CreateTestContext(getRecorder)
getCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
getCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID, nil)
AgentGetDeployment(getCtx)
var getBody map[string]any
require.NoError(t, common.Unmarshal(getRecorder.Body.Bytes(), &getBody))
require.Equal(t, true, getBody["success"])
data := getBody["data"].(map[string]any)
require.Equal(t, deploymentID, data["deployment_id"])
require.Equal(t, "accepted", data["status"])
listRecorder := httptest.NewRecorder()
listCtx, _ := gin.CreateTestContext(listRecorder)
listCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments?user_id=user-p1", nil)
AgentListDeployments(listCtx)
var listBody map[string]any
require.NoError(t, common.Unmarshal(listRecorder.Body.Bytes(), &listBody))
require.Equal(t, true, listBody["success"])
listData := listBody["data"].(map[string]any)
require.Equal(t, float64(1), listData["total"])
}
func TestAgentDeploymentPersistsSubMode(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.SubMode = "waterfall"
recorder, envelope := postAgentCreateDeployment(t, plan)
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
require.Contains(t, recorder.Body.String(), `"sub_mode":"waterfall"`)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, "waterfall", stored.SubMode)
resetAgentControlPlaneState(t)
getRecorder := httptest.NewRecorder()
getCtx, _ := gin.CreateTestContext(getRecorder)
getCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
getCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID, nil)
AgentGetDeployment(getCtx)
require.Equal(t, http.StatusOK, getRecorder.Code)
require.Contains(t, getRecorder.Body.String(), `"sub_mode":"waterfall"`)
}
func TestAgentDeploymentDefaultsSubModeToAgile(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.SubMode = ""
recorder, envelope := postAgentCreateDeployment(t, plan)
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
require.Contains(t, recorder.Body.String(), `"sub_mode":"agile"`)
}
func TestAgentRuntimeHealthNotConfigured(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_RUNTIME_BASE_URL", "")
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/runtime/health", nil)
AgentRuntimeHealth(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"status":"not_configured"`)
require.Contains(t, recorder.Body.String(), `"configured":false`)
}
func TestAgentRuntimeHealthProxy(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/api/agent/health", r.URL.Path)
require.Equal(t, "Bearer service-token", r.Header.Get("Authorization"))
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true,"data":{"status":"healthy"}}`))
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "service-token")
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/runtime/health", nil)
AgentRuntimeHealth(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"status":"healthy"`)
require.Contains(t, recorder.Body.String(), `"http_status":200`)
require.Contains(t, recorder.Body.String(), `"remote"`)
}
func TestAgentRuntimeShadowCreateStoresRuntimeMapping(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/api/agent/sub-agile/deployments", r.URL.Path)
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "Bearer service-token", r.Header.Get("Authorization"))
require.NotEmpty(t, 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), `"manager_deployment_id"`)
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"])
metadata := runtimeBody["metadata"].(map[string]any)
require.NotEmpty(t, metadata["manager_deployment_id"])
grants := runtimeBody["resource_grants"].([]any)
require.NotEmpty(t, grants)
require.NotEmpty(t, grants[0].(map[string]any)["ref"])
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-dep-1","swarm_id":"swarm-1","runtime_status":"accepted"}}`))
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "service-token")
plan := baseAgentResourceGrantPlan()
plan.SubMode = "agile"
recorder, envelope := postAgentCreateDeployment(t, plan)
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
require.Contains(t, recorder.Body.String(), `"runtime_deployment_id":"runtime-dep-1"`)
require.Contains(t, recorder.Body.String(), `"runtime_swarm_id":"swarm-1"`)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, "runtime-dep-1", stored.RuntimeDeploymentID)
require.Equal(t, "swarm-1", stored.RuntimeSwarmID)
require.Equal(t, "accepted", stored.RuntimeState)
}
func TestAgentRuntimeDiagnosticsWarnsOnCompletedRuntimeWithFailedAgents(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "Bearer service-token", r.Header.Get("Authorization"))
switch {
case r.Method == http.MethodPost && r.URL.Path == "/api/agent/sub-agile/deployments":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-dep-1","swarm_id":"swarm-1","status":"running"}}`))
case r.Method == http.MethodGet && r.URL.Path == "/api/swarms/swarm-1/status":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{
"deployment_id":"swarm-1",
"swarm_id":"swarm-1",
"status":"completed",
"phase":"development",
"agents":[{"agent_id":"agi_backend_1","role":"backend","status":"failed","output":"Cannot connect to host agent svc"}],
"artifacts":[{"artifact_id":"art_summary","artifact_type":"document","title":"Runtime execution summary","summary":"Runtime completed without per-agent artifacts; review swarm logs for details.","uri":"runtime://swarm-1/artifacts/summary"}],
"metrics":{"tokens_used":0}
}`))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "service-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.ResourceGrants {
plan.ResourceGrants[idx].UserID = "7"
}
for agentIdx := range plan.Agents {
for grantIdx := range plan.Agents[agentIdx].ResourceGrants {
plan.Agents[agentIdx].ResourceGrants[grantIdx].UserID = "7"
}
}
createRecorder, envelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, envelope.Success, createRecorder.Body.String())
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", 7)
ctx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/runtime-diagnostics", nil)
AgentGetUserDeploymentRuntimeDiagnostics(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"data_source":"runtime_status"`)
require.Contains(t, recorder.Body.String(), `"runtime_agent_failed"`)
require.Contains(t, recorder.Body.String(), `"runtime_completed_with_failed_agents"`)
require.Contains(t, recorder.Body.String(), `"runtime_summary_artifact_only"`)
require.Contains(t, recorder.Body.String(), `"runtime_zero_model_usage"`)
}
func TestAgentArtifactContentProxiesRuntimeContent(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "Bearer service-token", r.Header.Get("Authorization"))
switch {
case r.Method == http.MethodPost && r.URL.Path == "/api/agent/sub-agile/deployments":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-dep-1","swarm_id":"swarm-1","status":"running"}}`))
case r.Method == http.MethodGet && r.URL.Path == "/api/swarms/swarm-1/artifacts/art-1/content":
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Disposition", `attachment; filename="art-1.txt"`)
_, _ = w.Write([]byte("artifact body"))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "service-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.ResourceGrants {
plan.ResourceGrants[idx].UserID = "7"
}
for agentIdx := range plan.Agents {
for grantIdx := range plan.Agents[agentIdx].ResourceGrants {
plan.Agents[agentIdx].ResourceGrants[grantIdx].UserID = "7"
}
}
createRecorder, envelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, envelope.Success, createRecorder.Body.String())
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
require.NoError(t, model.UpsertAgentArtifact(&model.AgentArtifact{
ArtifactID: "art-1",
DeploymentID: deploymentID,
UserID: "7",
URI: "runtime://swarm-1/artifacts/art-1",
CreatedAtMs: time.Now().UnixMilli(),
}))
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", 7)
ctx.Params = gin.Params{
{Key: "deployment_id", Value: deploymentID},
{Key: "artifact_id", Value: "art-1"},
}
ctx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/artifacts/art-1/content", nil)
AgentGetUserDeploymentArtifactContent(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Equal(t, "artifact body", recorder.Body.String())
require.Equal(t, "text/plain; charset=utf-8", recorder.Header().Get("Content-Type"))
require.Equal(t, `attachment; filename="art-1.txt"`, recorder.Header().Get("Content-Disposition"))
}
func TestAgentRuntimeShadowCreateFailureDoesNotFailLocalDeployment(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "runtime unavailable", http.StatusBadGateway)
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
recorder, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
require.Contains(t, recorder.Body.String(), `"runtime_state":"runtime_sync_failed"`)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, agentRuntimeStateFailed, stored.RuntimeState)
require.Contains(t, stored.FailureReason, "HTTP 502")
}
func TestAgentRuntimeShadowCreateTreatsSuccessFalseAsFailure(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":false,"error":{"code":"POLICY_REJECTED","message":"sub mode rejected"}}`))
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
recorder, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, agentRuntimeStateFailed, stored.RuntimeState)
require.Contains(t, stored.FailureReason, "sub mode rejected")
}
func TestAgentCreateDeploymentRejectsInvalidSubMode(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.SubMode = "scrum"
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "POLICY_REJECTED", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsNonAzureSecretRef(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.Agents[0].ResourceGrants[0].SecretRef = "vault:heicode/repo-main"
recorder, envelope := postAgentCreateDeployment(t, plan)
require.Equal(t, http.StatusOK, recorder.Code)
require.False(t, envelope.Success)
require.Equal(t, "SECRET_REF_INVALID", envelope.Error.Code)
}
func TestAgentCreateDeploymentAcceptsTopLevelResourceGrants(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.ResourceGrants = plan.Agents[0].ResourceGrants
plan.Agents[0].ResourceGrants = nil
recorder, envelope := postAgentCreateDeployment(t, plan)
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
require.Contains(t, recorder.Body.String(), `"resource_grants":[`)
require.Contains(t, recorder.Body.String(), `"grant_id":"grant-git-builder"`)
require.Contains(t, recorder.Body.String(), `"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/user-p1-project-main-res-git-main"`)
}
func TestAgentUserDeploymentForcesAuthenticatedUserScope(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = ""
plan.UserContext.ChannelID = ""
for agentIdx := range plan.Agents {
for grantIdx := range plan.Agents[agentIdx].ResourceGrants {
plan.Agents[agentIdx].ResourceGrants[grantIdx].UserID = ""
}
}
recorder, envelope := postAgentCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, "7", stored.UserID)
require.Equal(t, "development", stored.ChannelID)
require.Contains(t, stored.PlanJSON, `"user_id":"7"`)
require.NotContains(t, stored.PlanJSON, `"user_id":"user-p1"`)
}
func TestAgentUserDeploymentRejectsMismatchedUserScope(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "8"
_, envelope := postAgentCreateUserDeployment(t, 7, plan)
require.False(t, envelope.Success)
require.Equal(t, "USER_CONTEXT_FORBIDDEN", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentUserDeploymentListIsScopedToAuthenticatedUser(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
user7Plan := baseAgentResourceGrantPlan()
user7Plan.UserContext.UserID = "7"
user7Plan.Metadata.CorrelationID = "corr-user-7"
for idx := range user7Plan.Agents[0].ResourceGrants {
user7Plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
_, user7Envelope := postAgentCreateDeployment(t, user7Plan)
require.True(t, user7Envelope.Success)
user8Plan := baseAgentResourceGrantPlan()
user8Plan.UserContext.UserID = "8"
user8Plan.Metadata.CorrelationID = "corr-user-8"
for idx := range user8Plan.Agents[0].ResourceGrants {
user8Plan.Agents[0].ResourceGrants[idx].UserID = "8"
}
_, user8Envelope := postAgentCreateDeployment(t, user8Plan)
require.True(t, user8Envelope.Success)
listRecorder := httptest.NewRecorder()
listCtx, _ := gin.CreateTestContext(listRecorder)
listCtx.Set("id", 7)
listCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments", nil)
AgentListUserDeployments(listCtx)
require.Equal(t, http.StatusOK, listRecorder.Code)
var listBody map[string]any
require.NoError(t, common.Unmarshal(listRecorder.Body.Bytes(), &listBody))
require.Equal(t, true, listBody["success"])
listData := listBody["data"].(map[string]any)
require.Equal(t, float64(1), listData["total"])
items := listData["items"].([]any)
item := items[0].(map[string]any)
planData := item["orchestration_plan"].(map[string]any)
userContext := planData["user_context"].(map[string]any)
require.Equal(t, "7", userContext["user_id"])
}
func TestAgentTaskDeploymentDraftBuildsSafePlan(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
body := `{
"task":{"id":"task_bridge_1","name":"Ship Manager sub mode","intent":"Need a scoped implementation","status":"running","card":{"goal":"Implement the Manager-side sub task flow"}},
"sub_mode":"waterfall",
"binding_scope":"repo-main",
"role_templates":["backend"],
"default_model_id":"agent-model-builder",
"budget":{"max_tokens":10000,"max_cost_usd":2,"max_duration_sec":900}
}`
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", 7)
ctx.Set("group", "development")
ctx.Params = gin.Params{{Key: "task_id", Value: "task_bridge_1"}}
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/tasks/task_bridge_1/deployment-draft", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentCreateTaskDeploymentDraft(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.NotContains(t, strings.ToLower(recorder.Body.String()), "password")
require.NotContains(t, strings.ToLower(recorder.Body.String()), "access_token")
var env map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &env))
require.Equal(t, true, env["success"])
data := env["data"].(map[string]any)
plan := data["orchestration_plan"].(map[string]any)
require.Equal(t, "task_bridge_1", plan["intent_id"])
require.Equal(t, "waterfall", plan["sub_mode"])
require.Equal(t, "Implement the Manager-side sub task flow", plan["objective"])
userContext := plan["user_context"].(map[string]any)
require.Equal(t, "7", userContext["user_id"])
agents := plan["agents"].([]any)
agent := agents[0].(map[string]any)
grants := agent["resource_grants"].([]any)
grant := grants[0].(map[string]any)
require.Equal(t, "project_doc", grant["resource_type"])
require.Equal(t, "7", grant["user_id"])
}
func TestAgentTaskDeploymentDraftRequiresTaskSnapshot(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", 7)
ctx.Params = gin.Params{{Key: "task_id", Value: "task_missing"}}
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/tasks/task_missing/deployment-draft", strings.NewReader(`{"sub_mode":"agile"}`))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentCreateTaskDeploymentDraft(ctx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &envelope))
require.False(t, envelope.Success)
require.Equal(t, "TASK_NOT_FOUND", envelope.Error.Code)
}
func TestAgentTaskDeploymentDraftRejectsCredentialGrantWithoutSecretRef(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
body := `{
"task":{"id":"task_bridge_git","name":"Use a git repo","intent":"Need repo access","status":"running"},
"sub_mode":"agile",
"binding_scope":"repo-main",
"role_templates":["backend"],
"default_model_id":"agent-model-builder",
"resource_grants":[{
"resource_id":"repo-1",
"resource_type":"git",
"permission_scope":["repo:read"]
}]
}`
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", 7)
ctx.Params = gin.Params{{Key: "task_id", Value: "task_bridge_git"}}
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/tasks/task_bridge_git/deployment-draft", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentCreateTaskDeploymentDraft(ctx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &envelope))
require.False(t, envelope.Success)
require.Equal(t, "RESOURCE_GRANT_SECRET_REF_REQUIRED", envelope.Error.Code)
}
func TestAgentUserDeploymentSimulatedEventsArePersistedAndPrefixed(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, createRecorder.Code)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 7)
simCtx.Set("role", common.RoleAdminUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{"events":["deployment.started","simulation.agent.done"]}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
AgentSimulateUserDeploymentEvents(simCtx)
require.Equal(t, http.StatusOK, simRecorder.Code)
require.Contains(t, simRecorder.Body.String(), `"simulated":true`)
require.Contains(t, simRecorder.Body.String(), `"simulation.deployment.started"`)
require.Contains(t, simRecorder.Body.String(), `"simulation.agent.done"`)
resetAgentControlPlaneState(t)
eventsRecorder := httptest.NewRecorder()
eventsCtx, _ := gin.CreateTestContext(eventsRecorder)
eventsCtx.Set("id", 7)
eventsCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
eventsCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/events", nil)
AgentListUserDeploymentEvents(eventsCtx)
require.Equal(t, http.StatusOK, eventsRecorder.Code)
require.Contains(t, eventsRecorder.Body.String(), `"simulation.deployment.started"`)
require.Contains(t, eventsRecorder.Body.String(), `"simulation.agent.done"`)
}
func TestAgentUserDeploymentSimulationRejectsOtherUsersDeployment(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, createRecorder.Code)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 8)
simCtx.Set("role", common.RoleAdminUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
AgentSimulateUserDeploymentEvents(simCtx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(simRecorder.Body.Bytes(), &envelope))
require.False(t, envelope.Success)
require.Equal(t, "DEPLOYMENT_FORBIDDEN", envelope.Error.Code)
}
func TestAgentUserDeploymentSimulationIsDisabledForRegularUserByDefault(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, createRecorder.Code)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 7)
simCtx.Set("role", common.RoleCommonUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
AgentSimulateUserDeploymentEvents(simCtx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(simRecorder.Body.Bytes(), &envelope))
require.False(t, envelope.Success)
require.Equal(t, "SIMULATION_DISABLED", envelope.Error.Code)
}
func TestAgentUserDeploymentDefaultSimulationCreatesRelatedRecords(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, createRecorder.Code)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
simRecorder := httptest.NewRecorder()
simCtx, _ := gin.CreateTestContext(simRecorder)
simCtx.Set("id", 7)
simCtx.Set("role", common.RoleAdminUser)
simCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
simCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/user/deployments/"+deploymentID+"/simulate-events", strings.NewReader(`{}`))
simCtx.Request.Header.Set("Content-Type", "application/json")
AgentSimulateUserDeploymentEvents(simCtx)
require.Equal(t, http.StatusOK, simRecorder.Code)
require.Contains(t, simRecorder.Body.String(), `"simulated":true`)
artifactRecorder := httptest.NewRecorder()
artifactCtx, _ := gin.CreateTestContext(artifactRecorder)
artifactCtx.Set("id", 7)
artifactCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
artifactCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/artifacts", nil)
AgentListUserDeploymentArtifacts(artifactCtx)
require.Equal(t, http.StatusOK, artifactRecorder.Code)
require.Contains(t, artifactRecorder.Body.String(), `"artifact_type":"test_report"`)
require.Contains(t, artifactRecorder.Body.String(), `"artifact_id":"art-sim-`)
approvalRecorder := httptest.NewRecorder()
approvalCtx, _ := gin.CreateTestContext(approvalRecorder)
approvalCtx.Set("id", 7)
approvalCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/approvals?deployment_id="+deploymentID+"&status=pending", nil)
ListAgentApprovalRequests(approvalCtx)
require.Equal(t, http.StatusOK, approvalRecorder.Code)
require.Contains(t, approvalRecorder.Body.String(), `"approval_id":"sim-appr-`)
require.Contains(t, approvalRecorder.Body.String(), `"status":"pending"`)
require.NotContains(t, approvalRecorder.Body.String(), "azkv://")
timelineRecorder := httptest.NewRecorder()
timelineCtx, _ := gin.CreateTestContext(timelineRecorder)
timelineCtx.Set("id", 7)
timelineCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
timelineCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/timeline", nil)
AgentGetUserDeploymentTimeline(timelineCtx)
require.Equal(t, http.StatusOK, timelineRecorder.Code)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"artifact.created"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"approval.requested"`)
require.Contains(t, timelineRecorder.Body.String(), `"source":"agent-simulator"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"task.created"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"task.blocked"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"handoff.requested"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"handoff.completed"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"task.retried"`)
}
func TestAgentUserSwarmsAdapterCreatesScopedDeployment(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = ""
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = ""
}
body, err := common.Marshal(agentDeploymentRequest{Plan: plan})
require.NoError(t, err)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set("id", 7)
ctx.Set("group", "development")
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/swarms", strings.NewReader(string(body)))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentCreateUserSwarm(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"swarm_id"`)
require.Contains(t, recorder.Body.String(), `"deployment_id"`)
require.Contains(t, recorder.Body.String(), `"user_id":"7"`)
}
func TestAgentCallbackRejectsInvalidTaskEventSchema(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.Equal(t, http.StatusOK, createRecorder.Code)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
body := fmt.Sprintf(`{
"event_id":"evt-invalid-handoff",
"event_type":"handoff.requested",
"deployment_id":%q,
"task_id":"task-1",
"payload":{
"from_role":"backend"
}
}`, deploymentID)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(ctx)
var envelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &envelope))
require.False(t, envelope.Success)
require.Equal(t, "CALLBACK_SCHEMA_INVALID", envelope.Error.Code)
require.Contains(t, envelope.Error.Message, "to_role")
}
func TestAgentCallbackStoresEventArtifactAndIsIdempotent(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
body := fmt.Sprintf(`{
"event_id":"evt-callback-1",
"idempotency_key":"idem-callback-1",
"event_type":"artifact.created",
"deployment_id":%q,
"task_id":"task-1",
"correlation_id":"corr-p1-resource-grant",
"artifact":{
"artifact_id":"art-1",
"artifact_type":"summary",
"title":"Build summary",
"summary":"Created backend scaffold",
"uri":"artifact://task-1/summary"
}
}`, deploymentID)
for i := 0; i < 2; i++ {
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"success":true`)
}
var callbackCount int64
require.NoError(t, db.Model(&model.AgentCallbackEvent{}).Where("event_id = ?", "evt-callback-1").Count(&callbackCount).Error)
require.Equal(t, int64(1), callbackCount)
var artifactCount int64
require.NoError(t, db.Model(&model.AgentArtifact{}).Where("artifact_id = ?", "art-1").Count(&artifactCount).Error)
require.Equal(t, int64(1), artifactCount)
}
func TestAgentCallbackAcceptsDocumentedHMACPayloadArtifact(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_SIGNING_SECRET", "callback-signing-secret")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
body := fmt.Sprintf(`{
"event_type":"artifact.created",
"deployment_id":%q,
"swarm_id":"swarm-doc-1",
"agent_instance_id":"agi-backend-1",
"occurred_at":"2026-05-26T10:40:00Z",
"payload":{
"artifact_id":"art-doc-payload-1",
"artifact_type":"code_patch",
"title":"Backend API patch",
"summary":"Patch created",
"uri":"azblob://heicode-artifacts/task-123/backend.patch",
"checksum":"sha256:abc",
"stage":"development",
"checkpoint":"artifact_ready"
}
}`, deploymentID)
eventID := "evt-doc-hmac-1"
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
mac := hmac.New(sha256.New, []byte("callback-signing-secret"))
mac.Write([]byte(timestamp + "." + eventID + "." + body))
signature := "sha256=" + hex.EncodeToString(mac.Sum(nil))
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Event-Id", eventID)
ctx.Request.Header.Set("X-Agent-Timestamp", timestamp)
ctx.Request.Header.Set("X-Agent-Signature", signature)
ctx.Request.Header.Set("X-Correlation-ID", "corr-doc-hmac")
AgentReceiveRuntimeEventCallback(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"success":true`)
var callback model.AgentCallbackEvent
require.NoError(t, db.Where("event_id = ?", eventID).First(&callback).Error)
require.Equal(t, "swarm-doc-1", callback.SwarmID)
require.Equal(t, "agi-backend-1", callback.AgentInstanceID)
require.Equal(t, "2026-05-26T10:40:00Z", callback.OccurredAt)
var artifact model.AgentArtifact
require.NoError(t, db.Where("artifact_id = ?", "art-doc-payload-1").First(&artifact).Error)
require.Equal(t, "code_patch", artifact.ArtifactType)
require.Contains(t, artifact.MetadataJSON, "artifact_ready")
}
func TestAgentCallbackHMACCanUseAzureKeyVaultSigningSecretRef(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
tokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "true", r.Header.Get("Metadata"))
_, _ = w.Write([]byte(`{"access_token":"manager-token","expires_in":"3600"}`))
}))
defer tokenServer.Close()
vaultServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodGet, r.Method)
require.Equal(t, "Bearer manager-token", r.Header.Get("Authorization"))
require.Equal(t, "/secrets/callback-signing", r.URL.Path)
_, _ = w.Write([]byte(`{"value":"{\"callback_signing_secret\":\"from-kv-secret\"}"}`))
}))
defer vaultServer.Close()
t.Setenv("AZURE_KEY_VAULT_URL", vaultServer.URL)
t.Setenv("AZURE_MANAGED_IDENTITY_TOKEN_URL", tokenServer.URL)
t.Setenv("AGENT_CALLBACK_SIGNING_SECRET_REF", fmt.Sprintf("azkv://%s/secrets/callback-signing", strings.TrimPrefix(vaultServer.URL, "http://")))
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
body := fmt.Sprintf(`{"event_type":"timeline.updated","deployment_id":%q,"payload":{"title":"Ready"}}`, deploymentID)
eventID := "evt-kv-hmac-1"
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
mac := hmac.New(sha256.New, []byte("from-kv-secret"))
mac.Write([]byte(timestamp + "." + eventID + "." + body))
signature := "sha256=" + hex.EncodeToString(mac.Sum(nil))
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Event-Id", eventID)
ctx.Request.Header.Set("X-Agent-Timestamp", timestamp)
ctx.Request.Header.Set("X-Agent-Signature", signature)
AgentReceiveRuntimeEventCallback(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"success":true`)
var callback model.AgentCallbackEvent
require.NoError(t, db.Where("event_id = ?", eventID).First(&callback).Error)
}
func TestAgentCallbackUsesSwarmIDFallbackAndCreatesApproval(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
swarmID := "runtime-swarm-only-1"
require.NoError(t, db.Model(&model.AgentDeployment{}).
Where("deployment_id = ?", deploymentID).
Update("runtime_swarm_id", swarmID).Error)
resetAgentControlPlaneState(t)
body := `{
"event_id":"evt-approval-swarm-only",
"event_type":"approval.requested",
"swarm_id":"runtime-swarm-only-1",
"agent_instance_id":"agi-backend-approval",
"occurred_at":"2026-05-27T10:41:00Z",
"payload":{
"approval_id":"runtime-approval-1",
"operation":"repo.write",
"resource_id":"res-git-main",
"resource_type":"git",
"resource_scope":"heicode/",
"target_role":"builder",
"risk_level":"high",
"requires_credential":true,
"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/repo-main",
"ttl_seconds":600,
"reason":"Runtime needs write access for backend patch"
}
}`
for i := 0; i < 2; i++ {
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"success":true`)
if i == 0 {
require.Contains(t, recorder.Body.String(), `"deduplicated":false`)
} else {
require.Contains(t, recorder.Body.String(), `"deduplicated":true`)
}
}
var callback model.AgentCallbackEvent
require.NoError(t, db.Where("event_id = ?", "evt-approval-swarm-only").First(&callback).Error)
require.Equal(t, deploymentID, callback.DeploymentID)
require.Equal(t, swarmID, callback.SwarmID)
require.Equal(t, "agi-backend-approval", callback.AgentInstanceID)
var approval model.AgentApprovalRequest
require.NoError(t, db.Where("approval_id = ?", "runtime-approval-1").First(&approval).Error)
require.Equal(t, 7, approval.UserId)
require.Equal(t, deploymentID, approval.DeploymentID)
require.Equal(t, "pending", approval.Status)
require.Equal(t, "repo.write", approval.Operation)
require.Equal(t, "azkv://heicode-kv.vault.azure.net/secrets/repo-main", approval.SecretRef)
}
func TestAgentCallbackUsesRuntimeDeploymentIDFallback(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
plan.BillingContext.DefaultModelID = "model-runtime-smoke"
plan.BillingContext.AllowedModelIDs = []string{"model-runtime-smoke", "model-runtime-fallback"}
plan.BillingContext.SecretRef = "azkv://heicode-kv.vault.azure.net/secrets/model-gateway-key"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
runtimeID := "swm-runtime-deployment-only-1"
require.NoError(t, db.Model(&model.AgentDeployment{}).
Where("deployment_id = ?", deploymentID).
Updates(map[string]any{
"runtime_deployment_id": runtimeID,
"runtime_swarm_id": runtimeID,
}).Error)
resetAgentControlPlaneState(t)
body := `{
"event_id":"evt-runtime-deployment-fallback",
"event_type":"timeline.updated",
"deployment_id":"swm-runtime-deployment-only-1",
"agent_instance_id":"agi-backend-runtime",
"occurred_at":"2026-05-28T12:30:00Z",
"payload":{
"title":"Runtime accepted",
"summary":"Agent Manager returned a runtime deployment id only",
"stage":"testing",
"checkpoint":"runtime_accepted"
}
}`
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"success":true`)
require.Contains(t, recorder.Body.String(), `"deployment_id":"`+deploymentID+`"`)
var callback model.AgentCallbackEvent
require.NoError(t, db.Where("event_id = ?", "evt-runtime-deployment-fallback").First(&callback).Error)
require.Equal(t, deploymentID, callback.DeploymentID)
require.Equal(t, runtimeID, callback.SwarmID)
require.Equal(t, "7", callback.UserID)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Contains(t, stored.PlanJSON, `"default_model_id":"model-runtime-smoke"`)
require.Contains(t, stored.PlanJSON, `"allowed_model_ids":["model-runtime-smoke","model-runtime-fallback"]`)
require.Contains(t, stored.PlanJSON, `"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/model-gateway-key"`)
}
func TestAgentCallbackUpdatesDeploymentRuntimeSnapshot(t *testing.T) {
db := setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
runtimeID := "swm-runtime-state-1"
require.NoError(t, db.Model(&model.AgentDeployment{}).
Where("deployment_id = ?", deploymentID).
Updates(map[string]any{
"runtime_deployment_id": runtimeID,
"runtime_swarm_id": runtimeID,
}).Error)
resetAgentControlPlaneState(t)
postCallback := func(body string) {
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(body))
ctx.Request.Header.Set("Content-Type", "application/json")
ctx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(ctx)
require.Equal(t, http.StatusOK, recorder.Code)
require.Contains(t, recorder.Body.String(), `"success":true`)
}
postCallback(`{
"event_id":"evt-runtime-agent-started",
"event_type":"agent.started",
"deployment_id":"swm-runtime-state-1",
"swarm_id":"swm-runtime-state-1",
"agent_instance_id":"agi-runtime-builder",
"occurred_at":"2026-05-28T12:40:00Z",
"payload":{"agent_role":"builder","status":"running"}
}`)
postCallback(`{
"event_id":"evt-runtime-phase-deploy",
"event_type":"phase.changed",
"deployment_id":"swm-runtime-state-1",
"swarm_id":"swm-runtime-state-1",
"occurred_at":"2026-05-28T12:41:00Z",
"payload":{"stage":"deploy","checkpoint":"completed","summary":"Runtime completed"}
}`)
postCallback(`{
"event_id":"evt-runtime-status-completed",
"event_type":"deployment.status_changed",
"deployment_id":"swm-runtime-state-1",
"swarm_id":"swm-runtime-state-1",
"occurred_at":"2026-05-28T12:42:00Z",
"payload":{"status":"completed"}
}`)
var stored model.AgentDeployment
require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error)
require.Equal(t, "completed", stored.Status)
require.Equal(t, "deploy", stored.Phase)
require.Equal(t, "completed", stored.RuntimeState)
require.Equal(t, "2026-05-28T12:42:00Z", stored.UpdatedAtText)
var instances []agentAgentInstance
require.NoError(t, common.UnmarshalJsonStr(stored.AgentInstancesJSON, &instances))
require.Len(t, instances, 1)
require.Equal(t, "agi-runtime-builder", instances[0].InstanceID)
require.Equal(t, "builder", instances[0].Role)
require.Equal(t, "deploy", instances[0].Phase)
require.Equal(t, "completed", instances[0].RuntimeState)
}
func TestAgentCallbackRejectsPlaintextSecretsAndMissingToken(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(`{"event_id":"evt-no-token","event_type":"status.updated"}`))
ctx.Request.Header.Set("Content-Type", "application/json")
AgentReceiveRuntimeEventCallback(ctx)
var missingToken agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &missingToken))
require.False(t, missingToken.Success)
require.Equal(t, "CALLBACK_UNAUTHORIZED", missingToken.Error.Code)
secretRecorder := httptest.NewRecorder()
secretCtx, _ := gin.CreateTestContext(secretRecorder)
secretCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(`{
"event_id":"evt-secret",
"event_type":"status.updated",
"metadata":{"access_token":"must-not-store"}
}`))
secretCtx.Request.Header.Set("Content-Type", "application/json")
secretCtx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(secretCtx)
var secretEnvelope agentCreateTestEnvelope
require.NoError(t, common.Unmarshal(secretRecorder.Body.Bytes(), &secretEnvelope))
require.False(t, secretEnvelope.Success)
require.Equal(t, "CALLBACK_SECRET_REJECTED", secretEnvelope.Error.Code)
}
func TestAgentArtifactsAndTimelineReturnPersistedRelatedRecords(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
t.Setenv("AGENT_CALLBACK_TOKEN", "callback-token")
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
plan.SubMode = "waterfall"
for idx := range plan.Agents[0].ResourceGrants {
plan.Agents[0].ResourceGrants[idx].UserID = "7"
}
createRecorder, createEnvelope := postAgentCreateUserDeployment(t, 7, plan)
require.True(t, createEnvelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
callbackBody := fmt.Sprintf(`{
"event_id":"evt-artifact-timeline",
"idempotency_key":"idem-artifact-timeline",
"event_type":"artifact.created",
"deployment_id":%q,
"task_id":"task-timeline",
"correlation_id":"corr-p1-resource-grant",
"artifact":{"artifact_id":"art-timeline","artifact_type":"summary","title":"Timeline artifact","summary":"Done","uri":"artifact://task-timeline/summary"}
}`, deploymentID)
callbackRecorder := httptest.NewRecorder()
callbackCtx, _ := gin.CreateTestContext(callbackRecorder)
callbackCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/callbacks/runtime-events", strings.NewReader(callbackBody))
callbackCtx.Request.Header.Set("Content-Type", "application/json")
callbackCtx.Request.Header.Set("X-Agent-Service-Token", "callback-token")
AgentReceiveRuntimeEventCallback(callbackCtx)
require.Contains(t, callbackRecorder.Body.String(), `"success":true`)
snapshotRecorder := httptest.NewRecorder()
snapshotCtx, _ := gin.CreateTestContext(snapshotRecorder)
snapshotCtx.Set("id", 7)
snapshotCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/sk-snapshots/resolve", strings.NewReader(fmt.Sprintf(`{"deployment_id":%q}`, deploymentID)))
snapshotCtx.Request.Header.Set("Content-Type", "application/json")
AgentResolveSKSnapshots(snapshotCtx)
require.Equal(t, http.StatusOK, snapshotRecorder.Code)
resetAgentControlPlaneState(t)
artifactRecorder := httptest.NewRecorder()
artifactCtx, _ := gin.CreateTestContext(artifactRecorder)
artifactCtx.Set("id", 7)
artifactCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
artifactCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/artifacts", nil)
AgentListUserDeploymentArtifacts(artifactCtx)
require.Equal(t, http.StatusOK, artifactRecorder.Code)
require.Contains(t, artifactRecorder.Body.String(), `"art-timeline"`)
require.Contains(t, artifactRecorder.Body.String(), `"deployment_id":"`+deploymentID+`"`)
require.Contains(t, artifactRecorder.Body.String(), `"artifacts":[`)
snapshotListRecorder := httptest.NewRecorder()
snapshotListCtx, _ := gin.CreateTestContext(snapshotListRecorder)
snapshotListCtx.Set("id", 7)
snapshotListCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
snapshotListCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/sk-snapshots", nil)
AgentListUserSKSnapshots(snapshotListCtx)
require.Equal(t, http.StatusOK, snapshotListRecorder.Code)
require.Contains(t, snapshotListRecorder.Body.String(), `"deployment_id":"`+deploymentID+`"`)
require.Contains(t, snapshotListRecorder.Body.String(), `"sk_snapshots":[`)
timelineRecorder := httptest.NewRecorder()
timelineCtx, _ := gin.CreateTestContext(timelineRecorder)
timelineCtx.Set("id", 7)
timelineCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
timelineCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/user/deployments/"+deploymentID+"/timeline", nil)
AgentGetUserDeploymentTimeline(timelineCtx)
require.Equal(t, http.StatusOK, timelineRecorder.Code)
require.Contains(t, timelineRecorder.Body.String(), `"callbacks"`)
require.Contains(t, timelineRecorder.Body.String(), `"artifacts"`)
require.Contains(t, timelineRecorder.Body.String(), `"sk_snapshots"`)
require.Contains(t, timelineRecorder.Body.String(), `"timeline"`)
require.Contains(t, timelineRecorder.Body.String(), `"deployment_id":"`+deploymentID+`"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_id":"evt-artifact-timeline"`)
require.Contains(t, timelineRecorder.Body.String(), `"event_type":"artifact.created"`)
}
func TestAgentStopDeploymentPersistsState(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
recorder, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
stopRecorder := httptest.NewRecorder()
stopCtx, _ := gin.CreateTestContext(stopRecorder)
stopCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
stopCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/deployments/"+deploymentID+"/stop", nil)
AgentStopDeployment(stopCtx)
require.Equal(t, http.StatusOK, stopRecorder.Code)
require.Contains(t, stopRecorder.Body.String(), `"status":"stopped"`)
resetAgentControlPlaneState(t)
getRecorder := httptest.NewRecorder()
getCtx, _ := gin.CreateTestContext(getRecorder)
getCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
getCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID, nil)
AgentGetDeployment(getCtx)
require.Equal(t, http.StatusOK, getRecorder.Code)
require.Contains(t, getRecorder.Body.String(), `"status":"stopped"`)
require.Contains(t, getRecorder.Body.String(), `"runtime_state":"stopped"`)
}
func TestAgentStopDeploymentPropagatesToRuntime(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
stopCalled := false
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodPost && r.URL.Path == "/api/agent/sub-agile/deployments":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true,"data":{"deployment_id":"runtime-dep-stop","status":"pending"}}`))
case r.Method == http.MethodPost && r.URL.Path == "/api/agent/sub-agile/deployments/runtime-dep-stop/stop":
stopCalled = true
require.Equal(t, "Bearer service-token", r.Header.Get("Authorization"))
body, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.Contains(t, string(body), `"reason":"runtime stop smoke"`)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"success":true,"deployment_id":"runtime-dep-stop","status":"stopped"}`))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
t.Setenv("AGENT_RUNTIME_ENABLED", "true")
t.Setenv("AGENT_RUNTIME_ASYNC", "false")
t.Setenv("AGENT_RUNTIME_BASE_URL", server.URL)
t.Setenv("AGENT_RUNTIME_SERVICE_TOKEN", "service-token")
recorder, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
var createBody map[string]any
require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &createBody))
deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string)
stopRecorder := httptest.NewRecorder()
stopCtx, _ := gin.CreateTestContext(stopRecorder)
stopCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
stopCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/deployments/"+deploymentID+"/stop", strings.NewReader(`{"reason":"runtime stop smoke"}`))
stopCtx.Request.Header.Set("Content-Type", "application/json")
AgentStopDeployment(stopCtx)
require.Equal(t, http.StatusOK, stopRecorder.Code)
require.Contains(t, stopRecorder.Body.String(), `"success":true`)
require.Contains(t, stopRecorder.Body.String(), `"runtime_state":"stopped"`)
require.True(t, stopCalled)
}
func TestManagerOnlyAgentSmokeFlow(t *testing.T) {
setupAgentControlPlaneTestDB(t)
resetAgentControlPlaneState(t)
resourceBody := `{
"binding_scope":"project-smoke",
"name":"Smoke project repo",
"resource_type":"git",
"provider":"gitee",
"external_id":"https://example.invalid/acme/smoke.git",
"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/project-smoke",
"metadata":{"repo_url":"https://example.invalid/acme/smoke.git","ref":"main"},
"permission_scope":{"actions":["repo:read"]},
"constraints":{"allowed_paths":"src/**,docs/**"}
}`
resourceRecorder := performResourceRequest(CreateResource, 7, http.MethodPost, "/", resourceBody)
require.Equal(t, http.StatusOK, resourceRecorder.Code)
require.Contains(t, resourceRecorder.Body.String(), `"success":true`)
require.NotContains(t, strings.ToLower(resourceRecorder.Body.String()), "password")
var resourceEnv map[string]any
require.NoError(t, common.Unmarshal(resourceRecorder.Body.Bytes(), &resourceEnv))
resource := resourceEnv["data"].(map[string]any)
resourceID := int(resource["id"].(float64))
grantBody := fmt.Sprintf(`{
"binding_scope":"project-smoke",
"resource_id":%d,
"role":"builder",
"agent_id":"agent-builder-1",
"permission_scope":{"actions":["repo:read","repo:write:feature-branches"]},
"constraints":{"allowed_paths":"src/**,docs/**","ref":"main"}
}`, resourceID)
grantRecorder := performResourceRequest(CreateResourceGrant, 7, http.MethodPost, "/", grantBody)
require.Equal(t, http.StatusOK, grantRecorder.Code)
require.Contains(t, grantRecorder.Body.String(), `"success":true`)
var grantEnv map[string]any
require.NoError(t, common.Unmarshal(grantRecorder.Body.Bytes(), &grantEnv))
grant := grantEnv["data"].(map[string]any)
grantID := int(grant["id"].(float64))
manifestRecorder := performResourceRequestWithRoute(
GenerateResourceGrantManifest,
7,
http.MethodGet,
"/manifest",
"/manifest?binding_scope=project-smoke&role=builder&agent_id=agent-builder-1",
"",
)
require.Equal(t, http.StatusOK, manifestRecorder.Code)
require.Contains(t, manifestRecorder.Body.String(), `"resource_grants":[`)
require.Contains(t, manifestRecorder.Body.String(), `"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/project-smoke"`)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = "7"
plan.UserContext.ChannelID = "smoke-channel"
plan.BillingContext.NewAPIUserRef = "newapi-user-smoke"
plan.BillingContext.NewAPIGroup = "vip"
plan.BillingContext.QuotaRef = "quota-smoke"
plan.Metadata.CorrelationID = "corr-manager-smoke"
plan.Agents[0].ResourceGrants = []agentResourceGrant{
{
GrantID: fmt.Sprintf("grant-%d", grantID),
ResourceID: fmt.Sprintf("%d", resourceID),
ResourceType: agentResourceGit,
UserID: "7",
BindingScope: "project-smoke",
TargetRole: "builder",
TargetAgentRef: "agent-builder-1",
PermissionScope: []string{"repo:read", "repo:write:feature-branches"},
Constraints: map[string]string{"allowed_paths": "src/**,docs/**", "ref": "main"},
Metadata: map[string]string{
"provider": "gitee",
"resource_ref": "https://example.invalid/acme/smoke.git",
},
Status: agentGrantStatusActive,
SecretRef: "azkv://heicode-kv.vault.azure.net/secrets/project-smoke",
Audit: map[string]string{"confirmed_by": "7"},
},
}
deployRecorder, envelope := postAgentCreateDeployment(t, plan)
require.Equal(t, http.StatusOK, deployRecorder.Code)
require.True(t, envelope.Success)
var deployEnv map[string]any
require.NoError(t, common.Unmarshal(deployRecorder.Body.Bytes(), &deployEnv))
deploymentID := deployEnv["data"].(map[string]any)["deployment_id"].(string)
require.NotEmpty(t, deploymentID)
logRecorder := httptest.NewRecorder()
logCtx, _ := gin.CreateTestContext(logRecorder)
logCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
logCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID+"/logs", nil)
AgentListDeploymentLogs(logCtx)
require.Equal(t, http.StatusOK, logRecorder.Code)
require.Contains(t, logRecorder.Body.String(), `"redacted":true`)
require.NotContains(t, strings.ToLower(logRecorder.Body.String()), "access_token")
stopRecorder := httptest.NewRecorder()
stopCtx, _ := gin.CreateTestContext(stopRecorder)
stopCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
stopCtx.Request = httptest.NewRequest(http.MethodPost, "/api/agent/deployments/"+deploymentID+"/stop", nil)
AgentStopDeployment(stopCtx)
require.Equal(t, http.StatusOK, stopRecorder.Code)
require.Contains(t, stopRecorder.Body.String(), `"status":"stopped"`)
resetAgentControlPlaneState(t)
getRecorder := httptest.NewRecorder()
getCtx, _ := gin.CreateTestContext(getRecorder)
getCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
getCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID, nil)
AgentGetDeployment(getCtx)
require.Equal(t, http.StatusOK, getRecorder.Code)
require.Contains(t, getRecorder.Body.String(), `"status":"stopped"`)
revokeGrantRecorder := performResourceRequestWithRoute(
DeleteResourceGrant,
7,
http.MethodDelete,
"/:id",
fmt.Sprintf("/%d", grantID),
"",
)
require.Equal(t, http.StatusOK, revokeGrantRecorder.Code)
require.Contains(t, revokeGrantRecorder.Body.String(), `"status":"revoked"`)
emptyManifestRecorder := performResourceRequestWithRoute(
GenerateResourceGrantManifest,
7,
http.MethodGet,
"/manifest",
"/manifest?binding_scope=project-smoke&role=builder&agent_id=agent-builder-1",
"",
)
require.Equal(t, http.StatusOK, emptyManifestRecorder.Code)
require.Contains(t, emptyManifestRecorder.Body.String(), `"resource_grants":[]`)
}
func TestAgentCreateDeploymentAcceptsP1ResourceGrantModel(t *testing.T) {
resetAgentControlPlaneState(t)
recorder, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.Equal(t, http.StatusOK, recorder.Code)
require.True(t, envelope.Success)
agentMu.RLock()
require.Len(t, agentDeployments, 1)
var stored agentDeploymentRecord
for _, record := range agentDeployments {
stored = record
}
agentMu.RUnlock()
grants := stored.Plan.Agents[0].ResourceGrants
require.Len(t, grants, 2)
require.Equal(t, agentResourceGit, grants[0].ResourceType)
require.Equal(t, "user-p1", grants[0].UserID)
require.Equal(t, "https://example.invalid/acme/project.git#main", grants[0].BindingScope)
require.Equal(t, "builder", grants[0].TargetRole)
require.Equal(t, "agent-builder-1", grants[0].TargetAgentRef)
require.Equal(t, "azkv://heicode-kv.vault.azure.net/secrets/user-p1-project-main-res-git-main", grants[0].SecretRef)
require.Empty(t, grants[1].SecretRef, "project document grants should not require credential material")
require.Equal(t, "newapi", stored.Plan.BillingContext.Provider)
require.Equal(t, "channel-p1", stored.Plan.UserContext.ChannelID)
require.Equal(t, "agent", stored.Plan.AgentRuntime.Platform)
require.Equal(t, "agent-model-profile-builder", stored.Plan.AgentRuntime.Agents[0].ModelRef)
require.Equal(t, "queued", stored.RuntimeState)
require.Empty(t, stored.FailureReason)
require.Len(t, stored.AgentInstances, 1)
require.Equal(t, "queued", stored.AgentInstances[0].RuntimeState)
require.Len(t, stored.ResourceGrantManifest.ResourceGrants, 2)
require.Equal(t, "user-p1", stored.ResourceGrantManifest.UserID)
require.Equal(t, "builder", stored.ResourceGrantManifest.AgentRole)
require.Equal(t, "agent-builder-1", stored.ResourceGrantManifest.TargetAgentRef)
require.Equal(t, "azkv://heicode-kv.vault.azure.net/secrets/user-p1-project-main-res-git-main", stored.ResourceGrantManifest.ResourceGrants[0].SecretRef)
}
func TestAgentPermissionManifestExcludesInactiveResourceGrants(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.Agents[0].ResourceGrants[1].Status = agentGrantStatusRevoked
_, envelope := postAgentCreateDeployment(t, plan)
require.True(t, envelope.Success)
agentMu.RLock()
var stored agentDeploymentRecord
for _, record := range agentDeployments {
stored = record
}
agentMu.RUnlock()
require.Len(t, stored.ResourceGrantManifest.ResourceGrants, 1)
require.Equal(t, "grant-git-builder", stored.ResourceGrantManifest.ResourceGrants[0].GrantID)
}
func TestAgentCreateDeploymentRejectsPlaintextResourceGrantCredentialFields(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.Agents[0].ResourceGrants[0].Metadata["access_token"] = "must-not-be-stored"
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "RESOURCE_GRANT_SECRET_REJECTED", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsResourceGrantWithoutSecretRef(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.Agents[0].ResourceGrants[0].SecretRef = ""
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "RESOURCE_GRANT_SECRET_REF_REQUIRED", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsMissingUserContext(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.UserID = ""
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "POLICY_REJECTED", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsResourceGrantUserMismatch(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.Agents[0].ResourceGrants[0].UserID = "user-other"
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "RESOURCE_GRANT_INVALID", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsResourceGrantWithoutBindingScope(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.Agents[0].ResourceGrants[0].BindingScope = ""
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "RESOURCE_GRANT_INVALID", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsNewAPIBillingWithoutUserMapping(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.UserContext.ChannelID = ""
plan.BillingContext.NewAPIUserRef = ""
plan.BillingContext.NewAPIGroup = ""
plan.BillingContext.QuotaRef = ""
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "BILLING_CONTEXT_INVALID", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentCreateDeploymentRejectsRuntimeRoleOutsideAgentPlan(t *testing.T) {
resetAgentControlPlaneState(t)
plan := baseAgentResourceGrantPlan()
plan.AgentRuntime.Agents[0].Role = "ops"
_, envelope := postAgentCreateDeployment(t, plan)
require.False(t, envelope.Success)
require.Equal(t, "AGENT_RUNTIME_INVALID", envelope.Error.Code)
require.Empty(t, agentDeployments)
}
func TestAgentDeploymentLogsAndMetricsExposeRedactedReadiness(t *testing.T) {
resetAgentControlPlaneState(t)
_, envelope := postAgentCreateDeployment(t, baseAgentResourceGrantPlan())
require.True(t, envelope.Success)
agentMu.RLock()
var deploymentID string
for id := range agentDeployments {
deploymentID = id
break
}
agentMu.RUnlock()
require.NotEmpty(t, deploymentID)
logRecorder := httptest.NewRecorder()
logCtx, _ := gin.CreateTestContext(logRecorder)
logCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID+"/logs", nil)
logCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
AgentListDeploymentLogs(logCtx)
require.Equal(t, http.StatusOK, logRecorder.Code)
require.Contains(t, logRecorder.Body.String(), `"redacted":true`)
require.NotContains(t, strings.ToLower(logRecorder.Body.String()), "password")
require.NotContains(t, strings.ToLower(logRecorder.Body.String()), "token")
metricRecorder := httptest.NewRecorder()
metricCtx, _ := gin.CreateTestContext(metricRecorder)
metricCtx.Request = httptest.NewRequest(http.MethodGet, "/api/agent/deployments/"+deploymentID+"/metrics?window=15m&step=60s", nil)
metricCtx.Params = gin.Params{{Key: "deployment_id", Value: deploymentID}}
AgentGetDeploymentMetrics(metricCtx)
require.Equal(t, http.StatusOK, metricRecorder.Code)
require.Contains(t, metricRecorder.Body.String(), `"deployment_id":"`+deploymentID+`"`)
require.Contains(t, metricRecorder.Body.String(), `"platform_estimated":true`)
require.Contains(t, metricRecorder.Body.String(), `"runtime_state":"queued"`)
require.Contains(t, metricRecorder.Body.String(), `"failure_reason":""`)
}
func TestAgentDeploymentDisplayStatus_JudgesDeliverable(t *testing.T) {
setupAgentControlPlaneTestDB(t)
rec := agentDeploymentRecord{DeploymentID: "dep_ds_1", Status: "completed"}
// completed but no artifacts -> completed_without_deliverable
require.Equal(t, "completed_without_deliverable", agentDeploymentDisplayStatus(rec))
// only a summary/document artifact -> needs_codegen
require.NoError(t, model.UpsertAgentArtifact(&model.AgentArtifact{
ArtifactID: "art_ds_doc", DeploymentID: "dep_ds_1", ArtifactType: "document", Title: "plan", CreatedAtMs: 1,
}))
require.Equal(t, "needs_codegen", agentDeploymentDisplayStatus(rec))
// a real code deliverable -> completed
require.NoError(t, model.UpsertAgentArtifact(&model.AgentArtifact{
ArtifactID: "art_ds_code", DeploymentID: "dep_ds_1", ArtifactType: "code_patch", Title: "backend", CreatedAtMs: 2,
}))
require.Equal(t, "completed", agentDeploymentDisplayStatus(rec))
// synthesized fallback (even if code_patch typed) is not a real deliverable
require.NoError(t, model.UpsertAgentArtifact(&model.AgentArtifact{
ArtifactID: "art_ds_synth", DeploymentID: "dep_ds_2", ArtifactType: "code_patch", Title: "fallback",
MetadataJSON: `{"synthesized":true}`, CreatedAtMs: 1,
}))
require.Equal(t, "needs_codegen", agentDeploymentDisplayStatus(agentDeploymentRecord{DeploymentID: "dep_ds_2", Status: "completed"}))
// non-completed mirrors the runtime status
require.Equal(t, "running", agentDeploymentDisplayStatus(agentDeploymentRecord{DeploymentID: "dep_ds_x", Status: "running"}))
}