Merge pull request #423 from christianlouis/copilot/fix-one-drive-tests

fix(tests): remove Docker registry dependency from OAuth integration tests
This commit is contained in:
Christian Krakau-Louis
2026-02-26 15:59:25 +01:00
committed by GitHub
2 changed files with 61 additions and 17 deletions
+56 -11
View File
@@ -7,6 +7,7 @@ Provides fixtures for:
- OAuth test helpers
"""
import logging
import os
from typing import Dict, Generator, Optional
@@ -23,6 +24,13 @@ _REAL_OAUTH_AVAILABLE = all(
]
)
# Static fallback OAuth endpoint constants used when Docker is unavailable
_STATIC_OAUTH_AUTHORIZE_URL = "http://mock-oauth.test/default/authorize"
_STATIC_OAUTH_TOKEN_URL = "http://mock-oauth.test/default/token"
_STATIC_OAUTH_USERINFO_URL = "http://mock-oauth.test/default/userinfo"
_STATIC_OAUTH_JWKS_URL = "http://mock-oauth.test/default/jwks"
_STATIC_OAUTH_ISSUER = "http://mock-oauth.test/default"
@pytest.fixture(scope="session")
def use_real_oauth() -> bool:
@@ -45,7 +53,7 @@ def use_real_oauth() -> bool:
@pytest.fixture(scope="session")
def mock_oauth_server() -> Generator[MockOAuth2ServerContainer, None, None]:
def mock_oauth_server() -> Generator[Optional[MockOAuth2ServerContainer], None, None]:
"""
Provide a mock OAuth2/OIDC server for testing.
@@ -53,16 +61,31 @@ def mock_oauth_server() -> Generator[MockOAuth2ServerContainer, None, None]:
a complete OIDC provider with all necessary endpoints.
Yields:
MockOAuth2ServerContainer: Running mock OAuth server
MockOAuth2ServerContainer: Running mock OAuth server, or None if Docker is unavailable
"""
# 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()
container = None
try:
container = MockOAuth2ServerContainer()
container.start()
# Wait for the server to be ready
container.wait_for_ready()
except Exception as exc:
# Docker not accessible or image pull failed fall back to static mock config.
# Attempt cleanup in case the container was partially started.
if container is not None:
try:
container.stop()
except Exception: # noqa: BLE001
pass
logging.getLogger(__name__).warning(
"Mock OAuth2 server unavailable (Docker inaccessible): %s using static fallback config", exc
)
yield None
return
try:
yield container
finally:
container.stop()
@@ -78,7 +101,7 @@ def oauth_config(mock_oauth_server: Optional[MockOAuth2ServerContainer], use_rea
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)
mock_oauth_server: Mock OAuth server fixture (None if Docker unavailable)
use_real_oauth: Whether to use real OAuth credentials
Returns:
@@ -93,11 +116,22 @@ def oauth_config(mock_oauth_server: Optional[MockOAuth2ServerContainer], use_rea
"issuer": os.environ["AUTHENTIK_CONFIG_URL"].replace("/.well-known/openid-configuration", ""),
"mode": "real",
}
elif mock_oauth_server is None:
# Docker unavailable use a static in-process mock configuration so
# tests that mock the OAuth token exchange still work without a container.
return {
"client_id": "test-client-id",
"client_secret": "test-client-secret",
"server_metadata_url": f"{_STATIC_OAUTH_ISSUER}/.well-known/openid-configuration",
"authorization_endpoint": _STATIC_OAUTH_AUTHORIZE_URL,
"token_endpoint": _STATIC_OAUTH_TOKEN_URL,
"userinfo_endpoint": _STATIC_OAUTH_USERINFO_URL,
"jwks_uri": _STATIC_OAUTH_JWKS_URL,
"issuer": _STATIC_OAUTH_ISSUER,
"mode": "static",
}
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",
@@ -200,12 +234,19 @@ def oauth_enabled_app(oauth_config: Dict[str, str]):
auth_module.OAUTH_CONFIGURED = True
auth_module.OAUTH_PROVIDER_NAME = oauth_config.get("provider_name", "Test SSO")
# Register OAuth client
# Clear any previously cached client so the new params take effect.
# authlib caches created clients in _clients; we must evict before re-registering.
auth_module.oauth._clients.pop("authentik", None)
auth_module.oauth._registry.pop("authentik", None)
# Register OAuth client using direct endpoint URLs to avoid HTTP metadata
# discovery this allows tests to work without a running OAuth server.
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"],
authorize_url=oauth_config.get("authorization_endpoint", _STATIC_OAUTH_AUTHORIZE_URL),
access_token_url=oauth_config.get("token_endpoint", _STATIC_OAUTH_TOKEN_URL),
client_kwargs={"scope": "openid profile email"},
)
@@ -232,3 +273,7 @@ def oauth_enabled_app(oauth_config: Dict[str, str]):
# Remove added routes
app.router.routes = app.router.routes[:original_route_count]
# Clean up OAuth registration to avoid cross-test contamination
auth_module.oauth._clients.pop("authentik", None)
auth_module.oauth._registry.pop("authentik", None)
+5 -6
View File
@@ -5,7 +5,7 @@ Focuses on uncovered lines: 98-99, 121-143, 160-161, 170-171,
324-326, 400-402, 436-438.
"""
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, PropertyMock, patch
import pytest
from fastapi.testclient import TestClient
@@ -296,7 +296,7 @@ class TestSaveSettingsException:
def test_save_settings_outer_exception(self, client: TestClient):
"""Trigger the outer exception handler in save_onedrive_settings."""
with patch("app.api.onedrive.os.path.join", side_effect=Exception("Unexpected boom")):
with patch("app.api.onedrive.notify_settings_updated", side_effect=Exception("Unexpected boom")):
response = client.post(
"/api/onedrive/save-settings",
data={
@@ -332,11 +332,10 @@ class TestGetFullConfigException:
def test_get_full_config_exception(self, client: TestClient):
"""Trigger the exception handler in get_onedrive_full_config."""
from app.config import settings
mock_settings = MagicMock()
type(mock_settings).onedrive_client_id = PropertyMock(side_effect=Exception("boom"))
with patch.object(
type(settings), "onedrive_client_id", property(fget=lambda self: (_ for _ in ()).throw(Exception("boom")))
):
with patch("app.api.onedrive.settings", mock_settings):
response = client.get("/api/onedrive/get-full-config")
assert response.status_code == 200