Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion examples/mock-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ terminal, then run a consumer demo in another (`make demo-config` etc. — see
| `/user/` | GET | Default happy-path body when the script queue is empty (unversioned, mirrors the backend) |
| `/headers/` | GET | Default happy-path body when the script queue is empty (unversioned, mirrors the backend) |
| `/status/` | GET | Default happy-path body when the script queue is empty |
| (anything else) | * | `404 {"s":"error","errmsg":"..."}` when no script step matches |
| (anything else) | * | `404 {"s":"no_data"}` when no script step matches — mirrors the backend's `custom_404` |

## Scripted-step semantics

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

## Why this is separate from the SDK's tests
Expand Down
24 changes: 23 additions & 1 deletion examples/mock-server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ class ScriptedStep(BaseModel):
)


def _default_ratelimit_headers() -> dict[str, str]:
# Mirrors the backend's update_user_quota(): every response — including the
# 404 from custom_404 — carries the four x-api-ratelimit-* headers the SDK's
# RateLimitHeaders.parse() reads. Parse is all-or-nothing, so always emit all
# four. Scripts can override any of these (e.g. remaining=0 to drive the
# §10.3 preflight / exhausted-credits path) — see catch_all's setdefault.
reset_epoch = int(time.time()) + 24 * 3600 # next daily quota reset
return {
"x-api-ratelimit-limit": "100000",
"x-api-ratelimit-remaining": "99999",
"x-api-ratelimit-reset": str(reset_epoch),
"x-api-ratelimit-consumed": "1",
}


def _default_status_body() -> str:
now_epoch = int(time.time())
return json.dumps(
Expand Down Expand Up @@ -185,6 +200,9 @@ async def catch_all(full_path: str, request: Request) -> Response:
# unless the script explicitly overrode it.
response_headers.setdefault("cf-ray", f"mock-{int(time.time() * 1000)}")
response_headers.setdefault("Content-Type", "application/json")
# Rate-limit headers, unless the script set its own (e.g. remaining=0).
for _k, _v in _default_ratelimit_headers().items():
response_headers.setdefault(_k, _v)
_request_log.append(
{"path": path, "method": method, "status": step.status, "scripted": True}
)
Expand All @@ -206,6 +224,7 @@ async def catch_all(full_path: str, request: Request) -> Response:
headers={
"Content-Type": "application/json",
"cf-ray": f"mock-{int(time.time() * 1000)}",
**_default_ratelimit_headers(),
},
)

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