Merged in feature/admin-api (pull request #191)

admin api and associated tools

* in progress

admin api started and permit.io setup tool started

* docs

* build fix

* integration tests passing

* new permit variables

* fix permit

* fix delete regex

* fix delete response

* docs

* docs
This commit is contained in:
Jay Brown
2025-10-23 22:57:15 +00:00
parent 7638fd3a90
commit 2b43799f56
29 changed files with 10579 additions and 165 deletions
+58
View File
@@ -0,0 +1,58 @@
# AWS Configuration for Integration Tests
# These credentials must have permissions to manage users in the specified Cognito User Pool
# AWS Region where your Cognito User Pool is located
AWS_REGION=us-east-1
# AWS Credentials
# Option 1: Use IAM user credentials
AWS_ACCESS_KEY_ID=your_access_key_here
AWS_SECRET_ACCESS_KEY=your_secret_key_here
# Option 2: Use temporary credentials (SSO/STS)
# AWS_SESSION_TOKEN=your_session_token_here
# Cognito Configuration
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
# Optional: Cognito Client ID if needed for JWT generation
# COGNITO_CLIENT_ID=your_client_id_here
# Suppress Cognito welcome emails to avoid hitting the 50 emails/day limit
# Set to "true" for integration tests, leave unset or set to "false" for production
COGNITO_SUPPRESS_EMAILS=true
# Skip Permit.io cleanup after tests (useful for manual inspection of created users)
# Set to "true" to leave users in Permit.io after tests complete
# Remember to manually delete test users afterward to avoid clutter
SKIP_PERMITIO_CLEANUP=false
# Permit.io Configuration
# Get these from your Permit.io dashboard
# Permit.io API Key (starts with "permit_key_")
PERMIT_IO_API_KEY=permit_key_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Permit.io Project ID
PERMIT_IO_PROJECT_ID=your_project_id_here
# Permit.io Environment ID (e.g., "dev", "staging", "production")
PERMIT_IO_ENV_ID=dev
# Permit.io Tenant (usually "default")
PERMIT_IO_TENANT=default
# Optional: Permit.io Base URL (defaults to https://api.permit.io)
# PERMIT_IO_BASE_URL=https://api.permit.io
# Test Configuration
# These control how test user emails are generated
# Prefix for test user emails (helps identify test users)
TEST_USER_EMAIL_PREFIX=integration-test-
# Domain for test user emails
TEST_USER_DOMAIN=example.com
# Example: With the above settings, test users will have emails like:
# integration-test-create-1697123456@example.com
+410
View File
@@ -0,0 +1,410 @@
# Admin User Management Integration Tests
This directory contains integration tests for admin user management functionality that use **real AWS Cognito and Permit.io APIs** (no mocks).
## Overview
These tests validate the complete user management workflow including:
- User creation in AWS Cognito and Permit.io
- User retrieval and attribute updates
- User enable/disable operations
- User deletion from both systems
- Role assignment and unassignment in Permit.io
- User listing with pagination
- Idempotent operations
## Why Integration Tests Are Separate
The admin user management code is **excluded from standard CI/CD coverage requirements** because:
1. Free tier LocalStack doesn't support AWS Cognito
2. Integration tests require real AWS credentials
3. Integration tests require real Permit.io API access
4. Tests create and modify real user accounts
5. Tests should not run automatically in CI/CD pipelines
These tests are designed for **manual execution** during development and pre-production validation.
## Prerequisites
### AWS Requirements
use `task aws:login` then
`eval $(aws configure export-credentials --profile aarete --format env)`
to set fresh credentials for the test to use.
1. **AWS Account** with access to Cognito User Pool
2. **IAM User or Role** with the following permissions:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminDeleteUser",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminUpdateUserAttributes",
"cognito-idp:AdminDisableUser",
"cognito-idp:AdminEnableUser",
"cognito-idp:ListUsers"
],
"Resource": "arn:aws:cognito-idp:REGION:ACCOUNT_ID:userpool/POOL_ID"
}
]
}
```
3. **Cognito User Pool ID** - Get this from AWS Console or CLI
### Permit.io Requirements
1. **Permit.io Account** with API access
2. **API Key** with permissions to:
- Create users
- Delete users
- Assign/unassign roles
- Read user roles
3. **Project ID** and **Environment ID** from Permit.io dashboard
4. **Roles configured** in Permit.io matching your `permit_policies.yaml`:
- `super_admin`
- `user_admin`
- `auditor`
- `client_user`
## Setup Instructions
### Step 1: Create Environment File
```bash
cd test/integration
cp .env.example .env
```
### Step 2: Configure Credentials
Edit `test/integration/.env` and fill in your actual credentials:
```bash
# AWS Configuration
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
COGNITO_USER_POOL_ID=us-east-1_AbCdEfGhI
# Suppress welcome emails to avoid AWS Cognito's 50 emails/day limit
COGNITO_SUPPRESS_EMAILS=true
# Skip Permit.io cleanup (set to true to inspect users after tests)
SKIP_PERMITIO_CLEANUP=false
# Permit.io Configuration
PERMIT_IO_API_KEY=permit_key_1234567890abcdef
PERMIT_IO_PROJECT_ID=my-project-123
PERMIT_IO_ENV_ID=dev
PERMIT_IO_TENANT=default
# Test Configuration
TEST_USER_EMAIL_PREFIX=int-test-
TEST_USER_DOMAIN=example.com
```
**IMPORTANT:** Never commit the `.env` file to git! It contains sensitive credentials.
### Step 3: Verify Permit.io Roles
**Note:** The test suite automatically loads environment variables from `test/integration/.env` when you run the tests. You do NOT need to manually source the file.
Ensure the following roles exist in your Permit.io environment (as defined in `cmd/cognito_test/permit.setup/permit_policies.yaml`):
- `super_admin`
- `user_admin`
- `auditor`
- `client_user`
You can create roles through the Permit.io dashboard, API, or use the setup tool at `cmd/cognito_test/permit.setup/`.
## Running the Tests
### Run All Integration Tests (13 total)
```bash
go test -tags=integration -v ./test/integration/...
```
This runs:
- **8 integrated tests** (Cognito + Permit.io)
- **5 Permit.io-only tests** (no Cognito calls)
### Run Only Integrated Tests (Cognito + Permit.io)
```bash
go test -tags=integration -v -run 'TestSuite/Test[^P]' ./test/integration/...
```
This excludes tests starting with `TestPermitIO_`.
### Run Only Permit.io Tests (No Cognito)
```bash
go test -tags=integration -v -run 'TestSuite/TestPermitIO' ./test/integration/...
```
Useful when you've hit AWS Cognito's email limit or want to test Permit.io in isolation.
### Run a Specific Test
```bash
go test -tags=integration -v -run TestSuite/TestCreateUser ./test/integration/...
```
### Run via Task Command
Add this task to your Taskfile.yml:
```yaml
integration:admin:
desc: "Run admin user management integration tests (requires real AWS/Permit.io credentials)"
dir: test/integration
cmds:
- |
if [ ! -f .env ]; then
echo "❌ Error: test/integration/.env not found"
echo "Copy .env.example to .env and configure your credentials"
exit 1
fi
- go test -tags=integration -v ./...
```
Then run:
```bash
task integration:admin
```
## Test Coverage
The test suite includes **13 integration tests** organized into two categories:
### Integrated Tests (8 tests - Cognito + Permit.io)
These tests validate the complete user management workflow across both systems:
| Test | Description |
|------|-------------|
| `TestCreateUser` | Creates a new user in both Cognito and Permit.io |
| `TestCreateUserIdempotent` | Verifies idempotent user creation behavior |
| `TestGetUser` | Retrieves an existing user's details from Cognito |
| `TestUpdateUserAttributes` | Updates user's first name and last name in Cognito |
| `TestDisableEnableUser` | Disables and re-enables a user account in Cognito |
| `TestDeleteUser` | Deletes user from both Cognito and Permit.io |
| `TestListUsers` | Lists users with pagination support from Cognito |
| `TestRoleAssignment` | Assigns and unassigns roles in Permit.io for Cognito user |
**Note:** These tests require AWS credentials and will send emails unless `COGNITO_SUPPRESS_EMAILS=true`.
### Permit.io-Only Tests (5 tests - No Cognito)
These tests validate Permit.io functionality in isolation using generated UUIDs:
| Test | Description |
|------|-------------|
| `TestPermitIO_CreateAndGetUser` | Creates user in Permit.io and verifies it exists |
| `TestPermitIO_AssignMultipleRoles` | Assigns/unassigns multiple roles (auditor, client_user) |
| `TestPermitIO_DeleteUser` | Deletes user and verifies with 404 response |
| `TestPermitIO_UserLifecycle` | Complete 9-step user lifecycle (create → roles → delete) |
| `TestPermitIO_IdempotentOperations` | Tests duplicate user/role operations are idempotent |
**Note:** These tests do NOT call AWS Cognito and can run unlimited times per day.
## Test User Management
### Automatic Cleanup
The test suite implements automatic cleanup in the `TearDownSuite` method:
- All test users are tracked during creation
- After all tests complete, users are automatically deleted from both Cognito and Permit.io
- If cleanup fails, warnings are logged but tests still pass
- **Optional:** Set `SKIP_PERMITIO_CLEANUP=true` to preserve Permit.io users for manual inspection (see "Manual Inspection of Test Users" section)
### Manual Cleanup
If tests are interrupted and cleanup doesn't run:
```bash
# List users in Cognito
aws cognito-idp list-users --user-pool-id YOUR_POOL_ID
# Delete a user from Cognito
aws cognito-idp admin-delete-user \
--user-pool-id YOUR_POOL_ID \
--username user@example.com
# Delete from Permit.io (requires API call)
curl -X DELETE \
"https://api.permit.io/v2/facts/PROJECT_ID/ENV_ID/users/SUBJECT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Test Email Format
Test users are created with emails in this format:
```
{PREFIX}{TEST_NAME}-{TIMESTAMP}@{DOMAIN}
```
Examples:
- Integrated test: `int-test-create-1697123456@example.com`
- Permit.io test: `int-test-permitio-create-1697123456@example.com`
This makes it easy to identify and clean up test users. You can customize the prefix and domain using environment variables:
- `TEST_USER_EMAIL_PREFIX` (default: `int-test-`)
- `TEST_USER_DOMAIN` (default: `example.com`)
## AWS Cognito Email Limits
AWS Cognito has a **daily limit of 50 emails** when using the default email service. This limit:
- Applies to welcome emails sent during user creation
- Resets daily at **09:00 UTC**
- Is shared across **all user pools** in your AWS account
- Cannot be increased without configuring Amazon SES
### Email Suppression for Testing
To avoid hitting this limit during integration testing, the test suite uses the `COGNITO_SUPPRESS_EMAILS=true` environment variable. When set:
- ✅ No welcome emails are sent to test users
- ✅ Users are still created successfully with `email_verified: true`
- ✅ Tests can run unlimited times per day
- ✅ All user management operations work normally
**This is the recommended approach for integration tests.**
### If You Need to Test Email Delivery
For production environments or when you need to test actual email delivery:
1. **Configure Amazon SES** with your Cognito User Pool
2. **Move SES out of sandbox mode** (requires AWS support ticket, 24hr response)
3. **Email limits increase to 50,000/day** in SES production mode
See [AWS Cognito Email Settings Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-email.html) for setup instructions.
## Manual Inspection of Test Users
To inspect users created during tests in the Permit.io dashboard:
1. **Set the skip cleanup flag** in `test/integration/.env`:
```bash
SKIP_PERMITIO_CLEANUP=true
```
2. **Run the Permit.io-only tests:**
```bash
go test -tags=integration -v -run 'TestSuite/TestPermitIO' ./test/integration/...
```
3. **Check test output** for user details:
```
🔵 Permit.io-only user (SubjectID: cbfc1ba6-81a1-40e1-8e4d-14950737c08f)
✅ User found in Permit.io:
- Roles: [auditor]
⏭️ Skipping Permit.io cleanup - user left for manual inspection
```
4. **Log into Permit.io dashboard** and inspect the users with the SubjectIDs from the test output
5. **Clean up manually** when done:
- In Permit.io UI: Delete the test users
- Or re-run tests with `SKIP_PERMITIO_CLEANUP=false`
**Note:** Cognito users (if any) are still cleaned up automatically to avoid AWS resource charges. Only Permit.io users are preserved when this flag is set.
## Troubleshooting
### Error: Required environment variable not set
**Solution:** Ensure all required variables are set in `test/integration/.env`
### Error: User pool not found
**Solution:** Verify `COGNITO_USER_POOL_ID` is correct and your AWS credentials have access
### Error: Permit.io API authentication failed
**Solution:**
- Verify `PERMIT_IO_API_KEY` is correct
- Check that the API key has not expired
- Ensure the API key has correct permissions
### Error: Role not found in Permit.io
**Solution:** Create the required roles in your Permit.io environment or update test to use existing roles
### Error: LimitExceededException - Exceeded daily email limit
```
api error LimitExceededException: Exceeded daily email limit for the operation or the account.
```
**Solution:**
- Ensure `COGNITO_SUPPRESS_EMAILS=true` is set in `test/integration/.env`
- The email limit resets at 09:00 UTC each day
- For production use, configure Amazon SES with your user pool (see AWS Cognito Email Limits section above)
### Tests hang or timeout
**Solution:**
- Check network connectivity to AWS and Permit.io
- Verify AWS credentials are not expired (especially for SSO/temporary credentials)
- Increase timeout if needed (edit `httpClient.Timeout` in test suite)
### Cleanup warnings
**Solution:** Warnings during cleanup are usually okay (e.g., user already deleted). However, if you see many failures:
- Check AWS credentials are still valid
- Verify Permit.io API key permissions
- Manually clean up orphaned test users
## CI/CD Exclusion
The admin handler code is **intentionally excluded** from CI/CD coverage checks:
**Excluded files in `scripts/tests.yml`:**
- `api/queryAPI/adminHandlers.go`
- `internal/usermanagement/audit.go`
- `internal/usermanagement/cognito.go`
- `internal/usermanagement/permitio.go`
- `internal/usermanagement/types.go`
This allows the main test suite to pass without requiring cloud credentials.
## Security Considerations
1. **Never commit credentials** - The `.env` file is gitignored
2. **Use test environments** - Don't run against production Cognito/Permit.io
3. **Limit IAM permissions** - Use least privilege for test credentials
4. **Rotate credentials** - Regularly rotate API keys and access keys
5. **Monitor test accounts** - Review test user creation in CloudWatch/Permit.io logs
6. **Clean up regularly** - Ensure test users are deleted after runs
## Future Enhancements
Potential improvements for the integration test suite:
- [ ] Add tests for concurrent user operations
- [ ] Test bulk user creation/deletion
- [ ] Add tests for edge cases (special characters in names, etc.)
- [ ] Test Cognito password reset flow
- [ ] Test MFA enrollment/verification
- [ ] Add performance benchmarks
- [ ] Test rate limiting behavior
- [ ] Add tests for invalid/malformed requests
- [ ] Test permission boundary scenarios in Permit.io
- [ ] Add chaos testing (network failures, timeouts, etc.)
## Support
For issues with:
- **AWS Cognito**: Check AWS documentation or contact AWS Support
- **Permit.io**: Check Permit.io documentation or contact support
- **Test suite**: Open an issue in the project repository
+785
View File
@@ -0,0 +1,785 @@
//go:build integration
// admin_integration_test.go provides integration tests for admin user management operations.
// These tests require real AWS Cognito and Permit.io credentials and are not run in CI/CD.
// Run with: go test -tags=integration ./test/integration/...
package integration
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider"
"github.com/google/uuid"
"github.com/joho/godotenv"
"github.com/stretchr/testify/suite"
"queryorchestration/internal/serviceconfig/aws"
"queryorchestration/internal/usermanagement"
)
// AdminIntegrationTestSuite provides integration testing for admin user management operations
// using real AWS Cognito and Permit.io services.
type AdminIntegrationTestSuite struct {
suite.Suite
cognitoClient *cognitoidentityprovider.Client
cognitoConfig *usermanagement.CognitoConfig
httpClient *http.Client
permitConfig *usermanagement.PermitConfig
testUsers []string // Track users for cleanup (emails)
permitOnlyUsers map[string]string // Track Permit.io-only users: email -> SubjectID
}
// SetupSuite runs once before all tests to initialize clients and validate configuration.
func (s *AdminIntegrationTestSuite) SetupSuite() {
// Load .env file if it exists (supports running tests from project root or test/integration)
// Using Overload() to override shell environment variables (e.g., from AWS SSO)
envPaths := []string{
".env", // Running from test/integration directory
"test/integration/.env", // Running from project root
filepath.Join("..", "..", ".env"), // Alternative from test/integration
}
envLoaded := false
for _, envPath := range envPaths {
if err := godotenv.Overload(envPath); err == nil {
s.T().Logf("Loaded environment variables from: %s (overriding shell variables)", envPath)
envLoaded = true
break
}
}
if !envLoaded {
s.T().Log("No .env file found - using environment variables from shell")
}
// Validate required environment variables
requiredEnvVars := map[string]string{
"AWS_REGION": "AWS region where Cognito User Pool is located (e.g., us-east-1)",
"COGNITO_USER_POOL_ID": "AWS Cognito User Pool ID (e.g., us-east-1_XXXXXXXXX)",
"PERMIT_IO_API_KEY": "Permit.io API key (starts with 'permit_key_')",
"PERMIT_IO_PROJECT_ID": "Permit.io project identifier",
"PERMIT_IO_ENV_ID": "Permit.io environment identifier (e.g., dev, staging, production)",
}
missingVars := make([]string, 0)
for envVar, description := range requiredEnvVars {
if os.Getenv(envVar) == "" {
missingVars = append(missingVars, fmt.Sprintf(" - %s: %s", envVar, description))
}
}
if len(missingVars) > 0 {
errorMsg := "\n\n❌ Missing required environment variables for integration tests\n\n" +
"The following environment variables must be set:\n"
for _, line := range missingVars {
errorMsg += line + "\n"
}
errorMsg += "\nSetup instructions:\n" +
" 1. Copy test/integration/.env.example to test/integration/.env\n" +
" 2. Fill in your AWS and Permit.io credentials\n" +
" 3. Run tests: go test -tags=integration -v ./test/integration/...\n\n" +
"Note: The .env file will be automatically loaded from test/integration/.env\n" +
"See test/integration/README.md for detailed setup instructions.\n"
s.T().Fatal(errorMsg)
}
// Initialize AWS Cognito client
awsConfig, err := aws.GetAWSConfig(context.Background())
s.Require().NoError(err, "Failed to get AWS config")
s.cognitoClient = cognitoidentityprovider.NewFromConfig(awsConfig)
s.cognitoConfig = &usermanagement.CognitoConfig{
UserPoolID: os.Getenv("COGNITO_USER_POOL_ID"),
Region: os.Getenv("AWS_REGION"),
}
// Initialize Permit.io client
s.httpClient = &http.Client{
Timeout: 30 * time.Second,
}
defaultTenant := os.Getenv("PERMIT_IO_TENANT")
if defaultTenant == "" {
defaultTenant = "default"
}
baseURL := os.Getenv("PERMIT_IO_BASE_URL")
if baseURL == "" {
baseURL = "https://api.permit.io"
}
s.permitConfig = &usermanagement.PermitConfig{
APIKey: os.Getenv("PERMIT_IO_API_KEY"),
ProjectID: os.Getenv("PERMIT_IO_PROJECT_ID"),
EnvID: os.Getenv("PERMIT_IO_ENV_ID"),
BaseURL: baseURL,
DefaultTenant: defaultTenant,
}
s.testUsers = make([]string, 0)
s.permitOnlyUsers = make(map[string]string)
s.T().Logf("Integration test suite initialized")
s.T().Logf("AWS Region: %s", s.cognitoConfig.Region)
s.T().Logf("Cognito User Pool: %s", s.cognitoConfig.UserPoolID)
s.T().Logf("Permit.io Project: %s", s.permitConfig.ProjectID)
s.T().Logf("Permit.io Environment: %s", s.permitConfig.EnvID)
}
// TearDownSuite runs once after all tests to cleanup test resources.
func (s *AdminIntegrationTestSuite) TearDownSuite() {
s.T().Log("\n=== Verifying and Cleaning Up Test Users ===")
ctx := context.Background()
// Check if Permit.io cleanup should be skipped (useful for manual inspection)
skipPermitCleanup := os.Getenv("SKIP_PERMITIO_CLEANUP") == "true"
if skipPermitCleanup {
s.T().Log("⚠️ SKIP_PERMITIO_CLEANUP=true - Permit.io users will NOT be deleted")
}
for _, email := range s.testUsers {
s.T().Logf("\n--- User: %s ---", email)
// Check if this is a Permit.io-only user
if subjectID, isPermitOnly := s.permitOnlyUsers[email]; isPermitOnly {
s.T().Logf("🔵 Permit.io-only user (SubjectID: %s)", subjectID)
// Verify user exists in Permit.io
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
if err != nil {
s.T().Logf("❌ User NOT found in Permit.io: %v", err)
} else {
s.T().Logf("✅ User found in Permit.io:")
s.T().Logf(" - Roles: %v", roles)
}
// Delete from Permit.io (unless skipped)
if skipPermitCleanup {
s.T().Logf("⏭️ Skipping Permit.io cleanup - user left for manual inspection")
} else {
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, subjectID)
if err != nil {
s.T().Logf("⚠️ Warning: Failed to delete from Permit.io: %v", err)
} else {
s.T().Logf("🗑️ Deleted from Permit.io")
}
}
continue
}
// Full integration user - cleanup both Cognito and Permit.io
cognitoUser, err := usermanagement.GetCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
if err != nil {
s.T().Logf("❌ User NOT found in Cognito: %v", err)
} else {
s.T().Logf("✅ User found in Cognito:")
s.T().Logf(" - SubjectID: %s", cognitoUser.SubjectID)
s.T().Logf(" - Email: %s", cognitoUser.Email)
s.T().Logf(" - Name: %s %s", cognitoUser.FirstName, cognitoUser.LastName)
s.T().Logf(" - Enabled: %v", cognitoUser.Enabled)
// Verify user exists in Permit.io
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, cognitoUser.SubjectID)
if err != nil {
s.T().Logf("❌ User NOT found in Permit.io: %v", err)
} else {
s.T().Logf("✅ User found in Permit.io:")
s.T().Logf(" - Roles: %v", roles)
}
// Delete from Permit.io first (unless skipped)
if skipPermitCleanup {
s.T().Logf("⏭️ Skipping Permit.io cleanup - user left for manual inspection")
} else {
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, cognitoUser.SubjectID)
if err != nil {
s.T().Logf("⚠️ Warning: Failed to delete from Permit.io: %v", err)
} else {
s.T().Logf("🗑️ Deleted from Permit.io")
}
}
}
// Delete from Cognito
err = usermanagement.DeleteCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
if err != nil {
s.T().Logf("⚠️ Warning: Failed to delete from Cognito: %v", err)
} else {
s.T().Logf("🗑️ Deleted from Cognito")
}
}
s.T().Logf("\n=== Cleanup Complete ===")
}
// generateTestEmail generates a unique email for testing.
func (s *AdminIntegrationTestSuite) generateTestEmail(testName string) string {
prefix := os.Getenv("TEST_USER_EMAIL_PREFIX")
if prefix == "" {
prefix = "integration-test-"
}
domain := os.Getenv("TEST_USER_DOMAIN")
if domain == "" {
domain = "example.com"
}
timestamp := time.Now().Unix()
return fmt.Sprintf("%s%s-%d@%s", prefix, testName, timestamp, domain)
}
// generateTestSubjectID generates a UUID for testing Permit.io without Cognito.
// Returns email and subjectID. The email is tracked for cleanup, subjectID is used for Permit.io operations.
func (s *AdminIntegrationTestSuite) generateTestSubjectID(testName string) (email string, subjectID string) {
email = s.generateTestEmail(testName)
subjectID = uuid.New().String()
s.testUsers = append(s.testUsers, email)
s.permitOnlyUsers[email] = subjectID
return email, subjectID
}
// TestCreateUser tests creating a new user in both Cognito and Permit.io.
func (s *AdminIntegrationTestSuite) TestCreateUser() {
ctx := context.Background()
email := s.generateTestEmail("create")
s.testUsers = append(s.testUsers, email)
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Test",
LastName: "User",
}
// Create user
user, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create Cognito user")
s.Require().NotEmpty(user.SubjectID, "Subject ID should not be empty")
s.Equal(email, user.Email)
s.Equal("Test", user.FirstName)
s.Equal("User", user.LastName)
// Create in Permit.io
err = usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, user)
s.Require().NoError(err, "Failed to create Permit.io user")
// Verify user exists in Cognito
retrieved, err := usermanagement.GetCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to get Cognito user")
s.Equal(user.SubjectID, retrieved.SubjectID)
}
// TestCreateUserIdempotent tests that creating an existing user is idempotent.
func (s *AdminIntegrationTestSuite) TestCreateUserIdempotent() {
ctx := context.Background()
email := s.generateTestEmail("idempotent")
s.testUsers = append(s.testUsers, email)
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Idempotent",
LastName: "Test",
}
// Create user first time
user1, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create Cognito user first time")
// Create in Permit.io
err = usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, user1)
s.Require().NoError(err, "Failed to create Permit.io user")
// Attempt to create same user again should succeed (idempotent behavior tested in handler)
exists := usermanagement.UserExistsInCognito(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.True(exists, "User should exist")
}
// TestGetUser tests retrieving an existing user.
func (s *AdminIntegrationTestSuite) TestGetUser() {
ctx := context.Background()
email := s.generateTestEmail("get")
s.testUsers = append(s.testUsers, email)
// Create user first
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Get",
LastName: "Test",
}
user, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create user")
// Get user
retrieved, err := usermanagement.GetCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to get user")
s.Equal(user.SubjectID, retrieved.SubjectID)
s.Equal(email, retrieved.Email)
s.Equal("Get", retrieved.FirstName)
s.Equal("Test", retrieved.LastName)
}
// TestUpdateUserAttributes tests updating user attributes in Cognito.
func (s *AdminIntegrationTestSuite) TestUpdateUserAttributes() {
ctx := context.Background()
email := s.generateTestEmail("update")
s.testUsers = append(s.testUsers, email)
// Create user
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Original",
LastName: "Name",
}
_, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create user")
// Update attributes
updateAttrs := usermanagement.UserAttributes{
FirstName: "Updated",
LastName: "Name",
}
err = usermanagement.UpdateCognitoUserAttributes(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email, updateAttrs)
s.Require().NoError(err, "Failed to update user attributes")
// Verify updates
retrieved, err := usermanagement.GetCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to get updated user")
s.Equal("Updated", retrieved.FirstName)
s.Equal("Name", retrieved.LastName)
}
// TestDisableEnableUser tests disabling and enabling a user.
func (s *AdminIntegrationTestSuite) TestDisableEnableUser() {
ctx := context.Background()
email := s.generateTestEmail("disable")
s.testUsers = append(s.testUsers, email)
// Create user
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Disable",
LastName: "Test",
}
user, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create user")
// User should be enabled by default
enabled, err := usermanagement.IsCognitoUserEnabled(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to check if user is enabled")
s.True(enabled, "User should be enabled initially")
// Disable user
err = usermanagement.DisableCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to disable user")
// Verify disabled
enabled, err = usermanagement.IsCognitoUserEnabled(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to check if user is disabled")
s.False(enabled, "User should be disabled")
// Re-enable user
err = usermanagement.EnableCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, user.Username)
s.Require().NoError(err, "Failed to enable user")
// Verify enabled
enabled, err = usermanagement.IsCognitoUserEnabled(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to check if user is re-enabled")
s.True(enabled, "User should be re-enabled")
}
// TestDeleteUser tests deleting a user from both systems.
func (s *AdminIntegrationTestSuite) TestDeleteUser() {
ctx := context.Background()
email := s.generateTestEmail("delete")
// Create user
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Delete",
LastName: "Test",
}
user, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create user")
// Create in Permit.io
err = usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, user)
s.Require().NoError(err, "Failed to create Permit.io user")
// Delete user
err = usermanagement.DeleteCognitoUser(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.Require().NoError(err, "Failed to delete Cognito user")
// Delete from Permit.io
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, user.SubjectID)
s.Require().NoError(err, "Failed to delete Permit.io user")
// Verify user doesn't exist
exists := usermanagement.UserExistsInCognito(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, email)
s.False(exists, "User should not exist after deletion")
// Don't add to cleanup list since already deleted
}
// TestListUsers tests listing users with pagination.
func (s *AdminIntegrationTestSuite) TestListUsers() {
ctx := context.Background()
// Create multiple test users
numUsers := 3
for i := 0; i < numUsers; i++ {
email := s.generateTestEmail(fmt.Sprintf("list-%d", i))
s.testUsers = append(s.testUsers, email)
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: fmt.Sprintf("User%d", i),
LastName: "Test",
}
_, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create test user %d", i)
}
// List users (may include other users in the pool)
result, err := usermanagement.ListCognitoUsers(ctx, s.cognitoClient, s.cognitoConfig.UserPoolID, 60, nil)
s.Require().NoError(err, "Failed to list users")
s.NotNil(result, "Result should not be nil")
s.NotEmpty(result.Users, "Should have at least some users")
// Verify our test users are in the list
foundCount := 0
for _, user := range result.Users {
for _, testEmail := range s.testUsers {
if user.Email == testEmail {
foundCount++
}
}
}
s.GreaterOrEqual(foundCount, 1, "Should find at least one of our test users")
}
// TestRoleAssignment tests assigning and unassigning roles in Permit.io.
func (s *AdminIntegrationTestSuite) TestRoleAssignment() {
ctx := context.Background()
email := s.generateTestEmail("role")
s.testUsers = append(s.testUsers, email)
// Create user
attrs := usermanagement.UserAttributes{
Email: email,
FirstName: "Role",
LastName: "Test",
}
user, err := usermanagement.CreateCognitoUser(ctx, s.cognitoClient, s.cognitoConfig, attrs)
s.Require().NoError(err, "Failed to create user")
// Create in Permit.io
err = usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, user)
s.Require().NoError(err, "Failed to create Permit.io user")
// Try roles defined in permit_policies.yaml - use the first one that exists
testRoles := []string{"super_admin", "user_admin", "auditor", "client_user"}
var testRole string
var roleFound bool
for _, role := range testRoles {
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, user.SubjectID, role)
if err == nil {
testRole = role
roleFound = true
s.T().Logf("✅ Using role '%s' for testing", testRole)
break
}
// Check if it's a 404 (role doesn't exist) vs other errors
if err != nil && !strings.Contains(err.Error(), "404") && !strings.Contains(err.Error(), "NOT_FOUND") {
// Some other error occurred, fail the test
s.Require().NoError(err, "Failed to assign role %s (unexpected error)", role)
}
}
if !roleFound {
s.T().Skip("\n❌ SKIPPED: No suitable roles found in Permit.io environment.\n\n" +
"To run this test, ensure the Permit.io environment has roles defined.\n" +
"Expected roles (from permit_policies.yaml):\n" +
" - super_admin\n - user_admin\n - auditor\n - client_user\n\n" +
"Run: task permit:setup (or use cmd/cognito_test/permit.setup/setup_permit)\n" +
"Visit: https://app.permit.io")
return
}
// Get roles - verify assignment worked
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, user.SubjectID)
s.Require().NoError(err, "Failed to get user roles")
s.Contains(roles, testRole, "User should have the assigned role")
s.T().Logf("✅ User has role '%s': %v", testRole, roles)
// Unassign role
err = usermanagement.UnassignRole(s.httpClient, s.permitConfig, user.SubjectID, testRole)
s.Require().NoError(err, "Failed to unassign role")
// Verify role removed
roles, err = usermanagement.GetUserRoles(s.httpClient, s.permitConfig, user.SubjectID)
s.Require().NoError(err, "Failed to get user roles after unassignment")
s.NotContains(roles, testRole, "User should not have the role after unassignment")
s.T().Logf("✅ Role '%s' successfully unassigned. Current roles: %v", testRole, roles)
}
// ============================================================================
// Permit.io-Specific Integration Tests
// ============================================================================
// These tests focus specifically on Permit.io operations independent of Cognito.
// They verify that all Permit.io user management functions work correctly.
// TestPermitIO_CreateAndGetUser tests creating a user in Permit.io and verifying it exists.
func (s *AdminIntegrationTestSuite) TestPermitIO_CreateAndGetUser() {
email, subjectID := s.generateTestSubjectID("permitio-create")
// Create user in Permit.io with generated UUID
permitUser := &usermanagement.CognitoUserResponse{
SubjectID: subjectID,
Email: email,
FirstName: "PermitIO",
LastName: "CreateTest",
}
err := usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, permitUser)
s.Require().NoError(err, "Failed to create Permit.io user")
s.T().Logf("✅ Created user in Permit.io: %s (SubjectID: %s)", email, subjectID)
// Verify user exists by getting roles (should return empty list, not error)
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get user roles from Permit.io")
s.NotNil(roles, "Roles should not be nil")
s.Empty(roles, "New user should have no roles initially")
s.T().Logf("✅ Verified user exists in Permit.io with no roles")
}
// TestPermitIO_AssignMultipleRoles tests assigning multiple roles to a user.
func (s *AdminIntegrationTestSuite) TestPermitIO_AssignMultipleRoles() {
email, subjectID := s.generateTestSubjectID("permitio-multirole")
// Create user in Permit.io
permitUser := &usermanagement.CognitoUserResponse{
SubjectID: subjectID,
Email: email,
FirstName: "PermitIO",
LastName: "MultiRoleTest",
}
err := usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, permitUser)
s.Require().NoError(err, "Failed to create Permit.io user")
// Try to assign multiple roles from permit_policies.yaml
rolesToAssign := []string{"auditor", "client_user"}
assignedRoles := make([]string, 0)
for _, role := range rolesToAssign {
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, subjectID, role)
if err == nil {
assignedRoles = append(assignedRoles, role)
s.T().Logf("✅ Assigned role '%s'", role)
} else if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "NOT_FOUND") {
s.T().Logf("⚠️ Role '%s' not found in environment, skipping", role)
} else {
s.Require().NoError(err, "Unexpected error assigning role %s", role)
}
}
// Verify at least one role was assigned
s.NotEmpty(assignedRoles, "Should have assigned at least one role")
// Get roles and verify
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get user roles")
for _, assignedRole := range assignedRoles {
s.Contains(roles, assignedRole, "User should have role %s", assignedRole)
}
s.T().Logf("✅ Verified user has all assigned roles: %v", roles)
// Unassign all roles
for _, role := range assignedRoles {
err = usermanagement.UnassignRole(s.httpClient, s.permitConfig, subjectID, role)
s.Require().NoError(err, "Failed to unassign role %s", role)
s.T().Logf("✅ Unassigned role '%s'", role)
}
// Verify all roles removed
roles, err = usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get user roles after unassignment")
s.Empty(roles, "User should have no roles after unassignment")
s.T().Logf("✅ Verified all roles unassigned: %v", roles)
}
// TestPermitIO_DeleteUser tests deleting a user from Permit.io and verifying it's gone.
func (s *AdminIntegrationTestSuite) TestPermitIO_DeleteUser() {
email, subjectID := s.generateTestSubjectID("permitio-delete")
// Create user in Permit.io
permitUser := &usermanagement.CognitoUserResponse{
SubjectID: subjectID,
Email: email,
FirstName: "PermitIO",
LastName: "DeleteTest",
}
err := usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, permitUser)
s.Require().NoError(err, "Failed to create Permit.io user")
s.T().Logf("✅ Created user in Permit.io")
// Verify user exists
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "User should exist before deletion")
s.T().Logf("✅ Verified user exists (roles: %v)", roles)
// Delete user from Permit.io
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to delete Permit.io user")
s.T().Logf("✅ Deleted user from Permit.io")
// Verify user no longer exists by attempting to delete again
// Note: GetUserRoles returns empty array for non-existent users (normal API behavior)
// so we verify deletion by trying to delete again and expecting 404
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, subjectID)
s.Error(err, "Deleting non-existent user should return 404 error")
s.Contains(err.Error(), "404", "Should get 404 error for deleted user")
s.T().Logf("✅ Verified user deleted (second delete returned 404)")
}
// TestPermitIO_UserLifecycle tests the complete user lifecycle in Permit.io.
func (s *AdminIntegrationTestSuite) TestPermitIO_UserLifecycle() {
email, subjectID := s.generateTestSubjectID("permitio-lifecycle")
s.T().Log("=== Starting Permit.io User Lifecycle Test ===")
// Step 1: Generate test SubjectID
s.T().Logf("Step 1: ✅ Generated test SubjectID: %s", subjectID)
// Step 2: Create user in Permit.io
permitUser := &usermanagement.CognitoUserResponse{
SubjectID: subjectID,
Email: email,
FirstName: "PermitIO",
LastName: "LifecycleTest",
}
err := usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, permitUser)
s.Require().NoError(err, "Failed to create Permit.io user")
s.T().Log("Step 2: ✅ Created user in Permit.io")
// Step 3: Verify user has no roles initially
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get initial roles")
s.Empty(roles, "New user should have no roles")
s.T().Logf("Step 3: ✅ Verified no initial roles: %v", roles)
// Step 4: Assign a role
testRole := "auditor" // Use a role from permit_policies.yaml
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, subjectID, testRole)
if err != nil && (strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "NOT_FOUND")) {
// Try alternative role if auditor doesn't exist
testRole = "client_user"
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, subjectID, testRole)
}
s.Require().NoError(err, "Failed to assign role")
s.T().Logf("Step 4: ✅ Assigned role '%s'", testRole)
// Step 5: Verify role was assigned
roles, err = usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get roles after assignment")
s.Contains(roles, testRole, "User should have the assigned role")
s.T().Logf("Step 5: ✅ Verified role assigned: %v", roles)
// Step 6: Unassign the role
err = usermanagement.UnassignRole(s.httpClient, s.permitConfig, subjectID, testRole)
s.Require().NoError(err, "Failed to unassign role")
s.T().Logf("Step 6: ✅ Unassigned role '%s'", testRole)
// Step 7: Verify role was unassigned
roles, err = usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get roles after unassignment")
s.NotContains(roles, testRole, "User should not have the role after unassignment")
s.T().Logf("Step 7: ✅ Verified role unassigned: %v", roles)
// Step 8: Delete user from Permit.io
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to delete user")
s.T().Log("Step 8: ✅ Deleted user from Permit.io")
// Step 9: Verify user is deleted by attempting to delete again (should get 404)
err = usermanagement.DeletePermitUser(s.httpClient, s.permitConfig, subjectID)
s.Error(err, "Deleting non-existent user should return 404 error")
s.Contains(err.Error(), "404", "Should get 404 error for deleted user")
s.T().Log("Step 9: ✅ Verified user deleted (second delete returned 404)")
s.T().Log("=== Permit.io User Lifecycle Test Complete ===")
}
// TestPermitIO_IdempotentOperations tests that Permit.io operations handle duplicates gracefully.
func (s *AdminIntegrationTestSuite) TestPermitIO_IdempotentOperations() {
email, subjectID := s.generateTestSubjectID("permitio-idempotent")
// Create user in Permit.io
permitUser := &usermanagement.CognitoUserResponse{
SubjectID: subjectID,
Email: email,
FirstName: "PermitIO",
LastName: "IdempotentTest",
}
err := usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, permitUser)
s.Require().NoError(err, "Failed to create Permit.io user first time")
s.T().Log("✅ Created user in Permit.io (first time)")
// Try to create the same user again - should handle gracefully
err = usermanagement.CreatePermitUser(s.httpClient, s.permitConfig, permitUser)
// Note: Depending on implementation, this might succeed (idempotent) or return conflict
// Just log the result, don't fail the test
if err != nil {
s.T().Logf("Creating duplicate user returned error (expected): %v", err)
} else {
s.T().Log("✅ Creating duplicate user succeeded (idempotent)")
}
// Assign a role
testRole := "auditor"
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, subjectID, testRole)
if err != nil && (strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "NOT_FOUND")) {
testRole = "client_user"
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, subjectID, testRole)
}
s.Require().NoError(err, "Failed to assign role")
s.T().Logf("✅ Assigned role '%s'", testRole)
// Assign the same role again - should handle gracefully
err = usermanagement.AssignRole(s.httpClient, s.permitConfig, subjectID, testRole)
if err != nil {
s.T().Logf("Assigning duplicate role returned error (may be expected): %v", err)
} else {
s.T().Logf("✅ Assigning duplicate role succeeded (idempotent)")
}
// Verify user still has the role (only once)
roles, err := usermanagement.GetUserRoles(s.httpClient, s.permitConfig, subjectID)
s.Require().NoError(err, "Failed to get user roles")
s.Contains(roles, testRole, "User should have the role")
// Count occurrences of the role (should be exactly 1)
count := 0
for _, r := range roles {
if r == testRole {
count++
}
}
s.Equal(1, count, "Role should appear exactly once, not duplicated")
s.T().Logf("✅ Verified role appears exactly once: %v", roles)
}
// TestSuite runs the integration test suite.
func TestSuite(t *testing.T) {
suite.Run(t, new(AdminIntegrationTestSuite))
}
+1
View File
@@ -0,0 +1 @@
go test -tags=integration -v -run 'TestSuite/TestPermitIO' ./...
+1
View File
@@ -0,0 +1 @@
go test -tags=integration -v ./...