Files
gh-christianlouis-docuelevate/tests/conftest_oauth.py
T
2026-02-13 09:10:52 +00:00

235 lines
7.7 KiB
Python

"""
Pytest fixtures for OAuth/OIDC testing.
Provides fixtures for:
- Mock OAuth2 server (using testcontainers)
- Real OAuth credentials (from environment/GitHub Actions secrets)
- OAuth test helpers
"""
import os
from typing import Dict, Generator, Optional
import pytest
from tests.mock_oauth_server import MockOAuth2ServerContainer, create_test_userinfo
# Check if we should use real OAuth credentials from environment
_REAL_OAUTH_AVAILABLE = all(
[
os.environ.get("AUTHENTIK_CLIENT_ID") not in {"", "NOT_SET", "test-key", None},
os.environ.get("AUTHENTIK_CLIENT_SECRET") not in {"", "NOT_SET", "test-key", None},
os.environ.get("AUTHENTIK_CONFIG_URL") not in {"", "NOT_SET", "test-key", None},
]
)
@pytest.fixture(scope="session")
def use_real_oauth() -> bool:
"""
Determine if tests should use real OAuth credentials.
Returns True if valid OAuth credentials are available in the environment
(typically from GitHub Actions secrets).
Returns:
bool: True if real OAuth should be used, False for mock
"""
# Can be overridden with environment variable
if os.environ.get("USE_REAL_OAUTH", "").lower() in ("true", "1", "yes"):
return True
if os.environ.get("USE_MOCK_OAUTH", "").lower() in ("true", "1", "yes"):
return False
return _REAL_OAUTH_AVAILABLE
@pytest.fixture(scope="session")
def mock_oauth_server() -> Generator[MockOAuth2ServerContainer, None, None]:
"""
Provide a mock OAuth2/OIDC server for testing.
This fixture starts a mock-oauth2-server container that provides
a complete OIDC provider with all necessary endpoints.
Yields:
MockOAuth2ServerContainer: Running mock OAuth server
"""
# Only start if we're not using real OAuth
if not _REAL_OAUTH_AVAILABLE or os.environ.get("USE_MOCK_OAUTH", "").lower() in ("true", "1", "yes"):
container = MockOAuth2ServerContainer()
container.start()
try:
# Wait for the server to be ready
container.wait_for_ready()
yield container
finally:
container.stop()
else:
pytest.skip("Using real OAuth credentials, mock server not needed")
@pytest.fixture(scope="session")
def oauth_config(mock_oauth_server: Optional[MockOAuth2ServerContainer], use_real_oauth: bool) -> Dict[str, str]:
"""
Provide OAuth configuration for tests.
Returns either mock OAuth config or real OAuth config based on availability.
Args:
mock_oauth_server: Mock OAuth server fixture (may be None if using real)
use_real_oauth: Whether to use real OAuth credentials
Returns:
Dictionary with OAuth configuration
"""
if use_real_oauth and _REAL_OAUTH_AVAILABLE:
# Use real OAuth credentials from environment
return {
"client_id": os.environ["AUTHENTIK_CLIENT_ID"],
"client_secret": os.environ["AUTHENTIK_CLIENT_SECRET"],
"server_metadata_url": os.environ["AUTHENTIK_CONFIG_URL"],
"issuer": os.environ["AUTHENTIK_CONFIG_URL"].replace("/.well-known/openid-configuration", ""),
"mode": "real",
}
else:
# Use mock OAuth server
if mock_oauth_server is None:
pytest.fail("Mock OAuth server not available and real credentials not configured")
config = mock_oauth_server.get_config()
return {
"client_id": "test-client-id",
"client_secret": "test-client-secret",
"server_metadata_url": config["well_known_url"],
"issuer": config["issuer"],
"token_endpoint": config["token_endpoint"],
"authorization_endpoint": config["authorization_endpoint"],
"userinfo_endpoint": config["userinfo_endpoint"],
"jwks_uri": config["jwks_uri"],
"mode": "mock",
}
@pytest.fixture
def test_user_info() -> Dict:
"""
Provide test user information for OAuth flows.
Returns:
Dictionary with test user claims
"""
return create_test_userinfo(
sub="test-user-123",
email="testuser@example.com",
name="Test User",
preferred_username="testuser",
groups=["admin"],
)
@pytest.fixture
def oauth_test_token(
mock_oauth_server: Optional[MockOAuth2ServerContainer],
test_user_info: Dict,
use_real_oauth: bool,
) -> Optional[str]:
"""
Generate a test OAuth token.
For mock mode: Creates a valid JWT from the mock server.
For real mode: Skips (would need real authentication flow).
Args:
mock_oauth_server: Mock OAuth server
test_user_info: User information to include in token
use_real_oauth: Whether using real OAuth
Returns:
JWT token string or None if using real OAuth
"""
if use_real_oauth:
# Can't generate tokens for real OAuth - would need actual auth flow
return None
if mock_oauth_server is None:
pytest.fail("Mock OAuth server not available")
# Create a token with the test user info
return mock_oauth_server.create_token(
subject=test_user_info["sub"],
claims={
"email": test_user_info["email"],
"name": test_user_info["name"],
"preferred_username": test_user_info["preferred_username"],
"groups": test_user_info["groups"],
},
audience="test-client-id",
)
@pytest.fixture
def oauth_enabled_app(oauth_config: Dict[str, str]):
"""
Configure the FastAPI app with OAuth enabled for testing.
This fixture temporarily enables OAuth and configures it with the
test OAuth provider (mock or real).
Args:
oauth_config: OAuth configuration
Yields:
Configured test client
"""
import app.auth as auth_module
from app.auth import auth, login, logout, oauth_callback, oauth_login
from app.main import app
# Save original state
original_auth_enabled = auth_module.AUTH_ENABLED
original_oauth_configured = auth_module.OAUTH_CONFIGURED
original_oauth_provider = auth_module.OAUTH_PROVIDER_NAME
original_route_count = len(app.router.routes)
try:
# Enable auth and configure OAuth flags
auth_module.AUTH_ENABLED = True
auth_module.OAUTH_CONFIGURED = True
auth_module.OAUTH_PROVIDER_NAME = oauth_config.get("provider_name", "Test SSO")
# Register OAuth client
auth_module.oauth.register(
name="authentik",
client_id=oauth_config["client_id"],
client_secret=oauth_config["client_secret"],
server_metadata_url=oauth_config["server_metadata_url"],
client_kwargs={"scope": "openid profile email"},
)
# Add auth routes directly to the app (since include_router was called at startup
# with AUTH_ENABLED=False, routes weren't registered)
app.add_api_route("/login", login, methods=["GET"])
app.add_api_route("/oauth-login", oauth_login, methods=["GET"])
app.add_api_route("/oauth-callback", oauth_callback, methods=["GET"], name="oauth_callback")
app.add_api_route("/auth", auth, methods=["POST"])
app.add_api_route("/logout", logout, methods=["GET"])
from fastapi.testclient import TestClient
# Create test client with base_url to satisfy TrustedHostMiddleware
client = TestClient(app, base_url="http://localhost")
yield client
finally:
# Restore original auth state
auth_module.AUTH_ENABLED = original_auth_enabled
auth_module.OAUTH_CONFIGURED = original_oauth_configured
auth_module.OAUTH_PROVIDER_NAME = original_oauth_provider
# Remove added routes
app.router.routes = app.router.routes[:original_route_count]