mockserver-client 9.0.0__tar.gz → 9.2.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-9.0.0 → mockserver_client-9.2.0}/PKG-INFO +113 -2
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/README.md +112 -1
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/__init__.py +12 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/a2a.py +23 -2
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/async_client.py +211 -18
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/client.py +71 -14
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/fluent.py +29 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/launcher.py +1 -1
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/mcp.py +24 -3
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/models.py +523 -24
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/websocket_client.py +30 -1
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver_client.egg-info/PKG-INFO +113 -2
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver_client.egg-info/SOURCES.txt +5 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/pyproject.toml +1 -1
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_a2a.py +6 -0
- mockserver_client-9.2.0/tests/test_any_of_request_definition.py +102 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_async_client.py +6 -4
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_auth_tls.py +62 -1
- mockserver_client-9.2.0/tests/test_body_date_time_matcher.py +62 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_client.py +3 -3
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_control_plane.py +71 -0
- mockserver_client-9.2.0/tests/test_date_time_matcher.py +166 -0
- mockserver_client-9.2.0/tests/test_expectation_filter.py +319 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_explain.py +2 -1
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_init.py +2 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_mcp.py +68 -1
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_models.py +76 -0
- mockserver_client-9.2.0/tests/test_recorded_expectations_options.py +176 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_websocket_client.py +229 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/exceptions.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/llm.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/py.typed +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver/pytest_plugin.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver_client.egg-info/dependency_links.txt +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver_client.egg-info/entry_points.txt +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver_client.egg-info/requires.txt +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/mockserver_client.egg-info/top_level.txt +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/setup.cfg +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_breakpoint.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_conditional_request_and_load_check.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_exceptions.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_fluent.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_from_dict_fidelity.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_integration.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_key_multi_value_forms.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_launcher.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_llm.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_override_forwarded_request.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_pytest_plugin.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_roundtrip_fidelity.py +0 -0
- {mockserver_client-9.0.0 → mockserver_client-9.2.0}/tests/test_sync_chain_parity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mockserver-client
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.2.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
|
|
@@ -238,6 +238,35 @@ client.when(
|
|
|
238
238
|
HttpRequest(headers={"keyMatchStyle": "MATCHING_KEY", "X-Tag": ["a", "b"]})
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
A value can also be a `DateTimeMatcher`, which matches a header, query or path
|
|
242
|
+
parameter, or cookie holding a date-time before, after or equal to an expected
|
|
243
|
+
value (`now`, `now -1 hours`, or a date-time such as `2026-10-21T00:00:00Z`).
|
|
244
|
+
A plain string such as `"before:2026-01-01"` is always matched literally.
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from mockserver import DateTimeMatcher, HttpRequest
|
|
248
|
+
|
|
249
|
+
HttpRequest(path="/orders") \
|
|
250
|
+
.with_header("X-Issued-At", DateTimeMatcher.after("now -1 hours")) \
|
|
251
|
+
.with_header("If-Modified-Since", DateTimeMatcher.before("now").with_actual_format("rfc1123")) \
|
|
252
|
+
.with_cookie("seen", DateTimeMatcher.equal_to_date_time("now").with_truncate_actual("startOfDay"))
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Each `with_*` option (`with_actual_format`, `with_truncate_expected`,
|
|
256
|
+
`with_truncate_actual`, `with_apply_truncation_last`, `with_zone`, `with_not`)
|
|
257
|
+
returns a new matcher.
|
|
258
|
+
|
|
259
|
+
A JSONPath or XPath body matcher can take a `DateTimeMatcher` too: the body
|
|
260
|
+
matches when a value the JSONPath selects, or the text of a node the XPath
|
|
261
|
+
selects, is a matching date-time.
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from mockserver import Body, DateTimeMatcher, HttpRequest
|
|
265
|
+
|
|
266
|
+
HttpRequest(path="/orders", body=Body.json_path("$.order.created", DateTimeMatcher.after("now -1 days")))
|
|
267
|
+
HttpRequest(path="/soap", body=Body.xpath("/order/@expires", None, DateTimeMatcher.after("now")))
|
|
268
|
+
```
|
|
269
|
+
|
|
241
270
|
### JWT matcher
|
|
242
271
|
|
|
243
272
|
Match requests by the claims (and metadata) of a JWT carried in a header. Each
|
|
@@ -269,6 +298,32 @@ client.when(
|
|
|
269
298
|
HttpRequest.request("/secure").with_jwt(Jwt(claims={"sub": "user-123"}))
|
|
270
299
|
```
|
|
271
300
|
|
|
301
|
+
### Client IP matcher
|
|
302
|
+
|
|
303
|
+
Match requests by the client IP address or CIDR range, IPv4 or IPv6. The client address is the TCP peer of the connection, not `X-Forwarded-For`; prefix with `!` to match every other address.
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
HttpRequest.request("/internal").with_client_ip("10.0.0.0/8")
|
|
307
|
+
# or HttpRequest(path="/internal", client_ip="!10.0.0.0/8")
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Any-of (logical OR) matcher
|
|
311
|
+
|
|
312
|
+
`AnyOfRequestDefinition` matches a request when any one of its request matchers does, tried in order. Entries may be `HttpRequest`, `OpenAPIDefinition`, `ConditionalRequestDefinition` or a nested `AnyOfRequestDefinition`; `not_any_of=True` matches requests that match none of them. It serialises to `{"anyOf": [...]}` inside `httpRequest`.
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
from mockserver import AnyOfRequestDefinition, HttpRequest, HttpResponse
|
|
316
|
+
|
|
317
|
+
client.when(
|
|
318
|
+
AnyOfRequestDefinition.request_any_of(
|
|
319
|
+
HttpRequest(method="GET", path="/pets"),
|
|
320
|
+
HttpRequest(method="GET", path="/animals"),
|
|
321
|
+
)
|
|
322
|
+
).respond(HttpResponse(body="pets or animals"))
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`ExplainedExpectation.any_of` (an `AnyOfBranchComparison`) says which entry came closest when none matches.
|
|
326
|
+
|
|
272
327
|
### allOf body matcher
|
|
273
328
|
|
|
274
329
|
Require a request body to satisfy **all** of several body matchers at once.
|
|
@@ -329,6 +384,40 @@ expectations = client.retrieve_active_expectations()
|
|
|
329
384
|
logs = client.retrieve_log_messages()
|
|
330
385
|
```
|
|
331
386
|
|
|
387
|
+
### Expectation metadata, unmatched expectations and paging
|
|
388
|
+
|
|
389
|
+
Label expectations with metadata (string keys, any JSON value). Metadata never
|
|
390
|
+
affects matching; it is returned on retrieve and lets you retrieve or clear
|
|
391
|
+
just your own expectations.
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
from mockserver import ExpectationFilter, JSON_NULL, RecordedExpectationsOptions
|
|
395
|
+
|
|
396
|
+
client.when(HttpRequest.request("/api/users")).with_metadata("owner", "team-a").with_metadata(
|
|
397
|
+
"ticket", 4211
|
|
398
|
+
).respond(HttpResponse(status_code=200))
|
|
399
|
+
|
|
400
|
+
mine = client.retrieve_active_expectations_by_metadata("owner", "team-a")
|
|
401
|
+
labelled = client.retrieve_active_expectations_by_metadata_key("owner")
|
|
402
|
+
never_matched = client.retrieve_unmatched_expectations()
|
|
403
|
+
page = client.retrieve_active_expectations(
|
|
404
|
+
expectation_filter=ExpectationFilter().with_metadata_json_path("$.tags[?(@ == 'smoke')]").with_offset(0).with_limit(20)
|
|
405
|
+
)
|
|
406
|
+
recorded_page = client.retrieve_recorded_expectations(offset=0, limit=50)
|
|
407
|
+
# replay changing responses in order, keep only some request headers, move large bodies into files
|
|
408
|
+
shaped = client.retrieve_recorded_expectations(
|
|
409
|
+
options=RecordedExpectationsOptions().with_repeats_as_scenarios().with_capture_headers("X-Tenant").with_extract_bodies_larger_than(1024)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
client.clear_by_metadata("owner", "team-a") # str compared as text, other values as JSON
|
|
413
|
+
client.clear_by_metadata_key("ticket") # any value
|
|
414
|
+
client.clear_unmatched_expectations()
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
These calls raise `ValueError` before sending anything for a blank key or JSONPath,
|
|
418
|
+
a `None` value (use `..._by_metadata_key` for any value, or `JSON_NULL` for a JSON
|
|
419
|
+
null), a negative offset or limit, or a clear whose filter selects nothing or pages.
|
|
420
|
+
|
|
332
421
|
## Control
|
|
333
422
|
|
|
334
423
|
```python
|
|
@@ -465,6 +554,28 @@ client = MockServerClient(
|
|
|
465
554
|
)
|
|
466
555
|
```
|
|
467
556
|
|
|
557
|
+
## Control-Plane Authentication
|
|
558
|
+
|
|
559
|
+
When MockServer requires a control-plane JWT, pass the token (or a supplier for a token that is refreshed). The client sends `Authorization: Bearer <token>` on every control-plane request and on the callback WebSocket used by callbacks and breakpoints; a supplier is called again on each reconnect.
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
client = MockServerClient("localhost", 1080, control_plane_bearer_token=jwt)
|
|
563
|
+
client = MockServerClient("localhost", 1080, control_plane_bearer_token_supplier=fetch_jwt)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
When MockServer accepts control-plane Basic authentication, pass `control_plane_basic_auth=(username, password)` with `secure=True` (MockServer refuses Basic credentials without TLS). They are sent on the same requests and WebSocket; a bearer token, when also set, is sent instead.
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
client = MockServerClient("localhost", 1080, secure=True, control_plane_basic_auth=("admin", os.environ["MOCKSERVER_PASSWORD"]))
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
When MockServer requires mutual TLS for the control plane, pass `client_cert_path` and `client_key_path` (with `secure=True` and, for a private CA, `ca_cert_path`). The client presents that certificate, and trusts that CA, on every control-plane request and on the callback WebSocket.
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
client = MockServerClient("localhost", 1080, secure=True, ca_cert_path="ca.pem",
|
|
576
|
+
client_cert_path="client-cert.pem", client_key_path="client-key.pem")
|
|
577
|
+
```
|
|
578
|
+
|
|
468
579
|
## Domain Model
|
|
469
580
|
|
|
470
581
|
All domain model classes support builder-style chaining:
|
|
@@ -568,7 +679,7 @@ launcher_path = ensure_binary() # returns Path to the launcher executable
|
|
|
568
679
|
```python
|
|
569
680
|
from mockserver.launcher import start
|
|
570
681
|
|
|
571
|
-
server = start(port=1080, version="9.
|
|
682
|
+
server = start(port=1080, version="9.2.0")
|
|
572
683
|
# ...
|
|
573
684
|
server.stop()
|
|
574
685
|
```
|
|
@@ -208,6 +208,35 @@ client.when(
|
|
|
208
208
|
HttpRequest(headers={"keyMatchStyle": "MATCHING_KEY", "X-Tag": ["a", "b"]})
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
A value can also be a `DateTimeMatcher`, which matches a header, query or path
|
|
212
|
+
parameter, or cookie holding a date-time before, after or equal to an expected
|
|
213
|
+
value (`now`, `now -1 hours`, or a date-time such as `2026-10-21T00:00:00Z`).
|
|
214
|
+
A plain string such as `"before:2026-01-01"` is always matched literally.
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from mockserver import DateTimeMatcher, HttpRequest
|
|
218
|
+
|
|
219
|
+
HttpRequest(path="/orders") \
|
|
220
|
+
.with_header("X-Issued-At", DateTimeMatcher.after("now -1 hours")) \
|
|
221
|
+
.with_header("If-Modified-Since", DateTimeMatcher.before("now").with_actual_format("rfc1123")) \
|
|
222
|
+
.with_cookie("seen", DateTimeMatcher.equal_to_date_time("now").with_truncate_actual("startOfDay"))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Each `with_*` option (`with_actual_format`, `with_truncate_expected`,
|
|
226
|
+
`with_truncate_actual`, `with_apply_truncation_last`, `with_zone`, `with_not`)
|
|
227
|
+
returns a new matcher.
|
|
228
|
+
|
|
229
|
+
A JSONPath or XPath body matcher can take a `DateTimeMatcher` too: the body
|
|
230
|
+
matches when a value the JSONPath selects, or the text of a node the XPath
|
|
231
|
+
selects, is a matching date-time.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from mockserver import Body, DateTimeMatcher, HttpRequest
|
|
235
|
+
|
|
236
|
+
HttpRequest(path="/orders", body=Body.json_path("$.order.created", DateTimeMatcher.after("now -1 days")))
|
|
237
|
+
HttpRequest(path="/soap", body=Body.xpath("/order/@expires", None, DateTimeMatcher.after("now")))
|
|
238
|
+
```
|
|
239
|
+
|
|
211
240
|
### JWT matcher
|
|
212
241
|
|
|
213
242
|
Match requests by the claims (and metadata) of a JWT carried in a header. Each
|
|
@@ -239,6 +268,32 @@ client.when(
|
|
|
239
268
|
HttpRequest.request("/secure").with_jwt(Jwt(claims={"sub": "user-123"}))
|
|
240
269
|
```
|
|
241
270
|
|
|
271
|
+
### Client IP matcher
|
|
272
|
+
|
|
273
|
+
Match requests by the client IP address or CIDR range, IPv4 or IPv6. The client address is the TCP peer of the connection, not `X-Forwarded-For`; prefix with `!` to match every other address.
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
HttpRequest.request("/internal").with_client_ip("10.0.0.0/8")
|
|
277
|
+
# or HttpRequest(path="/internal", client_ip="!10.0.0.0/8")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Any-of (logical OR) matcher
|
|
281
|
+
|
|
282
|
+
`AnyOfRequestDefinition` matches a request when any one of its request matchers does, tried in order. Entries may be `HttpRequest`, `OpenAPIDefinition`, `ConditionalRequestDefinition` or a nested `AnyOfRequestDefinition`; `not_any_of=True` matches requests that match none of them. It serialises to `{"anyOf": [...]}` inside `httpRequest`.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from mockserver import AnyOfRequestDefinition, HttpRequest, HttpResponse
|
|
286
|
+
|
|
287
|
+
client.when(
|
|
288
|
+
AnyOfRequestDefinition.request_any_of(
|
|
289
|
+
HttpRequest(method="GET", path="/pets"),
|
|
290
|
+
HttpRequest(method="GET", path="/animals"),
|
|
291
|
+
)
|
|
292
|
+
).respond(HttpResponse(body="pets or animals"))
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`ExplainedExpectation.any_of` (an `AnyOfBranchComparison`) says which entry came closest when none matches.
|
|
296
|
+
|
|
242
297
|
### allOf body matcher
|
|
243
298
|
|
|
244
299
|
Require a request body to satisfy **all** of several body matchers at once.
|
|
@@ -299,6 +354,40 @@ expectations = client.retrieve_active_expectations()
|
|
|
299
354
|
logs = client.retrieve_log_messages()
|
|
300
355
|
```
|
|
301
356
|
|
|
357
|
+
### Expectation metadata, unmatched expectations and paging
|
|
358
|
+
|
|
359
|
+
Label expectations with metadata (string keys, any JSON value). Metadata never
|
|
360
|
+
affects matching; it is returned on retrieve and lets you retrieve or clear
|
|
361
|
+
just your own expectations.
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from mockserver import ExpectationFilter, JSON_NULL, RecordedExpectationsOptions
|
|
365
|
+
|
|
366
|
+
client.when(HttpRequest.request("/api/users")).with_metadata("owner", "team-a").with_metadata(
|
|
367
|
+
"ticket", 4211
|
|
368
|
+
).respond(HttpResponse(status_code=200))
|
|
369
|
+
|
|
370
|
+
mine = client.retrieve_active_expectations_by_metadata("owner", "team-a")
|
|
371
|
+
labelled = client.retrieve_active_expectations_by_metadata_key("owner")
|
|
372
|
+
never_matched = client.retrieve_unmatched_expectations()
|
|
373
|
+
page = client.retrieve_active_expectations(
|
|
374
|
+
expectation_filter=ExpectationFilter().with_metadata_json_path("$.tags[?(@ == 'smoke')]").with_offset(0).with_limit(20)
|
|
375
|
+
)
|
|
376
|
+
recorded_page = client.retrieve_recorded_expectations(offset=0, limit=50)
|
|
377
|
+
# replay changing responses in order, keep only some request headers, move large bodies into files
|
|
378
|
+
shaped = client.retrieve_recorded_expectations(
|
|
379
|
+
options=RecordedExpectationsOptions().with_repeats_as_scenarios().with_capture_headers("X-Tenant").with_extract_bodies_larger_than(1024)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
client.clear_by_metadata("owner", "team-a") # str compared as text, other values as JSON
|
|
383
|
+
client.clear_by_metadata_key("ticket") # any value
|
|
384
|
+
client.clear_unmatched_expectations()
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
These calls raise `ValueError` before sending anything for a blank key or JSONPath,
|
|
388
|
+
a `None` value (use `..._by_metadata_key` for any value, or `JSON_NULL` for a JSON
|
|
389
|
+
null), a negative offset or limit, or a clear whose filter selects nothing or pages.
|
|
390
|
+
|
|
302
391
|
## Control
|
|
303
392
|
|
|
304
393
|
```python
|
|
@@ -435,6 +524,28 @@ client = MockServerClient(
|
|
|
435
524
|
)
|
|
436
525
|
```
|
|
437
526
|
|
|
527
|
+
## Control-Plane Authentication
|
|
528
|
+
|
|
529
|
+
When MockServer requires a control-plane JWT, pass the token (or a supplier for a token that is refreshed). The client sends `Authorization: Bearer <token>` on every control-plane request and on the callback WebSocket used by callbacks and breakpoints; a supplier is called again on each reconnect.
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
client = MockServerClient("localhost", 1080, control_plane_bearer_token=jwt)
|
|
533
|
+
client = MockServerClient("localhost", 1080, control_plane_bearer_token_supplier=fetch_jwt)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
When MockServer accepts control-plane Basic authentication, pass `control_plane_basic_auth=(username, password)` with `secure=True` (MockServer refuses Basic credentials without TLS). They are sent on the same requests and WebSocket; a bearer token, when also set, is sent instead.
|
|
537
|
+
|
|
538
|
+
```python
|
|
539
|
+
client = MockServerClient("localhost", 1080, secure=True, control_plane_basic_auth=("admin", os.environ["MOCKSERVER_PASSWORD"]))
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
When MockServer requires mutual TLS for the control plane, pass `client_cert_path` and `client_key_path` (with `secure=True` and, for a private CA, `ca_cert_path`). The client presents that certificate, and trusts that CA, on every control-plane request and on the callback WebSocket.
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
client = MockServerClient("localhost", 1080, secure=True, ca_cert_path="ca.pem",
|
|
546
|
+
client_cert_path="client-cert.pem", client_key_path="client-key.pem")
|
|
547
|
+
```
|
|
548
|
+
|
|
438
549
|
## Domain Model
|
|
439
550
|
|
|
440
551
|
All domain model classes support builder-style chaining:
|
|
@@ -538,7 +649,7 @@ launcher_path = ensure_binary() # returns Path to the launcher executable
|
|
|
538
649
|
```python
|
|
539
650
|
from mockserver.launcher import start
|
|
540
651
|
|
|
541
|
-
server = start(port=1080, version="9.
|
|
652
|
+
server = start(port=1080, version="9.2.0")
|
|
542
653
|
# ...
|
|
543
654
|
server.stop()
|
|
544
655
|
```
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from mockserver.models import (
|
|
2
2
|
AfterAction,
|
|
3
3
|
AllOfBody,
|
|
4
|
+
AnyOfBranchComparison,
|
|
5
|
+
AnyOfRequestDefinition,
|
|
4
6
|
BinaryResponse,
|
|
5
7
|
BinaryUpstream,
|
|
6
8
|
Body,
|
|
@@ -8,6 +10,7 @@ from mockserver.models import (
|
|
|
8
10
|
CaptureSource,
|
|
9
11
|
ConditionalRequestDefinition,
|
|
10
12
|
ConnectionOptions,
|
|
13
|
+
DateTimeMatcher,
|
|
11
14
|
CrossProtocolScenario,
|
|
12
15
|
CrossProtocolTrigger,
|
|
13
16
|
Delay,
|
|
@@ -18,7 +21,10 @@ from mockserver.models import (
|
|
|
18
21
|
DnsRequestDefinition,
|
|
19
22
|
DnsResponse,
|
|
20
23
|
Expectation,
|
|
24
|
+
ExpectationFilter,
|
|
25
|
+
RecordedExpectationsOptions,
|
|
21
26
|
ExpectationId,
|
|
27
|
+
JSON_NULL,
|
|
22
28
|
ExpectationStep,
|
|
23
29
|
GraphQLBody,
|
|
24
30
|
GraphQLSubscriptionFilter,
|
|
@@ -165,6 +171,8 @@ from mockserver.launcher import (
|
|
|
165
171
|
__all__ = [
|
|
166
172
|
"AfterAction",
|
|
167
173
|
"AllOfBody",
|
|
174
|
+
"AnyOfBranchComparison",
|
|
175
|
+
"AnyOfRequestDefinition",
|
|
168
176
|
"AsyncMockServerClient",
|
|
169
177
|
"BinaryResponse",
|
|
170
178
|
"BinaryUpstream",
|
|
@@ -173,6 +181,7 @@ __all__ = [
|
|
|
173
181
|
"CaptureSource",
|
|
174
182
|
"ConditionalRequestDefinition",
|
|
175
183
|
"ConnectionOptions",
|
|
184
|
+
"DateTimeMatcher",
|
|
176
185
|
"CrossProtocolScenario",
|
|
177
186
|
"CrossProtocolTrigger",
|
|
178
187
|
"Delay",
|
|
@@ -183,7 +192,10 @@ __all__ = [
|
|
|
183
192
|
"DnsRequestDefinition",
|
|
184
193
|
"DnsResponse",
|
|
185
194
|
"Expectation",
|
|
195
|
+
"ExpectationFilter",
|
|
196
|
+
"RecordedExpectationsOptions",
|
|
186
197
|
"ExpectationId",
|
|
198
|
+
"JSON_NULL",
|
|
187
199
|
"ExpectationStep",
|
|
188
200
|
"GraphQLBody",
|
|
189
201
|
"GraphQLSubscriptionFilter",
|
|
@@ -44,6 +44,7 @@ Example::
|
|
|
44
44
|
from __future__ import annotations
|
|
45
45
|
|
|
46
46
|
import json
|
|
47
|
+
import re
|
|
47
48
|
from dataclasses import dataclass, field
|
|
48
49
|
from urllib.parse import urlparse
|
|
49
50
|
|
|
@@ -70,11 +71,31 @@ def _escape_json(value: str | None) -> str:
|
|
|
70
71
|
return quoted[1:-1]
|
|
71
72
|
|
|
72
73
|
|
|
74
|
+
# group 2 consumes the metacharacter after a backslash run, is empty at the end
|
|
75
|
+
# and unset (empty alternative) otherwise, so no lookaround is needed
|
|
76
|
+
_VELOCITY_METACHARACTERS = re.compile(r"(\\+)(?:([$#]|\Z)|)|[$#]")
|
|
77
|
+
_VELOCITY_METACHARACTER_ESCAPES = {"$": "${esc.d}", "#": "${esc.h}", "": ""}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _velocity_escape_match(match: re.Match) -> str:
|
|
81
|
+
run, following = match.group(1), match.group(2)
|
|
82
|
+
if run is None:
|
|
83
|
+
return _VELOCITY_METACHARACTER_ESCAPES[match.group(0)]
|
|
84
|
+
if following is None:
|
|
85
|
+
return run
|
|
86
|
+
return "${esc.b}" * len(run) + _VELOCITY_METACHARACTER_ESCAPES[following]
|
|
87
|
+
|
|
88
|
+
|
|
73
89
|
def _escape_velocity(value: str | None) -> str | None:
|
|
74
|
-
"""Escape Velocity metacharacters so literal ``$`` / ``#`` survive rendering.
|
|
90
|
+
"""Escape Velocity metacharacters so literal ``$`` / ``#`` survive rendering.
|
|
91
|
+
|
|
92
|
+
Velocity consumes backslashes written before a reference or directive, so a
|
|
93
|
+
run of backslashes followed by ``$``, ``#`` or the end of the value is
|
|
94
|
+
escaped too; other backslashes are literal to Velocity and left as-is.
|
|
95
|
+
"""
|
|
75
96
|
if value is None:
|
|
76
97
|
return None
|
|
77
|
-
return
|
|
98
|
+
return _VELOCITY_METACHARACTERS.sub(_velocity_escape_match, value)
|
|
78
99
|
|
|
79
100
|
|
|
80
101
|
def _escape_message_pattern(pattern: str) -> str:
|