diff --git a/docs/OAuth_Testing_CI_CD.md b/docs/OAuth_Testing_CI_CD.md new file mode 100644 index 00000000..3888c800 --- /dev/null +++ b/docs/OAuth_Testing_CI_CD.md @@ -0,0 +1,360 @@ +# Using Mock OAuth2 Server in CI/CD + +This guide explains how to use the mock OAuth2 server infrastructure in continuous integration pipelines. + +## GitHub Actions Configuration + +### Running with Mock OAuth (Default) + +The tests automatically use mock OAuth by default. No special configuration needed: + +```yaml +name: Tests with Mock OAuth + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + + - name: Run OAuth tests (mock) + run: | + pytest tests/test_oauth_integration_flows.py -v +``` + +### Running with Real OAuth (Using Secrets) + +To test with real OAuth credentials (e.g., Authentik, Auth0): + +```yaml +name: Tests with Real OAuth + +on: [push, pull_request] + +jobs: + test-real-oauth: + runs-on: ubuntu-latest + # Only run if secrets are available (not on external PRs) + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + + - name: Run OAuth tests (real) + env: + AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }} + AUTHENTIK_CLIENT_SECRET: ${{ secrets.AUTHENTIK_CLIENT_SECRET }} + AUTHENTIK_CONFIG_URL: ${{ secrets.AUTHENTIK_CONFIG_URL }} + run: | + # Tests auto-detect real credentials and use them + pytest tests/test_oauth_integration_flows.py -v -m requires_external +``` + +### Hybrid Approach (Best Practice) + +Run both mock and real tests in separate jobs: + +```yaml +name: OAuth Tests + +on: [push, pull_request] + +jobs: + test-mock-oauth: + name: OAuth Tests (Mock) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + + - name: Run mock OAuth tests + run: | + pytest tests/test_oauth_integration_flows.py \ + -v \ + -m "not requires_external" + + test-real-oauth: + name: OAuth Tests (Real - Internal Only) + runs-on: ubuntu-latest + # Only run on internal commits where secrets are available + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + + - name: Run real OAuth tests + env: + AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }} + AUTHENTIK_CLIENT_SECRET: ${{ secrets.AUTHENTIK_CLIENT_SECRET }} + AUTHENTIK_CONFIG_URL: ${{ secrets.AUTHENTIK_CONFIG_URL }} + run: | + pytest tests/test_oauth_integration_flows.py \ + -v \ + -m requires_external +``` + +## Required GitHub Secrets + +To enable real OAuth testing, configure these secrets in your repository: + +1. Go to **Settings → Secrets and variables → Actions** +2. Add the following secrets: + +| Secret Name | Description | Example Value | +|-------------|-------------|---------------| +| `AUTHENTIK_CLIENT_ID` | OAuth client ID | `docuelevate-app` | +| `AUTHENTIK_CLIENT_SECRET` | OAuth client secret | `super-secret-value` | +| `AUTHENTIK_CONFIG_URL` | OIDC discovery URL | `https://auth.example.com/application/o/docuelevate/.well-known/openid-configuration` | + +## Docker Service (Alternative to Testcontainers) + +If you prefer not to use testcontainers in CI, you can run mock-oauth2-server as a service: + +```yaml +name: Tests with OAuth Service + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + services: + mock-oauth: + image: ghcr.io/navikt/mock-oauth2-server:2.1.1 + ports: + - 8080:8080 + options: >- + --health-cmd "wget -q -O /dev/null http://localhost:8080/default/.well-known/openid-configuration || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install -r requirements-dev.txt + + - name: Configure OAuth to use service + run: | + export OAUTH_MOCK_URL=http://localhost:8080 + export USE_MOCK_OAUTH=true + + - name: Run tests + run: | + pytest tests/test_oauth_integration_flows.py -v +``` + +## Forcing Mock or Real Mode + +You can override the automatic detection with environment variables: + +```bash +# Force mock mode (even if real credentials available) +export USE_MOCK_OAUTH=true +pytest tests/test_oauth_integration_flows.py -v + +# Force real mode (will skip if credentials not available) +export USE_REAL_OAUTH=true +pytest tests/test_oauth_integration_flows.py -v +``` + +## Debugging OAuth Tests in CI + +### View Container Logs + +Add this step to debug mock OAuth server issues: + +```yaml +- name: Show mock OAuth logs (on failure) + if: failure() + run: | + docker ps -a + docker logs $(docker ps -aq --filter ancestor=ghcr.io/navikt/mock-oauth2-server:2.1.1) +``` + +### Enable Verbose Logging + +```yaml +- name: Run tests with verbose logging + run: | + pytest tests/test_oauth_integration_flows.py -vvs --log-cli-level=DEBUG +``` + +### Check Well-Known Endpoint + +```yaml +- name: Verify mock OAuth server + run: | + curl -f http://localhost:8080/default/.well-known/openid-configuration || exit 1 +``` + +## Performance Considerations + +- **Mock OAuth**: ~1s startup time, tests run in <10s +- **Real OAuth**: Depends on network latency, typically <30s +- **Docker Service**: Fastest for CI (pre-started), ~0.5s overhead + +## Security Best Practices + +1. ✅ **Never commit real OAuth credentials** to the repository +2. ✅ **Use GitHub secrets** for real credentials +3. ✅ **Restrict real OAuth tests** to internal PRs only +4. ✅ **Use mock OAuth** for external/fork PRs +5. ✅ **Rotate secrets** regularly if compromised + +## Troubleshooting + +### Tests Skip with "OAuth credentials not available" + +**Cause**: Real OAuth credentials not configured or not accessible. + +**Solution**: +- For local dev: Use mock mode (default) +- For CI: Add secrets to GitHub repository settings +- Check secret availability: `if github.event_name == 'push'` + +### Mock OAuth Server Won't Start + +**Cause**: Docker not available or testcontainers can't start container. + +**Solution**: +```yaml +- name: Start Docker + run: | + sudo systemctl start docker + docker ps +``` + +### Tests Timeout Waiting for Server + +**Cause**: Server taking too long to start or health check failing. + +**Solution**: Increase timeout in `conftest_oauth.py`: +```python +container.wait_for_ready(timeout=60) # Increase from 30 +``` + +## Example: Complete GitHub Actions Workflow + +```yaml +name: Full OAuth Testing Suite + +on: [push, pull_request] + +jobs: + # Fast mock OAuth tests (always run) + mock-oauth-tests: + name: OAuth Tests (Mock) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install -r requirements-dev.txt + + - name: Run mock OAuth tests + run: | + pytest tests/test_oauth_integration_flows.py \ + -v \ + -m "not requires_external" \ + --cov=app.auth \ + --cov-report=term-missing + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + flags: oauth-mock + + # Real OAuth tests (only for internal PRs/pushes) + real-oauth-tests: + name: OAuth Tests (Real - Internal) + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install -r requirements-dev.txt + + - name: Run real OAuth tests + env: + AUTHENTIK_CLIENT_ID: ${{ secrets.AUTHENTIK_CLIENT_ID }} + AUTHENTIK_CLIENT_SECRET: ${{ secrets.AUTHENTIK_CLIENT_SECRET }} + AUTHENTIK_CONFIG_URL: ${{ secrets.AUTHENTIK_CONFIG_URL }} + run: | + pytest tests/test_oauth_integration_flows.py \ + -v \ + -m requires_external + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + flags: oauth-real +``` + +This workflow: +- ✅ Runs mock tests on all PRs (fast, no secrets needed) +- ✅ Runs real tests only when secrets available +- ✅ Uploads separate coverage reports +- ✅ Provides detailed feedback