Files
heicode/heicode/controller/resource_test.go
T
gongzhiyong ba02ae5be7 feat: align manager agnet boundaries
- add Manager user_context, NewAPI billing_context, and Agnet agent_runtime deployment fields

- move resource binding/grant scope toward user-owned binding_scope and secret_ref-only paths

- document OpenBao internal access and unified heicode.xinghanlab.com routing boundaries

- fix Manager session user id preservation after external auth login
2026-05-04 09:28:03 +08:00

231 lines
8.2 KiB
Go

package controller
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/heicode/manager/common"
"github.com/heicode/manager/model"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
)
func setupResourceControllerTestDB(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{}))
t.Cleanup(func() {
sqlDB, err := db.DB()
if err == nil {
_ = sqlDB.Close()
}
})
return db
}
func performResourceRequest(handler gin.HandlerFunc, userID int, method string, path string, body string) *httptest.ResponseRecorder {
return performResourceRequestWithRoute(handler, userID, method, path, path, body)
}
func performResourceRequestWithRoute(handler gin.HandlerFunc, userID int, method string, routePath string, requestPath string, body string) *httptest.ResponseRecorder {
r := gin.New()
r.Handle(method, routePath, func(c *gin.Context) {
c.Set("id", userID)
handler(c)
})
req := httptest.NewRequest(method, requestPath, bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func TestCreateResourceStoresMetadataAndSecretRefOnly(t *testing.T) {
db := setupResourceControllerTestDB(t)
body := `{
"binding_scope":"https://example.com/org/repo#main",
"name":"Project repository",
"resource_type":"git",
"provider":"github",
"external_id":"https://example.com/org/repo",
"secret_ref":"vault://secret/resources/repo",
"metadata":{"repo_url":"https://example.com/org/repo","ref":"main","allowed_paths":["."]},
"permission_scope":{"actions":["read","write"]},
"constraints":{"environment":"dev"}
}`
w := performResourceRequest(CreateResource, 7, http.MethodPost, "/", body)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"success":true`)
require.Contains(t, w.Body.String(), `"binding_scope":"https://example.com/org/repo#main"`)
require.Contains(t, w.Body.String(), `"secret_ref":"vault://secret/resources/repo"`)
var resource model.ResourceBinding
require.NoError(t, db.First(&resource).Error)
require.Equal(t, "git", resource.ResourceType)
require.Equal(t, "https://example.com/org/repo#main", resource.BindingScope)
require.Equal(t, "vault://secret/resources/repo", resource.SecretRef)
require.NotContains(t, resource.Metadata, "token")
require.NotContains(t, resource.PermissionScope, "token")
require.NotContains(t, resource.Constraints, "token")
}
func TestCreateResourceRejectsPlaintextSecretKeys(t *testing.T) {
setupResourceControllerTestDB(t)
body := `{
"name":"Cloud account",
"resource_type":"cloud_account",
"metadata":{"account_id":"sub-1","access_key":"do-not-store"},
"secret_ref":"vault://secret/cloud/sub-1"
}`
w := performResourceRequest(CreateResource, 7, http.MethodPost, "/", body)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"success":false`)
require.Contains(t, w.Body.String(), "plaintext secrets are not allowed")
}
func TestCreateResourceGrantAssignsBoundResourceToRoleAgnet(t *testing.T) {
db := setupResourceControllerTestDB(t)
resource := model.ResourceBinding{
UserId: 7,
BindingScope: "https://example.com/sk.git#main",
Name: "SK repo",
ResourceType: "sk",
Provider: "git",
SecretRef: "vault://secret/resources/sk-repo",
Metadata: `{"repo_url":"https://example.com/sk.git"}`,
Status: "active",
}
require.NoError(t, db.Create(&resource).Error)
body := fmt.Sprintf(`{
"binding_scope":"https://example.com/sk.git#main",
"resource_id":%d,
"role":"developer",
"agnet_id":"agnet-dev-1",
"permission_scope":{"actions":["read"]},
"constraints":{"paths":["skills/**"]}
}`, resource.Id)
w := performResourceRequest(CreateResourceGrant, 7, http.MethodPost, "/", body)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"success":true`)
require.Contains(t, w.Body.String(), `"binding_scope":"https://example.com/sk.git#main"`)
require.Contains(t, w.Body.String(), `"role":"developer"`)
require.Contains(t, w.Body.String(), `"agnet_id":"agnet-dev-1"`)
var grant model.ResourceGrant
require.NoError(t, db.First(&grant).Error)
require.Equal(t, resource.Id, grant.ResourceId)
require.Equal(t, "https://example.com/sk.git#main", grant.BindingScope)
require.Equal(t, "developer", grant.Role)
require.Equal(t, "agnet-dev-1", grant.AgnetId)
}
func TestCreateResourceGrantRejectsMismatchedBindingScope(t *testing.T) {
db := setupResourceControllerTestDB(t)
resource := model.ResourceBinding{
UserId: 7,
BindingScope: "azure-vm-prod",
Name: "VM",
ResourceType: "cloud_resource",
Status: "active",
}
require.NoError(t, db.Create(&resource).Error)
body := fmt.Sprintf(`{
"binding_scope":"azure-vm-dev",
"resource_id":%d,
"role":"operator",
"agnet_id":"agnet-ops-1"
}`, resource.Id)
w := performResourceRequest(CreateResourceGrant, 7, http.MethodPost, "/", body)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"success":false`)
require.Contains(t, w.Body.String(), "resource binding_scope does not match grant binding_scope")
}
func TestUpsertResourceSecretWritesOpenBaoAndStoresOnlySecretRef(t *testing.T) {
db := setupResourceControllerTestDB(t)
resource := model.ResourceBinding{
UserId: 7,
BindingScope: "github-org-repo-main",
Name: "GitHub",
ResourceType: "git",
Provider: "github",
Status: "active",
}
require.NoError(t, db.Create(&resource).Error)
var writtenPath string
var writtenBody map[string]map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/v1/secret/data/users/7/bindings/github-org-repo-main/resources/1", r.URL.Path)
require.Equal(t, "manager-token", r.Header.Get("X-Vault-Token"))
require.NoError(t, common.DecodeJson(r.Body, &writtenBody))
writtenPath = r.URL.Path
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"data":{"version":1}}`))
}))
defer server.Close()
t.Setenv("OPENBAO_ADDR", server.URL)
t.Setenv("OPENBAO_TOKEN", "manager-token")
t.Setenv("OPENBAO_KV_MOUNT", "secret")
body := `{"data":{"access_token":"do-not-echo","username":"octo"}}`
w := performResourceRequestWithRoute(UpsertResourceSecret, 7, http.MethodPost, "/:id/secret", "/1/secret", body)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"success":true`)
require.Contains(t, w.Body.String(), `"secret_ref":"vault://secret/users/7/bindings/github-org-repo-main/resources/1"`)
require.NotContains(t, w.Body.String(), "do-not-echo")
require.Equal(t, "/v1/secret/data/users/7/bindings/github-org-repo-main/resources/1", writtenPath)
require.Equal(t, "do-not-echo", writtenBody["data"]["access_token"])
var stored model.ResourceBinding
require.NoError(t, db.First(&stored, resource.Id).Error)
require.Equal(t, "vault://secret/users/7/bindings/github-org-repo-main/resources/1", stored.SecretRef)
require.NotContains(t, stored.Metadata, "do-not-echo")
}
func TestUpsertResourceSecretRequiresSecretStoreToken(t *testing.T) {
db := setupResourceControllerTestDB(t)
resource := model.ResourceBinding{
UserId: 7,
BindingScope: "github",
Name: "GitHub",
ResourceType: "git",
Status: "active",
}
require.NoError(t, db.Create(&resource).Error)
t.Setenv("OPENBAO_ADDR", "http://127.0.0.1:8200")
t.Setenv("OPENBAO_TOKEN", "")
t.Setenv("VAULT_TOKEN", "")
t.Setenv("OPENBAO_TOKEN_FILE", "")
t.Setenv("VAULT_TOKEN_FILE", "")
w := performResourceRequestWithRoute(UpsertResourceSecret, 7, http.MethodPost, "/:id/secret", "/1/secret", `{"data":{"token":"x"}}`)
require.Equal(t, http.StatusOK, w.Code)
require.Contains(t, w.Body.String(), `"success":false`)
require.Contains(t, w.Body.String(), "secret store token is not configured")
}