Skip to content

Commit 216e948

Browse files
authored
ci: add cross-platform install matrix and API smoke checks
Add cross-platform install and API smoke validation across Linux, macOS, and Windows. - validate editable install paths (PEP 517 and fallback) - run deterministic non-e2e test suite in matrix - add /health and /v1/models smoke checks - add optional manual live Claude+Haiku validation job - harden test portability for Windows/macOS path behavior and test dependencies
1 parent 40880c7 commit 216e948

8 files changed

Lines changed: 338 additions & 46 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
name: Install Matrix + API Smoke
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
inputs:
10+
run_live_claude:
11+
description: Run optional live Claude + Haiku validation
12+
required: false
13+
type: boolean
14+
default: false
15+
live_model:
16+
description: Model ID for optional live validation
17+
required: false
18+
type: string
19+
default: claude-haiku-4-5-20251001
20+
21+
permissions:
22+
contents: read
23+
24+
jobs:
25+
install-check:
26+
name: Install ${{ matrix.install_mode }} | ${{ matrix.os }} | py${{ matrix.python-version }}
27+
runs-on: ${{ matrix.os }}
28+
strategy:
29+
fail-fast: false
30+
matrix:
31+
os:
32+
- ubuntu-latest
33+
- macos-latest
34+
- windows-latest
35+
python-version:
36+
- '3.11'
37+
- '3.12'
38+
install_mode:
39+
- pep517
40+
- fallback
41+
steps:
42+
- name: Checkout repository
43+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
44+
45+
- name: Set up Python
46+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
47+
with:
48+
python-version: ${{ matrix.python-version }}
49+
50+
- name: Upgrade packaging tools
51+
run: python -m pip install --upgrade pip setuptools wheel
52+
53+
- name: Install package with PEP 517 editable mode
54+
if: ${{ matrix.install_mode == 'pep517' }}
55+
run: python -m pip install -e . --use-pep517
56+
57+
- name: Install package with editable fallback mode
58+
if: ${{ matrix.install_mode == 'fallback' }}
59+
run: python -m pip install -e .
60+
61+
- name: Validate dependency graph
62+
run: python -m pip check
63+
64+
- name: Validate runtime imports
65+
run: python -c "import claude_code_api, greenlet, sqlalchemy; print('install-import-smoke-ok')"
66+
67+
api-smoke:
68+
name: API smoke | ${{ matrix.os }} | py${{ matrix.python-version }}
69+
runs-on: ${{ matrix.os }}
70+
needs: install-check
71+
strategy:
72+
fail-fast: false
73+
matrix:
74+
os:
75+
- ubuntu-latest
76+
- macos-latest
77+
- windows-latest
78+
python-version:
79+
- '3.11'
80+
- '3.12'
81+
steps:
82+
- name: Checkout repository
83+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
84+
85+
- name: Set up Python
86+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
87+
with:
88+
python-version: ${{ matrix.python-version }}
89+
90+
- name: Upgrade packaging tools
91+
run: python -m pip install --upgrade pip setuptools wheel
92+
93+
- name: Install project with test extras
94+
run: python -m pip install -e ".[test]" --use-pep517
95+
96+
- name: Run deterministic tests (excluding live e2e)
97+
run: python -m pytest -q -m "not e2e"
98+
99+
- name: Run API health smoke test
100+
run: python -m pytest -q tests/test_end_to_end.py::TestHealthAndBasics::test_health_check
101+
102+
- name: Run API models smoke test
103+
run: python -m pytest -q tests/test_end_to_end.py::TestModelsAPI::test_list_models
104+
105+
- name: Upload test artifacts on failure
106+
if: ${{ failure() }}
107+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
108+
with:
109+
name: api-smoke-artifacts-${{ matrix.os }}-py${{ matrix.python-version }}
110+
path: |
111+
.pytest_cache
112+
dist/tests
113+
if-no-files-found: ignore
114+
retention-days: 14
115+
116+
live-claude-haiku:
117+
name: Live Claude + Haiku validation (manual)
118+
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.run_live_claude == 'true' }}
119+
runs-on: ubuntu-latest
120+
needs: api-smoke
121+
continue-on-error: true
122+
steps:
123+
- name: Checkout repository
124+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
125+
126+
- name: Set up Python
127+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
128+
with:
129+
python-version: '3.12'
130+
131+
- name: Upgrade packaging tools
132+
run: python -m pip install --upgrade pip setuptools wheel
133+
134+
- name: Install project with test extras
135+
run: python -m pip install -e ".[test]" --use-pep517
136+
137+
- name: Install Claude CLI
138+
run: curl -fsSL https://claude.ai/install.sh | bash
139+
140+
- name: Add Claude CLI path
141+
run: echo "$HOME/.local/bin" >> "$GITHUB_PATH"
142+
143+
- name: Verify ANTHROPIC_API_KEY is configured
144+
env:
145+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
146+
run: |
147+
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
148+
echo "ANTHROPIC_API_KEY secret is required for live Claude validation."
149+
exit 1
150+
fi
151+
152+
- name: Configure Claude auth from API key
153+
env:
154+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
155+
run: |
156+
python - <<'PY'
157+
import json
158+
import os
159+
from pathlib import Path
160+
161+
config_dir = Path.home() / ".config" / "claude"
162+
config_dir.mkdir(parents=True, exist_ok=True)
163+
config = {"apiKey": os.environ["ANTHROPIC_API_KEY"], "autoUpdate": False}
164+
with (config_dir / "config.json").open("w", encoding="utf-8") as handle:
165+
json.dump(config, handle)
166+
PY
167+
168+
- name: Start API server
169+
run: |
170+
nohup python -m claude_code_api.main > api-server.log 2>&1 &
171+
echo "$!" > api-server.pid
172+
173+
- name: Wait for health endpoint
174+
run: |
175+
python - <<'PY'
176+
import time
177+
import urllib.error
178+
import urllib.request
179+
180+
url = "http://127.0.0.1:8000/health"
181+
deadline = time.time() + 60
182+
while time.time() < deadline:
183+
try:
184+
with urllib.request.urlopen(url, timeout=5) as response:
185+
if response.status == 200:
186+
raise SystemExit(0)
187+
except urllib.error.URLError:
188+
pass
189+
time.sleep(2)
190+
191+
raise SystemExit("API health check failed after 60 seconds")
192+
PY
193+
194+
- name: Run live E2E against Haiku
195+
env:
196+
CLAUDE_CODE_API_E2E: '1'
197+
CLAUDE_CODE_API_BASE_URL: http://127.0.0.1:8000
198+
CLAUDE_CODE_API_TEST_MODEL: ${{ github.event.inputs.live_model || 'claude-haiku-4-5-20251001' }}
199+
run: python -m pytest -q tests/test_e2e_live_api.py -m e2e -v
200+
201+
- name: Stop API server
202+
if: ${{ always() }}
203+
run: |
204+
if [ -f api-server.pid ]; then
205+
kill "$(cat api-server.pid)" || true
206+
fi
207+
208+
- name: Upload live validation logs
209+
if: ${{ always() }}
210+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
211+
with:
212+
name: live-claude-haiku-logs
213+
path: |
214+
api-server.log
215+
dist/tests
216+
if-no-files-found: ignore
217+
retention-days: 14

