# Wiring Integration Test Secrets into Tests (Python)

**Reference**: PP-1114, python-jwtauth README

Python integration tests use `python-jwtauth` with the `[testing]` extra.

## Install the dependency

Add `jwtauth[testing]` and `pyotp` to your dev dependencies. Check the project's package manager and add to the appropriate file.

`pyotp` is included in the `jwtauth[testing]` extras as a transitive dependency, but must also be declared explicitly as a dev dependency so it is available for direct use in test files.

If `owsrequest` is already a dependency of the project, it pins `jwtauth` as its own dependency — just add the `[testing]` extra without a version constraint and let the package manager defer to owsrequest's pin. Also ensure `owsrequest` is at least `2.10.1`. Do not install `owsrequest` if it is not already present.

**If `owsrequest` is already installed:**

```toml
# pyproject.toml (Poetry)
[tool.poetry.dev-dependencies]
jwtauth = {extras = ["testing"]}
pyotp = "*"
```

```text
# requirements-dev.txt (pip)
jwtauth[testing]
pyotp
```

```toml
# pyproject.toml (uv)
# run: uv add "jwtauth[testing]" pyotp --dev
```

**If `owsrequest` is not installed:**

```toml
# pyproject.toml (Poetry)
[tool.poetry.dev-dependencies]
jwtauth = {version = ">=0.6.1,<1.0.0", extras = ["testing"]}
pyotp = "*"
```

```text
# requirements-dev.txt (pip)
jwtauth[testing]>=0.6.1,<1.0.0
pyotp
```

```toml
# pyproject.toml (uv)
# run: uv add "jwtauth[testing]>=0.6.1,<1.0.0" pyotp --dev
```

## Configure docker-compose.yml

The `jwtauth` library uses boto3 under the hood to read from Secrets Manager. boto3 requires AWS credentials and a region to be available as environment variables inside the container.

In `docker-compose.yml`, find the `integration-test` service and ensure it includes the following under `environment`. The credential variables and `AWS_REGION` are written without a value, telling Docker Compose to pass them through from the host where Jenkins' `withAWS` step will have injected them. `AWS_DEFAULT_REGION` is hardcoded to `us-east-1`, our exclusive default region, because boto3 requires this specific variable name.

```yaml
integration-test:
  environment:
    - AWS_REGION
    - AWS_DEFAULT_REGION=us-east-1
    - AWS_ACCESS_KEY_ID
    - AWS_SECRET_ACCESS_KEY
    - AWS_SESSION_TOKEN
```

Both region variables are included: boto3 reads `AWS_DEFAULT_REGION`, and `AWS_REGION` is passed through for other consumers.

Do not add AWS credentials to `.env.shadow` — they are ephemeral session credentials provided by `withAWS` at CI runtime and should never be committed.

### Watch out for mock AWS credentials in existing test setup

Some services have the following pattern under the `tests/` directory, in order to set mock AWS credentials to prevent boto3 from complaining during unit tests that don't touch AWS

```python
# common tests/ directory — breaks integration tests
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_SESSION_TOKEN"] = "testing"
```

If this pattern exists outside of `tests/unit/conftest.py`, refactor it there — integration tests that call Secrets Manager will silently use the fake credentials and fail to retrieve any secrets.

## Register the pytest plugin

In `tests/integration/conftest.py`, after module imports, add the plugin declaration so pytest can discover the jwtauth fixtures:

```python
pytest_plugins = ["jwtauth.testing.pytest_plugin"]
```

This must be at the root conftest — not inside a subdirectory — so the fixtures are available across all integration tests.

Registering the plugin provides two session-scoped fixtures automatically:
- `generate_bearer_token` — fetches both secrets and returns a bearer token in one call
- `jwtauth_secrets_manager` — a ready-made `JwtAuthSecretsManager` instance

## Use the fixtures in a test

Define a fixture in `conftest.py` that calls `generate_bearer_token` with `SecretLookupInfo` objects pointing at your secrets:

```python
import pytest
from jwtauth.testing.schemas import SecretLookupInfo

@pytest.fixture(scope="session")
def my_new_user_token(generate_bearer_token, jwtauth_secrets_manager):
    return generate_bearer_token(
        get_user_creds_args=SecretLookupInfo(
            environment="qa",
            service_name="my-service-integration-test",  # matches service_name in variables.tf
            secret_name="MY_NEW_USER_CREDENTIALS",       # suffix from terraform variable
        ),
        get_auth0_creds_args=SecretLookupInfo(
            environment="qa",
            service_name="my-service-integration-test",
            secret_name="MY_NEW_AUTH0_CREDENTIALS",      # suffix from terraform variable
        ),
        secrets_manager=jwtauth_secrets_manager,
    )
```

Then use the token fixture in tests:

```python
def test_my_scenario(my_new_user_token, http_client):
    response = http_client.get(
        "/some-endpoint",
        headers={"Authorization": f"Bearer {my_new_user_token}"},
    )
    assert response.status_code == 200
```

## Key details

- `generate_bearer_token` wraps `get_user_creds` + `get_auth0_creds` + `generate_bearer_jwt_token` into a single call — don't call those lower-level functions directly.
- `service_name` in `SecretLookupInfo` must match the `service_name` variable in `variables.tf` (typically `<service>-integration-test`), since that determines the Secrets Manager path `qa/<service_name>/<secret_name>`.
- Use `scope="session"` on token fixtures to avoid re-authenticating on every test.
- MFA is handled transparently if `otp_secret_key` is present in the user credentials secret.
