Microsoft Entra ID E2E TestingΒΆ
End-to-end testing guide for Microsoft Entra ID (Azure AD) SSO integration with ContextForge.
This guide walks you through setting up Azure resources and running fully automated E2E tests that validate group-based role assignment for platform_administrator.
OverviewΒΆ
The E2E tests in tests/e2e/test_entra_id_integration.py are fully self-contained:
- Create test users and groups in Azure AD before tests
- Execute SSO role mapping tests against real Azure infrastructure
- Delete all created resources after tests complete
This ensures repeatable, isolated test runs without manual cleanup.
PrerequisitesΒΆ
Azure SubscriptionΒΆ
You need access to an Azure tenant where you can:
- Create App Registrations
- Grant admin consent for API permissions
- Create users and groups (or have a service principal that can)
Local Development EnvironmentΒΆ
Step 1: Create an Azure App RegistrationΒΆ
1.1 Navigate to Azure PortalΒΆ
- Go to Azure Portal
- Navigate to Microsoft Entra ID > App registrations
- Click + New registration
1.2 Register the ApplicationΒΆ
| Field | Value |
|---|---|
| Name | ContextForge-E2E-Tests |
| Supported account types | Single tenant (this organization only) |
| Redirect URI | Leave blank (not needed for E2E tests) |
Click Register.
1.3 Note the Application DetailsΒΆ
After registration, note these values from the Overview page:
# You'll need these later
AZURE_CLIENT_ID=<Application (client) ID>
AZURE_TENANT_ID=<Directory (tenant) ID>
Step 2: Create a Client SecretΒΆ
- In your App Registration, go to Certificates & secrets
- Click + New client secret
- Add a description:
E2E Test Secret - Choose expiration:
12 months(or as appropriate) - Click Add
Important: Copy the secret value immediately - it won't be shown again!
Step 3: Configure API PermissionsΒΆ
The service principal needs permissions to create/delete users and groups.
3.1 Add Required PermissionsΒΆ
- Go to API permissions
- Click + Add a permission
- Select Microsoft Graph
- Choose Application permissions (not Delegated)
- Add these permissions:
| Permission | Purpose |
|---|---|
User.ReadWrite.All | Create and delete test users |
Group.ReadWrite.All | Create and delete test groups, manage membership |
GroupMember.ReadWrite.All | Add/remove users from groups |
3.2 Grant Admin ConsentΒΆ
- Click Grant admin consent for [Your Organization]
- Confirm by clicking Yes
You should see green checkmarks next to all permissions indicating consent is granted.
Step 4: Enable Public Client Flows (Optional)ΒΆ
If you want to test ROPC (Resource Owner Password Credentials) token acquisition:
- Go to Authentication
- Scroll to Advanced settings
- Set Allow public client flows to Yes
- Click Save
Note: ROPC is considered less secure and may be disabled by your organization's security policies. The E2E tests will skip ROPC tests gracefully if this is not enabled.
Step 5: Determine Your Test DomainΒΆ
Test users need a User Principal Name (UPN) in your tenant's domain.
Find Your DomainΒΆ
- Go to Microsoft Entra ID > Overview
- Look for Primary domain (e.g.,
yourcompany.onmicrosoft.com)
Or check Custom domain names for verified domains.
Step 6: Choose a Test User PasswordΒΆ
The password must meet Azure AD complexity requirements:
- Minimum 8 characters
- At least 3 of: uppercase, lowercase, number, special character
- Cannot contain the user's name
Step 7: Configure Environment VariablesΒΆ
Create a .env.test file or export variables directly:
# Azure Service Principal (for Graph API access)
export AZURE_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export AZURE_CLIENT_SECRET="your-client-secret-value"
export AZURE_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Test Configuration
export TEST_ENTRA_USER_PASSWORD="ContextForge2024!Test"
export TEST_ENTRA_DOMAIN="yourcompany.onmicrosoft.com"
Environment Variable ReferenceΒΆ
| Variable | Required | Description |
|---|---|---|
AZURE_CLIENT_ID | Yes | App Registration's Application (client) ID |
AZURE_CLIENT_SECRET | Yes | Client secret value |
AZURE_TENANT_ID | Yes | Azure AD tenant ID |
TEST_ENTRA_USER_PASSWORD | Yes | Password for dynamically created test users |
TEST_ENTRA_DOMAIN | Yes | Domain for test user UPNs (e.g., company.onmicrosoft.com) |
Step 8: Run the E2E TestsΒΆ
Run All Entra ID TestsΒΆ
# Source your environment variables
source .env.test
# Run the tests
uv run pytest tests/e2e/test_entra_id_integration.py -v
Run Specific Test ClassesΒΆ
# Test role mapping only
uv run pytest tests/e2e/test_entra_id_integration.py::TestEntraIDRoleMapping -v
# Test HTTP endpoints
uv run pytest tests/e2e/test_entra_id_integration.py::TestEntraIDEndToEndHTTP -v
# Test admin role retention behavior
uv run pytest tests/e2e/test_entra_id_integration.py::TestEntraIDAdminRoleRetention -v
# Test multiple admin groups
uv run pytest tests/e2e/test_entra_id_integration.py::TestEntraIDMultipleAdminGroups -v
# Test token validation
uv run pytest tests/e2e/test_entra_id_integration.py::TestEntraIDTokenValidation -v
Run with Debug OutputΒΆ
Test StructureΒΆ
The test suite includes these test classes:
| Class | Tests | Description |
|---|---|---|
TestEntraIDRoleMapping | 4 | Core tests for admin role assignment based on groups |
TestEntraIDEndToEndHTTP | 2 | True E2E with real ROPC tokens and HTTP endpoints |
TestEntraIDAdminRoleRetention | 2 | Verifies admin role retention behavior (by design) |
TestEntraIDMultipleAdminGroups | 2 | Tests multiple admin group configurations |
TestEntraIDTokenValidation | 4 | Tests token validation and error handling |
TestEntraIDSyncDisabled | 1 | Tests behavior when role sync is disabled |
Test Scenarios - Detailed ReferenceΒΆ
Complete Test ListΒΆ
| # | Test Name | Expected Behavior |
|---|---|---|
| TestEntraIDRoleMapping | ||
| 1 | test_admin_user_gets_admin_role | New user in Entra admin group β is_admin=True |
| 2 | test_regular_user_does_not_get_admin_role | New user NOT in admin group β is_admin=False |
| 3 | test_user_gains_admin_when_added_to_group | Existing non-admin user logs in with admin group β promoted to is_admin=True |
| 4 | test_admin_group_matching_is_case_insensitive | Group ID ABC123 matches config abc123 β is_admin=True |
| TestEntraIDEndToEndHTTP | ||
| 5 | test_sso_callback_with_real_token_creates_admin_user | Real ROPC token from Entra ID + admin group β user created with is_admin=True |
| 6 | test_sso_providers_endpoint_lists_enabled_providers | GET /auth/sso/providers returns 200 or 404 (valid response) |
| TestEntraIDAdminRoleRetention | ||
| 7 | test_admin_retains_role_when_removed_from_group | Existing admin logs in WITHOUT admin group β is_admin=True retained (by design) |
| 8 | test_admin_retains_role_when_no_groups_in_token | Existing admin logs in with empty groups β is_admin=True retained (by design) |
| TestEntraIDMultipleAdminGroups | ||
| 9 | test_user_in_secondary_admin_group_gets_admin | User in 2nd configured admin group β is_admin=True |
| 10 | test_user_in_both_admin_groups_gets_admin | User in both admin groups β is_admin=True |
| TestEntraIDTokenValidation | ||
| 11 | test_expired_token_claims_detected | Expired JWT β jwt.ExpiredSignatureError raised |
| 12 | test_invalid_audience_detected | Wrong aud claim β jwt.InvalidAudienceError raised |
| 13 | test_invalid_issuer_detected | Wrong iss claim β jwt.InvalidIssuerError raised |
| 14 | test_real_token_has_valid_claims | Real Entra token contains sub, iss, aud, exp, iat claims |
| TestEntraIDSyncDisabled | ||
| 15 | test_admin_retains_role_when_sync_disabled | sso_entra_sync_roles_on_login=False β existing admin status preserved |
Behavior Summary by ScenarioΒΆ
| Scenario | User State | Group Membership | Expected is_admin |
|---|---|---|---|
| New user | Does not exist | In admin group | True |
| New user | Does not exist | Not in admin group | False |
| Existing user | is_admin=False | Gains admin group | True (promoted) |
| Existing user | is_admin=True | Loses admin group | True (retained)* |
| Existing user | is_admin=True | Empty groups claim | True (retained)* |
| Existing user | is_admin=True | Still in admin group | True (unchanged) |
Admin Role Retention Behavior
*By design, the SSOService only upgrades admin status via SSO, never downgrades. This preserves manual admin grants made via Admin UI/API. To revoke admin access, administrators must use the Admin UI/API directly.
See Issue #2331 for security considerations and proposed improvements to this behavior.
What Each Test ValidatesΒΆ
| Category | Test Coverage |
|---|---|
| Admin promotion via SSO group membership | Tests 1, 3, 5 |
| Non-admin users don't get admin | Test 2 |
| Case-insensitive group ID matching | Test 4 |
| Real ROPC token acquisition | Tests 5, 14 |
| HTTP endpoint integration | Tests 5, 6 |
| Admin retention (no demotion) | Tests 7, 8, 15 |
| Multiple admin groups | Tests 9, 10 |
| Token validation errors | Tests 11, 12, 13 |
What Gets Created in AzureΒΆ
During test execution, the following resources are created and then deleted:
GroupsΒΆ
| Resource | Purpose |
|---|---|
ContextForge-TestAdmins-{uuid} | Primary admin group for testing |
ContextForge-TestAdmins2-{uuid} | Secondary admin group (for multiple admin groups tests) |
ContextForge-TestUsers-{uuid} | Regular (non-admin) group |
UsersΒΆ
| Resource | Purpose |
|---|---|
cftest-admin-{uuid}@{domain} | Test user in primary admin group |
cftest-regular-{uuid}@{domain} | Test user in regular group (not admin) |
All resources are automatically cleaned up after tests complete (even on test failure).
Resource LifecycleΒΆ
Test Start
β
βββ Create ContextForge-TestAdmins-{uuid}
βββ Create ContextForge-TestAdmins2-{uuid}
βββ Create ContextForge-TestUsers-{uuid}
β β
β βββ Wait for Azure AD replication
β β
βββ Create cftest-admin-{uuid} β Add to TestAdmins
βββ Create cftest-regular-{uuid} β Add to TestUsers
β β
β βββ Wait for membership replication
β β
βββ Run all 15 tests
β β
β βββ Tests use real Azure group IDs
β βββ Tests acquire real ROPC tokens (if enabled)
β β
Test End (success or failure)
β
βββ Delete cftest-admin-{uuid}
βββ Delete cftest-regular-{uuid}
βββ Delete ContextForge-TestAdmins-{uuid}
βββ Delete ContextForge-TestAdmins2-{uuid}
βββ Delete ContextForge-TestUsers-{uuid}
TroubleshootingΒΆ
Tests Skip with "Azure credentials not configured"ΒΆ
Ensure all required environment variables are set:
echo $AZURE_CLIENT_ID
echo $AZURE_CLIENT_SECRET
echo $AZURE_TENANT_ID
echo $TEST_ENTRA_USER_PASSWORD
echo $TEST_ENTRA_DOMAIN
"Insufficient privileges" ErrorΒΆ
The service principal lacks required permissions. Verify:
- Correct permissions are added (Application, not Delegated)
- Admin consent is granted
- The secret hasn't expired
"Invalid client secret" ErrorΒΆ
- Check the secret hasn't expired
- Verify you copied the Value, not the Secret ID
- Create a new secret if needed
User Creation Fails with Password Policy ErrorΒΆ
Your password doesn't meet Azure AD requirements:
# Good password examples
TEST_ENTRA_USER_PASSWORD='ContextForge2024!Test'
TEST_ENTRA_USER_PASSWORD='E2e-Testing-Password-123'
ROPC Tests SkipΒΆ
If ROPC tests are skipped, your tenant may have ROPC disabled. This is common in enterprise environments for security reasons. The other tests will still run.
CI/CD IntegrationΒΆ
GitHub Actions ExampleΒΆ
Add these secrets to your repository:
AZURE_CLIENT_IDAZURE_CLIENT_SECRETAZURE_TENANT_IDTEST_ENTRA_USER_PASSWORDTEST_ENTRA_DOMAIN
# .github/workflows/test.yml
jobs:
entra-e2e:
runs-on: ubuntu-latest
# Only run if secrets are configured
if: ${{ vars.AZURE_CLIENT_ID != '' }}
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install uv
uv sync
- name: Run Entra ID E2E Tests
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
TEST_ENTRA_USER_PASSWORD: ${{ secrets.TEST_ENTRA_USER_PASSWORD }}
TEST_ENTRA_DOMAIN: ${{ secrets.TEST_ENTRA_DOMAIN }}
run: |
uv run pytest tests/e2e/test_entra_id_integration.py -v
Security ConsiderationsΒΆ
Principle of Least PrivilegeΒΆ
The service principal has broad permissions (User.ReadWrite.All, Group.ReadWrite.All). Consider:
- Using a dedicated test tenant
- Limiting the service principal's scope via Conditional Access
- Rotating secrets regularly
Test IsolationΒΆ
Each test run creates uniquely-named resources with UUIDs, so multiple concurrent test runs won't conflict.
Cleanup GuaranteeΒΆ
The test fixtures use pytest finalizers that run even on test failure, ensuring resources are always cleaned up.
SummaryΒΆ
| Step | Action |
|---|---|
| 1 | Create Azure App Registration |
| 2 | Create client secret |
| 3 | Add API permissions (User.ReadWrite.All, Group.ReadWrite.All) |
| 4 | Grant admin consent |
| 5 | (Optional) Enable public client flows for ROPC |
| 6 | Configure environment variables |
| 7 | Run tests with uv run pytest tests/e2e/test_entra_id_integration.py -v |