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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa28c69e849b63f6fa75a1f57f939a348cf30d3032d6b1dfb040276dbd51eb6b
4
- data.tar.gz: 188c6469fc18b0ed51b4099a652d68a3b00b0bbccda233265b282e9d08e5c677
3
+ metadata.gz: 9eedfe80337a01b9026c0a59a4f95e98218ccfacf6344b4b7b435c1226d9e723
4
+ data.tar.gz: 06a19eaf8d0a7d435c5020c47ea7c2c764fc19d0b609b302e4116355c11df49b
5
5
  SHA512:
6
- metadata.gz: c4d3d24b31236d09f8684b10bc114e6cb2154f532ff230453f9e3f566ecbd271ef4c770a8d3f6db6a2476bd89efdd529471ba4fb46ca2ebe0e89e8a97f2fc63e
7
- data.tar.gz: daef70802e8ee57033d185f74c580c3b61d87eb97fc0c74ab31288f345e9fe86c9823b6ff6f90231d2066c8b9f05a6446d5ba06a7d917bdb61dd1215ef2094ae
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', '~> 5.16'
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 25 domain model classes are available under the `MockServer` module:
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.3.0')
356
+ handle = MockServer::BinaryLauncher.start(port: 1080, version: '7.4.0')
320
357
  ```
321
358
 
322
359
  ### API reference
@@ -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
  # -------------------------------------------------------------------
@@ -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
- attr_accessor :type, :string, :json, :base64_bytes, :not_body, :content_type, :charset,
424
- :file_path, :template_type
425
-
426
- def initialize(type: nil, string: nil, json: nil, base64_bytes: nil, not_body: nil,
427
- content_type: nil, charset: nil, file_path: nil, template_type: nil)
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', string: value)
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
- attr_accessor :query, :operation_name, :variables_schema, :not_body, :optional
539
-
540
- def initialize(query:, operation_name: nil, variables_schema: nil, not_body: false, optional: false)
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: data['query'] || '',
562
- operation_name: data['operationName'],
563
- variables_schema: data['variablesSchema'],
564
- not_body: data.fetch('not', false),
565
- optional: data.fetch('optional', false)
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.serialize_key_multi_values(@path_parameters),
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
- attr_accessor :subprotocol, :messages, :close_connection, :delay, :primary
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
- attr_accessor :json, :delay
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: data['json'],
1281
- delay: Delay.from_hash(data['delay'])
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
- scenario_name: nil, scenario_state: nil, new_scenario_state: nil)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MockServer
4
- VERSION = '7.3.0'
4
+ VERSION = '7.4.0'
5
5
  end
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.3.0
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-01 00:00:00.000000000 Z
11
+ date: 2026-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger