Integrate api-simulator for Quota Guard End-to-End HTTP Testing#613
Conversation
… dep Add api-simulator as a dev dependency (git source, v0.1.0) for HTTP-level testing. Add keyword-only base_url parameter (default: api.anthropic.com) to _fetch_quota and check_and_sleep_if_needed, threading it through both fetch call sites. Production behavior unchanged via default value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exercise the real httpx client path with mock_http_server fixture: normal utilization + header verification, double-fetch on threshold, resets_at null fallback, HTTP 429/503 fail-open, network timeout fail-open, and Z-suffix datetime parsing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…kfile Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trecek
left a comment
There was a problem hiding this comment.
AutoSkillit PR Review — Verdict: changes_requested
Found 12 inline findings (1 critical, 11 warnings). See inline comments.
| ) | ||
|
|
||
|
|
||
| @pytest.fixture(autouse=True) |
There was a problem hiding this comment.
[warning] tests: _reset_mock is autouse=True and calls mock_http_server.reset() per-test. If mock_http_server is session-scoped (common for server fixtures), route registrations from parallel xdist workers sharing the same server can cross-contaminate. Verify that mock_http_server is function-scoped before relying on this reset pattern.
There was a problem hiding this comment.
Investigated — this is intentional. pytest-xdist with -n 4 creates a separate OS process per worker; session-scoped fixtures are initialized once per worker process, not globally. Each worker gets its own mock_http_server instance on a different port (confirmed via api_simulator/plugin.py). Tests within a worker run sequentially, so the autouse _reset_mock cleanly isolates state. No cross-worker contamination is possible in this architecture.
…t param Extract duplicate 'https://api.anthropic.com' literal into module-level _DEFAULT_BASE_URL constant used by both _fetch_quota and check_and_sleep_if_needed. Add _httpx_timeout keyword param (default 10s) to both functions so tests can inject a short timeout without waiting the full 10s for httpx to time out against a delayed mock server. Addresses reviewer comments: duplicate base_url literal (cohesion) and network timeout test performance (tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SCII art - Tests 1 and 7: replace direct _fetch_quota calls with check_and_sleep_if_needed; assert on result dict keys instead of QuotaStatus fields. Removes import coupling to private function. - test_network_timeout_fails_open: use delay_seconds=0.5 with _httpx_timeout=0.1 so the test completes in ~0.1s instead of ~10s. - Remove 8 decorative ASCII art separators (# ── ... ──) throughout the file; test function names already communicate their purpose. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
uv.lock pinned api-simulator to commit 5576a53e which no longer exists after the upstream repo rewrote its main branch. Regenerated with --upgrade-package api-simulator to pin 41bed890. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f9d0c3d to
918378a
Compare
918378a to
a6c7683
Compare
uv lock fails in patch-bump-integration and version-bump workflows because api-simulator is a private git dependency added in PR #613. The tests.yml workflow has the auth step but both version-bump workflows were missed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary - Add `Configure git auth for private deps` step to `patch-bump-integration.yml` and `version-bump.yml` before `uv lock` runs - Fixes authentication failure when resolving the private `api-simulator` git dependency added in PR #613 - Mirrors the existing auth pattern already present in `tests.yml` (line 76) ## Root Cause PR #613 added `api-simulator` as a private git dependency in `pyproject.toml`. The `tests.yml` workflow was updated with git auth, but both version-bump workflows were missed. Every PR merged to `integration` since then fails at the `uv lock` step with: ``` fatal: could not read Username for 'https://github.com': terminal prompts disabled ``` ## Test plan - [ ] This PR's own CI passes (tests.yml) - [ ] After merge, the patch-bump workflow should succeed — verify by checking the `bump-patch` check on this PR's merge commit - [ ] Re-run a recent failed bump-patch workflow to confirm the fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Add
api-simulatoras a dev dependency and use itsmock_http_serverpytest fixture to test the quota guard's real HTTP path end-to-end. Currently all quota tests monkeypatch_fetch_quotaat the function level — the actual httpx client construction, header injection (Authorization: Bearer,anthropic-beta), response parsing, and error handling are never exercised. This plan introduces abase_urlparameter to_fetch_quotaandcheck_and_sleep_if_needed, then writes 7 tests that point the real httpx client atmock_http_serverto exercise the full HTTP path.Files changed: 3 (
pyproject.toml,src/autoskillit/execution/quota.py, newtests/execution/test_quota_http.py)Existing tests: Unchanged — all monkeypatch-based tests in
test_quota.pyremain as-is.Requirements
DEP — Dependency Integration
api-simulatoras a dev-only dependency with a pinned git tag source.CFG — URL Configurability
_fetch_quotamust accept abase_urlparameter defaulting tohttps://api.anthropic.com.check_and_sleep_if_neededmust thread thebase_urlparameter through to_fetch_quotaat both call sites.base_urlis not explicitly provided.HTTP — HTTP Path Verification
_fetch_quota.Authorization: Bearerheader is sent on the request.anthropic-beta: oauth-2025-04-20header is sent on the request.five_hourutilization shape.ERR — Error Handling Verification
COMPAT — Backward Compatibility
test_quota.pytests must continue to pass unchanged.Architecture Impact
Process Flow Diagram
%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 60, 'curve': 'basis'}}}%% flowchart TB classDef terminal fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; START([START: check_and_sleep_if_needed]) subgraph GatePhase ["Gate Phase"] direction TB ENABLED{"config.enabled?"} DISABLED(["RETURN<br/>should_sleep: false"]) end subgraph CachePhase ["Cache Phase"] direction TB CACHE["_read_cache<br/>━━━━━━━━━━<br/>Read local JSON cache"] CACHE_HIT{"Cache fresh?<br/>━━━━━━━━━━<br/>age ≤ max_age?"} end subgraph FetchPhase ["HTTP Fetch Phase"] direction TB FETCH["● _fetch_quota<br/>━━━━━━━━━━<br/>★ base_url parameter<br/>httpx.AsyncClient GET"] BASEURL["★ base_url<br/>━━━━━━━━━━<br/>default: api.anthropic.com<br/>test: mock_http_server.url"] PARSE["Parse Response<br/>━━━━━━━━━━<br/>five_hour.utilization<br/>Z→+00:00 normalization"] end subgraph DecisionPhase ["Threshold Decision"] direction TB THRESHOLD{"utilization<br/>≥ threshold?"} RESETS_AT1{"resets_at<br/>is None?<br/>(Gate 1)"} REFETCH["● _fetch_quota re-fetch<br/>━━━━━━━━━━<br/>★ base_url threaded<br/>Double-fetch for accuracy"] RESETS_AT2{"resets_at<br/>still None?<br/>(Gate 2)"} end subgraph Results ["Results"] BELOW(["RETURN<br/>should_sleep: false"]) FALLBACK1(["RETURN<br/>should_sleep: true<br/>reason: unknown_reset<br/>fallback ≥ 60s"]) FALLBACK2(["RETURN<br/>should_sleep: true<br/>reason: unknown_reset<br/>fallback ≥ 60s"]) SLEEP(["RETURN<br/>should_sleep: true<br/>sleep_seconds computed"]) FAILOPEN(["RETURN<br/>should_sleep: false<br/>error key present"]) end subgraph TestInfra ["★ Test Infrastructure (test_quota_http.py)"] direction TB MOCK["★ mock_http_server<br/>━━━━━━━━━━<br/>api-simulator fixture<br/>HTTP server"] REGISTER["★ register / register_sequence<br/>━━━━━━━━━━<br/>Custom endpoint responses<br/>Status codes, delays"] INSPECT["★ get_requests / request_count<br/>━━━━━━━━━━<br/>Header verification<br/>Double-fetch assertion"] end START --> ENABLED ENABLED -->|"false"| DISABLED ENABLED -->|"true"| CACHE CACHE --> CACHE_HIT CACHE_HIT -->|"fresh + below threshold"| BELOW CACHE_HIT -->|"miss or expired"| FETCH FETCH --> BASEURL BASEURL --> PARSE PARSE --> THRESHOLD THRESHOLD -->|"below"| BELOW THRESHOLD -->|"above"| RESETS_AT1 RESETS_AT1 -->|"None"| FALLBACK1 RESETS_AT1 -->|"present"| REFETCH REFETCH --> RESETS_AT2 RESETS_AT2 -->|"None"| FALLBACK2 RESETS_AT2 -->|"present"| SLEEP FETCH -.->|"HTTP error / timeout"| FAILOPEN MOCK -.->|"serves responses to"| BASEURL REGISTER -.->|"configures"| MOCK INSPECT -.->|"verifies headers / count"| FETCH class START terminal; class DISABLED,BELOW,FALLBACK1,FALLBACK2,SLEEP,FAILOPEN phase; class ENABLED,CACHE_HIT,THRESHOLD,RESETS_AT1,RESETS_AT2 stateNode; class CACHE,PARSE handler; class FETCH,REFETCH handler; class BASEURL,MOCK,REGISTER,INSPECT newComponent;Color Legend:
base_urlparameter and test infrastructureDevelopment Diagram
%%{init: {'flowchart': {'nodeSpacing': 50, 'rankSpacing': 60, 'curve': 'basis'}}}%% flowchart TB classDef cli fill:#1a237e,stroke:#7986cb,stroke-width:2px,color:#fff; classDef stateNode fill:#004d40,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef handler fill:#e65100,stroke:#ffb74d,stroke-width:2px,color:#fff; classDef phase fill:#6a1b9a,stroke:#ba68c8,stroke-width:2px,color:#fff; classDef newComponent fill:#2e7d32,stroke:#81c784,stroke-width:2px,color:#fff; classDef output fill:#00695c,stroke:#4db6ac,stroke-width:2px,color:#fff; classDef detector fill:#b71c1c,stroke:#ef5350,stroke-width:2px,color:#fff; subgraph Deps ["● DEPENDENCY MANIFEST (pyproject.toml)"] direction TB PYPROJECT["● pyproject.toml<br/>━━━━━━━━━━<br/>hatchling build backend<br/>requires-python ≥ 3.11"] DEVDEPS["● dev optional-dependencies<br/>━━━━━━━━━━<br/>pytest, pytest-asyncio,<br/>pytest-httpx, pytest-xdist,<br/>pytest-timeout, ruff,<br/>import-linter, packaging"] APISIM["★ api-simulator<br/>━━━━━━━━━━<br/>New dev dependency<br/>HTTP mock fixture provider"] UVSRC["★ [tool.uv.sources]<br/>━━━━━━━━━━<br/>api-simulator pinned<br/>git: TalonT-Org/api-simulator<br/>branch: main"] UVLOCK["● uv.lock<br/>━━━━━━━━━━<br/>Regenerated with<br/>api-simulator entry"] end subgraph Quality ["CODE QUALITY GATES (pre-commit)"] direction TB FORMAT["ruff format<br/>━━━━━━━━━━<br/>Auto-fix code style<br/>reads + modifies src"] LINT["ruff check<br/>━━━━━━━━━━<br/>Auto-fix lint violations<br/>reads + modifies src"] TYPES["mypy<br/>━━━━━━━━━━<br/>Type checking<br/>reads src, reports only"] UVCHECK["uv lock check<br/>━━━━━━━━━━<br/>Verifies lockfile sync<br/>reads uv.lock"] SECRETS["gitleaks<br/>━━━━━━━━━━<br/>Secret scanning<br/>reads staged files"] IMPORTLINT["import-linter<br/>━━━━━━━━━━<br/>Layer contract enforcement<br/>IL-001 through IL-007"] end subgraph Testing ["TEST FRAMEWORK"] direction TB PYTEST["pytest + pytest-asyncio<br/>━━━━━━━━━━<br/>asyncio_mode=auto<br/>timeout=60s signal"] XDIST["pytest-xdist -n 4<br/>━━━━━━━━━━<br/>Parallel test workers<br/>worksteal distribution"] UNITQUOTA["● test_quota.py<br/>━━━━━━━━━━<br/>23 unit tests<br/>monkeypatch _fetch_quota<br/>mock signature updated"] HTTPQUOTA["★ test_quota_http.py<br/>━━━━━━━━━━<br/>7 end-to-end HTTP tests<br/>real httpx client path<br/>no monkeypatching"] MOCKSERVER["★ mock_http_server fixture<br/>━━━━━━━━━━<br/>api-simulator provides<br/>register / register_sequence<br/>get_requests / request_count"] end subgraph EntryPoints ["ENTRY POINTS"] CLI["autoskillit CLI<br/>━━━━━━━━━━<br/>autoskillit.cli:main"] end PYPROJECT --> DEVDEPS DEVDEPS --> APISIM APISIM --> UVSRC UVSRC --> UVLOCK PYPROJECT --> FORMAT FORMAT --> LINT LINT --> TYPES TYPES --> UVCHECK UVCHECK --> SECRETS SECRETS --> IMPORTLINT IMPORTLINT --> PYTEST PYTEST --> XDIST XDIST --> UNITQUOTA XDIST --> HTTPQUOTA APISIM -.->|"provides fixture"| MOCKSERVER MOCKSERVER -.->|"injected into"| HTTPQUOTA PYPROJECT --> CLI class PYPROJECT,DEVDEPS,UVLOCK phase; class APISIM,UVSRC,HTTPQUOTA,MOCKSERVER newComponent; class UNITQUOTA handler; class FORMAT,LINT,TYPES,UVCHECK,SECRETS,IMPORTLINT detector; class PYTEST,XDIST handler; class CLI output;Color Legend:
Closes #607
Implementation Plan
Plan file:
/home/talon/projects/autoskillit-runs/impl-20260404-190816-816130/.autoskillit/temp/make-plan/integrate_api_simulator_quota_guard_plan_2026-04-04_191500.md🤖 Generated with Claude Code via AutoSkillit
Token Usage Summary