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 TestGenerateResourceGrantManifestIncludesActiveGrantsOnly(t *testing.T) { db := setupResourceControllerTestDB(t) resource := model.ResourceBinding{ UserId: 7, BindingScope: "repo-main", Name: "Project repository", ResourceType: "git", Provider: "github", ExternalId: "https://example.com/org/repo.git", SecretRef: "vault://secret/resources/repo-main", Metadata: `{"repo_url":"https://example.com/org/repo.git"}`, PermissionScope: `{"actions":["read","write"]}`, Status: "active", } require.NoError(t, db.Create(&resource).Error) require.NoError(t, db.Create(&model.ResourceGrant{ UserId: 7, BindingScope: "repo-main", ResourceId: resource.Id, Role: "backend", AgnetId: "agnet-backend-1", PermissionScope: `{"actions":["read"]}`, Constraints: `{"paths":["heicode/controller/**"]}`, Status: "active", }).Error) require.NoError(t, db.Create(&model.ResourceGrant{ UserId: 7, BindingScope: "repo-main", ResourceId: resource.Id, Role: "backend", AgnetId: "agnet-backend-1", PermissionScope: `{"actions":["write"]}`, Status: "revoked", }).Error) w := performResourceRequestWithRoute( GenerateResourceGrantManifest, 7, http.MethodGet, "/manifest", "/manifest?binding_scope=repo-main&role=backend&agnet_id=agnet-backend-1", "", ) require.Equal(t, http.StatusOK, w.Code) require.Contains(t, w.Body.String(), `"success":true`) require.Contains(t, w.Body.String(), `"binding_scope":"repo-main"`) require.Contains(t, w.Body.String(), `"agent_role":"backend"`) require.Contains(t, w.Body.String(), `"target_agent_ref":"agnet-backend-1"`) require.Contains(t, w.Body.String(), `"resource_type":"git"`) require.Contains(t, w.Body.String(), `"allowed_actions":["read"]`) require.Contains(t, w.Body.String(), `"secret_ref":"vault://secret/resources/repo-main"`) require.NotContains(t, w.Body.String(), `"allowed_actions":["write"]`) } 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") }