File size: 6,262 Bytes
da1a902
133255a
0dff311
0602b26
133255a
0d0f0f9
0602b26
f987504
7c98fe6
728f32a
f987504
d29db6e
db883b3
d29db6e
0602b26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684eaa9
 
db883b3
0602b26
db883b3
0602b26
 
 
684eaa9
db883b3
0602b26
ecc6b8a
da1a902
db883b3
 
ecc6b8a
 
0602b26
 
cd03cec
0602b26
 
f987504
 
7c98fe6
da1a902
f987504
7c98fe6
f987504
 
7c98fe6
da1a902
f987504
da1a902
7c98fe6
f987504
da1a902
7c98fe6
f987504
 
7c98fe6
 
 
f987504
7c98fe6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728f32a
 
7c98fe6
da1a902
7c98fe6
 
 
 
 
da1a902
 
728f32a
7c98fe6
728f32a
 
7c98fe6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728f32a
da1a902
7c98fe6
728f32a
da1a902
7c98fe6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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"