Files
Jay Brown dae7bd4cc6 Merged in feature/textExtractionsPart4 (pull request #197)
implement GET /identity

* GET /identity
2025-12-16 04:26:06 +00:00

309 lines
9.8 KiB
Go

package queryapi_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
queryapi "queryorchestration/api/queryAPI"
"queryorchestration/internal/test"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// mockScopeHandler returns an HTTP handler that mocks the Permit.io /v2/api-key/scope endpoint
func mockScopeHandler(t *testing.T) http.HandlerFunc {
return 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)
}
}
// TestGetIdentity_Success tests successful retrieval of user roles and permissions
func TestGetIdentity_Success(t *testing.T) {
// Create mock Permit.io server
roleAssignmentsHandler := func(w http.ResponseWriter, r *http.Request) {
// Return role assignments for the user
response := []map[string]interface{}{
{"role": "auditor", "user": "test-subject-id", "tenant": "default"},
{"role": "viewer", "user": "test-subject-id", "tenant": "default"},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
}
auditorRoleHandler := func(w http.ResponseWriter, r *http.Request) {
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)
}
viewerRoleHandler := func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"key": "viewer",
"name": "Viewer",
"description": "Basic viewing permissions",
"permissions": []string{"document:read"},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
}
// Create mock server with routing
mux := http.NewServeMux()
mux.HandleFunc("/v2/api-key/scope", mockScopeHandler(t))
mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", roleAssignmentsHandler)
mux.HandleFunc("/v2/schema/test-project/test-env/roles/auditor", auditorRoleHandler)
mux.HandleFunc("/v2/schema/test-project/test-env/roles/viewer", viewerRoleHandler)
server := httptest.NewServer(mux)
defer server.Close()
// Set environment variable for Permit.io base URL (project/env derived from API key scope)
t.Setenv("PERMIT_IO_BASE_URL", server.URL)
// Create controller config
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
cfg.SetPermitIOAPIKey("test-api-key")
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
// Create echo context with user claims
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/identity", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
// Set user claims in context (simulating authenticated user)
ctx.Set("user_claims", map[string]interface{}{
"sub": "test-subject-id",
})
err := cons.GetIdentity(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var response queryapi.IdentityResponse
err = json.Unmarshal(rec.Body.Bytes(), &response)
require.NoError(t, err)
assert.Len(t, response.Roles, 2)
// Find auditor role
var auditorRole *queryapi.IdentityRole
var viewerRole *queryapi.IdentityRole
for i := range response.Roles {
if response.Roles[i].Key == "auditor" {
auditorRole = &response.Roles[i]
}
if response.Roles[i].Key == "viewer" {
viewerRole = &response.Roles[i]
}
}
require.NotNil(t, auditorRole)
assert.Equal(t, "Auditor", auditorRole.Name)
assert.NotNil(t, auditorRole.Description)
assert.Equal(t, "Read-only access to view documents", *auditorRole.Description)
assert.Equal(t, []string{"document:read", "document:list", "export:read"}, auditorRole.Permissions)
require.NotNil(t, viewerRole)
assert.Equal(t, "Viewer", viewerRole.Name)
assert.Equal(t, []string{"document:read"}, viewerRole.Permissions)
}
// TestGetIdentity_NoRoles tests user with no roles assigned
func TestGetIdentity_NoRoles(t *testing.T) {
// Create mock Permit.io server that returns empty roles
mux := http.NewServeMux()
mux.HandleFunc("/v2/api-key/scope", mockScopeHandler(t))
mux.HandleFunc("/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(`[]`))
require.NoError(t, err)
})
server := httptest.NewServer(mux)
defer server.Close()
t.Setenv("PERMIT_IO_BASE_URL", server.URL)
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
cfg.SetPermitIOAPIKey("test-api-key")
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/identity", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.Set("user_claims", map[string]interface{}{
"sub": "test-subject-id",
})
err := cons.GetIdentity(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var response queryapi.IdentityResponse
err = json.Unmarshal(rec.Body.Bytes(), &response)
require.NoError(t, err)
assert.Empty(t, response.Roles)
}
// TestGetIdentity_Unauthorized tests request without authentication
func TestGetIdentity_Unauthorized(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
cfg.SetPermitIOAPIKey("test-api-key")
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/identity", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
// No user_claims set - simulating unauthenticated request
err := cons.GetIdentity(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, rec.Code)
var response queryapi.ErrorMessage
err = json.Unmarshal(rec.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response.Message, "Could not identify user")
}
// TestGetIdentity_PermitAPIError tests handling of Permit.io API errors
func TestGetIdentity_PermitAPIError(t *testing.T) {
// Create mock server that returns error
mux := http.NewServeMux()
mux.HandleFunc("/v2/api-key/scope", mockScopeHandler(t))
mux.HandleFunc("/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.StatusInternalServerError)
_, err := w.Write([]byte(`{"error": "Internal server error"}`))
require.NoError(t, err)
})
server := httptest.NewServer(mux)
defer server.Close()
t.Setenv("PERMIT_IO_BASE_URL", server.URL)
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
cfg.SetPermitIOAPIKey("test-api-key")
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/identity", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.Set("user_claims", map[string]interface{}{
"sub": "test-subject-id",
})
err := cons.GetIdentity(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusBadGateway, rec.Code)
var response queryapi.ErrorMessage
err = json.Unmarshal(rec.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response.Message, "Failed to retrieve roles")
}
// TestGetIdentity_RoleDetailsFallback tests graceful degradation when role details cannot be fetched
func TestGetIdentity_RoleDetailsFallback(t *testing.T) {
// Create mock server where role assignments work but role details fail
mux := http.NewServeMux()
mux.HandleFunc("/v2/api-key/scope", mockScopeHandler(t))
mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) {
response := []map[string]interface{}{
{"role": "auditor", "user": "test-subject-id", "tenant": "default"},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(response)
require.NoError(t, err)
})
mux.HandleFunc("/v2/schema/test-project/test-env/roles/auditor", func(w http.ResponseWriter, r *http.Request) {
// Role details endpoint returns error
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, err := w.Write([]byte(`{"error": "Role not found"}`))
require.NoError(t, err)
})
server := httptest.NewServer(mux)
defer server.Close()
t.Setenv("PERMIT_IO_BASE_URL", server.URL)
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
cfg.SetPermitIOAPIKey("test-api-key")
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/identity", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.Set("user_claims", map[string]interface{}{
"sub": "test-subject-id",
})
err := cons.GetIdentity(ctx)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var response queryapi.IdentityResponse
err = json.Unmarshal(rec.Body.Bytes(), &response)
require.NoError(t, err)
// Should still return the role, but with fallback values
require.Len(t, response.Roles, 1)
assert.Equal(t, "auditor", response.Roles[0].Key)
assert.Equal(t, "auditor", response.Roles[0].Name) // Fallback to key
assert.Empty(t, response.Roles[0].Permissions) // Empty permissions on failure
}