claude_code_api/core/database.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
update,
1818
)
1919
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
20-
from sqlalchemy.ext.declarative import declarative_base
21-
from sqlalchemy.orm import relationship
20+
from sqlalchemy.orm import declarative_base, relationship
2221

2322
from claude_code_api.models.claude import get_default_model
2423
from claude_code_api.utils.time import utc_now

claude_code_api/main.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,7 @@ async def http_exception_handler(request, exc):
132132
@app.exception_handler(RequestValidationError)
133133
async def validation_exception_handler(request, exc):
134134
"""Return OpenAI-style errors for validation failures."""
135-
status_code = getattr(
136-
status,
137-
"HTTP_422_UNPROCESSABLE_CONTENT",
138-
status.HTTP_422_UNPROCESSABLE_ENTITY,
139-
)
135+
status_code = getattr(status, "HTTP_422_UNPROCESSABLE_CONTENT", 422)
140136
for error in exc.errors():
141137
if error.get("type") in {"value_error.jsondecode", "json_invalid"}:
142138
status_code = status.HTTP_400_BAD_REQUEST

docs/dev.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,23 @@ Coverage-only artifacts for Sonar:
8080
make coverage-sonar
8181
```
8282

83+
## CI Matrix Testing
84+
85+
- Cross-platform install and API smoke checks run in `.github/workflows/install-matrix.yml`.
86+
- Matrix coverage includes:
87+
- OS: `ubuntu-latest`, `macos-latest`, `windows-latest`
88+
- Python: `3.11`, `3.12`
89+
- Install modes: explicit PEP 517 editable and editable fallback path (`pip install -e .`)
90+
- The workflow runs deterministic tests with `pytest -m "not e2e"` and targeted API smoke checks for `/health` and `/v1/models`.
91+
92+
Optional live validation (manual only):
93+
94+
- Trigger `Install Matrix + API Smoke` via `workflow_dispatch`.
95+
- Set `run_live_claude=true`.
96+
- Provide repository secret `ANTHROPIC_API_KEY`.
97+
- Optional input `live_model` defaults to `claude-haiku-4-5-20251001`.
98+
- The live job is non-blocking (`continue-on-error`) while reliability is being established.
99+
83100
## Logging
84101

85102
- Logging is configured centrally in `claude_code_api/core/logging_config.py`.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dependencies = [
3434
"python-multipart>=0.0.6",
3535
"pydantic-settings>=2.1.0",
3636
"sqlalchemy>=2.0.23",
37+
"greenlet>=3.0.0",
3738
"aiosqlite>=0.19.0",
3839
"alembic>=1.13.0",
3940
"passlib[bcrypt]>=1.7.4",
@@ -48,6 +49,7 @@ test = [
4849
"pytest-asyncio>=0.21.0",
4950
"pytest-cov>=4.1.0",
5051
"httpx>=0.25.0",
52+
"requests>=2.31.0",
5153
"pytest-mock>=3.12.0",
5254
]
5355
dev = [

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"python-multipart>=0.0.6",
2525
"pydantic-settings>=2.1.0",
2626
"sqlalchemy>=2.0.23",
27+
"greenlet>=3.0.0",
2728
"aiosqlite>=0.19.0",
2829
"alembic>=1.13.0",
2930
"passlib[bcrypt]>=1.7.4",
@@ -36,6 +37,7 @@
3637
"pytest-asyncio>=0.21.0",
3738
"pytest-cov>=4.1.0",
3839
"httpx>=0.25.0",
40+
"requests>=2.31.0",
3941
"pytest-mock>=3.12.0",
4042
],
4143
"dev": [

0 commit comments

Comments
 (0)