from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi.testclient import TestClient from free_claude_code.api.dependencies import get_settings from free_claude_code.api.ports import ApiServices from free_claude_code.application.ports import StopResult from free_claude_code.config.settings import Settings from tests.api.support import create_test_app app = create_test_app() @pytest.fixture def client(): return TestClient(app) @pytest.fixture def mock_settings(): settings = Settings() settings.fast_prefix_detection = True settings.enable_network_probe_mock = True settings.enable_title_generation_skip = True return settings def test_create_message_fast_prefix_detection(client, mock_settings): app.dependency_overrides[get_settings] = lambda: mock_settings payload = { "model": "claude-3-sonnet", "max_tokens": 100, "messages": [{"role": "user", "content": "What is the prefix?"}], } with ( patch( "free_claude_code.api.optimization_handlers.is_prefix_detection_request", return_value=(True, "/ask"), ), patch( "free_claude_code.api.optimization_handlers.extract_command_prefix", return_value="/ask", ), ): response = client.post("/v1/messages", json=payload) assert response.status_code == 200 data = response.json() assert "/ask" in data["content"][0]["text"] app.dependency_overrides.clear() def test_create_message_quota_check_mock(client, mock_settings): app.dependency_overrides[get_settings] = lambda: mock_settings payload = { "model": "claude-3-sonnet", "max_tokens": 100, "messages": [{"role": "user", "content": "quota check"}], } with patch( "free_claude_code.api.optimization_handlers.is_quota_check_request", return_value=True, ): response = client.post("/v1/messages", json=payload) assert response.status_code == 200 assert "Quota check passed" in response.json()["content"][0]["text"] app.dependency_overrides.clear() def test_create_message_title_generation_skip(client, mock_settings): app.dependency_overrides[get_settings] = lambda: mock_settings payload = { "model": "claude-3-sonnet", "max_tokens": 100, "messages": [{"role": "user", "content": "generate title"}], } with patch( "free_claude_code.api.optimization_handlers.is_title_generation_request", return_value=True, ): response = client.post("/v1/messages", json=payload) assert response.status_code == 200 assert "Conversation" in response.json()["content"][0]["text"] app.dependency_overrides.clear() def test_create_message_empty_messages_returns_400(client): """POST /v1/messages with messages: [] returns 400 invalid_request_error.""" payload = { "model": "claude-3-sonnet", "max_tokens": 100, "messages": [], } response = client.post("/v1/messages", json=payload) assert response.status_code == 400 data = response.json() assert data.get("type") == "error" assert data.get("error", {}).get("type") == "invalid_request_error" assert "cannot be empty" in data.get("error", {}).get("message", "") def test_count_tokens_empty_messages_returns_400(client): """POST /v1/messages/count_tokens with messages: [] matches messages validation.""" payload = {"model": "claude-3-sonnet", "messages": []} response = client.post("/v1/messages/count_tokens", json=payload) assert response.status_code == 400 data = response.json() assert data.get("type") == "error" assert data.get("error", {}).get("type") == "invalid_request_error" assert "cannot be empty" in data.get("error", {}).get("message", "") def test_count_tokens_endpoint(client): payload = { "model": "claude-3-sonnet", "messages": [{"role": "user", "content": "hello"}], } with patch("free_claude_code.api.routes.get_token_count", return_value=5): response = client.post("/v1/messages/count_tokens", json=payload) assert response.status_code == 200 assert response.json()["input_tokens"] == 5 def test_count_tokens_error_returns_500(client): """When get_token_count raises, count_tokens returns 500.""" payload = { "model": "claude-3-sonnet", "messages": [{"role": "user", "content": "hello"}], } with patch( "free_claude_code.api.routes.get_token_count", side_effect=RuntimeError("token error"), ): response = client.post("/v1/messages/count_tokens", json=payload) assert response.status_code == 500 assert "token error" in response.json()["detail"] def test_stop_cli_with_messaging_workflow(client): session_control = MagicMock() session_control.stop_all = AsyncMock(return_value=StopResult(cancelled_count=3)) services = app.state.services app.state.services = ApiServices( requests=services.requests, admin=services.admin, tasks=session_control, ) response = client.post("/stop") assert response.status_code == 200 assert response.json()["cancelled_count"] == 3 session_control.stop_all.assert_awaited_once() def test_stop_cli_fallback_to_manager(client): session_control = MagicMock() session_control.stop_all = AsyncMock(return_value=StopResult(source="cli_manager")) services = app.state.services app.state.services = ApiServices( requests=services.requests, admin=services.admin, tasks=session_control, ) response = client.post("/stop") assert response.status_code == 200 assert response.json()["source"] == "cli_manager" session_control.stop_all.assert_awaited_once()