| from unittest.mock import MagicMock, patch |
|
|
| import pytest |
| from fastapi import HTTPException, Request |
|
|
| from free_claude_code.api.dependencies import ( |
| get_services, |
| get_settings, |
| require_proxy_auth, |
| resolve_provider, |
| ) |
| from free_claude_code.api.ports import ApiServices |
| from free_claude_code.application.errors import ApplicationUnavailableError |
| from free_claude_code.application.ports import RequestRuntimeLease |
| from free_claude_code.config.settings import Settings |
| from tests.api.support import create_test_app |
|
|
|
|
| def _request(*, headers: dict[str, str], token: str) -> tuple[Request, Settings]: |
| request = Request( |
| { |
| "type": "http", |
| "method": "GET", |
| "path": "/", |
| "headers": [ |
| (key.lower().encode(), value.encode()) for key, value in headers.items() |
| ], |
| } |
| ) |
| settings = Settings.model_construct(anthropic_auth_token=token) |
| return request, settings |
|
|
|
|
| def _lease(*, provider=None, error: Exception | None = None): |
| lease = MagicMock(spec=RequestRuntimeLease) |
| lease.is_provider_cached.return_value = False |
| if error is None: |
| lease.resolve_provider.return_value = provider or MagicMock() |
| else: |
| lease.resolve_provider.side_effect = error |
| return lease |
|
|
|
|
| def test_get_services_reads_the_single_app_state_boundary() -> None: |
| app = create_test_app() |
| request = Request({"type": "http", "app": app}) |
|
|
| services = get_services(request) |
|
|
| assert services is app.state.services |
| assert isinstance(services, ApiServices) |
|
|
|
|
| def test_get_settings_reads_current_request_runtime_settings() -> None: |
| app = create_test_app( |
| Settings.model_construct( |
| model="deepseek/test-model", |
| anthropic_auth_token="", |
| ) |
| ) |
|
|
| assert get_settings(app.state.services).model == "deepseek/test-model" |
|
|
|
|
| def test_resolve_provider_uses_retained_lease_and_logs_first_initialization() -> None: |
| provider = MagicMock() |
| lease = _lease(provider=provider) |
|
|
| with patch("free_claude_code.api.dependencies.logger.info") as log_info: |
| result = resolve_provider("nvidia_nim", lease=lease) |
|
|
| assert result is provider |
| lease.resolve_provider.assert_called_once_with("nvidia_nim") |
| log_info.assert_called_once_with("Provider initialized: {}", "nvidia_nim") |
|
|
|
|
| def test_resolve_provider_skips_initialization_log_for_cached_provider() -> None: |
| lease = _lease() |
| lease.is_provider_cached.return_value = True |
|
|
| with patch("free_claude_code.api.dependencies.logger.info") as log_info: |
| resolve_provider("nvidia_nim", lease=lease) |
|
|
| log_info.assert_not_called() |
|
|
|
|
| def test_resolve_provider_missing_key_preserves_readiness_error() -> None: |
| lease = _lease( |
| error=ApplicationUnavailableError( |
| "OPENROUTER_API_KEY is required. Get one at https://openrouter.ai" |
| ) |
| ) |
|
|
| with pytest.raises(ApplicationUnavailableError) as exc_info: |
| resolve_provider("open_router", lease=lease) |
|
|
| assert exc_info.value.status_code == 503 |
| assert "OPENROUTER_API_KEY" in exc_info.value.message |
| assert "openrouter.ai" in exc_info.value.message |
|
|
|
|
| def test_resolve_provider_unrelated_error_is_not_reclassified() -> None: |
| lease = _lease(error=ValueError("unrelated config")) |
|
|
| with pytest.raises(ValueError, match="unrelated config"): |
| resolve_provider("nvidia_nim", lease=lease) |
|
|
|
|
| def test_require_proxy_auth_allows_when_no_token_configured(): |
| request, settings = _request(headers={}, token="") |
|
|
| require_proxy_auth(request, settings) |
|
|
|
|
| def test_require_proxy_auth_rejects_missing_authorization(): |
| request, settings = _request(headers={}, token="secret") |
|
|
| with pytest.raises(HTTPException) as exc_info: |
| require_proxy_auth(request, settings) |
|
|
| assert exc_info.value.status_code == 401 |
| assert exc_info.value.detail == "Missing proxy authentication token" |
|
|
|
|
| @pytest.mark.parametrize("header_name", ["x-api-key", "anthropic-auth-token"]) |
| def test_require_proxy_auth_rejects_legacy_header_only(header_name: str): |
| request, settings = _request(headers={header_name: "secret"}, token="secret") |
|
|
| with pytest.raises(HTTPException) as exc_info: |
| require_proxy_auth(request, settings) |
|
|
| assert exc_info.value.status_code == 401 |
| assert exc_info.value.detail == "Missing proxy authentication token" |
|
|
|
|
| def test_require_proxy_auth_accepts_exact_bearer_token(): |
| request, settings = _request( |
| headers={"authorization": "bEaReR secret"}, |
| token="secret", |
| ) |
|
|
| require_proxy_auth(request, settings) |
|
|
|
|
| def test_require_proxy_auth_accepts_colons_in_configured_token(): |
| request, settings = _request( |
| headers={"authorization": "Bearer secret:with:colons"}, |
| token="secret:with:colons", |
| ) |
|
|
| require_proxy_auth(request, settings) |
|
|
|
|
| def test_require_proxy_auth_accepts_valid_bearer_with_conflicting_legacy_headers(): |
| request, settings = _request( |
| headers={ |
| "authorization": "Bearer secret", |
| "x-api-key": "wrong", |
| "anthropic-auth-token": "also-wrong", |
| }, |
| token="secret", |
| ) |
|
|
| require_proxy_auth(request, settings) |
|
|
|
|
| @pytest.mark.parametrize( |
| "authorization", |
| [ |
| "secret", |
| "Basic secret", |
| "Bearer", |
| "Bearer wrong", |
| "Bearer secret:claude-sonnet", |
| ], |
| ) |
| def test_require_proxy_auth_rejects_malformed_or_invalid_authorization( |
| authorization: str, |
| ): |
| request, settings = _request( |
| headers={"authorization": authorization}, |
| token="secret", |
| ) |
|
|
| with pytest.raises(HTTPException) as exc_info: |
| require_proxy_auth(request, settings) |
|
|
| assert exc_info.value.status_code == 401 |
| assert exc_info.value.detail == "Invalid proxy authentication token" |
|
|
|
|
| def test_require_proxy_auth_rejects_invalid_bearer_when_legacy_header_matches(): |
| request, settings = _request( |
| headers={"authorization": "Bearer wrong", "x-api-key": "secret"}, |
| token="secret", |
| ) |
|
|
| with pytest.raises(HTTPException) as exc_info: |
| require_proxy_auth(request, settings) |
|
|
| assert exc_info.value.status_code == 401 |
| assert exc_info.value.detail == "Invalid proxy authentication token" |
|
|