Files
wehub-resource-sync 4ce4204b6c
CI / Lint (push) Failing after 2s
CI / Build (push) Has been skipped
SDK CI / PHP SDK (push) Failing after 1s
Split PHP SDK / PHP SDK tests (push) Failing after 1s
CI / Test (push) Failing after 1s
CI / Dashboard (push) Failing after 0s
SDK CI / JavaScript SDK (push) Failing after 0s
SDK CI / Python SDK (push) Failing after 2s
SDK CI / Java SDK (push) Failing after 1s
Split PHP SDK / Mirror sdk/php -> rmyndharis/openwa-php (push) Has been skipped
CI / Test (PostgreSQL migrations) (push) Failing after 7m47s
CI / Docker Build (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:24:08 +08:00

82 lines
2.8 KiB
Python

"""Test helpers — an httpx.MockTransport-based recorder.
Tests assert on exact method/url/body/headers without any real network or
global monkey-patching. ``httpx.MockTransport`` is the idiomatic, native way
to test an httpx-based client.
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from typing import Any, Callable
import httpx
@dataclass
class RecordedCall:
method: str
url: str
headers: dict[str, str]
body: Any
@dataclass
class MockBackend:
"""A scripted backend. Raises if a call doesn't match a route."""
calls: list[RecordedCall] = field(default_factory=list)
routes: list[tuple[str, str, Callable[[RecordedCall], httpx.Response]]] = field(default_factory=list)
fallback: Callable[[RecordedCall], httpx.Response] | None = None
def on(
self,
method: str,
path_prefix: str,
status: int = 200,
body: Any = None,
) -> "MockBackend":
def responder(_: RecordedCall) -> httpx.Response:
if status in (204, 100, 304) or body is None:
return httpx.Response(status, content=b"")
return httpx.Response(status, content=json.dumps(body).encode(), headers={"content-type": "application/json"})
self.routes.append((method.upper(), path_prefix, responder))
return self
def as_transport(self) -> httpx.MockTransport:
def handler(request: httpx.Request) -> httpx.Response:
method = request.method.upper()
url = str(request.url)
body: Any = None
if request.content:
try:
body = json.loads(request.content)
except ValueError:
body = request.content.decode(errors="replace")
call = RecordedCall(method=method, url=url, headers=dict(request.headers), body=body)
self.calls.append(call)
# Most-specific (longest) matching prefix wins, so a nested route like
# "/catalog/products" is preferred over "/catalog" regardless of
# registration order.
matches = [(pp, resp) for m, pp, resp in self.routes if m == method and pp in url]
if matches:
_, responder = max(matches, key=lambda pair: len(pair[0]))
return responder(call)
if self.fallback is not None:
return self.fallback(call)
raise AssertionError(f"MockBackend: no route for {method} {url}")
return httpx.MockTransport(handler)
@property
def last_call(self) -> RecordedCall:
return self.calls[-1]
def make_client(backend: MockBackend, base_url: str = "http://localhost:2785", api_key: str = "owa_k1_test"):
from openwa import OpenWAClient
return OpenWAClient(base_url=base_url, api_key=api_key, transport=backend.as_transport())