Skip to content

Commit 5e9dd80

Browse files
add rate limits headers to mock server response
1 parent 77489b8 commit 5e9dd80

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

examples/mock-server/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ terminal, then run a consumer demo in another (`make demo-config` etc. — see
3333
| `/user/` | GET | Default happy-path body when the script queue is empty (unversioned, mirrors the backend) |
3434
| `/headers/` | GET | Default happy-path body when the script queue is empty (unversioned, mirrors the backend) |
3535
| `/status/` | GET | Default happy-path body when the script queue is empty |
36-
| (anything else) | * | `404 {"s":"error","errmsg":"..."}` when no script step matches |
36+
| (anything else) | * | `404 {"s":"no_data"}` when no script step matches — mirrors the backend's `custom_404` |
3737

3838
## Scripted-step semantics
3939

@@ -44,6 +44,12 @@ terminal, then run a consumer demo in another (`make demo-config` etc. — see
4444
- `cf-ray` is added to every response if you don't set it yourself — that's
4545
what the SDK reads for `requestId` on the response envelope and exception
4646
context, so populating it makes the demos' logs traceable.
47+
- The four `x-api-ratelimit-*` headers (`limit`, `remaining`, `reset`,
48+
`consumed`) are added to **every** response — scripted, default, and 404 —
49+
mirroring the backend's `update_user_quota`. They populate
50+
`client.getRateLimits()`. To exercise the exhausted-credits / §10.3 preflight
51+
path, script your own `x-api-ratelimit-remaining: 0` (plus a future `reset`);
52+
your value wins via `setdefault`.
4753
- Once popped, a step is gone. Re-script if you need the same shape twice.
4854

4955
## Why this is separate from the SDK's tests

examples/mock-server/server.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ class ScriptedStep(BaseModel):
8585
)
8686

8787

88+
def _default_ratelimit_headers() -> dict[str, str]:
89+
# Mirrors the backend's update_user_quota(): every response — including the
90+
# 404 from custom_404 — carries the four x-api-ratelimit-* headers the SDK's
91+
# RateLimitHeaders.parse() reads. Parse is all-or-nothing, so always emit all
92+
# four. Scripts can override any of these (e.g. remaining=0 to drive the
93+
# §10.3 preflight / exhausted-credits path) — see catch_all's setdefault.
94+
reset_epoch = int(time.time()) + 24 * 3600 # next daily quota reset
95+
return {
96+
"x-api-ratelimit-limit": "100000",
97+
"x-api-ratelimit-remaining": "99999",
98+
"x-api-ratelimit-reset": str(reset_epoch),
99+
"x-api-ratelimit-consumed": "1",
100+
}
101+
102+
88103
def _default_status_body() -> str:
89104
now_epoch = int(time.time())
90105
return json.dumps(
@@ -185,6 +200,9 @@ async def catch_all(full_path: str, request: Request) -> Response:
185200
# unless the script explicitly overrode it.
186201
response_headers.setdefault("cf-ray", f"mock-{int(time.time() * 1000)}")
187202
response_headers.setdefault("Content-Type", "application/json")
203+
# Rate-limit headers, unless the script set its own (e.g. remaining=0).
204+
for _k, _v in _default_ratelimit_headers().items():
205+
response_headers.setdefault(_k, _v)
188206
_request_log.append(
189207
{"path": path, "method": method, "status": step.status, "scripted": True}
190208
)
@@ -206,6 +224,7 @@ async def catch_all(full_path: str, request: Request) -> Response:
206224
headers={
207225
"Content-Type": "application/json",
208226
"cf-ray": f"mock-{int(time.time() * 1000)}",
227+
**_default_ratelimit_headers(),
209228
},
210229
)
211230

@@ -223,4 +242,7 @@ def _default_response_for(path: str) -> tuple[str, int]:
223242
return _DEFAULT_HEADERS, 200
224243
if path in ("/status/", "/status"):
225244
return _default_status_body(), 200
226-
return json.dumps({"s": "error", "errmsg": f"unknown endpoint {path}"}), 404
245+
# Unknown routes mirror the backend's custom_404: {"s":"no_data"} at 404,
246+
# NOT an error envelope. The SDK treats 404+no_data as a successful empty
247+
# response (Response.isNoData()), so this keeps the mock faithful.
248+
return json.dumps({"s": "no_data"}), 404

0 commit comments

Comments
 (0)