mockserver-client 6.1.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.
Files changed (24) hide show
  1. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/PKG-INFO +1 -1
  2. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/__init__.py +18 -0
  3. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/async_client.py +116 -0
  4. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/client.py +56 -0
  5. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/fluent.py +72 -0
  6. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/models.py +484 -0
  7. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/PKG-INFO +1 -1
  8. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/pyproject.toml +1 -1
  9. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_client.py +149 -0
  10. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_fluent.py +156 -0
  11. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_models.py +1086 -0
  12. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/README.md +0 -0
  13. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/exceptions.py +0 -0
  14. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver/websocket_client.py +0 -0
  15. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/SOURCES.txt +0 -0
  16. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/dependency_links.txt +0 -0
  17. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/requires.txt +0 -0
  18. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/mockserver_client.egg-info/top_level.txt +0 -0
  19. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/setup.cfg +0 -0
  20. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_async_client.py +0 -0
  21. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_exceptions.py +0 -0
  22. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_init.py +0 -0
  23. {mockserver_client-6.1.0 → mockserver_client-7.0.0}/tests/test_integration.py +0 -0
  24. {mockserver_client-6.1.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: 6.1.0
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
+