mockserver-client 6.0.0__tar.gz → 7.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/PKG-INFO +1 -1
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/__init__.py +18 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/async_client.py +116 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/client.py +56 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/fluent.py +72 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/models.py +484 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/websocket_client.py +1 -1
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/PKG-INFO +1 -1
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/pyproject.toml +1 -1
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_client.py +149 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_fluent.py +156 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_models.py +1086 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/README.md +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver/exceptions.py +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/SOURCES.txt +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/dependency_links.txt +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/requires.txt +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/top_level.txt +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/setup.cfg +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_async_client.py +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_exceptions.py +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_init.py +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_integration.py +0 -0
- {mockserver_client-6.0.0 → mockserver_client-7.0.0}/tests/test_websocket_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mockserver-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.0.0
|
|
4
4
|
Summary: Official Python client for MockServer — create expectations, verify requests, and register dynamic callbacks via WebSocket
|
|
5
5
|
Author-email: James Bloom <jamesdbloom@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
from mockserver.models import (
|
|
2
2
|
AfterAction,
|
|
3
|
+
BinaryResponse,
|
|
3
4
|
Body,
|
|
4
5
|
ConnectionOptions,
|
|
5
6
|
Delay,
|
|
6
7
|
DelayDistribution,
|
|
8
|
+
DnsRecord,
|
|
9
|
+
DnsResponse,
|
|
7
10
|
Expectation,
|
|
8
11
|
ExpectationId,
|
|
12
|
+
ExpectationStep,
|
|
13
|
+
GrpcBidiResponse,
|
|
14
|
+
GrpcBidiRule,
|
|
15
|
+
GrpcStreamMessage,
|
|
16
|
+
GrpcStreamResponse,
|
|
17
|
+
HttpChaosProfile,
|
|
9
18
|
HttpClassCallback,
|
|
10
19
|
HttpError,
|
|
11
20
|
HttpForward,
|
|
@@ -45,12 +54,21 @@ from mockserver.async_client import AsyncMockServerClient
|
|
|
45
54
|
__all__ = [
|
|
46
55
|
"AfterAction",
|
|
47
56
|
"AsyncMockServerClient",
|
|
57
|
+
"BinaryResponse",
|
|
48
58
|
"Body",
|
|
49
59
|
"ConnectionOptions",
|
|
50
60
|
"Delay",
|
|
51
61
|
"DelayDistribution",
|
|
62
|
+
"DnsRecord",
|
|
63
|
+
"DnsResponse",
|
|
52
64
|
"Expectation",
|
|
53
65
|
"ExpectationId",
|
|
66
|
+
"ExpectationStep",
|
|
67
|
+
"GrpcBidiResponse",
|
|
68
|
+
"GrpcBidiRule",
|
|
69
|
+
"GrpcStreamMessage",
|
|
70
|
+
"GrpcStreamResponse",
|
|
71
|
+
"HttpChaosProfile",
|
|
54
72
|
"HttpClassCallback",
|
|
55
73
|
"HttpError",
|
|
56
74
|
"HttpForward",
|
|
@@ -18,6 +18,7 @@ from mockserver.exceptions import (
|
|
|
18
18
|
from mockserver.fluent import ForwardChainExpectation
|
|
19
19
|
from mockserver.models import (
|
|
20
20
|
Expectation,
|
|
21
|
+
HttpChaosProfile,
|
|
21
22
|
HttpObjectCallback,
|
|
22
23
|
HttpRequest,
|
|
23
24
|
HttpRequestAndHttpResponse,
|
|
@@ -166,6 +167,121 @@ class AsyncMockServerClient:
|
|
|
166
167
|
f"Failed to reset (status={status}): {response_body}"
|
|
167
168
|
)
|
|
168
169
|
|
|
170
|
+
async def freeze_clock(self, instant: str | None = None) -> dict:
|
|
171
|
+
"""Freeze the server clock at the given ISO-8601 instant.
|
|
172
|
+
|
|
173
|
+
If *instant* is ``None``, the clock freezes at the current real time.
|
|
174
|
+
|
|
175
|
+
Returns the JSON response as a dict with keys ``status``,
|
|
176
|
+
``currentInstant``, and ``currentEpochMillis``.
|
|
177
|
+
"""
|
|
178
|
+
payload: dict = {"action": "freeze"}
|
|
179
|
+
if instant is not None:
|
|
180
|
+
payload["instant"] = instant
|
|
181
|
+
body = json.dumps(payload)
|
|
182
|
+
status, response_body = await self._request("PUT", "/mockserver/clock", body)
|
|
183
|
+
if status >= 400:
|
|
184
|
+
raise MockServerError(
|
|
185
|
+
f"Failed to freeze clock (status={status}): {response_body}"
|
|
186
|
+
)
|
|
187
|
+
return json.loads(response_body) if response_body else {}
|
|
188
|
+
|
|
189
|
+
async def advance_clock(self, duration_millis: int) -> dict:
|
|
190
|
+
"""Advance the frozen clock by *duration_millis* milliseconds.
|
|
191
|
+
|
|
192
|
+
Returns the JSON response as a dict with keys ``status``,
|
|
193
|
+
``currentInstant``, and ``currentEpochMillis``.
|
|
194
|
+
"""
|
|
195
|
+
body = json.dumps({"action": "advance", "durationMillis": duration_millis})
|
|
196
|
+
status, response_body = await self._request("PUT", "/mockserver/clock", body)
|
|
197
|
+
if status >= 400:
|
|
198
|
+
raise MockServerError(
|
|
199
|
+
f"Failed to advance clock (status={status}): {response_body}"
|
|
200
|
+
)
|
|
201
|
+
return json.loads(response_body) if response_body else {}
|
|
202
|
+
|
|
203
|
+
async def reset_clock(self) -> dict:
|
|
204
|
+
"""Reset the server clock to real wall-clock time.
|
|
205
|
+
|
|
206
|
+
Returns the JSON response as a dict with keys ``status``,
|
|
207
|
+
``currentInstant``, and ``currentEpochMillis``.
|
|
208
|
+
"""
|
|
209
|
+
body = json.dumps({"action": "reset"})
|
|
210
|
+
status, response_body = await self._request("PUT", "/mockserver/clock", body)
|
|
211
|
+
if status >= 400:
|
|
212
|
+
raise MockServerError(
|
|
213
|
+
f"Failed to reset clock (status={status}): {response_body}"
|
|
214
|
+
)
|
|
215
|
+
return json.loads(response_body) if response_body else {}
|
|
216
|
+
|
|
217
|
+
async def clock_status(self) -> dict:
|
|
218
|
+
"""Query the current clock status.
|
|
219
|
+
|
|
220
|
+
Returns a dict with keys ``currentInstant``, ``currentEpochMillis``,
|
|
221
|
+
and ``frozen``.
|
|
222
|
+
"""
|
|
223
|
+
status, response_body = await self._request("GET", "/mockserver/clock")
|
|
224
|
+
if status >= 400:
|
|
225
|
+
raise MockServerError(
|
|
226
|
+
f"Failed to get clock status (status={status}): {response_body}"
|
|
227
|
+
)
|
|
228
|
+
return json.loads(response_body) if response_body else {}
|
|
229
|
+
|
|
230
|
+
async def set_service_chaos(
|
|
231
|
+
self, host: str, chaos: HttpChaosProfile, ttl_millis: int | None = None
|
|
232
|
+
) -> dict:
|
|
233
|
+
"""Register a service-scoped HTTP chaos profile for an upstream *host*.
|
|
234
|
+
|
|
235
|
+
The profile is applied to every matched forward expectation to that host
|
|
236
|
+
that does not define its own chaos (an expectation's own chaos always
|
|
237
|
+
wins). The host is matched case-insensitively, ignoring any ``:port``.
|
|
238
|
+
|
|
239
|
+
If *ttl_millis* is set, the chaos auto-reverts after that many milliseconds
|
|
240
|
+
(a dead-man's switch so it self-heals even if no clear is sent).
|
|
241
|
+
"""
|
|
242
|
+
payload: dict = {"host": host, "chaos": chaos.to_dict()}
|
|
243
|
+
if ttl_millis is not None:
|
|
244
|
+
payload["ttlMillis"] = ttl_millis
|
|
245
|
+
body = json.dumps(payload)
|
|
246
|
+
status, response_body = await self._request("PUT", "/mockserver/serviceChaos", body)
|
|
247
|
+
if status >= 400:
|
|
248
|
+
raise MockServerError(
|
|
249
|
+
f"Failed to set service chaos (status={status}): {response_body}"
|
|
250
|
+
)
|
|
251
|
+
return json.loads(response_body) if response_body else {}
|
|
252
|
+
|
|
253
|
+
async def remove_service_chaos(self, host: str) -> dict:
|
|
254
|
+
"""Remove the service-scoped chaos profile registered for *host*."""
|
|
255
|
+
body = json.dumps({"host": host, "remove": True})
|
|
256
|
+
status, response_body = await self._request("PUT", "/mockserver/serviceChaos", body)
|
|
257
|
+
if status >= 400:
|
|
258
|
+
raise MockServerError(
|
|
259
|
+
f"Failed to remove service chaos (status={status}): {response_body}"
|
|
260
|
+
)
|
|
261
|
+
return json.loads(response_body) if response_body else {}
|
|
262
|
+
|
|
263
|
+
async def clear_service_chaos(self) -> dict:
|
|
264
|
+
"""Clear all service-scoped chaos profiles."""
|
|
265
|
+
body = json.dumps({"clear": True})
|
|
266
|
+
status, response_body = await self._request("PUT", "/mockserver/serviceChaos", body)
|
|
267
|
+
if status >= 400:
|
|
268
|
+
raise MockServerError(
|
|
269
|
+
f"Failed to clear service chaos (status={status}): {response_body}"
|
|
270
|
+
)
|
|
271
|
+
return json.loads(response_body) if response_body else {}
|
|
272
|
+
|
|
273
|
+
async def service_chaos_status(self) -> dict:
|
|
274
|
+
"""Query the current service-scoped chaos registrations.
|
|
275
|
+
|
|
276
|
+
Returns a dict of the form ``{"services": {host: profile, ...}}``.
|
|
277
|
+
"""
|
|
278
|
+
status, response_body = await self._request("GET", "/mockserver/serviceChaos")
|
|
279
|
+
if status >= 400:
|
|
280
|
+
raise MockServerError(
|
|
281
|
+
f"Failed to get service chaos (status={status}): {response_body}"
|
|
282
|
+
)
|
|
283
|
+
return json.loads(response_body) if response_body else {}
|
|
284
|
+
|
|
169
285
|
async def verify(
|
|
170
286
|
self,
|
|
171
287
|
request: HttpRequest,
|
|
@@ -8,6 +8,7 @@ from mockserver.async_client import AsyncMockServerClient
|
|
|
8
8
|
from mockserver.models import (
|
|
9
9
|
Delay,
|
|
10
10
|
Expectation,
|
|
11
|
+
HttpChaosProfile,
|
|
11
12
|
HttpError,
|
|
12
13
|
HttpForward,
|
|
13
14
|
HttpOverrideForwardedRequest,
|
|
@@ -61,6 +62,21 @@ class SyncForwardChainExpectation:
|
|
|
61
62
|
def respond_with_websocket(self, websocket_response) -> list[Expectation]:
|
|
62
63
|
return self._run(self._async_chain.respond_with_websocket(websocket_response))
|
|
63
64
|
|
|
65
|
+
def respond_with_grpc_stream(self, grpc_stream_response) -> list[Expectation]:
|
|
66
|
+
return self._run(self._async_chain.respond_with_grpc_stream(grpc_stream_response))
|
|
67
|
+
|
|
68
|
+
def respond_with_binary(self, binary_response) -> list[Expectation]:
|
|
69
|
+
return self._run(self._async_chain.respond_with_binary(binary_response))
|
|
70
|
+
|
|
71
|
+
def respond_with_dns(self, dns_response) -> list[Expectation]:
|
|
72
|
+
return self._run(self._async_chain.respond_with_dns(dns_response))
|
|
73
|
+
|
|
74
|
+
def forward_with_template(self, template) -> list[Expectation]:
|
|
75
|
+
return self._run(self._async_chain.forward_with_template(template))
|
|
76
|
+
|
|
77
|
+
def forward_with_class_callback(self, class_callback) -> list[Expectation]:
|
|
78
|
+
return self._run(self._async_chain.forward_with_class_callback(class_callback))
|
|
79
|
+
|
|
64
80
|
def error(self, error: HttpError) -> list[Expectation]:
|
|
65
81
|
return self._run(self._async_chain.error(error))
|
|
66
82
|
|
|
@@ -111,6 +127,46 @@ class MockServerClient:
|
|
|
111
127
|
def reset(self) -> None:
|
|
112
128
|
return self._run(self._async_client.reset())
|
|
113
129
|
|
|
130
|
+
def freeze_clock(self, instant: str | None = None) -> dict:
|
|
131
|
+
"""Freeze the server clock at the given ISO-8601 instant.
|
|
132
|
+
|
|
133
|
+
If *instant* is ``None``, the clock freezes at the current real time.
|
|
134
|
+
"""
|
|
135
|
+
return self._run(self._async_client.freeze_clock(instant))
|
|
136
|
+
|
|
137
|
+
def advance_clock(self, duration_millis: int) -> dict:
|
|
138
|
+
"""Advance the frozen clock by *duration_millis* milliseconds."""
|
|
139
|
+
return self._run(self._async_client.advance_clock(duration_millis))
|
|
140
|
+
|
|
141
|
+
def reset_clock(self) -> dict:
|
|
142
|
+
"""Reset the server clock to real wall-clock time."""
|
|
143
|
+
return self._run(self._async_client.reset_clock())
|
|
144
|
+
|
|
145
|
+
def clock_status(self) -> dict:
|
|
146
|
+
"""Query the current clock status."""
|
|
147
|
+
return self._run(self._async_client.clock_status())
|
|
148
|
+
|
|
149
|
+
def set_service_chaos(
|
|
150
|
+
self, host: str, chaos: HttpChaosProfile, ttl_millis: int | None = None
|
|
151
|
+
) -> dict:
|
|
152
|
+
"""Register a service-scoped HTTP chaos profile for an upstream host.
|
|
153
|
+
|
|
154
|
+
If *ttl_millis* is set, the chaos auto-reverts after that many milliseconds.
|
|
155
|
+
"""
|
|
156
|
+
return self._run(self._async_client.set_service_chaos(host, chaos, ttl_millis))
|
|
157
|
+
|
|
158
|
+
def remove_service_chaos(self, host: str) -> dict:
|
|
159
|
+
"""Remove the service-scoped chaos profile registered for *host*."""
|
|
160
|
+
return self._run(self._async_client.remove_service_chaos(host))
|
|
161
|
+
|
|
162
|
+
def clear_service_chaos(self) -> dict:
|
|
163
|
+
"""Clear all service-scoped chaos profiles."""
|
|
164
|
+
return self._run(self._async_client.clear_service_chaos())
|
|
165
|
+
|
|
166
|
+
def service_chaos_status(self) -> dict:
|
|
167
|
+
"""Query the current service-scoped chaos registrations."""
|
|
168
|
+
return self._run(self._async_client.service_chaos_status())
|
|
169
|
+
|
|
114
170
|
def verify(
|
|
115
171
|
self,
|
|
116
172
|
request: HttpRequest,
|
|
@@ -4,8 +4,15 @@ import asyncio
|
|
|
4
4
|
from typing import TYPE_CHECKING, Callable
|
|
5
5
|
|
|
6
6
|
from mockserver.models import (
|
|
7
|
+
BinaryResponse,
|
|
7
8
|
Delay,
|
|
9
|
+
DnsResponse,
|
|
8
10
|
Expectation,
|
|
11
|
+
ExpectationStep,
|
|
12
|
+
GrpcBidiResponse,
|
|
13
|
+
GrpcStreamResponse,
|
|
14
|
+
HttpChaosProfile,
|
|
15
|
+
HttpClassCallback,
|
|
9
16
|
HttpError,
|
|
10
17
|
HttpForward,
|
|
11
18
|
HttpObjectCallback,
|
|
@@ -37,6 +44,10 @@ class ForwardChainExpectation:
|
|
|
37
44
|
self._expectation.priority = priority
|
|
38
45
|
return self
|
|
39
46
|
|
|
47
|
+
def with_chaos(self, chaos: HttpChaosProfile) -> ForwardChainExpectation:
|
|
48
|
+
self._expectation.chaos = chaos
|
|
49
|
+
return self
|
|
50
|
+
|
|
40
51
|
async def respond(self, response_or_callback) -> list[Expectation]:
|
|
41
52
|
if callable(response_or_callback):
|
|
42
53
|
client_id = await self._client._register_websocket_callback(
|
|
@@ -110,7 +121,68 @@ class ForwardChainExpectation:
|
|
|
110
121
|
self._expectation.http_websocket_response = websocket_response
|
|
111
122
|
return await self._client.upsert(self._expectation)
|
|
112
123
|
|
|
124
|
+
async def respond_with_grpc_stream(self, grpc_stream_response: GrpcStreamResponse) -> list[Expectation]:
|
|
125
|
+
if not isinstance(grpc_stream_response, GrpcStreamResponse):
|
|
126
|
+
raise TypeError(
|
|
127
|
+
f"Expected GrpcStreamResponse, got {type(grpc_stream_response).__name__}"
|
|
128
|
+
)
|
|
129
|
+
self._expectation.grpc_stream_response = grpc_stream_response
|
|
130
|
+
return await self._client.upsert(self._expectation)
|
|
131
|
+
|
|
132
|
+
async def respond_with_grpc_bidi(self, grpc_bidi_response: GrpcBidiResponse) -> list[Expectation]:
|
|
133
|
+
if not isinstance(grpc_bidi_response, GrpcBidiResponse):
|
|
134
|
+
raise TypeError(
|
|
135
|
+
f"Expected GrpcBidiResponse, got {type(grpc_bidi_response).__name__}"
|
|
136
|
+
)
|
|
137
|
+
self._expectation.grpc_bidi_response = grpc_bidi_response
|
|
138
|
+
return await self._client.upsert(self._expectation)
|
|
139
|
+
|
|
140
|
+
async def respond_with_binary(self, binary_response: BinaryResponse) -> list[Expectation]:
|
|
141
|
+
if not isinstance(binary_response, BinaryResponse):
|
|
142
|
+
raise TypeError(
|
|
143
|
+
f"Expected BinaryResponse, got {type(binary_response).__name__}"
|
|
144
|
+
)
|
|
145
|
+
self._expectation.binary_response = binary_response
|
|
146
|
+
return await self._client.upsert(self._expectation)
|
|
147
|
+
|
|
148
|
+
async def respond_with_dns(self, dns_response: DnsResponse) -> list[Expectation]:
|
|
149
|
+
if not isinstance(dns_response, DnsResponse):
|
|
150
|
+
raise TypeError(
|
|
151
|
+
f"Expected DnsResponse, got {type(dns_response).__name__}"
|
|
152
|
+
)
|
|
153
|
+
self._expectation.dns_response = dns_response
|
|
154
|
+
return await self._client.upsert(self._expectation)
|
|
155
|
+
|
|
156
|
+
async def forward_with_template(self, template: HttpTemplate) -> list[Expectation]:
|
|
157
|
+
if not isinstance(template, HttpTemplate):
|
|
158
|
+
raise TypeError(
|
|
159
|
+
f"Expected HttpTemplate, got {type(template).__name__}"
|
|
160
|
+
)
|
|
161
|
+
self._expectation.http_forward_template = template
|
|
162
|
+
return await self._client.upsert(self._expectation)
|
|
163
|
+
|
|
164
|
+
async def forward_with_class_callback(self, class_callback: HttpClassCallback) -> list[Expectation]:
|
|
165
|
+
if not isinstance(class_callback, HttpClassCallback):
|
|
166
|
+
raise TypeError(
|
|
167
|
+
f"Expected HttpClassCallback, got {type(class_callback).__name__}"
|
|
168
|
+
)
|
|
169
|
+
self._expectation.http_forward_class_callback = class_callback
|
|
170
|
+
return await self._client.upsert(self._expectation)
|
|
171
|
+
|
|
113
172
|
async def error(self, error: HttpError) -> list[Expectation]:
|
|
114
173
|
self._expectation.http_error = error
|
|
115
174
|
return await self._client.upsert(self._expectation)
|
|
116
175
|
|
|
176
|
+
async def with_steps(self, steps: list[ExpectationStep]) -> list[Expectation]:
|
|
177
|
+
"""Set an ordered multi-action pipeline of steps.
|
|
178
|
+
|
|
179
|
+
Exactly one step must have ``responder=True``; that step produces the
|
|
180
|
+
HTTP response. All other steps are side-effects executed in order.
|
|
181
|
+
"""
|
|
182
|
+
if not isinstance(steps, list) or not all(isinstance(s, ExpectationStep) for s in steps):
|
|
183
|
+
raise TypeError(
|
|
184
|
+
"Expected a list of ExpectationStep objects"
|
|
185
|
+
)
|
|
186
|
+
self._expectation.steps = steps
|
|
187
|
+
return await self._client.upsert(self._expectation)
|
|
188
|
+
|