Skip to content

Integrate api-simulator for Quota Guard End-to-End HTTP Testing#613

Merged
Trecek merged 8 commits intointegrationfrom
integrate-api-simulator-for-quota-guard-end-to-end-http-test/607
Apr 5, 2026
Merged

Integrate api-simulator for Quota Guard End-to-End HTTP Testing#613
Trecek merged 8 commits intointegrationfrom
integrate-api-simulator-for-quota-guard-end-to-end-http-test/607

Conversation

@Trecek
Copy link
Copy Markdown
Collaborator

@Trecek Trecek commented Apr 5, 2026

Summary

Add api-simulator as a dev dependency and use its mock_http_server pytest fixture to test the quota guard's real HTTP path end-to-end. Currently all quota tests monkeypatch _fetch_quota at 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 a base_url parameter to _fetch_quota and check_and_sleep_if_needed, then writes 7 tests that point the real httpx client at mock_http_server to exercise the full HTTP path.

Files changed: 3 (pyproject.toml, src/autoskillit/execution/quota.py, new tests/execution/test_quota_http.py)
Existing tests: Unchanged — all monkeypatch-based tests in test_quota.py remain as-is.

Requirements

DEP — Dependency Integration

  • REQ-DEP-001: The system must include api-simulator as a dev-only dependency with a pinned git tag source.
  • REQ-DEP-002: The api-simulator dependency must not appear in production runtime dependencies.

CFG — URL Configurability

  • REQ-CFG-001: _fetch_quota must accept a base_url parameter defaulting to https://api.anthropic.com.
  • REQ-CFG-002: check_and_sleep_if_needed must thread the base_url parameter through to _fetch_quota at both call sites.
  • REQ-CFG-003: The production behavior must be unchanged when base_url is not explicitly provided.

HTTP — HTTP Path Verification

  • REQ-HTTP-001: Tests must exercise the real httpx client construction path, not monkeypatch _fetch_quota.
  • REQ-HTTP-002: Tests must verify that the Authorization: Bearer header is sent on the request.
  • REQ-HTTP-003: Tests must verify that the anthropic-beta: oauth-2025-04-20 header is sent on the request.
  • REQ-HTTP-004: Tests must verify correct JSON response parsing for the five_hour utilization shape.

ERR — Error Handling Verification

  • REQ-ERR-001: Tests must verify fail-open behavior on HTTP 4xx/5xx responses.
  • REQ-ERR-002: Tests must verify fail-open behavior on network timeout.
  • REQ-ERR-003: Tests must verify that the above-threshold path triggers a double-fetch (two HTTP requests).

COMPAT — Backward Compatibility

  • REQ-COMPAT-001: Existing test_quota.py tests 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;
Loading

Color Legend:

Color Category Description
Dark Blue Terminal Entry point
Teal State Decision points and routing
Orange Handler Processing nodes (cache read, HTTP fetch, parse)
Green New Component ★ New base_url parameter and test infrastructure
Purple Phase Result return paths

Development 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;
Loading

Color Legend:

Color Category Description
Purple Build Config pyproject.toml, dev deps, lockfile
Green New Component ★ api-simulator dep, uv.sources, HTTP test file, mock fixture
Orange Test Framework pytest, xdist, existing test_quota.py
Red Quality Gates ruff, mypy, uv lock check, gitleaks, import-linter
Dark Teal Entry Points CLI entry point

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

Step input output cached count time
plan 5.5k 76.5k 6.0M 5 32m 41s
verify 3.1k 86.2k 5.4M 5 31m 25s
implement 1.1k 116.2k 22.6M 6 50m 55s
fix 214 28.4k 3.5M 5 30m 58s
audit_impl 137 58.9k 3.1M 5 19m 28s
open_pr 100 51.3k 3.9M 3 16m 38s
Total 10.2k 417.5k 44.5M 3h 2m

Trecek and others added 4 commits April 4, 2026 19:35
… 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>
Copy link
Copy Markdown
Collaborator Author

@Trecek Trecek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoSkillit PR Review — Verdict: changes_requested

Found 12 inline findings (1 critical, 11 warnings). See inline comments.

)


@pytest.fixture(autouse=True)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Trecek and others added 3 commits April 4, 2026 20:49
…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>
@Trecek Trecek force-pushed the integrate-api-simulator-for-quota-guard-end-to-end-http-test/607 branch from f9d0c3d to 918378a Compare April 5, 2026 04:43
@Trecek Trecek force-pushed the integrate-api-simulator-for-quota-guard-end-to-end-http-test/607 branch from 918378a to a6c7683 Compare April 5, 2026 04:46
@Trecek Trecek added this pull request to the merge queue Apr 5, 2026
Merged via the queue into integration with commit 7073db5 Apr 5, 2026
2 checks passed
@Trecek Trecek deleted the integrate-api-simulator-for-quota-guard-end-to-end-http-test/607 branch April 5, 2026 04:52
Trecek added a commit that referenced this pull request Apr 5, 2026
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>
github-merge-queue bot pushed a commit that referenced this pull request Apr 5, 2026
## 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>
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.

1 participant