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:
+56
-11
@@ -7,6 +7,7 @@ Provides fixtures for:
|
|||||||
- OAuth test helpers
|
- OAuth test helpers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Dict, Generator, Optional
|
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")
|
@pytest.fixture(scope="session")
|
||||||
def use_real_oauth() -> bool:
|
def use_real_oauth() -> bool:
|
||||||
@@ -45,7 +53,7 @@ def use_real_oauth() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@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.
|
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.
|
a complete OIDC provider with all necessary endpoints.
|
||||||
|
|
||||||
Yields:
|
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
|
# 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"):
|
if not _REAL_OAUTH_AVAILABLE or os.environ.get("USE_MOCK_OAUTH", "").lower() in ("true", "1", "yes"):
|
||||||
container = MockOAuth2ServerContainer()
|
container = None
|
||||||
container.start()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
container = MockOAuth2ServerContainer()
|
||||||
|
container.start()
|
||||||
# Wait for the server to be ready
|
# Wait for the server to be ready
|
||||||
container.wait_for_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
|
yield container
|
||||||
finally:
|
finally:
|
||||||
container.stop()
|
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.
|
Returns either mock OAuth config or real OAuth config based on availability.
|
||||||
|
|
||||||
Args:
|
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
|
use_real_oauth: Whether to use real OAuth credentials
|
||||||
|
|
||||||
Returns:
|
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", ""),
|
"issuer": os.environ["AUTHENTIK_CONFIG_URL"].replace("/.well-known/openid-configuration", ""),
|
||||||
"mode": "real",
|
"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:
|
else:
|
||||||
# Use mock OAuth server
|
# 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()
|
config = mock_oauth_server.get_config()
|
||||||
return {
|
return {
|
||||||
"client_id": "test-client-id",
|
"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_CONFIGURED = True
|
||||||
auth_module.OAUTH_PROVIDER_NAME = oauth_config.get("provider_name", "Test SSO")
|
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(
|
auth_module.oauth.register(
|
||||||
name="authentik",
|
name="authentik",
|
||||||
client_id=oauth_config["client_id"],
|
client_id=oauth_config["client_id"],
|
||||||
client_secret=oauth_config["client_secret"],
|
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"},
|
client_kwargs={"scope": "openid profile email"},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -232,3 +273,7 @@ def oauth_enabled_app(oauth_config: Dict[str, str]):
|
|||||||
|
|
||||||
# Remove added routes
|
# Remove added routes
|
||||||
app.router.routes = app.router.routes[:original_route_count]
|
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,7 +5,7 @@ Focuses on uncovered lines: 98-99, 121-143, 160-161, 170-171,
|
|||||||
324-326, 400-402, 436-438.
|
324-326, 400-402, 436-438.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
@@ -296,7 +296,7 @@ class TestSaveSettingsException:
|
|||||||
|
|
||||||
def test_save_settings_outer_exception(self, client: TestClient):
|
def test_save_settings_outer_exception(self, client: TestClient):
|
||||||
"""Trigger the outer exception handler in save_onedrive_settings."""
|
"""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(
|
response = client.post(
|
||||||
"/api/onedrive/save-settings",
|
"/api/onedrive/save-settings",
|
||||||
data={
|
data={
|
||||||
@@ -332,11 +332,10 @@ class TestGetFullConfigException:
|
|||||||
|
|
||||||
def test_get_full_config_exception(self, client: TestClient):
|
def test_get_full_config_exception(self, client: TestClient):
|
||||||
"""Trigger the exception handler in get_onedrive_full_config."""
|
"""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(
|
with patch("app.api.onedrive.settings", mock_settings):
|
||||||
type(settings), "onedrive_client_id", property(fget=lambda self: (_ for _ in ()).throw(Exception("boom")))
|
|
||||||
):
|
|
||||||
response = client.get("/api/onedrive/get-full-config")
|
response = client.get("/api/onedrive/get-full-config")
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|||||||
Reference in New Issue
Block a user