mockserver-client 7.3.0 → 7.4.0
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.
- checksums.yaml +4 -4
- data/README.md +40 -3
- data/lib/mockserver/client.rb +33 -0
- data/lib/mockserver/models.rb +551 -44
- data/lib/mockserver/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9eedfe80337a01b9026c0a59a4f95e98218ccfacf6344b4b7b435c1226d9e723
|
|
4
|
+
data.tar.gz: 06a19eaf8d0a7d435c5020c47ea7c2c764fc19d0b609b302e4116355c11df49b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3660064c5073f33c8dcb60cdb22df47c8609571d6328847f06912f2d211471974b515282bd9aeeb56ea7f654001a15efbaf78235e924f8ce85fac65abeed7248
|
|
7
|
+
data.tar.gz: 72a837da848c6ec9fc112c3f13f76362fa9b800694b55673d51fb38a7eaf62086dd89585cbac281dee65567bf56709bc037670fb116913e23546dcf7584f9aac
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Hand-written Ruby client for [MockServer](https://www.mock-server.com) with full
|
|
|
7
7
|
Add to your Gemfile:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
gem 'mockserver-client', '~>
|
|
10
|
+
gem 'mockserver-client', '~> 7.3'
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Or install directly:
|
|
@@ -85,7 +85,7 @@ client.close
|
|
|
85
85
|
|
|
86
86
|
## Models
|
|
87
87
|
|
|
88
|
-
All
|
|
88
|
+
All 26 domain model classes are available under the `MockServer` module:
|
|
89
89
|
|
|
90
90
|
- `Delay`, `Times`, `TimeToLive`
|
|
91
91
|
- `KeyToMultiValue`, `Body`, `SocketAddress`
|
|
@@ -98,6 +98,43 @@ All 25 domain model classes are available under the `MockServer` module:
|
|
|
98
98
|
- `Verification`, `VerificationSequence`, `VerificationTimes`
|
|
99
99
|
- `Ports`
|
|
100
100
|
- `RequestDefinition` (alias for `HttpRequest`)
|
|
101
|
+
- `Jwt` (JWT / bearer-token request matcher)
|
|
102
|
+
|
|
103
|
+
## JWT and combined body matchers
|
|
104
|
+
|
|
105
|
+
Match a request by the claims in its JWT (carried by default in the
|
|
106
|
+
`Authorization: Bearer <token>` header). Each claim value is an exact string or a
|
|
107
|
+
regular expression; a leading `!` negates it (the same NottableString convention
|
|
108
|
+
used elsewhere in MockServer). `issuer`, `audience`, `algorithm`, `header` and
|
|
109
|
+
`scheme` are optional.
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
jwt = MockServer::Jwt.new(
|
|
113
|
+
claims: {
|
|
114
|
+
'sub' => 'user-123',
|
|
115
|
+
'role' => '!admin', # must NOT be "admin"
|
|
116
|
+
'email' => '^.+@example.com$' # regex
|
|
117
|
+
},
|
|
118
|
+
issuer: 'https://issuer.example.com',
|
|
119
|
+
audience: 'my-api',
|
|
120
|
+
algorithm: 'RS256'
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
request = MockServer::HttpRequest.new(method: 'GET', path: '/api')
|
|
124
|
+
.with_jwt(jwt)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Combine several body matchers with `Body.all_of` — the request body must satisfy
|
|
128
|
+
**all** of them:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
body = MockServer::Body.all_of(
|
|
132
|
+
MockServer::Body.json_path('$.name'), # => {"type":"JSON_PATH","jsonPath":"$.name"}
|
|
133
|
+
MockServer::Body.regex('.*active.*') # => {"type":"REGEX","regex":".*active.*"}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
request = MockServer::HttpRequest.new(method: 'POST', path: '/api').with_body(body)
|
|
137
|
+
```
|
|
101
138
|
|
|
102
139
|
## LLM Mocking
|
|
103
140
|
|
|
@@ -316,7 +353,7 @@ launcher_path = MockServer::BinaryLauncher.ensure_launcher
|
|
|
316
353
|
### Specify a version
|
|
317
354
|
|
|
318
355
|
```ruby
|
|
319
|
-
handle = MockServer::BinaryLauncher.start(port: 1080, version: '7.
|
|
356
|
+
handle = MockServer::BinaryLauncher.start(port: 1080, version: '7.4.0')
|
|
320
357
|
```
|
|
321
358
|
|
|
322
359
|
### API reference
|
data/lib/mockserver/client.rb
CHANGED
|
@@ -278,6 +278,39 @@ module MockServer
|
|
|
278
278
|
response_body || ''
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
+
# -------------------------------------------------------------------
|
|
282
|
+
# Drift detection
|
|
283
|
+
# -------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
# Retrieve the recorded mock drift report (GET /mockserver/drift).
|
|
286
|
+
#
|
|
287
|
+
# Returns the parsed report, a Hash of the form
|
|
288
|
+
# +{ "count" => <n>, "drifts" => [ ... ] }+, where each entry describes a
|
|
289
|
+
# difference detected between a mock's configured response and the live
|
|
290
|
+
# upstream response for the same request. When no drift has been recorded an
|
|
291
|
+
# empty Hash is returned.
|
|
292
|
+
#
|
|
293
|
+
# @return [Hash] the parsed drift report
|
|
294
|
+
def retrieve_drift
|
|
295
|
+
status, response_body = request('GET', '/mockserver/drift')
|
|
296
|
+
if status >= 400
|
|
297
|
+
raise Error, "Failed to retrieve drift (status=#{status}): #{response_body}"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
response_body && !response_body.empty? ? JSON.parse(response_body) : {}
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Clear all recorded mock drift (PUT /mockserver/drift/clear).
|
|
304
|
+
# @return [nil]
|
|
305
|
+
def clear_drift
|
|
306
|
+
status, response_body = request('PUT', '/mockserver/drift/clear')
|
|
307
|
+
if status >= 400
|
|
308
|
+
raise Error, "Failed to clear drift (status=#{status}): #{response_body}"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
nil
|
|
312
|
+
end
|
|
313
|
+
|
|
281
314
|
# -------------------------------------------------------------------
|
|
282
315
|
# Pact (import / export / verify)
|
|
283
316
|
# -------------------------------------------------------------------
|
data/lib/mockserver/models.rb
CHANGED
|
@@ -103,7 +103,8 @@ module MockServer
|
|
|
103
103
|
# Known Body type strings used to distinguish Body objects from plain hashes
|
|
104
104
|
# during deserialization.
|
|
105
105
|
BODY_TYPES = Set.new(%w[
|
|
106
|
-
STRING JSON REGEX XML BINARY JSON_SCHEMA JSON_PATH XPATH XML_SCHEMA JSON_RPC GRAPHQL FILE
|
|
106
|
+
STRING JSON REGEX XML BINARY JSON_SCHEMA JSON_PATH XPATH XML_SCHEMA JSON_RPC GRAPHQL FILE ALL_OF
|
|
107
|
+
PARAMETERS WASM
|
|
107
108
|
]).freeze
|
|
108
109
|
|
|
109
110
|
# -------------------------------------------------------------------
|
|
@@ -204,6 +205,18 @@ module MockServer
|
|
|
204
205
|
items.map(&:to_h)
|
|
205
206
|
end
|
|
206
207
|
|
|
208
|
+
# @api private
|
|
209
|
+
# Serialise a keyToMultiValue collection in the { name => [values] } object form
|
|
210
|
+
# (rather than the [{name, values}] array form). MockServer accepts both wire
|
|
211
|
+
# encodings for keyToMultiValue fields, but pathParameters (a schema-matcher map
|
|
212
|
+
# whose values are themselves { "schema": {...} } objects) is conventionally
|
|
213
|
+
# emitted in the object form, so this preserves the caller's shape on round-trip.
|
|
214
|
+
def self.serialize_key_multi_values_object(items)
|
|
215
|
+
return nil if items.nil?
|
|
216
|
+
|
|
217
|
+
items.each_with_object({}) { |item, map| map[item.name] = item.values }
|
|
218
|
+
end
|
|
219
|
+
|
|
207
220
|
# @api private
|
|
208
221
|
def self.deserialize_key_multi_values(data)
|
|
209
222
|
return nil if data.nil?
|
|
@@ -420,49 +433,109 @@ module MockServer
|
|
|
420
433
|
end
|
|
421
434
|
|
|
422
435
|
class Body
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
436
|
+
# sub_string / match_type / match_numbers_as_strings / json_schema /
|
|
437
|
+
# parameter_styles / xml_schema / parameters / module_name / optional are the
|
|
438
|
+
# per-type matcher sub-fields carried by the server body DTOs
|
|
439
|
+
# (StringBodyDTO.subString, JsonBodyDTO.matchType/matchNumbersAsStrings,
|
|
440
|
+
# JsonSchemaBodyDTO.jsonSchema/parameterStyles, XmlSchemaBodyDTO.xmlSchema,
|
|
441
|
+
# ParameterBodyDTO.parameters, WasmBodyDTO.moduleName, BodyDTO.optional).
|
|
442
|
+
# xml carries the XML body's own +xml+ wire key (XmlBodyDTO.xml). It is distinct
|
|
443
|
+
# from +string+: the legacy Body.xml(...) factory populates +string+ (pinned by a
|
|
444
|
+
# test), whereas a server-produced XML body serialises its content under +xml+.
|
|
445
|
+
attr_accessor :type, :string, :xml, :json, :regex, :json_path, :xpath, :base64_bytes,
|
|
446
|
+
:not_body, :content_type, :charset, :file_path, :template_type, :body_all_of,
|
|
447
|
+
:sub_string, :match_type, :match_numbers_as_strings, :json_schema,
|
|
448
|
+
:parameter_styles, :xml_schema, :parameters, :module_name, :optional
|
|
449
|
+
|
|
450
|
+
def initialize(type: nil, string: nil, xml: nil, json: nil, regex: nil, json_path: nil, xpath: nil,
|
|
451
|
+
base64_bytes: nil, not_body: nil, content_type: nil, charset: nil,
|
|
452
|
+
file_path: nil, template_type: nil, body_all_of: nil,
|
|
453
|
+
sub_string: nil, match_type: nil, match_numbers_as_strings: nil,
|
|
454
|
+
json_schema: nil, parameter_styles: nil, xml_schema: nil,
|
|
455
|
+
parameters: nil, module_name: nil, optional: nil)
|
|
428
456
|
@type = type
|
|
429
457
|
@string = string
|
|
458
|
+
@xml = xml
|
|
430
459
|
@json = json
|
|
460
|
+
@regex = regex
|
|
461
|
+
@json_path = json_path
|
|
462
|
+
@xpath = xpath
|
|
431
463
|
@base64_bytes = base64_bytes
|
|
432
464
|
@not_body = not_body
|
|
433
465
|
@content_type = content_type
|
|
434
466
|
@charset = charset
|
|
435
467
|
@file_path = file_path
|
|
436
468
|
@template_type = template_type
|
|
469
|
+
@body_all_of = body_all_of
|
|
470
|
+
@sub_string = sub_string
|
|
471
|
+
@match_type = match_type
|
|
472
|
+
@match_numbers_as_strings = match_numbers_as_strings
|
|
473
|
+
@json_schema = json_schema
|
|
474
|
+
@parameter_styles = parameter_styles
|
|
475
|
+
@xml_schema = xml_schema
|
|
476
|
+
@parameters = parameters
|
|
477
|
+
@module_name = module_name
|
|
478
|
+
@optional = optional
|
|
437
479
|
end
|
|
438
480
|
|
|
439
481
|
def to_h
|
|
440
482
|
result = {}
|
|
441
483
|
result['type'] = @type unless @type.nil?
|
|
442
484
|
result['string'] = @string unless @string.nil?
|
|
485
|
+
result['xml'] = @xml unless @xml.nil?
|
|
443
486
|
result['json'] = @json unless @json.nil?
|
|
487
|
+
result['regex'] = @regex unless @regex.nil?
|
|
488
|
+
result['jsonPath'] = @json_path unless @json_path.nil?
|
|
489
|
+
result['xpath'] = @xpath unless @xpath.nil?
|
|
444
490
|
result['base64Bytes'] = @base64_bytes unless @base64_bytes.nil?
|
|
445
491
|
result['not'] = @not_body unless @not_body.nil?
|
|
446
492
|
result['contentType'] = @content_type unless @content_type.nil?
|
|
447
493
|
result['charset'] = @charset unless @charset.nil?
|
|
448
494
|
result['filePath'] = @file_path unless @file_path.nil?
|
|
449
495
|
result['templateType'] = @template_type unless @template_type.nil?
|
|
496
|
+
result['subString'] = @sub_string unless @sub_string.nil?
|
|
497
|
+
result['matchType'] = @match_type unless @match_type.nil?
|
|
498
|
+
result['matchNumbersAsStrings'] = @match_numbers_as_strings unless @match_numbers_as_strings.nil?
|
|
499
|
+
result['jsonSchema'] = @json_schema unless @json_schema.nil?
|
|
500
|
+
result['parameterStyles'] = @parameter_styles unless @parameter_styles.nil?
|
|
501
|
+
result['xmlSchema'] = @xml_schema unless @xml_schema.nil?
|
|
502
|
+
result['parameters'] = @parameters unless @parameters.nil?
|
|
503
|
+
result['moduleName'] = @module_name unless @module_name.nil?
|
|
504
|
+
result['optional'] = @optional unless @optional.nil?
|
|
505
|
+
unless @body_all_of.nil?
|
|
506
|
+
result['bodyAllOf'] = @body_all_of.map { |b| MockServer.serialize_body(b) }
|
|
507
|
+
end
|
|
450
508
|
result
|
|
451
509
|
end
|
|
452
510
|
|
|
453
511
|
def self.from_hash(data)
|
|
454
512
|
return nil if data.nil?
|
|
455
513
|
|
|
514
|
+
all_of = data['bodyAllOf']
|
|
456
515
|
new(
|
|
457
516
|
type: data['type'],
|
|
458
517
|
string: data['string'],
|
|
518
|
+
xml: data['xml'],
|
|
459
519
|
json: data['json'],
|
|
520
|
+
regex: data['regex'],
|
|
521
|
+
json_path: data['jsonPath'],
|
|
522
|
+
xpath: data['xpath'],
|
|
460
523
|
base64_bytes: data['base64Bytes'],
|
|
461
524
|
not_body: data['not'],
|
|
462
525
|
content_type: data['contentType'],
|
|
463
526
|
charset: data['charset'],
|
|
464
527
|
file_path: data['filePath'],
|
|
465
|
-
template_type: data['templateType']
|
|
528
|
+
template_type: data['templateType'],
|
|
529
|
+
sub_string: data['subString'],
|
|
530
|
+
match_type: data['matchType'],
|
|
531
|
+
match_numbers_as_strings: data['matchNumbersAsStrings'],
|
|
532
|
+
json_schema: data['jsonSchema'],
|
|
533
|
+
parameter_styles: data['parameterStyles'],
|
|
534
|
+
xml_schema: data['xmlSchema'],
|
|
535
|
+
parameters: data['parameters'],
|
|
536
|
+
module_name: data['moduleName'],
|
|
537
|
+
optional: data['optional'],
|
|
538
|
+
body_all_of: all_of&.map { |b| MockServer.deserialize_body(b) }
|
|
466
539
|
)
|
|
467
540
|
end
|
|
468
541
|
|
|
@@ -475,7 +548,7 @@ module MockServer
|
|
|
475
548
|
end
|
|
476
549
|
|
|
477
550
|
def self.regex(value)
|
|
478
|
-
new(type: 'REGEX',
|
|
551
|
+
new(type: 'REGEX', regex: value)
|
|
479
552
|
end
|
|
480
553
|
|
|
481
554
|
def self.exact(value)
|
|
@@ -486,6 +559,50 @@ module MockServer
|
|
|
486
559
|
new(type: 'XML', string: value)
|
|
487
560
|
end
|
|
488
561
|
|
|
562
|
+
# Combine several body matchers into a single ALL_OF matcher; all of the
|
|
563
|
+
# supplied sub-bodies must match the request body. Each sub-body is any
|
|
564
|
+
# existing body matcher (e.g. +Body.json_path+, +Body.regex+) and is
|
|
565
|
+
# serialised through the normal body-serialisation path.
|
|
566
|
+
def self.all_of(*bodies)
|
|
567
|
+
new(type: 'ALL_OF', body_all_of: bodies.flatten)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def self.json_path(value)
|
|
571
|
+
new(type: 'JSON_PATH', json_path: value)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def self.xpath(value)
|
|
575
|
+
new(type: 'XPATH', xpath: value)
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# A JSON_SCHEMA body matcher. +parameter_styles+ is an optional
|
|
579
|
+
# { parameter-name => style } map (serialised as +parameterStyles+).
|
|
580
|
+
def self.json_schema(schema, parameter_styles: nil)
|
|
581
|
+
new(type: 'JSON_SCHEMA', json_schema: schema, parameter_styles: parameter_styles)
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# An XML_SCHEMA body matcher.
|
|
585
|
+
def self.xml_schema(schema)
|
|
586
|
+
new(type: 'XML_SCHEMA', xml_schema: schema)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# A STRING body matcher that matches when +value+ appears as a substring.
|
|
590
|
+
def self.sub_string(value)
|
|
591
|
+
new(type: 'STRING', string: value, sub_string: true)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# A PARAMETERS (form / query) body matcher. +parameters+ is the wire form
|
|
595
|
+
# of the parameter collection (either a { name => [values] } map or the
|
|
596
|
+
# [{name, values}] array form).
|
|
597
|
+
def self.parameters(parameters)
|
|
598
|
+
new(type: 'PARAMETERS', parameters: parameters)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# A WASM body matcher that delegates matching to the named WASM module.
|
|
602
|
+
def self.wasm(module_name)
|
|
603
|
+
new(type: 'WASM', module_name: module_name)
|
|
604
|
+
end
|
|
605
|
+
|
|
489
606
|
def self.file(file_path, content_type: nil, template_type: nil)
|
|
490
607
|
new(type: 'FILE', file_path: file_path, content_type: content_type, template_type: template_type)
|
|
491
608
|
end
|
|
@@ -535,12 +652,22 @@ module MockServer
|
|
|
535
652
|
end
|
|
536
653
|
|
|
537
654
|
class GraphQLBody
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
655
|
+
# selection_set_match_type selects how the query's selection set is matched
|
|
656
|
+
# (NORMALISED_STRING / AST_EXACT / AST_SUBSET) and +fields+ is the array of
|
|
657
|
+
# top-level field names the matcher restricts to, mirroring the server
|
|
658
|
+
# GraphQLBodyDTO +selectionSetMatchType+ / +fields+ (the GRAPHQL body's +fields+
|
|
659
|
+
# is a plain string array, distinct from the MULTIPART body's keyToMultiValue
|
|
660
|
+
# +fields+ map — the server discriminates the two by the body +type+).
|
|
661
|
+
attr_accessor :query, :operation_name, :variables_schema,
|
|
662
|
+
:selection_set_match_type, :fields, :not_body, :optional
|
|
663
|
+
|
|
664
|
+
def initialize(query:, operation_name: nil, variables_schema: nil,
|
|
665
|
+
selection_set_match_type: nil, fields: nil, not_body: false, optional: false)
|
|
541
666
|
@query = query
|
|
542
667
|
@operation_name = operation_name
|
|
543
668
|
@variables_schema = variables_schema
|
|
669
|
+
@selection_set_match_type = selection_set_match_type
|
|
670
|
+
@fields = fields
|
|
544
671
|
@not_body = not_body
|
|
545
672
|
@optional = optional
|
|
546
673
|
end
|
|
@@ -549,6 +676,8 @@ module MockServer
|
|
|
549
676
|
result = { 'type' => 'GRAPHQL', 'query' => @query }
|
|
550
677
|
result['operationName'] = @operation_name if @operation_name
|
|
551
678
|
result['variablesSchema'] = @variables_schema if @variables_schema
|
|
679
|
+
result['selectionSetMatchType'] = @selection_set_match_type unless @selection_set_match_type.nil?
|
|
680
|
+
result['fields'] = @fields unless @fields.nil?
|
|
552
681
|
result['not'] = true if @not_body
|
|
553
682
|
result['optional'] = true if @optional
|
|
554
683
|
result
|
|
@@ -558,11 +687,13 @@ module MockServer
|
|
|
558
687
|
return nil if data.nil?
|
|
559
688
|
|
|
560
689
|
new(
|
|
561
|
-
query:
|
|
562
|
-
operation_name:
|
|
563
|
-
variables_schema:
|
|
564
|
-
|
|
565
|
-
|
|
690
|
+
query: data['query'] || '',
|
|
691
|
+
operation_name: data['operationName'],
|
|
692
|
+
variables_schema: data['variablesSchema'],
|
|
693
|
+
selection_set_match_type: data['selectionSetMatchType'],
|
|
694
|
+
fields: data['fields'],
|
|
695
|
+
not_body: data.fetch('not', false),
|
|
696
|
+
optional: data.fetch('optional', false)
|
|
566
697
|
)
|
|
567
698
|
end
|
|
568
699
|
end
|
|
@@ -595,14 +726,75 @@ module MockServer
|
|
|
595
726
|
end
|
|
596
727
|
end
|
|
597
728
|
|
|
729
|
+
# JWT (bearer-token) request matcher. Matches a request whose JWT — carried by
|
|
730
|
+
# default in the +Authorization: Bearer <token>+ header — satisfies every
|
|
731
|
+
# constraint below.
|
|
732
|
+
#
|
|
733
|
+
# +claims+ is a { claim-name => value } map where each value is an exact string
|
|
734
|
+
# or a regular expression; a leading +!+ negates the match (the NottableString
|
|
735
|
+
# convention shared across MockServer matchers). +issuer+, +audience+,
|
|
736
|
+
# +algorithm+, +header+ and +scheme+ are optional; unset keys are omitted.
|
|
737
|
+
class Jwt
|
|
738
|
+
attr_accessor :claims, :issuer, :audience, :algorithm, :header, :scheme
|
|
739
|
+
|
|
740
|
+
def initialize(claims: nil, issuer: nil, audience: nil, algorithm: nil, header: nil, scheme: nil)
|
|
741
|
+
@claims = claims
|
|
742
|
+
@issuer = issuer
|
|
743
|
+
@audience = audience
|
|
744
|
+
@algorithm = algorithm
|
|
745
|
+
@header = header
|
|
746
|
+
@scheme = scheme
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def to_h
|
|
750
|
+
result = {}
|
|
751
|
+
result['claims'] = @claims unless @claims.nil?
|
|
752
|
+
result['issuer'] = @issuer unless @issuer.nil?
|
|
753
|
+
result['audience'] = @audience unless @audience.nil?
|
|
754
|
+
result['algorithm'] = @algorithm unless @algorithm.nil?
|
|
755
|
+
result['header'] = @header unless @header.nil?
|
|
756
|
+
result['scheme'] = @scheme unless @scheme.nil?
|
|
757
|
+
result
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
def self.from_hash(data)
|
|
761
|
+
return nil if data.nil?
|
|
762
|
+
|
|
763
|
+
new(
|
|
764
|
+
claims: data['claims'],
|
|
765
|
+
issuer: data['issuer'],
|
|
766
|
+
audience: data['audience'],
|
|
767
|
+
algorithm: data['algorithm'],
|
|
768
|
+
header: data['header'],
|
|
769
|
+
scheme: data['scheme']
|
|
770
|
+
)
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
def with_claim(name, value)
|
|
774
|
+
@claims ||= {}
|
|
775
|
+
@claims[name] = value
|
|
776
|
+
self
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
598
780
|
class HttpRequest
|
|
781
|
+
# dns_name / dns_type / dns_class carry a DNS request matcher
|
|
782
|
+
# (org.mockserver.model.DnsRequestDefinition). The server discriminates a DNS
|
|
783
|
+
# request definition from an HTTP one purely by the presence of a non-blank
|
|
784
|
+
# +dnsName+ on the +httpRequest+ object, so these fields round-trip through
|
|
785
|
+
# the same +httpRequest+ slot as the HTTP matcher fields.
|
|
786
|
+
# protocol pins the transport the matcher requires
|
|
787
|
+
# (org.mockserver.model.Protocol: HTTP_1_1 / HTTP_2 / HTTP_3 / SOCKS / ...),
|
|
788
|
+
# round-tripping the httpRequest +protocol+ wire key.
|
|
599
789
|
attr_accessor :method, :path, :query_string_parameters, :headers,
|
|
600
|
-
:cookies, :body, :secure, :keep_alive, :respond_before_body,
|
|
601
|
-
:path_parameters, :socket_address
|
|
790
|
+
:cookies, :body, :secure, :protocol, :keep_alive, :respond_before_body,
|
|
791
|
+
:path_parameters, :socket_address, :jwt,
|
|
792
|
+
:dns_name, :dns_type, :dns_class
|
|
602
793
|
|
|
603
794
|
def initialize(method: nil, path: nil, query_string_parameters: nil, headers: nil,
|
|
604
|
-
cookies: nil, body: nil, secure: nil, keep_alive: nil,
|
|
605
|
-
respond_before_body: nil, path_parameters: nil, socket_address: nil
|
|
795
|
+
cookies: nil, body: nil, secure: nil, protocol: nil, keep_alive: nil,
|
|
796
|
+
respond_before_body: nil, path_parameters: nil, socket_address: nil,
|
|
797
|
+
jwt: nil, dns_name: nil, dns_type: nil, dns_class: nil)
|
|
606
798
|
@method = method
|
|
607
799
|
@path = path
|
|
608
800
|
@query_string_parameters = query_string_parameters
|
|
@@ -610,10 +802,15 @@ module MockServer
|
|
|
610
802
|
@cookies = cookies
|
|
611
803
|
@body = body
|
|
612
804
|
@secure = secure
|
|
805
|
+
@protocol = protocol
|
|
613
806
|
@keep_alive = keep_alive
|
|
614
807
|
@respond_before_body = respond_before_body
|
|
615
808
|
@path_parameters = path_parameters
|
|
616
809
|
@socket_address = socket_address
|
|
810
|
+
@jwt = jwt
|
|
811
|
+
@dns_name = dns_name
|
|
812
|
+
@dns_type = dns_type
|
|
813
|
+
@dns_class = dns_class
|
|
617
814
|
end
|
|
618
815
|
|
|
619
816
|
def to_h
|
|
@@ -625,10 +822,15 @@ module MockServer
|
|
|
625
822
|
'cookies' => MockServer.serialize_cookies(@cookies),
|
|
626
823
|
'body' => MockServer.serialize_body(@body),
|
|
627
824
|
'secure' => @secure,
|
|
825
|
+
'protocol' => @protocol,
|
|
628
826
|
'keepAlive' => @keep_alive,
|
|
629
827
|
'respondBeforeBody' => @respond_before_body,
|
|
630
|
-
'pathParameters' => MockServer.
|
|
631
|
-
'socketAddress' => @socket_address&.to_h
|
|
828
|
+
'pathParameters' => MockServer.serialize_key_multi_values_object(@path_parameters),
|
|
829
|
+
'socketAddress' => @socket_address&.to_h,
|
|
830
|
+
'jwt' => @jwt&.to_h,
|
|
831
|
+
'dnsName' => @dns_name,
|
|
832
|
+
'dnsType' => @dns_type,
|
|
833
|
+
'dnsClass' => @dns_class
|
|
632
834
|
})
|
|
633
835
|
end
|
|
634
836
|
|
|
@@ -643,13 +845,23 @@ module MockServer
|
|
|
643
845
|
cookies: MockServer.deserialize_cookies(data['cookies']),
|
|
644
846
|
body: MockServer.deserialize_body(data['body']),
|
|
645
847
|
secure: data['secure'],
|
|
848
|
+
protocol: data['protocol'],
|
|
646
849
|
keep_alive: data['keepAlive'],
|
|
647
850
|
respond_before_body: data['respondBeforeBody'],
|
|
648
851
|
path_parameters: MockServer.deserialize_key_multi_values(data['pathParameters']),
|
|
649
|
-
socket_address: SocketAddress.from_hash(data['socketAddress'])
|
|
852
|
+
socket_address: SocketAddress.from_hash(data['socketAddress']),
|
|
853
|
+
jwt: Jwt.from_hash(data['jwt']),
|
|
854
|
+
dns_name: data['dnsName'],
|
|
855
|
+
dns_type: data['dnsType'],
|
|
856
|
+
dns_class: data['dnsClass']
|
|
650
857
|
)
|
|
651
858
|
end
|
|
652
859
|
|
|
860
|
+
# Build a DNS request matcher (org.mockserver.model.DnsRequestDefinition).
|
|
861
|
+
def self.dns_request(dns_name, dns_type: nil, dns_class: nil)
|
|
862
|
+
new(dns_name: dns_name, dns_type: dns_type, dns_class: dns_class)
|
|
863
|
+
end
|
|
864
|
+
|
|
653
865
|
def self.request(path: nil)
|
|
654
866
|
new(path: path)
|
|
655
867
|
end
|
|
@@ -687,6 +899,11 @@ module MockServer
|
|
|
687
899
|
self
|
|
688
900
|
end
|
|
689
901
|
|
|
902
|
+
def with_jwt(jwt)
|
|
903
|
+
@jwt = jwt
|
|
904
|
+
self
|
|
905
|
+
end
|
|
906
|
+
|
|
690
907
|
def with_secure(secure)
|
|
691
908
|
@secure = secure
|
|
692
909
|
self
|
|
@@ -752,11 +969,13 @@ module MockServer
|
|
|
752
969
|
end
|
|
753
970
|
|
|
754
971
|
class HttpResponse
|
|
972
|
+
# trailers carries HTTP trailing headers (org.mockserver.model.Headers on the
|
|
973
|
+
# response's +trailers+ slot), a keyToMultiValue collection like +headers+.
|
|
755
974
|
attr_accessor :status_code, :reason_phrase, :headers, :cookies,
|
|
756
|
-
:body, :delay, :connection_options, :primary
|
|
975
|
+
:body, :delay, :connection_options, :trailers, :primary
|
|
757
976
|
|
|
758
977
|
def initialize(status_code: nil, reason_phrase: nil, headers: nil, cookies: nil,
|
|
759
|
-
body: nil, delay: nil, connection_options: nil, primary: nil)
|
|
978
|
+
body: nil, delay: nil, connection_options: nil, trailers: nil, primary: nil)
|
|
760
979
|
@status_code = status_code
|
|
761
980
|
@reason_phrase = reason_phrase
|
|
762
981
|
@headers = headers
|
|
@@ -764,6 +983,7 @@ module MockServer
|
|
|
764
983
|
@body = body
|
|
765
984
|
@delay = delay
|
|
766
985
|
@connection_options = connection_options
|
|
986
|
+
@trailers = trailers
|
|
767
987
|
@primary = primary
|
|
768
988
|
end
|
|
769
989
|
|
|
@@ -776,6 +996,7 @@ module MockServer
|
|
|
776
996
|
'body' => MockServer.serialize_body(@body),
|
|
777
997
|
'delay' => @delay&.to_h,
|
|
778
998
|
'connectionOptions' => @connection_options&.to_h,
|
|
999
|
+
'trailers' => MockServer.serialize_key_multi_values(@trailers),
|
|
779
1000
|
'primary' => @primary
|
|
780
1001
|
})
|
|
781
1002
|
end
|
|
@@ -791,6 +1012,7 @@ module MockServer
|
|
|
791
1012
|
body: MockServer.deserialize_body(data['body']),
|
|
792
1013
|
delay: Delay.from_hash(data['delay']),
|
|
793
1014
|
connection_options: ConnectionOptions.from_hash(data['connectionOptions']),
|
|
1015
|
+
trailers: MockServer.deserialize_key_multi_values(data['trailers']),
|
|
794
1016
|
primary: data['primary']
|
|
795
1017
|
)
|
|
796
1018
|
end
|
|
@@ -1222,12 +1444,49 @@ module MockServer
|
|
|
1222
1444
|
end
|
|
1223
1445
|
end
|
|
1224
1446
|
|
|
1447
|
+
# A per-incoming-frame response rule for a WebSocket mock (mirrors the server
|
|
1448
|
+
# WebSocketMessageMatcher). When +frame_type+ (TEXT / BINARY / PING / PONG / ANY)
|
|
1449
|
+
# and the optional +text_matcher+ match an inbound frame, the rule's +responses+
|
|
1450
|
+
# (each a {WebSocketMessage}) are sent back.
|
|
1451
|
+
class WebSocketFrameMatcher
|
|
1452
|
+
attr_accessor :frame_type, :text_matcher, :responses
|
|
1453
|
+
|
|
1454
|
+
def initialize(frame_type: nil, text_matcher: nil, responses: nil)
|
|
1455
|
+
@frame_type = frame_type
|
|
1456
|
+
@text_matcher = text_matcher
|
|
1457
|
+
@responses = responses
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
def to_h
|
|
1461
|
+
result = {}
|
|
1462
|
+
result['frameType'] = @frame_type unless @frame_type.nil?
|
|
1463
|
+
result['textMatcher'] = @text_matcher unless @text_matcher.nil?
|
|
1464
|
+
result['responses'] = @responses&.map(&:to_h) if @responses
|
|
1465
|
+
result
|
|
1466
|
+
end
|
|
1467
|
+
|
|
1468
|
+
def self.from_hash(data)
|
|
1469
|
+
return nil if data.nil?
|
|
1470
|
+
|
|
1471
|
+
responses_data = data['responses']
|
|
1472
|
+
responses = responses_data&.map { |r| WebSocketMessage.from_hash(r) }
|
|
1473
|
+
new(
|
|
1474
|
+
frame_type: data['frameType'],
|
|
1475
|
+
text_matcher: data['textMatcher'],
|
|
1476
|
+
responses: responses
|
|
1477
|
+
)
|
|
1478
|
+
end
|
|
1479
|
+
end
|
|
1480
|
+
|
|
1225
1481
|
class HttpWebSocketResponse
|
|
1226
|
-
|
|
1482
|
+
# matchers carries per-incoming-frame response rules (server
|
|
1483
|
+
# HttpWebSocketResponseDTO.matchers); each is a {WebSocketFrameMatcher}.
|
|
1484
|
+
attr_accessor :subprotocol, :messages, :matchers, :close_connection, :delay, :primary
|
|
1227
1485
|
|
|
1228
|
-
def initialize(subprotocol: nil, messages: nil, close_connection: nil, delay: nil, primary: nil)
|
|
1486
|
+
def initialize(subprotocol: nil, messages: nil, matchers: nil, close_connection: nil, delay: nil, primary: nil)
|
|
1229
1487
|
@subprotocol = subprotocol
|
|
1230
1488
|
@messages = messages
|
|
1489
|
+
@matchers = matchers
|
|
1231
1490
|
@close_connection = close_connection
|
|
1232
1491
|
@delay = delay
|
|
1233
1492
|
@primary = primary
|
|
@@ -1237,6 +1496,7 @@ module MockServer
|
|
|
1237
1496
|
result = {}
|
|
1238
1497
|
result['subprotocol'] = @subprotocol unless @subprotocol.nil?
|
|
1239
1498
|
result['messages'] = @messages&.map(&:to_h) if @messages
|
|
1499
|
+
result['matchers'] = @matchers&.map(&:to_h) if @matchers
|
|
1240
1500
|
result['closeConnection'] = @close_connection unless @close_connection.nil?
|
|
1241
1501
|
result['delay'] = @delay.to_h if @delay
|
|
1242
1502
|
result['primary'] = @primary unless @primary.nil?
|
|
@@ -1248,9 +1508,12 @@ module MockServer
|
|
|
1248
1508
|
|
|
1249
1509
|
messages_data = data['messages']
|
|
1250
1510
|
messages = messages_data&.map { |m| WebSocketMessage.from_hash(m) }
|
|
1511
|
+
matchers_data = data['matchers']
|
|
1512
|
+
matchers = matchers_data&.map { |m| WebSocketFrameMatcher.from_hash(m) }
|
|
1251
1513
|
new(
|
|
1252
1514
|
subprotocol: data['subprotocol'],
|
|
1253
1515
|
messages: messages,
|
|
1516
|
+
matchers: matchers,
|
|
1254
1517
|
close_connection: data['closeConnection'],
|
|
1255
1518
|
delay: Delay.from_hash(data['delay']),
|
|
1256
1519
|
primary: data['primary']
|
|
@@ -1259,16 +1522,21 @@ module MockServer
|
|
|
1259
1522
|
end
|
|
1260
1523
|
|
|
1261
1524
|
class GrpcStreamMessage
|
|
1262
|
-
|
|
1525
|
+
# template_type marks the message +json+ as a response template rendered by the
|
|
1526
|
+
# named engine (VELOCITY / JAVASCRIPT / MUSTACHE), mirroring the server
|
|
1527
|
+
# GrpcStreamResponseDTO / GrpcBidiResponseDTO message +templateType+.
|
|
1528
|
+
attr_accessor :json, :template_type, :delay
|
|
1263
1529
|
|
|
1264
|
-
def initialize(json: nil, delay: nil)
|
|
1530
|
+
def initialize(json: nil, template_type: nil, delay: nil)
|
|
1265
1531
|
@json = json
|
|
1532
|
+
@template_type = template_type
|
|
1266
1533
|
@delay = delay
|
|
1267
1534
|
end
|
|
1268
1535
|
|
|
1269
1536
|
def to_h
|
|
1270
1537
|
result = {}
|
|
1271
1538
|
result['json'] = @json unless @json.nil?
|
|
1539
|
+
result['templateType'] = @template_type unless @template_type.nil?
|
|
1272
1540
|
result['delay'] = @delay.to_h if @delay
|
|
1273
1541
|
result
|
|
1274
1542
|
end
|
|
@@ -1277,8 +1545,9 @@ module MockServer
|
|
|
1277
1545
|
return nil if data.nil?
|
|
1278
1546
|
|
|
1279
1547
|
new(
|
|
1280
|
-
json:
|
|
1281
|
-
|
|
1548
|
+
json: data['json'],
|
|
1549
|
+
template_type: data['templateType'],
|
|
1550
|
+
delay: Delay.from_hash(data['delay'])
|
|
1282
1551
|
)
|
|
1283
1552
|
end
|
|
1284
1553
|
end
|
|
@@ -1547,13 +1816,20 @@ module MockServer
|
|
|
1547
1816
|
end
|
|
1548
1817
|
|
|
1549
1818
|
class HttpChaosProfile
|
|
1819
|
+
# graphql_errors / graphql_error_message / graphql_error_code /
|
|
1820
|
+
# graphql_nullify_data carry the GraphQL fault-injection knobs from the server
|
|
1821
|
+
# HttpChaosProfileDTO: when graphqlErrors is set the mocked GraphQL response is
|
|
1822
|
+
# rewritten to carry an "errors" array (message/code from graphqlErrorMessage /
|
|
1823
|
+
# graphqlErrorCode) and, when graphqlNullifyData is true, a null "data" field.
|
|
1550
1824
|
attr_accessor :error_status, :error_probability, :drop_connection_probability,
|
|
1551
1825
|
:retry_after, :latency, :seed, :succeed_first, :fail_request_count,
|
|
1552
1826
|
:outage_after_millis, :outage_duration_millis,
|
|
1553
1827
|
:truncate_body_at_fraction, :malformed_body,
|
|
1554
1828
|
:slow_response_chunk_size, :slow_response_chunk_delay,
|
|
1555
1829
|
:quota_name, :quota_limit, :quota_window_millis, :quota_error_status,
|
|
1556
|
-
:degradation_ramp_millis
|
|
1830
|
+
:degradation_ramp_millis,
|
|
1831
|
+
:graphql_errors, :graphql_error_message, :graphql_error_code,
|
|
1832
|
+
:graphql_nullify_data
|
|
1557
1833
|
|
|
1558
1834
|
def initialize(error_status: nil, error_probability: nil, drop_connection_probability: nil,
|
|
1559
1835
|
retry_after: nil, latency: nil, seed: nil, succeed_first: nil, fail_request_count: nil,
|
|
@@ -1561,7 +1837,9 @@ module MockServer
|
|
|
1561
1837
|
truncate_body_at_fraction: nil, malformed_body: nil,
|
|
1562
1838
|
slow_response_chunk_size: nil, slow_response_chunk_delay: nil,
|
|
1563
1839
|
quota_name: nil, quota_limit: nil, quota_window_millis: nil, quota_error_status: nil,
|
|
1564
|
-
degradation_ramp_millis: nil
|
|
1840
|
+
degradation_ramp_millis: nil,
|
|
1841
|
+
graphql_errors: nil, graphql_error_message: nil, graphql_error_code: nil,
|
|
1842
|
+
graphql_nullify_data: nil)
|
|
1565
1843
|
@error_status = error_status
|
|
1566
1844
|
@error_probability = error_probability
|
|
1567
1845
|
@drop_connection_probability = drop_connection_probability
|
|
@@ -1581,6 +1859,10 @@ module MockServer
|
|
|
1581
1859
|
@quota_window_millis = quota_window_millis
|
|
1582
1860
|
@quota_error_status = quota_error_status
|
|
1583
1861
|
@degradation_ramp_millis = degradation_ramp_millis
|
|
1862
|
+
@graphql_errors = graphql_errors
|
|
1863
|
+
@graphql_error_message = graphql_error_message
|
|
1864
|
+
@graphql_error_code = graphql_error_code
|
|
1865
|
+
@graphql_nullify_data = graphql_nullify_data
|
|
1584
1866
|
end
|
|
1585
1867
|
|
|
1586
1868
|
def to_h
|
|
@@ -1603,7 +1885,11 @@ module MockServer
|
|
|
1603
1885
|
'quotaLimit' => @quota_limit,
|
|
1604
1886
|
'quotaWindowMillis' => @quota_window_millis,
|
|
1605
1887
|
'quotaErrorStatus' => @quota_error_status,
|
|
1606
|
-
'degradationRampMillis' => @degradation_ramp_millis
|
|
1888
|
+
'degradationRampMillis' => @degradation_ramp_millis,
|
|
1889
|
+
'graphqlErrors' => @graphql_errors,
|
|
1890
|
+
'graphqlErrorMessage' => @graphql_error_message,
|
|
1891
|
+
'graphqlErrorCode' => @graphql_error_code,
|
|
1892
|
+
'graphqlNullifyData' => @graphql_nullify_data
|
|
1607
1893
|
})
|
|
1608
1894
|
end
|
|
1609
1895
|
|
|
@@ -1629,7 +1915,11 @@ module MockServer
|
|
|
1629
1915
|
quota_limit: data['quotaLimit'],
|
|
1630
1916
|
quota_window_millis: data['quotaWindowMillis'],
|
|
1631
1917
|
quota_error_status: data['quotaErrorStatus'],
|
|
1632
|
-
degradation_ramp_millis: data['degradationRampMillis']
|
|
1918
|
+
degradation_ramp_millis: data['degradationRampMillis'],
|
|
1919
|
+
graphql_errors: data['graphqlErrors'],
|
|
1920
|
+
graphql_error_message: data['graphqlErrorMessage'],
|
|
1921
|
+
graphql_error_code: data['graphqlErrorCode'],
|
|
1922
|
+
graphql_nullify_data: data['graphqlNullifyData']
|
|
1633
1923
|
)
|
|
1634
1924
|
end
|
|
1635
1925
|
end
|
|
@@ -1884,34 +2174,230 @@ module MockServer
|
|
|
1884
2174
|
end
|
|
1885
2175
|
end
|
|
1886
2176
|
|
|
2177
|
+
# Per-expectation rate limit (org.mockserver.model.RateLimit). When the counter
|
|
2178
|
+
# keyed by +name+ (or the expectation id when +name+ is nil) exceeds the
|
|
2179
|
+
# configured limit, the server returns +error_status+ (default 429).
|
|
2180
|
+
#
|
|
2181
|
+
# +algorithm+ is the wire enum value: "fixed_window" (default) or
|
|
2182
|
+
# "token_bucket" (the server serialises it lower-case). FIXED_WINDOW uses
|
|
2183
|
+
# +limit+ / +window_millis+; TOKEN_BUCKET uses +burst+ / +refill_per_second+.
|
|
2184
|
+
class RateLimit
|
|
2185
|
+
attr_accessor :name, :algorithm, :limit, :window_millis, :burst,
|
|
2186
|
+
:refill_per_second, :error_status, :retry_after
|
|
2187
|
+
|
|
2188
|
+
def initialize(name: nil, algorithm: nil, limit: nil, window_millis: nil, burst: nil,
|
|
2189
|
+
refill_per_second: nil, error_status: nil, retry_after: nil)
|
|
2190
|
+
@name = name
|
|
2191
|
+
@algorithm = algorithm
|
|
2192
|
+
@limit = limit
|
|
2193
|
+
@window_millis = window_millis
|
|
2194
|
+
@burst = burst
|
|
2195
|
+
@refill_per_second = refill_per_second
|
|
2196
|
+
@error_status = error_status
|
|
2197
|
+
@retry_after = retry_after
|
|
2198
|
+
end
|
|
2199
|
+
|
|
2200
|
+
def to_h
|
|
2201
|
+
MockServer.strip_none({
|
|
2202
|
+
'name' => @name,
|
|
2203
|
+
'algorithm' => @algorithm,
|
|
2204
|
+
'limit' => @limit,
|
|
2205
|
+
'windowMillis' => @window_millis,
|
|
2206
|
+
'burst' => @burst,
|
|
2207
|
+
'refillPerSecond' => @refill_per_second,
|
|
2208
|
+
'errorStatus' => @error_status,
|
|
2209
|
+
'retryAfter' => @retry_after
|
|
2210
|
+
})
|
|
2211
|
+
end
|
|
2212
|
+
|
|
2213
|
+
def self.from_hash(data)
|
|
2214
|
+
return nil if data.nil?
|
|
2215
|
+
|
|
2216
|
+
new(
|
|
2217
|
+
name: data['name'],
|
|
2218
|
+
algorithm: data['algorithm'],
|
|
2219
|
+
limit: data['limit'],
|
|
2220
|
+
window_millis: data['windowMillis'],
|
|
2221
|
+
burst: data['burst'],
|
|
2222
|
+
refill_per_second: data['refillPerSecond'],
|
|
2223
|
+
error_status: data['errorStatus'],
|
|
2224
|
+
retry_after: data['retryAfter']
|
|
2225
|
+
)
|
|
2226
|
+
end
|
|
2227
|
+
|
|
2228
|
+
def self.fixed_window(limit, window_millis, name: nil)
|
|
2229
|
+
new(name: name, algorithm: 'fixed_window', limit: limit, window_millis: window_millis)
|
|
2230
|
+
end
|
|
2231
|
+
|
|
2232
|
+
def self.token_bucket(burst, refill_per_second, name: nil)
|
|
2233
|
+
new(name: name, algorithm: 'token_bucket', burst: burst, refill_per_second: refill_per_second)
|
|
2234
|
+
end
|
|
2235
|
+
end
|
|
2236
|
+
|
|
2237
|
+
# A single expectation-level capture rule (org.mockserver.model.CaptureRule):
|
|
2238
|
+
# extract a value from the matched request via +source+/+expression+ and store
|
|
2239
|
+
# it under the +into+ key for use by later actions/templates.
|
|
2240
|
+
#
|
|
2241
|
+
# +source+ is the wire enum value, one of: "jsonPath", "xpath", "header",
|
|
2242
|
+
# "queryStringParameter", "cookie", "pathParameter".
|
|
2243
|
+
class CaptureRule
|
|
2244
|
+
attr_accessor :source, :expression, :into
|
|
2245
|
+
|
|
2246
|
+
def initialize(source: nil, expression: nil, into: nil)
|
|
2247
|
+
@source = source
|
|
2248
|
+
@expression = expression
|
|
2249
|
+
@into = into
|
|
2250
|
+
end
|
|
2251
|
+
|
|
2252
|
+
def to_h
|
|
2253
|
+
MockServer.strip_none({
|
|
2254
|
+
'source' => @source,
|
|
2255
|
+
'expression' => @expression,
|
|
2256
|
+
'into' => @into
|
|
2257
|
+
})
|
|
2258
|
+
end
|
|
2259
|
+
|
|
2260
|
+
def self.from_hash(data)
|
|
2261
|
+
return nil if data.nil?
|
|
2262
|
+
|
|
2263
|
+
new(
|
|
2264
|
+
source: data['source'],
|
|
2265
|
+
expression: data['expression'],
|
|
2266
|
+
into: data['into']
|
|
2267
|
+
)
|
|
2268
|
+
end
|
|
2269
|
+
end
|
|
2270
|
+
|
|
2271
|
+
# Forward-and-validate action (org.mockserver.model.HttpForwardValidateAction):
|
|
2272
|
+
# forwards the request to +host+/+port+ (+scheme+) and validates the request
|
|
2273
|
+
# and/or response against the OpenAPI +spec_url_or_payload+.
|
|
2274
|
+
#
|
|
2275
|
+
# +validation_mode+ is the wire enum value "STRICT" (default) or "LOG_ONLY".
|
|
2276
|
+
class HttpForwardValidateAction
|
|
2277
|
+
attr_accessor :spec_url_or_payload, :host, :port, :scheme,
|
|
2278
|
+
:validate_request, :validate_response, :validation_mode,
|
|
2279
|
+
:delay, :primary
|
|
2280
|
+
|
|
2281
|
+
def initialize(spec_url_or_payload: nil, host: nil, port: nil, scheme: nil,
|
|
2282
|
+
validate_request: nil, validate_response: nil, validation_mode: nil,
|
|
2283
|
+
delay: nil, primary: nil)
|
|
2284
|
+
@spec_url_or_payload = spec_url_or_payload
|
|
2285
|
+
@host = host
|
|
2286
|
+
@port = port
|
|
2287
|
+
@scheme = scheme
|
|
2288
|
+
@validate_request = validate_request
|
|
2289
|
+
@validate_response = validate_response
|
|
2290
|
+
@validation_mode = validation_mode
|
|
2291
|
+
@delay = delay
|
|
2292
|
+
@primary = primary
|
|
2293
|
+
end
|
|
2294
|
+
|
|
2295
|
+
def to_h
|
|
2296
|
+
MockServer.strip_none({
|
|
2297
|
+
'specUrlOrPayload' => @spec_url_or_payload,
|
|
2298
|
+
'host' => @host,
|
|
2299
|
+
'port' => @port,
|
|
2300
|
+
'scheme' => @scheme,
|
|
2301
|
+
'validateRequest' => @validate_request,
|
|
2302
|
+
'validateResponse' => @validate_response,
|
|
2303
|
+
'validationMode' => @validation_mode,
|
|
2304
|
+
'delay' => @delay&.to_h,
|
|
2305
|
+
'primary' => @primary
|
|
2306
|
+
})
|
|
2307
|
+
end
|
|
2308
|
+
|
|
2309
|
+
def self.from_hash(data)
|
|
2310
|
+
return nil if data.nil?
|
|
2311
|
+
|
|
2312
|
+
new(
|
|
2313
|
+
spec_url_or_payload: data['specUrlOrPayload'],
|
|
2314
|
+
host: data['host'],
|
|
2315
|
+
port: data['port'],
|
|
2316
|
+
scheme: data['scheme'],
|
|
2317
|
+
validate_request: data['validateRequest'],
|
|
2318
|
+
validate_response: data['validateResponse'],
|
|
2319
|
+
validation_mode: data['validationMode'],
|
|
2320
|
+
delay: Delay.from_hash(data['delay']),
|
|
2321
|
+
primary: data['primary']
|
|
2322
|
+
)
|
|
2323
|
+
end
|
|
2324
|
+
end
|
|
2325
|
+
|
|
2326
|
+
# Forward-with-fallback action (org.mockserver.model.HttpForwardWithFallback):
|
|
2327
|
+
# forwards via +http_forward+, but returns +fallback_response+ when the
|
|
2328
|
+
# upstream responds with one of +fallback_on_status_codes+ or (when
|
|
2329
|
+
# +fallback_on_timeout+ is true) times out.
|
|
2330
|
+
class HttpForwardWithFallback
|
|
2331
|
+
attr_accessor :http_forward, :fallback_response, :fallback_on_status_codes,
|
|
2332
|
+
:fallback_on_timeout, :delay, :primary
|
|
2333
|
+
|
|
2334
|
+
def initialize(http_forward: nil, fallback_response: nil, fallback_on_status_codes: nil,
|
|
2335
|
+
fallback_on_timeout: nil, delay: nil, primary: nil)
|
|
2336
|
+
@http_forward = http_forward
|
|
2337
|
+
@fallback_response = fallback_response
|
|
2338
|
+
@fallback_on_status_codes = fallback_on_status_codes
|
|
2339
|
+
@fallback_on_timeout = fallback_on_timeout
|
|
2340
|
+
@delay = delay
|
|
2341
|
+
@primary = primary
|
|
2342
|
+
end
|
|
2343
|
+
|
|
2344
|
+
def to_h
|
|
2345
|
+
MockServer.strip_none({
|
|
2346
|
+
'httpForward' => @http_forward&.to_h,
|
|
2347
|
+
'fallbackResponse' => @fallback_response&.to_h,
|
|
2348
|
+
'fallbackOnStatusCodes' => @fallback_on_status_codes,
|
|
2349
|
+
'fallbackOnTimeout' => @fallback_on_timeout,
|
|
2350
|
+
'delay' => @delay&.to_h,
|
|
2351
|
+
'primary' => @primary
|
|
2352
|
+
})
|
|
2353
|
+
end
|
|
2354
|
+
|
|
2355
|
+
def self.from_hash(data)
|
|
2356
|
+
return nil if data.nil?
|
|
2357
|
+
|
|
2358
|
+
new(
|
|
2359
|
+
http_forward: HttpForward.from_hash(data['httpForward']),
|
|
2360
|
+
fallback_response: HttpResponse.from_hash(data['fallbackResponse']),
|
|
2361
|
+
fallback_on_status_codes: data['fallbackOnStatusCodes'],
|
|
2362
|
+
fallback_on_timeout: data['fallbackOnTimeout'],
|
|
2363
|
+
delay: Delay.from_hash(data['delay']),
|
|
2364
|
+
primary: data['primary']
|
|
2365
|
+
)
|
|
2366
|
+
end
|
|
2367
|
+
end
|
|
2368
|
+
|
|
1887
2369
|
class Expectation
|
|
1888
2370
|
attr_accessor :id, :priority, :percentage, :http_request, :http_response,
|
|
1889
2371
|
:http_response_template, :http_response_class_callback,
|
|
1890
2372
|
:http_response_object_callback, :http_forward,
|
|
1891
2373
|
:http_forward_template, :http_forward_class_callback,
|
|
1892
2374
|
:http_forward_object_callback, :http_override_forwarded_request,
|
|
1893
|
-
:http_error, :times, :time_to_live, :chaos,
|
|
1894
|
-
:http_sse_response, :http_websocket_response,
|
|
2375
|
+
:http_error, :times, :time_to_live, :chaos, :rate_limit,
|
|
2376
|
+
:http_sse_response, :http_llm_response, :http_websocket_response,
|
|
1895
2377
|
:grpc_stream_response, :grpc_bidi_response,
|
|
1896
2378
|
:binary_response, :dns_response,
|
|
2379
|
+
:http_forward_with_fallback, :http_forward_validate_action,
|
|
1897
2380
|
:before_actions, :after_actions,
|
|
1898
2381
|
:http_responses, :response_mode, :response_weights, :switch_after,
|
|
1899
|
-
:cross_protocol_scenarios, :steps,
|
|
1900
|
-
:scenario_name, :scenario_state, :new_scenario_state
|
|
2382
|
+
:cross_protocol_scenarios, :steps, :capture, :namespace,
|
|
2383
|
+
:scenario_name, :scenario_state, :new_scenario_state, :timestamp
|
|
1901
2384
|
|
|
1902
2385
|
def initialize(id: nil, priority: nil, percentage: nil, http_request: nil, http_response: nil,
|
|
1903
2386
|
http_response_template: nil, http_response_class_callback: nil,
|
|
1904
2387
|
http_response_object_callback: nil, http_forward: nil,
|
|
1905
2388
|
http_forward_template: nil, http_forward_class_callback: nil,
|
|
1906
2389
|
http_forward_object_callback: nil, http_override_forwarded_request: nil,
|
|
1907
|
-
http_error: nil, times: nil, time_to_live: nil, chaos: nil,
|
|
1908
|
-
http_sse_response: nil, http_websocket_response: nil,
|
|
2390
|
+
http_error: nil, times: nil, time_to_live: nil, chaos: nil, rate_limit: nil,
|
|
2391
|
+
http_sse_response: nil, http_llm_response: nil, http_websocket_response: nil,
|
|
1909
2392
|
grpc_stream_response: nil, grpc_bidi_response: nil,
|
|
1910
2393
|
binary_response: nil, dns_response: nil,
|
|
2394
|
+
http_forward_with_fallback: nil, http_forward_validate_action: nil,
|
|
1911
2395
|
before_actions: nil, after_actions: nil,
|
|
1912
2396
|
http_responses: nil, response_mode: nil, response_weights: nil,
|
|
1913
2397
|
switch_after: nil, cross_protocol_scenarios: nil, steps: nil,
|
|
1914
|
-
|
|
2398
|
+
capture: nil, namespace: nil,
|
|
2399
|
+
scenario_name: nil, scenario_state: nil, new_scenario_state: nil,
|
|
2400
|
+
timestamp: nil)
|
|
1915
2401
|
@id = id
|
|
1916
2402
|
@priority = priority
|
|
1917
2403
|
@percentage = percentage
|
|
@@ -1932,12 +2418,16 @@ module MockServer
|
|
|
1932
2418
|
@times = times
|
|
1933
2419
|
@time_to_live = time_to_live
|
|
1934
2420
|
@chaos = chaos
|
|
2421
|
+
@rate_limit = rate_limit
|
|
1935
2422
|
@http_sse_response = http_sse_response
|
|
2423
|
+
@http_llm_response = http_llm_response
|
|
1936
2424
|
@http_websocket_response = http_websocket_response
|
|
1937
2425
|
@grpc_stream_response = grpc_stream_response
|
|
1938
2426
|
@grpc_bidi_response = grpc_bidi_response
|
|
1939
2427
|
@binary_response = binary_response
|
|
1940
2428
|
@dns_response = dns_response
|
|
2429
|
+
@http_forward_with_fallback = http_forward_with_fallback
|
|
2430
|
+
@http_forward_validate_action = http_forward_validate_action
|
|
1941
2431
|
@before_actions = before_actions
|
|
1942
2432
|
@after_actions = after_actions
|
|
1943
2433
|
@http_responses = http_responses
|
|
@@ -1946,9 +2436,12 @@ module MockServer
|
|
|
1946
2436
|
@switch_after = switch_after
|
|
1947
2437
|
@cross_protocol_scenarios = cross_protocol_scenarios
|
|
1948
2438
|
@steps = steps
|
|
2439
|
+
@capture = capture
|
|
2440
|
+
@namespace = namespace
|
|
1949
2441
|
@scenario_name = scenario_name
|
|
1950
2442
|
@scenario_state = scenario_state
|
|
1951
2443
|
@new_scenario_state = new_scenario_state
|
|
2444
|
+
@timestamp = timestamp
|
|
1952
2445
|
end
|
|
1953
2446
|
|
|
1954
2447
|
# Set the response class-callback action. Accepts either a fully-qualified
|
|
@@ -1996,8 +2489,11 @@ module MockServer
|
|
|
1996
2489
|
'httpForwardClassCallback' => @http_forward_class_callback&.to_h,
|
|
1997
2490
|
'httpForwardObjectCallback' => @http_forward_object_callback&.to_h,
|
|
1998
2491
|
'httpOverrideForwardedRequest' => @http_override_forwarded_request&.to_h,
|
|
2492
|
+
'httpForwardValidateAction' => @http_forward_validate_action&.to_h,
|
|
2493
|
+
'httpForwardWithFallback' => @http_forward_with_fallback&.to_h,
|
|
1999
2494
|
'httpError' => @http_error&.to_h,
|
|
2000
2495
|
'httpSseResponse' => @http_sse_response&.to_h,
|
|
2496
|
+
'httpLlmResponse' => MockServer.serialize_value(@http_llm_response),
|
|
2001
2497
|
'httpWebSocketResponse' => @http_websocket_response&.to_h,
|
|
2002
2498
|
'grpcStreamResponse' => @grpc_stream_response&.to_h,
|
|
2003
2499
|
'grpcBidiResponse' => @grpc_bidi_response&.to_h,
|
|
@@ -2011,12 +2507,16 @@ module MockServer
|
|
|
2011
2507
|
'switchAfter' => @switch_after,
|
|
2012
2508
|
'crossProtocolScenarios' => @cross_protocol_scenarios&.map(&:to_h),
|
|
2013
2509
|
'steps' => @steps&.map(&:to_h),
|
|
2510
|
+
'capture' => @capture&.map(&:to_h),
|
|
2511
|
+
'rateLimit' => @rate_limit&.to_h,
|
|
2512
|
+
'namespace' => @namespace,
|
|
2014
2513
|
'times' => @times&.to_h,
|
|
2015
2514
|
'timeToLive' => @time_to_live&.to_h,
|
|
2016
2515
|
'chaos' => @chaos&.to_h,
|
|
2017
2516
|
'scenarioName' => @scenario_name,
|
|
2018
2517
|
'scenarioState' => @scenario_state,
|
|
2019
|
-
'newScenarioState' => @new_scenario_state
|
|
2518
|
+
'newScenarioState' => @new_scenario_state,
|
|
2519
|
+
'timestamp' => @timestamp
|
|
2020
2520
|
})
|
|
2021
2521
|
end
|
|
2022
2522
|
|
|
@@ -2051,8 +2551,11 @@ module MockServer
|
|
|
2051
2551
|
http_forward_class_callback: HttpClassCallback.from_hash(data['httpForwardClassCallback']),
|
|
2052
2552
|
http_forward_object_callback: HttpObjectCallback.from_hash(data['httpForwardObjectCallback']),
|
|
2053
2553
|
http_override_forwarded_request: HttpOverrideForwardedRequest.from_hash(data['httpOverrideForwardedRequest']),
|
|
2554
|
+
http_forward_validate_action: HttpForwardValidateAction.from_hash(data['httpForwardValidateAction']),
|
|
2555
|
+
http_forward_with_fallback: HttpForwardWithFallback.from_hash(data['httpForwardWithFallback']),
|
|
2054
2556
|
http_error: HttpError.from_hash(data['httpError']),
|
|
2055
2557
|
http_sse_response: HttpSseResponse.from_hash(data['httpSseResponse']),
|
|
2558
|
+
http_llm_response: data['httpLlmResponse'],
|
|
2056
2559
|
http_websocket_response: HttpWebSocketResponse.from_hash(data['httpWebSocketResponse']),
|
|
2057
2560
|
grpc_stream_response: GrpcStreamResponse.from_hash(data['grpcStreamResponse']),
|
|
2058
2561
|
grpc_bidi_response: GrpcBidiResponse.from_hash(data['grpcBidiResponse']),
|
|
@@ -2066,12 +2569,16 @@ module MockServer
|
|
|
2066
2569
|
switch_after: data['switchAfter'],
|
|
2067
2570
|
cross_protocol_scenarios: data['crossProtocolScenarios']&.map { |c| CrossProtocolScenario.from_hash(c) },
|
|
2068
2571
|
steps: data['steps']&.map { |s| ExpectationStep.from_hash(s) },
|
|
2572
|
+
capture: data['capture']&.map { |c| CaptureRule.from_hash(c) },
|
|
2573
|
+
rate_limit: RateLimit.from_hash(data['rateLimit']),
|
|
2574
|
+
namespace: data['namespace'],
|
|
2069
2575
|
times: Times.from_hash(data['times']),
|
|
2070
2576
|
time_to_live: TimeToLive.from_hash(data['timeToLive']),
|
|
2071
2577
|
chaos: HttpChaosProfile.from_hash(data['chaos']),
|
|
2072
2578
|
scenario_name: data['scenarioName'],
|
|
2073
2579
|
scenario_state: data['scenarioState'],
|
|
2074
|
-
new_scenario_state: data['newScenarioState']
|
|
2580
|
+
new_scenario_state: data['newScenarioState'],
|
|
2581
|
+
timestamp: data['timestamp']
|
|
2075
2582
|
)
|
|
2076
2583
|
end
|
|
2077
2584
|
end
|
data/lib/mockserver/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mockserver-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Bloom
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|