from fastapi import Depends, FastAPI from fastapi.testclient import TestClient from services.shared.core import Role from services.shared.security import get_actor, issue_app_token, require_roles def build_app() -> FastAPI: app = FastAPI() @app.get('/actor') def actor_view(actor: dict = Depends(get_actor)) -> dict: return actor @app.get('/admin') def admin_view(_: dict = Depends(require_roles(Role.ADMIN))) -> dict: return {'ok': True} return app def test_valid_bearer_token_resolves_actor(monkeypatch): monkeypatch.setenv('APP_TOKEN_SECRET', 'track1-secret') client = TestClient(build_app()) token = issue_app_token( subject='usr_1', username='alice', role='admin', auth_source='oidc', provider='keycloak', ) response = client.get('/actor', headers={'Authorization': f'Bearer {token}'}) assert response.status_code == 200 body = response.json() assert body['user'] == 'alice' assert body['role'] == 'admin' assert body['auth_source'] == 'oidc' def test_invalid_bearer_token_returns_401(monkeypatch): monkeypatch.setenv('APP_TOKEN_SECRET', 'track1-secret') client = TestClient(build_app()) response = client.get('/actor', headers={'Authorization': 'Bearer broken.token.value'}) assert response.status_code == 401 def test_legacy_header_auth_can_be_disabled(monkeypatch): monkeypatch.setenv('ALLOW_LEGACY_HEADER_AUTH', '0') client = TestClient(build_app()) response = client.get('/admin', headers={'X-User': 'admin', 'X-Role': 'admin'}) assert response.status_code == 403 def test_bearer_token_can_use_tenant_header_when_legacy_auth_disabled(monkeypatch): monkeypatch.setenv('APP_TOKEN_SECRET', 'track1-secret') monkeypatch.setenv('ALLOW_LEGACY_HEADER_AUTH', '0') client = TestClient(build_app()) token = issue_app_token( subject='usr_1', username='alice', role='admin', auth_source='local', ) response = client.get( '/actor', headers={'Authorization': f'Bearer {token}', 'X-Tenant-ID': 'default'}, ) assert response.status_code == 200 body = response.json() assert body['user'] == 'alice' assert body['role'] == 'admin' assert body['tenant_id'] == 'default' assert body['tenant_source'] == 'header' def test_bearer_token_tenant_takes_precedence_over_header(monkeypatch): monkeypatch.setenv('APP_TOKEN_SECRET', 'track1-secret') client = TestClient(build_app()) token = issue_app_token( subject='usr_1', username='alice', role='admin', auth_source='oidc', tenant_id='token-tenant', ) response = client.get( '/actor', headers={'Authorization': f'Bearer {token}', 'X-Tenant-ID': 'header-tenant'}, ) assert response.status_code == 200 body = response.json() assert body['tenant_id'] == 'token-tenant' assert body['tenant_source'] == 'token' def test_legacy_header_auth_still_works_when_enabled(monkeypatch): monkeypatch.setenv('ALLOW_LEGACY_HEADER_AUTH', '1') client = TestClient(build_app()) response = client.get('/admin', headers={'X-User': 'admin', 'X-Role': 'admin'}) assert response.status_code == 200