Files
wehub-resource-sync 5296d0e97c
CI / Ban suppressions and legacy annotations (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / ruff-check (push) Has been cancelled
CI / ruff-format (push) Has been cancelled
CI / ty (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:44 +08:00

380 lines
12 KiB
Python

import logging
from pathlib import Path
from typing import cast
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from free_claude_code.application.errors import (
ApplicationUnavailableError,
InvalidRequestError,
)
from free_claude_code.config.settings import Settings
from free_claude_code.messaging.transcription import TranscriptionService
from free_claude_code.providers.nvidia_nim.client import NvidiaNimProvider
from free_claude_code.providers.nvidia_nim.voice import NvidiaNimTranscriber
from free_claude_code.runtime.application import (
ApplicationRuntime,
startup_failure_message,
warn_if_process_auth_token,
)
from free_claude_code.runtime.asgi import RuntimeASGIApp
from free_claude_code.runtime.bootstrap import _create_transcriber, build_asgi_app
from free_claude_code.runtime.provider_manager import ProviderRuntimeManager
from tests.api.support import create_test_app
def _settings(**updates: object) -> Settings:
return Settings().model_copy(update=updates)
@pytest.fixture(autouse=True)
def _redirect_fcc_home(monkeypatch, tmp_path):
home = tmp_path / "home"
monkeypatch.setenv("HOME", str(home))
monkeypatch.setenv("USERPROFILE", str(home))
def test_warn_if_process_auth_token_logs_warning(monkeypatch):
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "process-token")
monkeypatch.setitem(Settings.model_config, "env_file", ())
with patch("free_claude_code.runtime.application.logger.warning") as warning:
warn_if_process_auth_token(Settings.model_construct())
warning.assert_called_once()
assert "ANTHROPIC_AUTH_TOKEN" in warning.call_args.args[0]
def test_warn_if_process_auth_token_skips_explicit_dotenv_config(monkeypatch, tmp_path):
env_file = tmp_path / ".env"
env_file.write_text("ANTHROPIC_AUTH_TOKEN=\n", encoding="utf-8")
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "process-token")
monkeypatch.setitem(Settings.model_config, "env_file", (env_file,))
with patch("free_claude_code.runtime.application.logger.warning") as warning:
warn_if_process_auth_token(Settings.model_construct())
warning.assert_not_called()
@pytest.mark.asyncio
async def test_runtime_startup_logs_admin_url_without_printed_server_banner():
settings = _settings(
messaging_platform="none",
host="127.0.0.1",
port=9099,
)
manager = ProviderRuntimeManager(settings)
runtime = ApplicationRuntime(manager, transcriber=None)
uvicorn_logger = MagicMock()
with (
patch("builtins.print") as printed,
patch.object(manager, "validate_configured_models", new=AsyncMock()),
patch.object(manager, "start_model_list_refresh") as start_refresh,
patch.object(manager, "close", new=AsyncMock()),
patch(
"free_claude_code.runtime.application.messaging_platform_factory.create_messaging_components",
return_value=None,
),
patch.object(logging, "getLogger", return_value=uvicorn_logger) as get_logger,
):
await runtime.start()
await runtime.close()
printed.assert_not_called()
start_refresh.assert_called_once()
get_logger.assert_any_call("uvicorn.error")
uvicorn_logger.info.assert_called_once_with(
"Admin UI: %s (local-only)",
"http://127.0.0.1:9099/admin",
)
def test_create_app_application_error_handler_returns_anthropic_format():
app = create_test_app(_settings(log_api_error_tracebacks=False))
@app.get("/raise_application")
async def _raise_application():
raise InvalidRequestError("bad request")
response = TestClient(app).get("/raise_application")
assert response.status_code == 400
body = response.json()
assert body["type"] == "error"
assert body["error"]["type"] == "invalid_request_error"
assert body["request_id"] == response.headers["request-id"]
assert "x-should-retry" not in response.headers
def test_application_error_handler_does_not_log_error_message():
app = create_test_app(_settings(log_api_error_tracebacks=False))
secret = "provider-upstream-secret-detail"
@app.get("/raise_application_secret")
async def _raise_application_secret():
raise InvalidRequestError(secret)
with patch("free_claude_code.api.app.logger.error") as log_error:
response = TestClient(app).get("/raise_application_secret")
assert response.status_code == 400
blob = " ".join(
str(value) for call in log_error.call_args_list for value in call.args
)
assert secret not in blob
log_error.assert_not_called()
def test_create_app_general_exception_handler_returns_correlated_500():
app = create_test_app(_settings(log_api_error_tracebacks=False))
@app.get("/raise_general")
async def _raise_general():
raise RuntimeError("boom")
response = TestClient(app, raise_server_exceptions=False).get("/raise_general")
assert response.status_code == 500
body = response.json()
assert body["type"] == "error"
assert body["error"]["type"] == "api_error"
assert body["request_id"] == response.headers["request-id"]
def test_general_exception_default_log_excludes_exception_message():
app = create_test_app(_settings(log_api_error_tracebacks=False))
secret = "user-provided-secret-token-xyzzy"
@app.get("/raise_secret")
async def _raise_secret():
raise ValueError(secret)
with patch("free_claude_code.api.app.logger.error") as log_error:
response = TestClient(app, raise_server_exceptions=False).get("/raise_secret")
assert response.status_code == 500
blob = " ".join(
str(value) for call in log_error.call_args_list for value in call.args
)
assert secret not in blob
assert "ValueError" in blob
@pytest.mark.asyncio
async def test_model_validation_failure_does_not_block_runtime_startup():
settings = _settings(messaging_platform="none")
manager = ProviderRuntimeManager(settings)
runtime = ApplicationRuntime(manager, transcriber=None)
validation = AsyncMock(side_effect=ApplicationUnavailableError("bad model"))
with (
patch.object(manager, "validate_configured_models", new=validation),
patch.object(manager, "start_model_list_refresh") as start_refresh,
patch.object(manager, "close", new=AsyncMock()),
patch(
"free_claude_code.runtime.application.messaging_platform_factory.create_messaging_components",
return_value=None,
),
):
await runtime.start()
await runtime.close()
validation.assert_awaited_once()
start_refresh.assert_called_once()
def test_startup_failure_message_preserves_existing_concise_contract():
quiet = _settings(log_api_error_tracebacks=False)
verbose = _settings(log_api_error_tracebacks=True)
assert startup_failure_message(quiet, RuntimeError("secret")) == (
"Server startup failed: exc_type=RuntimeError"
)
assert startup_failure_message(verbose, RuntimeError("visible")) == (
"RuntimeError: visible"
)
assert (
startup_failure_message(
quiet,
ApplicationUnavailableError("configured model is unavailable"),
)
== "configured model is unavailable"
)
@pytest.mark.asyncio
async def test_runtime_asgi_app_starts_and_closes_owner_once():
runtime = MagicMock(spec=ApplicationRuntime)
runtime.settings = _settings()
runtime.start = AsyncMock()
runtime.close = AsyncMock(return_value=True)
app = RuntimeASGIApp(AsyncMock(), runtime)
received = iter(
[
{"type": "lifespan.startup"},
{"type": "lifespan.shutdown"},
]
)
sent: list[dict[str, str]] = []
async def receive():
return next(received)
async def send(message):
sent.append(message)
await app({"type": "lifespan"}, receive, send)
runtime.start.assert_awaited_once()
runtime.close.assert_awaited_once()
assert sent == [
{"type": "lifespan.startup.complete"},
{"type": "lifespan.shutdown.complete"},
]
@pytest.mark.asyncio
async def test_runtime_asgi_app_reports_incomplete_owned_shutdown() -> None:
runtime = MagicMock(spec=ApplicationRuntime)
runtime.settings = _settings()
runtime.start = AsyncMock()
runtime.close = AsyncMock(return_value=False)
app = RuntimeASGIApp(AsyncMock(), runtime)
received = iter(
[
{"type": "lifespan.startup"},
{"type": "lifespan.shutdown"},
]
)
sent: list[dict[str, str]] = []
async def receive():
return next(received)
async def send(message):
sent.append(message)
await app({"type": "lifespan"}, receive, send)
assert sent == [
{"type": "lifespan.startup.complete"},
{"type": "lifespan.shutdown.failed", "message": ""},
]
@pytest.mark.asyncio
async def test_runtime_asgi_app_reports_concise_startup_failure():
runtime = MagicMock(spec=ApplicationRuntime)
runtime.settings = _settings(log_api_error_tracebacks=False)
runtime.start = AsyncMock(side_effect=RuntimeError("secret"))
runtime.close = AsyncMock()
app = RuntimeASGIApp(AsyncMock(), runtime)
sent: list[dict[str, str]] = []
async def receive():
return {"type": "lifespan.startup"}
async def send(message):
sent.append(message)
await app({"type": "lifespan"}, receive, send)
assert sent == [
{
"type": "lifespan.startup.failed",
"message": "Server startup failed: exc_type=RuntimeError",
}
]
runtime.close.assert_not_awaited()
def test_bootstrap_configures_default_log_and_publishes_only_services(tmp_path):
log_path = tmp_path / "server.log"
settings = _settings()
with (
patch(
"free_claude_code.runtime.bootstrap.server_log_path",
return_value=log_path,
),
patch("free_claude_code.runtime.bootstrap.configure_logging") as configure,
):
asgi_app = build_asgi_app(settings)
configure.assert_called_once_with(
Path(log_path),
verbose_third_party=settings.log_raw_api_payloads,
)
api_app = cast(FastAPI, asgi_app.app)
assert set(api_app.state._state) == {"services"}
def test_bootstrap_honors_process_log_file_override(monkeypatch, tmp_path):
log_path = tmp_path / "custom.log"
monkeypatch.setenv("LOG_FILE", str(log_path))
with patch("free_claude_code.runtime.bootstrap.configure_logging") as configure:
build_asgi_app(_settings())
assert configure.call_args.args[0] == log_path
def test_bootstrap_constructs_fresh_runtime_owned_transcribers() -> None:
settings = _settings(voice_note_enabled=True, whisper_device="cpu")
first = _create_transcriber(settings)
second = _create_transcriber(settings)
assert isinstance(first, TranscriptionService)
assert isinstance(second, TranscriptionService)
assert first is not second
@pytest.mark.asyncio
async def test_bootstrap_constructs_isolated_runtime_resource_graphs() -> None:
settings = _settings(
model="nvidia_nim/test-model",
voice_note_enabled=True,
whisper_device="cpu",
)
with patch("free_claude_code.runtime.bootstrap.configure_logging"):
first = build_asgi_app(settings)
second = build_asgi_app(settings)
first_lease = await first.runtime.provider_manager.acquire()
second_lease = await second.runtime.provider_manager.acquire()
try:
first_provider = first_lease.resolve_provider("nvidia_nim")
second_provider = second_lease.resolve_provider("nvidia_nim")
assert isinstance(first_provider, NvidiaNimProvider)
assert isinstance(second_provider, NvidiaNimProvider)
assert first_provider._rate_limiter is not second_provider._rate_limiter
assert first.runtime._transcriber is not second.runtime._transcriber
finally:
await first_lease.release()
await second_lease.release()
await first.runtime.close()
await second.runtime.close()
def test_bootstrap_selects_nvidia_transcriber_without_loading_riva() -> None:
settings = _settings(
voice_note_enabled=True,
whisper_device="nvidia_nim",
whisper_model="openai/whisper-large-v3",
nvidia_nim_api_key="nvapi-test",
)
assert isinstance(_create_transcriber(settings), NvidiaNimTranscriber)
def test_bootstrap_disables_transcription_as_one_owned_resource() -> None:
assert _create_transcriber(_settings(voice_note_enabled=False)) is None