free-claude-code/smoke/test_api_live.py
Ali Khokhar 462a9430bb
Add local live smoke test suite (#148)
## Summary
- add an opt-in local `smoke/` pytest suite for API, auth, providers,
CLI, IDE-shaped requests, messaging, voice, tools, and thinking stream
contracts
- keep smoke tests out of normal CI collection with `testpaths =
["tests"]`
- write sanitized smoke artifacts under `.smoke-results/`

## Verification
- `uv run ruff format`
- `uv run ruff check`
- `uv run ty check`
- `uv run ty check smoke`
- `FCC_LIVE_SMOKE=1 FCC_SMOKE_TARGETS=all FCC_SMOKE_RUN_VOICE=1 uv run
pytest smoke -n 0 -m live -s --tb=short` -> 17 passed, 9 skipped
- `uv run pytest` -> 904 passed

## Notes
- Skipped live checks require local credentials/tools/services, such as
provider models, Telegram/Discord targets, voice backend, or Claude CLI.
- `claude-pick` smoke was intentionally removed.
2026-04-23 19:06:09 -07:00

175 lines
5.4 KiB
Python

from __future__ import annotations
from typing import Any
import httpx
import pytest
from smoke.lib.config import SmokeConfig
from smoke.lib.http import post_json
from smoke.lib.server import RunningServer
pytestmark = [pytest.mark.live, pytest.mark.smoke_target("api")]
def test_probe_and_models_routes(
smoke_server: RunningServer, smoke_headers: dict[str, str]
) -> None:
with httpx.Client(base_url=smoke_server.base_url, headers=smoke_headers) as client:
assert client.get("/health").json()["status"] == "healthy"
root = client.get("/")
assert root.status_code == 200
assert root.json()["status"] == "ok"
models = client.get("/v1/models")
assert models.status_code == 200
assert models.json()["data"]
for path in ("/", "/health", "/v1/messages", "/v1/messages/count_tokens"):
head = client.head(path)
assert head.status_code == 204, (path, head.status_code, head.text)
options = client.options(path)
assert options.status_code == 204, (path, options.status_code, options.text)
def test_count_tokens_accepts_thinking_tools_and_results(
smoke_server: RunningServer,
smoke_config: SmokeConfig,
smoke_headers: dict[str, str],
) -> None:
payload: dict[str, Any] = {
"model": "claude-3-5-sonnet-20241022",
"messages": [
{"role": "user", "content": "Use the tool."},
{
"role": "assistant",
"content": [
{"type": "thinking", "thinking": "Need to inspect the file."},
{
"type": "tool_use",
"id": "toolu_smoke",
"name": "Read",
"input": {"file_path": "README.md"},
},
],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_smoke",
"content": "Free Claude Code",
}
],
},
],
"tools": [
{
"name": "Read",
"description": "Read a file",
"input_schema": {
"type": "object",
"properties": {"file_path": {"type": "string"}},
"required": ["file_path"],
},
}
],
}
response = post_json(
smoke_server,
"/v1/messages/count_tokens",
payload,
smoke_config,
headers=smoke_headers,
)
assert response.status_code == 200, response.text
assert response.json()["input_tokens"] > 0
def test_optimization_fast_paths_do_not_need_provider(
smoke_server: RunningServer,
smoke_config: SmokeConfig,
smoke_headers: dict[str, str],
) -> None:
cases: tuple[tuple[str, dict[str, Any], str], ...] = (
(
"quota",
{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1,
"messages": [{"role": "user", "content": "quota"}],
},
"Quota check passed.",
),
(
"title",
{
"model": "claude-3-5-sonnet-20241022",
"system": "Generate a title for the new conversation topic.",
"messages": [{"role": "user", "content": "hello"}],
},
"Conversation",
),
(
"prefix",
{
"model": "claude-3-5-sonnet-20241022",
"messages": [
{
"role": "user",
"content": "<policy_spec>extract command</policy_spec>\nCommand: git status --short",
}
],
},
"git",
),
(
"suggestion",
{
"model": "claude-3-5-sonnet-20241022",
"messages": [{"role": "user", "content": "[SUGGESTION MODE: next]"}],
},
"",
),
(
"filepath",
{
"model": "claude-3-5-sonnet-20241022",
"system": "Extract any file paths that this command output contains.",
"messages": [
{
"role": "user",
"content": "Command: cat smoke/test_api_live.py\nOutput: file contents\n<filepaths>",
}
],
},
"smoke/test_api_live.py",
),
)
for name, payload, expected_text in cases:
response = post_json(
smoke_server, "/v1/messages", payload, smoke_config, headers=smoke_headers
)
assert response.status_code == 200, (name, response.text)
text = response.json()["content"][0]["text"]
assert expected_text in text
def test_invalid_messages_returns_anthropic_error(
smoke_server: RunningServer,
smoke_config: SmokeConfig,
smoke_headers: dict[str, str],
) -> None:
response = post_json(
smoke_server,
"/v1/messages",
{"model": "claude-3-5-sonnet-20241022", "messages": []},
smoke_config,
headers=smoke_headers,
)
assert response.status_code == 400
payload = response.json()
assert payload["type"] == "error"
assert payload["error"]["type"] == "invalid_request_error"