# OAuth Testing with Mock OAuth2 Server

This directory contains infrastructure for testing OAuth/OIDC authentication flows using a mock OAuth2 server.

## Overview

The test setup supports two modes:

1. **Mock Mode (Default)**: Uses `mock-oauth2-server` via testcontainers for fast, deterministic tests
2. **Real Mode**: Uses actual OAuth credentials from GitHub Actions secrets for integration testing

## Quick Start

### Running Tests with Mock OAuth

```bash
# Run all OAuth integration tests (uses mock by default)
pytest tests/test_oauth_integration_flows.py -v

# Run with coverage
pytest tests/test_oauth_integration_flows.py --cov=app.auth --cov-report=term-missing
```

### Running Tests with Real OAuth (CI/GitHub Actions)

When running in GitHub Actions with secrets configured:

```bash
# Tests automatically detect real credentials and use them
pytest tests/test_oauth_integration_flows.py -v -m requires_external

# Force mock mode even with real credentials available
USE_MOCK_OAUTH=true pytest tests/test_oauth_integration_flows.py -v

# Force real mode (will skip if credentials not available)
USE_REAL_OAUTH=true pytest tests/test_oauth_integration_flows.py -v
```

## Architecture

### Components

1. **mock_oauth_server.py**: Testcontainers wrapper for mock-oauth2-server
   - Provides complete OIDC endpoints (.well-known, token, userinfo, JWKS)
   - Generates valid JWTs for testing
   - Fast startup (<1s), no persistence needed

2. **conftest_oauth.py**: Pytest fixtures for OAuth testing
   - `mock_oauth_server`: Session-scoped mock server fixture
   - `oauth_config`: OAuth configuration (mock or real)
   - `oauth_enabled_app`: Test client with OAuth enabled
   - `oauth_test_token`: Generate test JWT tokens
   - `test_user_info`: Test user claims

3. **test_oauth_integration_flows.py**: Integration tests
   - OAuth login flow
   - Token exchange and callback
   - Session management
   - Error handling
   - Real OAuth provider tests (when credentials available)

### Mock OAuth Server

The mock server (https://github.com/navikt/mock-oauth2-server) provides:

- **Authorization endpoint**: `/default/authorize`
- **Token endpoint**: `/default/token`
- **Userinfo endpoint**: `/default/userinfo`
- **JWKS endpoint**: `/default/jwks`
- **Discovery**: `/default/.well-known/openid-configuration`
- **Debug token creation**: `/debugger/token`

## Usage Examples

### Basic OAuth Test

```python
import pytest

@pytest.mark.integration
def test_oauth_login(oauth_enabled_app):
    """Test OAuth login redirects to provider."""
    response = oauth_enabled_app.get("/oauth-login", follow_redirects=False)
    assert response.status_code == 302
    assert "authorize" in response.headers["location"]
```

### Testing with Mock User

```python
from unittest.mock import patch

@pytest.mark.integration
@patch("app.auth.oauth.authentik.authorize_access_token")
async def test_oauth_callback(mock_authorize, oauth_enabled_app, test_user_info):
    """Test OAuth callback with test user."""
    mock_authorize.return_value = {
        "access_token": "test-token",
        "userinfo": test_user_info,
    }

    response = oauth_enabled_app.get("/oauth-callback?code=test-code")
    assert response.status_code == 302  # Redirects after login
```

### Testing with Generated Token

```python
@pytest.mark.integration
def test_with_jwt_token(oauth_test_token, test_user_info):
    """Test with a valid JWT from mock server."""
    # oauth_test_token is a valid JWT signed by the mock server
    # It can be verified using the mock server's JWKS endpoint
    assert oauth_test_token is not None
    print(f"Token for user: {test_user_info['email']}")
```

## Configuration

### Environment Variables

- `USE_MOCK_OAUTH=true`: Force mock mode
- `USE_REAL_OAUTH=true`: Force real mode (fails if credentials not available)
- `AUTHENTIK_CLIENT_ID`: OAuth client ID (for real mode)
- `AUTHENTIK_CLIENT_SECRET`: OAuth client secret (for real mode)
- `AUTHENTIK_CONFIG_URL`: OIDC discovery URL (for real mode)

### GitHub Actions Secrets

When these secrets are set in GitHub Actions, tests automatically use real OAuth:

```yaml
# .github/workflows/test.yml
env:
  AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }}
  AUTHENTIK_CLIENT_SECRET: ${{ secrets.AUTHENTIK_CLIENT_SECRET }}
  AUTHENTIK_CONFIG_URL: ${{ secrets.AUTHENTIK_CONFIG_URL }}
```

## Troubleshooting

### Mock Server Won't Start

```bash
# Check Docker is running
docker ps

# Pull the image manually
docker pull ghcr.io/navikt/mock-oauth2-server:2.1.1

# Check logs
pytest tests/test_oauth_integration_flows.py -v -s
```

### Tests Hang on Container Startup

The mock server fixture waits up to 30 seconds for the server to be ready. If tests hang:

1. Check Docker resources (CPU, memory)
2. Check if port 8080 is available
3. Try running with `-s` flag to see container logs

### Token Validation Fails

The mock server generates valid JWTs that can be verified using its JWKS endpoint. If validation fails:

1. Ensure the token was created from the correct mock server instance
2. Check the `aud` (audience) claim matches your client ID
3. Verify the `iss` (issuer) claim matches the mock server URL

## Benefits of This Approach

1. **Fast**: Mock server starts in <1s, tests run quickly
2. **Deterministic**: No external dependencies, same results every time
3. **Realistic**: Tests actual OAuth flows with real OIDC endpoints
4. **Flexible**: Can switch to real OAuth for integration tests
5. **CI-Friendly**: Works in ephemeral CI environments
6. **Complete**: All OIDC endpoints available for testing

## Further Reading

- [Mock OAuth2 Server Documentation](https://github.com/navikt/mock-oauth2-server)
- [Testcontainers Python](https://testcontainers-python.readthedocs.io/)
- [OAuth 2.0 RFC 6749](https://tools.ietf.org/html/rfc6749)
- [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)
