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 }