Skip to content

test: add unit coverage for llm/client.py error mapping#150

Merged
CGFixIT merged 1 commit into
mainfrom
claude/cyclaw-optimization-review-xc121y-test-client
Jun 21, 2026
Merged

test: add unit coverage for llm/client.py error mapping#150
CGFixIT merged 1 commit into
mainfrom
claude/cyclaw-optimization-review-xc121y-test-client

Conversation

@CGFixIT

@CGFixIT CGFixIT commented Jun 21, 2026

Copy link
Copy Markdown
Owner

What

Adds tests/test_client.py — the first dedicated test for llm/client.py (LocalLLMClient + GrokClient) — and wires it into CI.

Why

llm/client.py was only ever mocked (in test_graph.py / test_gate.py via conftest.MockLocalLLM / MockGrokClient), so its real generate() error-translation code was never executed by the suite. That code is a contract the graph relies on: local_llm_node and grok_fallback_node only except LLMServiceError / GrokServiceError. If a wire-level httpx exception ever leaked through unmapped, it would bypass those handlers and surface to the client as a raw 500 instead of a graceful [LLM Error: ...]. A regression here is exactly the kind of thing a unit test should catch.

Coverage added

With the per-instance httpx.Client monkeypatched (no live LM Studio / xAI contacted; real httpx.Request/Response objects so the e.response.status_code path runs for real):

  • __init__ config parsing
  • successful generate() — asserts endpoint, model, prompt body, and Grok's Authorization: Bearer header
  • HTTPStatusErrorLLMServiceError / GrokServiceError with details["status"]
  • TimeoutException → typed error with details["timeout_sec"]
  • unexpected Exception → typed error (original message preserved)
  • GrokClient.is_available() and the no-GROK_API_KEY guard

CI

  • Registered tests/test_client.py in the workflow's pytest file list.
  • Added --cov=llm.client so the newly exercised module counts toward the coverage gate.

Risk

Low — test-only plus two additive CI lines. New file passes locally (11 passed). No production code touched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NXYYNSfqvBrAgghyNmzbHs


Generated by Claude Code

llm/client.py (LocalLLMClient + GrokClient) had no dedicated test — it
was only ever mocked in test_graph/test_gate, so its real generate()
error-translation paths were never exercised. The graph nodes depend on
that contract: local_llm_node / grok_fallback_node only catch
LLMServiceError / GrokServiceError, so any leaked httpx exception would
escape the node handlers and surface as a raw 500.

tests/test_client.py covers, with the per-instance httpx.Client
monkeypatched (no live LM Studio / xAI endpoint):
  - config parsing in __init__
  - successful generate() (endpoint, model, prompt, Bearer header)
  - HTTPStatusError  -> LLMServiceError / GrokServiceError (+ status detail)
  - TimeoutException -> typed error (+ timeout_sec detail)
  - unexpected Exception -> typed error (message preserved)
  - GrokClient.is_available() and the no-API-key guard

Registered tests/test_client.py in the CI pytest list and added
--cov=llm.client so the new paths count toward the coverage gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NXYYNSfqvBrAgghyNmzbHs
@CGFixIT CGFixIT marked this pull request as ready for review June 21, 2026 02:46
@CGFixIT CGFixIT merged commit 85c850a into main Jun 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants