Files
query-orchestration/internal/usermanagement/permitio_test.go
T
Jacob Mathison e3d9047143 Merged in jmathison/DEVOPS-722 (pull request #226)
DEVOPS-722 enforce team management visibility

* DEVOPS-722 enforce team management visibility

* DEVOPS-722 reduce admin user list complexity

* DEVOPS-722 cover admin access helpers

* DEVOPS-722 address team management PR feedback

* DEVOPS-722 fix admin access tests in CI

* DEVOPS-722 fix remaining admin PR feedback
2026-05-12 16:49:12 +00:00

495 lines
16 KiB
Go

package usermanagement
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// createMockPermitServer creates a mock server with scope endpoint and optional additional handlers.
// It returns the server and a PermitConfig configured to use it.
func createMockPermitServer(t *testing.T, additionalHandlers map[string]http.HandlerFunc) (*httptest.Server, *PermitConfig) {
mux := http.NewServeMux()
// Always add the scope handler
mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"organization_id": "test-org",
"project_id": "test-project",
"environment_id": "test-env",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
})
// Add any additional handlers
for path, handler := range additionalHandlers {
mux.HandleFunc(path, handler)
}
server := httptest.NewServer(mux)
config := &PermitConfig{
APIKey: "test-api-key",
BaseURL: server.URL,
}
return server, config
}
// TestGetAPIKeyScope_Success tests successful API key scope retrieval
func TestGetAPIKeyScope_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method)
assert.Equal(t, "/v2/api-key/scope", r.URL.Path)
assert.Equal(t, "Bearer test-api-key", r.Header.Get("Authorization"))
response := map[string]string{
"organization_id": "org-123",
"project_id": "proj-456",
"environment_id": "env-789",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
}))
defer server.Close()
scope, err := GetAPIKeyScope(server.Client(), "test-api-key", server.URL)
require.NoError(t, err)
assert.NotNil(t, scope)
assert.Equal(t, "org-123", scope.OrganizationID)
assert.Equal(t, "proj-456", scope.ProjectID)
assert.Equal(t, "env-789", scope.EnvironmentID)
}
func TestGetPermitUserRolesEscapesUserKey(t *testing.T) {
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "jacob+2@mathisonprojects.com", r.URL.Query().Get("user"))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`[{"role":"super_admin","user":"jacob+2@mathisonprojects.com","tenant":"default"}]`))
require.NoError(t, err)
},
})
defer server.Close()
roles, err := GetPermitUserRoles(server.Client(), config, "jacob+2@mathisonprojects.com")
require.NoError(t, err)
assert.Equal(t, []string{"super_admin"}, roles)
}
func TestListAllPermitRoleAssignmentsDirectArray(t *testing.T) {
requests := 0
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
requests++
assert.Empty(t, r.URL.Query().Get("user"))
assert.Equal(t, "1", r.URL.Query().Get("page"))
assert.Equal(t, "100", r.URL.Query().Get("per_page"))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`[{"role":"client_user","user":"sub-1","tenant":"default"},{"role":"user_admin","user":"sub-1","tenant":"default"}]`))
require.NoError(t, err)
},
})
defer server.Close()
assignments, err := ListAllPermitRoleAssignments(server.Client(), config)
require.NoError(t, err)
require.Len(t, assignments, 2)
assert.Equal(t, "client_user", assignments[0].Role)
assert.Equal(t, "sub-1", assignments[0].User)
assert.Equal(t, 1, requests)
}
func TestListAllPermitRoleAssignmentsObjectWrappedPagination(t *testing.T) {
requests := 0
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
requests++
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
if r.URL.Query().Get("page") == "1" {
page := make([]map[string]string, 0, 100)
for i := 0; i < 100; i++ {
page = append(page, map[string]string{"role": "client_user", "user": "sub-page-1", "tenant": "default"})
}
err := json.NewEncoder(w).Encode(map[string]interface{}{"data": page})
require.NoError(t, err)
return
}
err := json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]string{{"role": "auditor", "user": "sub-page-2", "tenant": "default"}},
})
require.NoError(t, err)
},
})
defer server.Close()
assignments, err := ListAllPermitRoleAssignments(server.Client(), config)
require.NoError(t, err)
assert.Len(t, assignments, 101)
assert.Equal(t, "auditor", assignments[100].Role)
assert.Equal(t, "sub-page-2", assignments[100].User)
assert.Equal(t, 2, requests)
}
func TestListAllPermitRoleAssignmentsRejectsUnknownSuccessfulShape(t *testing.T) {
server, config := createMockPermitServer(t, map[string]http.HandlerFunc{
"/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`{"unexpected":[]}`))
require.NoError(t, err)
},
})
defer server.Close()
assignments, err := ListAllPermitRoleAssignments(server.Client(), config)
require.Error(t, err)
assert.Nil(t, assignments)
assert.Contains(t, err.Error(), "unexpected Permit.io role assignments response format")
}
// TestGetAPIKeyScope_OrgLevelKey tests API key with only organization scope
func TestGetAPIKeyScope_OrgLevelKey(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"organization_id": "org-123",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
}))
defer server.Close()
scope, err := GetAPIKeyScope(server.Client(), "test-api-key", server.URL)
require.NoError(t, err)
assert.NotNil(t, scope)
assert.Equal(t, "org-123", scope.OrganizationID)
assert.Empty(t, scope.ProjectID)
assert.Empty(t, scope.EnvironmentID)
}
// TestGetAPIKeyScope_Unauthorized tests unauthorized API key
func TestGetAPIKeyScope_Unauthorized(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
_, err := w.Write([]byte(`{"error": "Invalid API key"}`))
require.NoError(t, err)
}))
defer server.Close()
scope, err := GetAPIKeyScope(server.Client(), "invalid-key", server.URL)
assert.Error(t, err)
assert.Nil(t, scope)
assert.Contains(t, err.Error(), "API error (status 401)")
}
// TestPermitConfig_GetProjectID tests the GetProjectID method
func TestPermitConfig_GetProjectID(t *testing.T) {
server, config := createMockPermitServer(t, nil)
defer server.Close()
projectID, err := config.GetProjectID(server.Client())
require.NoError(t, err)
assert.Equal(t, "test-project", projectID)
}
// TestPermitConfig_GetEnvID tests the GetEnvID method
func TestPermitConfig_GetEnvID(t *testing.T) {
server, config := createMockPermitServer(t, nil)
defer server.Close()
envID, err := config.GetEnvID(server.Client())
require.NoError(t, err)
assert.Equal(t, "test-env", envID)
}
// TestPermitConfig_GetProjectID_OrgLevelKey tests GetProjectID with org-level key
func TestPermitConfig_GetProjectID_OrgLevelKey(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"organization_id": "org-123",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
}))
defer server.Close()
config := &PermitConfig{
APIKey: "test-api-key",
BaseURL: server.URL,
}
projectID, err := config.GetProjectID(server.Client())
assert.Error(t, err)
assert.Empty(t, projectID)
assert.ErrorIs(t, err, ErrOrgLevelKey)
}
// TestGetRoleDetails_Success tests successful role details retrieval
func TestGetRoleDetails_Success(t *testing.T) {
handlers := map[string]http.HandlerFunc{
"/v2/schema/test-project/test-env/roles/auditor": func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "GET", r.Method)
assert.Equal(t, "Bearer test-api-key", r.Header.Get("Authorization"))
response := map[string]interface{}{
"key": "auditor",
"name": "Auditor",
"description": "Read-only access to view documents",
"permissions": []string{"document:read", "document:list", "export:read"},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
roleDetails, err := GetRoleDetails(server.Client(), config, "auditor")
require.NoError(t, err)
assert.NotNil(t, roleDetails)
assert.Equal(t, "auditor", roleDetails.Key)
assert.Equal(t, "Auditor", roleDetails.Name)
assert.Equal(t, "Read-only access to view documents", roleDetails.Description)
assert.Equal(t, []string{"document:read", "document:list", "export:read"}, roleDetails.Permissions)
}
// TestGetRoleDetails_EmptyPermissions tests role with no permissions
func TestGetRoleDetails_EmptyPermissions(t *testing.T) {
handlers := map[string]http.HandlerFunc{
"/v2/schema/test-project/test-env/roles/viewer": func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"key": "viewer",
"name": "Viewer",
"description": "",
"permissions": []string{},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
roleDetails, err := GetRoleDetails(server.Client(), config, "viewer")
require.NoError(t, err)
assert.NotNil(t, roleDetails)
assert.Equal(t, "viewer", roleDetails.Key)
assert.Equal(t, "Viewer", roleDetails.Name)
assert.Empty(t, roleDetails.Description)
assert.Empty(t, roleDetails.Permissions)
}
// TestGetRoleDetails_NilPermissions tests role with nil permissions field
func TestGetRoleDetails_NilPermissions(t *testing.T) {
handlers := map[string]http.HandlerFunc{
"/v2/schema/test-project/test-env/roles/basic": func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"key": "basic",
"name": "Basic User",
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
roleDetails, err := GetRoleDetails(server.Client(), config, "basic")
require.NoError(t, err)
assert.NotNil(t, roleDetails)
assert.Equal(t, "basic", roleDetails.Key)
assert.Equal(t, "Basic User", roleDetails.Name)
assert.Nil(t, roleDetails.Permissions)
}
// TestGetRoleDetails_NotFound tests role not found error
func TestGetRoleDetails_NotFound(t *testing.T) {
handlers := map[string]http.HandlerFunc{
"/v2/schema/test-project/test-env/roles/nonexistent": func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, err := w.Write([]byte(`{"error": "Role not found"}`))
require.NoError(t, err)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
roleDetails, err := GetRoleDetails(server.Client(), config, "nonexistent")
assert.Error(t, err)
assert.Nil(t, roleDetails)
assert.Contains(t, err.Error(), "API error (status 404)")
}
// TestGetRoleDetails_Unauthorized tests unauthorized error
func TestGetRoleDetails_Unauthorized(t *testing.T) {
handlers := map[string]http.HandlerFunc{
"/v2/schema/test-project/test-env/roles/auditor": func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
_, err := w.Write([]byte(`{"error": "Invalid API key"}`))
require.NoError(t, err)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
roleDetails, err := GetRoleDetails(server.Client(), config, "auditor")
assert.Error(t, err)
assert.Nil(t, roleDetails)
assert.Contains(t, err.Error(), "API error (status 401)")
}
// TestGetRoleDetails_InvalidJSON tests invalid JSON response handling
func TestGetRoleDetails_InvalidJSON(t *testing.T) {
handlers := map[string]http.HandlerFunc{
"/v2/schema/test-project/test-env/roles/auditor": func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(`invalid json`))
require.NoError(t, err)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
roleDetails, err := GetRoleDetails(server.Client(), config, "auditor")
assert.Error(t, err)
assert.Nil(t, roleDetails)
assert.Contains(t, err.Error(), "error parsing response")
}
// TestGetRoleDetails_ConnectionError tests connection error handling
func TestGetRoleDetails_ConnectionError(t *testing.T) {
config := &PermitConfig{
APIKey: "test-api-key",
BaseURL: "http://nonexistent-host:9999",
}
client := &http.Client{}
roleDetails, err := GetRoleDetails(client, config, "auditor")
assert.Error(t, err)
assert.Nil(t, roleDetails)
// Error will be from GetProjectID trying to fetch scope
assert.Contains(t, err.Error(), "failed to get project ID")
}
// TestCreatePermitUser_IncludesEnvironmentID verifies that CreatePermitUser includes
// the environment_id attribute when the CognitoUserResponse has a non-empty EnvironmentID.
func TestCreatePermitUser_IncludesEnvironmentID(t *testing.T) {
var capturedBody PermitUser
handlers := map[string]http.HandlerFunc{
"/v2/facts/test-project/test-env/users": func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method)
err := json.NewDecoder(r.Body).Decode(&capturedBody)
require.NoError(t, err)
w.WriteHeader(http.StatusCreated)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
cognitoUser := &CognitoUserResponse{
SubjectID: "sub-123",
Username: "test@example.com",
Email: "test@example.com",
FirstName: "Test",
LastName: "User",
Status: "CONFIRMED",
Enabled: true,
EnvironmentID: "acmehealth",
}
err := CreatePermitUser(server.Client(), config, cognitoUser)
require.NoError(t, err)
assert.Equal(t, "acmehealth", capturedBody.Attributes["environment_id"],
"environment_id attribute should be set when user has EnvironmentID")
assert.Equal(t, "test@example.com", capturedBody.Attributes["cognito_username"])
assert.Equal(t, "CONFIRMED", capturedBody.Attributes["cognito_status"])
}
// TestCreatePermitUser_OmitsEnvironmentIDWhenEmpty verifies that CreatePermitUser
// does NOT include environment_id in attributes when EnvironmentID is empty.
func TestCreatePermitUser_OmitsEnvironmentIDWhenEmpty(t *testing.T) {
var capturedBody PermitUser
handlers := map[string]http.HandlerFunc{
"/v2/facts/test-project/test-env/users": func(w http.ResponseWriter, r *http.Request) {
err := json.NewDecoder(r.Body).Decode(&capturedBody)
require.NoError(t, err)
w.WriteHeader(http.StatusCreated)
},
}
server, config := createMockPermitServer(t, handlers)
defer server.Close()
cognitoUser := &CognitoUserResponse{
SubjectID: "sub-456",
Username: "legacy@example.com",
Email: "legacy@example.com",
FirstName: "Legacy",
LastName: "User",
Status: "CONFIRMED",
Enabled: true,
EnvironmentID: "", // empty -- should NOT appear
}
err := CreatePermitUser(server.Client(), config, cognitoUser)
require.NoError(t, err)
_, hasEnvID := capturedBody.Attributes["environment_id"]
assert.False(t, hasEnvID,
"environment_id attribute should NOT be present when user has no EnvironmentID")
assert.Equal(t, "legacy@example.com", capturedBody.Attributes["cognito_username"])
}