HM-side logic for the template-agent model, written against a proposed AM contract that is fully isolated in agent_template_runtime.go — when AM ships its real API, only the default paths + response field names in that one file change. - agent_template_runtime.go: AM adapter (list templates, start template agent with env, stop/delete) reusing the existing runtime config/url/envelope helpers. - agent_template_handlers.go: GET /api/heicode/agent-templates; POST /api/heicode/ agents (resolve bindings -> env, start via AM, persist subdomain+token); GET /agents, GET/:id, POST/:id/stop, DELETE/:id. Owner-scoped; env never logged. - reuses AgentDeployment as the agent record (TemplateID/Subdomain/AccessToken/ BindingIDsJSON) and buildAgentEnvFromBindings for env assembly. - routes wired under /api/heicode (UserOrV2DeviceAuth), verified no registration panic (router tests pass). - agent_template_test.go: 7 independent unit tests (env assembly metadata-only, ownership, secret-without-ref, unknown-source, empty; path substitution; response mapping) — all pass without AM/KV. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
3.0 KiB
Go
97 lines
3.0 KiB
Go
package controller
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/heicode/manager/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBuildAgentEnvFromBindings_MetadataOnly(t *testing.T) {
|
|
setupResourceControllerTestDB(t)
|
|
|
|
b := model.ResourceBinding{
|
|
UserId: 7,
|
|
Name: "blob1",
|
|
ResourceType: "blob",
|
|
Metadata: `{"account":"myacct","container":"uploads"}`,
|
|
EnvMap: `{"AZURE_BLOB_ACCOUNT":{"source":"metadata","key":"account"},"AZURE_BLOB_CONTAINER":{"source":"metadata","key":"container"}}`,
|
|
}
|
|
require.NoError(t, model.DB.Create(&b).Error)
|
|
|
|
env, err := buildAgentEnvFromBindings(7, []int{b.Id})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "myacct", env["AZURE_BLOB_ACCOUNT"])
|
|
require.Equal(t, "uploads", env["AZURE_BLOB_CONTAINER"])
|
|
}
|
|
|
|
func TestBuildAgentEnvFromBindings_OwnershipEnforced(t *testing.T) {
|
|
setupResourceControllerTestDB(t)
|
|
b := model.ResourceBinding{UserId: 7, Name: "x", ResourceType: "blob", Metadata: `{}`, EnvMap: `{}`}
|
|
require.NoError(t, model.DB.Create(&b).Error)
|
|
|
|
_, err := buildAgentEnvFromBindings(99, []int{b.Id}) // different user
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestBuildAgentEnvFromBindings_SecretWithoutRefFailsBeforeKV(t *testing.T) {
|
|
setupResourceControllerTestDB(t)
|
|
b := model.ResourceBinding{
|
|
UserId: 7,
|
|
Name: "vm1",
|
|
ResourceType: "vm",
|
|
Metadata: `{"host":"1.2.3.4"}`,
|
|
EnvMap: `{"VM_PASSWORD":{"source":"secret","key":"password"}}`,
|
|
// SecretRef intentionally empty -> must error before any Key Vault call
|
|
}
|
|
require.NoError(t, model.DB.Create(&b).Error)
|
|
|
|
_, err := buildAgentEnvFromBindings(7, []int{b.Id})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "secret_ref")
|
|
}
|
|
|
|
func TestBuildAgentEnvFromBindings_UnknownSource(t *testing.T) {
|
|
setupResourceControllerTestDB(t)
|
|
b := model.ResourceBinding{
|
|
UserId: 7,
|
|
Name: "x",
|
|
ResourceType: "blob",
|
|
Metadata: `{}`,
|
|
EnvMap: `{"FOO":{"source":"bogus","key":"x"}}`,
|
|
}
|
|
require.NoError(t, model.DB.Create(&b).Error)
|
|
|
|
_, err := buildAgentEnvFromBindings(7, []int{b.Id})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestBuildAgentEnvFromBindings_Empty(t *testing.T) {
|
|
setupResourceControllerTestDB(t)
|
|
env, err := buildAgentEnvFromBindings(7, nil)
|
|
require.NoError(t, err)
|
|
require.Empty(t, env)
|
|
}
|
|
|
|
func TestAgentTemplatePathSubstitution(t *testing.T) {
|
|
require.Contains(t, agentTemplateStartPath("tpl1"), "/templates/tpl1/start")
|
|
require.Contains(t, agentTemplateAgentStopPath("rt-9"), "/agents/rt-9/stop")
|
|
require.Contains(t, agentTemplateAgentPath("rt-9"), "/agents/rt-9")
|
|
}
|
|
|
|
func TestTemplateAgentResponse(t *testing.T) {
|
|
row := model.AgentDeployment{
|
|
DeploymentID: "dep_abc",
|
|
TemplateID: "tpl1",
|
|
Subdomain: "https://abc.agents.example",
|
|
AccessToken: "tok",
|
|
BindingIDsJSON: "[1,2]",
|
|
Status: "running",
|
|
}
|
|
resp := templateAgentResponse(row)
|
|
require.Equal(t, "dep_abc", resp["agent_id"])
|
|
require.Equal(t, "tpl1", resp["template_id"])
|
|
require.Equal(t, []int{1, 2}, resp["binding_ids"])
|
|
require.Equal(t, "running", resp["status"])
|
|
}
|