mockserver-client 7.2.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 +100 -3
- data/lib/mockserver/a2a.rb +529 -0
- data/lib/mockserver/client.rb +537 -0
- data/lib/mockserver/models.rb +821 -60
- data/lib/mockserver/version.rb +1 -1
- data/lib/mockserver-client.rb +1 -0
- metadata +3 -2
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
|
|
@@ -2368,39 +2875,262 @@ module MockServer
|
|
|
2368
2875
|
end
|
|
2369
2876
|
end
|
|
2370
2877
|
|
|
2371
|
-
#
|
|
2372
|
-
#
|
|
2373
|
-
#
|
|
2374
|
-
# the
|
|
2878
|
+
# A declarative named load shape that expands into ordinary {LoadStage} stages.
|
|
2879
|
+
# Set this as a {LoadProfile}'s +shape+ instead of an explicit +stages+ list;
|
|
2880
|
+
# only the parameters its +type+ needs are read. +type+ is one of +SPIKE+,
|
|
2881
|
+
# +STAIRS+ or +RAMP_HOLD+; +metric+ (+VU+ or +RATE+) selects what the shape
|
|
2882
|
+
# drives.
|
|
2883
|
+
class LoadShape
|
|
2884
|
+
attr_accessor :type, :metric, :curve, :baseline, :peak, :ramp_up_millis,
|
|
2885
|
+
:hold_millis, :ramp_down_millis, :recovery_hold_millis,
|
|
2886
|
+
:start, :step, :steps, :step_duration_millis, :target, :ramp_millis
|
|
2887
|
+
|
|
2888
|
+
def initialize(type:, metric: nil, curve: nil, baseline: nil, peak: nil,
|
|
2889
|
+
ramp_up_millis: nil, hold_millis: nil, ramp_down_millis: nil,
|
|
2890
|
+
recovery_hold_millis: nil, start: nil, step: nil, steps: nil,
|
|
2891
|
+
step_duration_millis: nil, target: nil, ramp_millis: nil)
|
|
2892
|
+
@type = type
|
|
2893
|
+
@metric = metric
|
|
2894
|
+
@curve = curve
|
|
2895
|
+
@baseline = baseline
|
|
2896
|
+
@peak = peak
|
|
2897
|
+
@ramp_up_millis = ramp_up_millis
|
|
2898
|
+
@hold_millis = hold_millis
|
|
2899
|
+
@ramp_down_millis = ramp_down_millis
|
|
2900
|
+
@recovery_hold_millis = recovery_hold_millis
|
|
2901
|
+
@start = start
|
|
2902
|
+
@step = step
|
|
2903
|
+
@steps = steps
|
|
2904
|
+
@step_duration_millis = step_duration_millis
|
|
2905
|
+
@target = target
|
|
2906
|
+
@ramp_millis = ramp_millis
|
|
2907
|
+
end
|
|
2908
|
+
|
|
2909
|
+
def to_h
|
|
2910
|
+
MockServer.strip_none({
|
|
2911
|
+
'type' => @type,
|
|
2912
|
+
'metric' => @metric,
|
|
2913
|
+
'curve' => @curve,
|
|
2914
|
+
'baseline' => @baseline,
|
|
2915
|
+
'peak' => @peak,
|
|
2916
|
+
'rampUpMillis' => @ramp_up_millis,
|
|
2917
|
+
'holdMillis' => @hold_millis,
|
|
2918
|
+
'rampDownMillis' => @ramp_down_millis,
|
|
2919
|
+
'recoveryHoldMillis' => @recovery_hold_millis,
|
|
2920
|
+
'start' => @start,
|
|
2921
|
+
'step' => @step,
|
|
2922
|
+
'steps' => @steps,
|
|
2923
|
+
'stepDurationMillis' => @step_duration_millis,
|
|
2924
|
+
'target' => @target,
|
|
2925
|
+
'rampMillis' => @ramp_millis
|
|
2926
|
+
})
|
|
2927
|
+
end
|
|
2928
|
+
|
|
2929
|
+
def self.from_hash(data)
|
|
2930
|
+
return nil if data.nil?
|
|
2931
|
+
|
|
2932
|
+
new(
|
|
2933
|
+
type: data['type'],
|
|
2934
|
+
metric: data['metric'],
|
|
2935
|
+
curve: data['curve'],
|
|
2936
|
+
baseline: data['baseline'],
|
|
2937
|
+
peak: data['peak'],
|
|
2938
|
+
ramp_up_millis: data['rampUpMillis'],
|
|
2939
|
+
hold_millis: data['holdMillis'],
|
|
2940
|
+
ramp_down_millis: data['rampDownMillis'],
|
|
2941
|
+
recovery_hold_millis: data['recoveryHoldMillis'],
|
|
2942
|
+
start: data['start'],
|
|
2943
|
+
step: data['step'],
|
|
2944
|
+
steps: data['steps'],
|
|
2945
|
+
step_duration_millis: data['stepDurationMillis'],
|
|
2946
|
+
target: data['target'],
|
|
2947
|
+
ramp_millis: data['rampMillis']
|
|
2948
|
+
)
|
|
2949
|
+
end
|
|
2950
|
+
end
|
|
2951
|
+
|
|
2952
|
+
# The traffic-shaping profile of a load scenario: EITHER an ordered list of
|
|
2953
|
+
# {LoadStage} objects run in sequence, each holding or ramping a setpoint
|
|
2954
|
+
# (virtual users, an arrival rate, or a pause) for its duration, OR a single
|
|
2955
|
+
# named {LoadShape} that expands into stages. Set one, not both; if both are
|
|
2956
|
+
# set the explicit stages win. The total run length is the sum of the stage
|
|
2957
|
+
# durations.
|
|
2375
2958
|
class LoadProfile
|
|
2376
|
-
attr_accessor :stages
|
|
2959
|
+
attr_accessor :stages, :shape
|
|
2377
2960
|
|
|
2378
|
-
def initialize(stages:
|
|
2379
|
-
@stages = stages
|
|
2961
|
+
def initialize(stages: nil, shape: nil)
|
|
2962
|
+
@stages = stages
|
|
2963
|
+
@shape = shape
|
|
2380
2964
|
end
|
|
2381
2965
|
|
|
2382
2966
|
def to_h
|
|
2383
|
-
|
|
2967
|
+
MockServer.strip_none({
|
|
2968
|
+
'stages' => @stages.nil? ? nil : @stages.map(&:to_h),
|
|
2969
|
+
'shape' => @shape&.to_h
|
|
2970
|
+
})
|
|
2384
2971
|
end
|
|
2385
2972
|
|
|
2386
2973
|
def self.from_hash(data)
|
|
2387
2974
|
return nil if data.nil?
|
|
2388
2975
|
|
|
2389
|
-
stages_data = data['stages']
|
|
2390
|
-
new(
|
|
2976
|
+
stages_data = data['stages']
|
|
2977
|
+
new(
|
|
2978
|
+
stages: stages_data ? stages_data.map { |s| LoadStage.from_hash(s) } : nil,
|
|
2979
|
+
shape: LoadShape.from_hash(data['shape'])
|
|
2980
|
+
)
|
|
2981
|
+
end
|
|
2982
|
+
end
|
|
2983
|
+
|
|
2984
|
+
# A declarative cross-step capture / correlation rule: extracts a value from a
|
|
2985
|
+
# step's response and binds it to +name+, which a later step in the same
|
|
2986
|
+
# iteration can reference from its templated request fields. +source+ is one of
|
|
2987
|
+
# +BODY_JSONPATH+, +HEADER+ or +BODY_REGEX+; +expression+ drives the extraction;
|
|
2988
|
+
# +default_value+ is an optional fallback when extraction yields nothing.
|
|
2989
|
+
class LoadCapture
|
|
2990
|
+
attr_accessor :name, :source, :expression, :default_value
|
|
2991
|
+
|
|
2992
|
+
def initialize(name:, source:, expression:, default_value: nil)
|
|
2993
|
+
@name = name
|
|
2994
|
+
@source = source
|
|
2995
|
+
@expression = expression
|
|
2996
|
+
@default_value = default_value
|
|
2997
|
+
end
|
|
2998
|
+
|
|
2999
|
+
def to_h
|
|
3000
|
+
MockServer.strip_none({
|
|
3001
|
+
'name' => @name,
|
|
3002
|
+
'source' => @source,
|
|
3003
|
+
'expression' => @expression,
|
|
3004
|
+
'defaultValue' => @default_value
|
|
3005
|
+
})
|
|
3006
|
+
end
|
|
3007
|
+
|
|
3008
|
+
def self.from_hash(data)
|
|
3009
|
+
return nil if data.nil?
|
|
3010
|
+
|
|
3011
|
+
new(
|
|
3012
|
+
name: data['name'],
|
|
3013
|
+
source: data['source'],
|
|
3014
|
+
expression: data['expression'],
|
|
3015
|
+
default_value: data['defaultValue']
|
|
3016
|
+
)
|
|
3017
|
+
end
|
|
3018
|
+
end
|
|
3019
|
+
|
|
3020
|
+
# An in-run pass/fail threshold for a load scenario: a per-run +metric+ compared
|
|
3021
|
+
# with +comparator+ against +threshold+. All thresholds must hold for the run
|
|
3022
|
+
# verdict to be +PASS+; any breach makes it +FAIL+. +metric+ is one of
|
|
3023
|
+
# +LATENCY_P50+/+LATENCY_P95+/+LATENCY_P99+/+LATENCY_P999+/+ERROR_RATE+/
|
|
3024
|
+
# +THROUGHPUT_RPS+; +comparator+ is one of +LESS_THAN+/+LESS_THAN_OR_EQUAL+/
|
|
3025
|
+
# +GREATER_THAN+/+GREATER_THAN_OR_EQUAL+.
|
|
3026
|
+
class LoadThreshold
|
|
3027
|
+
attr_accessor :metric, :comparator, :threshold
|
|
3028
|
+
|
|
3029
|
+
def initialize(metric:, comparator:, threshold:)
|
|
3030
|
+
@metric = metric
|
|
3031
|
+
@comparator = comparator
|
|
3032
|
+
@threshold = threshold
|
|
3033
|
+
end
|
|
3034
|
+
|
|
3035
|
+
def to_h
|
|
3036
|
+
MockServer.strip_none({
|
|
3037
|
+
'metric' => @metric,
|
|
3038
|
+
'comparator' => @comparator,
|
|
3039
|
+
'threshold' => @threshold
|
|
3040
|
+
})
|
|
3041
|
+
end
|
|
3042
|
+
|
|
3043
|
+
def self.from_hash(data)
|
|
3044
|
+
return nil if data.nil?
|
|
3045
|
+
|
|
3046
|
+
new(
|
|
3047
|
+
metric: data['metric'],
|
|
3048
|
+
comparator: data['comparator'],
|
|
3049
|
+
threshold: data['threshold']
|
|
3050
|
+
)
|
|
3051
|
+
end
|
|
3052
|
+
end
|
|
3053
|
+
|
|
3054
|
+
# Adaptive iteration pacing (think-time) for a load scenario: a target
|
|
3055
|
+
# per-virtual-user iteration cycle time. +mode+ is one of +NONE+,
|
|
3056
|
+
# +CONSTANT_PACING+ (+value+ is the target cycle in milliseconds) or
|
|
3057
|
+
# +CONSTANT_THROUGHPUT+ (+value+ is the target iterations/second per VU).
|
|
3058
|
+
class LoadPacing
|
|
3059
|
+
attr_accessor :mode, :value
|
|
3060
|
+
|
|
3061
|
+
def initialize(mode:, value: nil)
|
|
3062
|
+
@mode = mode
|
|
3063
|
+
@value = value
|
|
3064
|
+
end
|
|
3065
|
+
|
|
3066
|
+
def to_h
|
|
3067
|
+
MockServer.strip_none({
|
|
3068
|
+
'mode' => @mode,
|
|
3069
|
+
'value' => @value
|
|
3070
|
+
})
|
|
3071
|
+
end
|
|
3072
|
+
|
|
3073
|
+
def self.from_hash(data)
|
|
3074
|
+
return nil if data.nil?
|
|
3075
|
+
|
|
3076
|
+
new(
|
|
3077
|
+
mode: data['mode'],
|
|
3078
|
+
value: data['value']
|
|
3079
|
+
)
|
|
3080
|
+
end
|
|
3081
|
+
end
|
|
3082
|
+
|
|
3083
|
+
# Parameterized test data (a data feeder) for a load scenario: an inline dataset
|
|
3084
|
+
# from which one row is selected per iteration and exposed to the templated
|
|
3085
|
+
# request. Supply EITHER +rows+ (inline list of column-name to value maps, the
|
|
3086
|
+
# primary form) OR +data+ + +format+ (raw CSV/JSON parsed server-side).
|
|
3087
|
+
# +strategy+ (+CIRCULAR+, +RANDOM+ or +SEQUENTIAL+) chooses how a row is picked.
|
|
3088
|
+
class LoadFeeder
|
|
3089
|
+
attr_accessor :rows, :data, :format, :strategy
|
|
3090
|
+
|
|
3091
|
+
def initialize(rows: nil, data: nil, format: nil, strategy: nil)
|
|
3092
|
+
@rows = rows
|
|
3093
|
+
@data = data
|
|
3094
|
+
@format = format
|
|
3095
|
+
@strategy = strategy
|
|
3096
|
+
end
|
|
3097
|
+
|
|
3098
|
+
def to_h
|
|
3099
|
+
MockServer.strip_none({
|
|
3100
|
+
'rows' => @rows,
|
|
3101
|
+
'data' => @data,
|
|
3102
|
+
'format' => @format,
|
|
3103
|
+
'strategy' => @strategy
|
|
3104
|
+
})
|
|
3105
|
+
end
|
|
3106
|
+
|
|
3107
|
+
def self.from_hash(data)
|
|
3108
|
+
return nil if data.nil?
|
|
3109
|
+
|
|
3110
|
+
new(
|
|
3111
|
+
rows: data['rows'],
|
|
3112
|
+
data: data['data'],
|
|
3113
|
+
format: data['format'],
|
|
3114
|
+
strategy: data['strategy']
|
|
3115
|
+
)
|
|
2391
3116
|
end
|
|
2392
3117
|
end
|
|
2393
3118
|
|
|
2394
3119
|
# A single step within a load scenario. Each step fires +request+ (an HttpRequest)
|
|
2395
3120
|
# against the target, optionally pausing for +think_time+ (a Delay) afterwards.
|
|
3121
|
+
# +captures+ binds values from this step's response for later steps in the same
|
|
3122
|
+
# iteration; +weight+ is the relative selection weight when the scenario's
|
|
3123
|
+
# +step_selection+ is +WEIGHTED+.
|
|
2396
3124
|
class LoadStep
|
|
2397
|
-
attr_accessor :name, :labels, :think_time, :request
|
|
3125
|
+
attr_accessor :name, :labels, :think_time, :request, :captures, :weight
|
|
2398
3126
|
|
|
2399
|
-
def initialize(request:, name: nil, labels: nil, think_time: nil)
|
|
3127
|
+
def initialize(request:, name: nil, labels: nil, think_time: nil, captures: nil, weight: nil)
|
|
2400
3128
|
@request = request
|
|
2401
3129
|
@name = name
|
|
2402
3130
|
@labels = labels
|
|
2403
3131
|
@think_time = think_time
|
|
3132
|
+
@captures = captures
|
|
3133
|
+
@weight = weight
|
|
2404
3134
|
end
|
|
2405
3135
|
|
|
2406
3136
|
def to_h
|
|
@@ -2408,18 +3138,23 @@ module MockServer
|
|
|
2408
3138
|
'name' => @name,
|
|
2409
3139
|
'labels' => @labels,
|
|
2410
3140
|
'thinkTime' => @think_time&.to_h,
|
|
2411
|
-
'request' => @request&.to_h
|
|
3141
|
+
'request' => @request&.to_h,
|
|
3142
|
+
'captures' => @captures.nil? ? nil : @captures.map(&:to_h),
|
|
3143
|
+
'weight' => @weight
|
|
2412
3144
|
})
|
|
2413
3145
|
end
|
|
2414
3146
|
|
|
2415
3147
|
def self.from_hash(data)
|
|
2416
3148
|
return nil if data.nil?
|
|
2417
3149
|
|
|
3150
|
+
captures_data = data['captures']
|
|
2418
3151
|
new(
|
|
2419
3152
|
name: data['name'],
|
|
2420
3153
|
labels: data['labels'],
|
|
2421
3154
|
think_time: Delay.from_hash(data['thinkTime']),
|
|
2422
|
-
request: HttpRequest.from_hash(data['request'])
|
|
3155
|
+
request: HttpRequest.from_hash(data['request']),
|
|
3156
|
+
captures: captures_data ? captures_data.map { |c| LoadCapture.from_hash(c) } : nil,
|
|
3157
|
+
weight: data['weight']
|
|
2423
3158
|
)
|
|
2424
3159
|
end
|
|
2425
3160
|
end
|
|
@@ -2427,11 +3162,18 @@ module MockServer
|
|
|
2427
3162
|
# A load-injection scenario: a named set of +steps+ driven by a traffic +profile+.
|
|
2428
3163
|
# +template_type+ selects the templating engine (+VELOCITY+ or +MUSTACHE+) used to
|
|
2429
3164
|
# render step requests; +max_requests+ caps the total requests issued.
|
|
3165
|
+
# +thresholds+ are in-run pass/fail checks; +abort_on_fail+/+abort_grace_millis+
|
|
3166
|
+
# control early-abort behaviour; +pacing+ shapes inter-iteration timing; +feeder+
|
|
3167
|
+
# supplies per-iteration data; +step_selection+ (+SEQUENTIAL+ or +WEIGHTED+)
|
|
3168
|
+
# controls how each iteration selects steps.
|
|
2430
3169
|
class LoadScenario
|
|
2431
|
-
attr_accessor :name, :template_type, :labels, :max_requests, :start_delay_millis,
|
|
3170
|
+
attr_accessor :name, :template_type, :labels, :max_requests, :start_delay_millis,
|
|
3171
|
+
:profile, :steps, :thresholds, :abort_on_fail, :abort_grace_millis,
|
|
3172
|
+
:pacing, :feeder, :step_selection
|
|
2432
3173
|
|
|
2433
3174
|
def initialize(name:, profile:, steps:, template_type: nil, labels: nil, max_requests: nil,
|
|
2434
|
-
start_delay_millis: nil
|
|
3175
|
+
start_delay_millis: nil, thresholds: nil, abort_on_fail: nil,
|
|
3176
|
+
abort_grace_millis: nil, pacing: nil, feeder: nil, step_selection: nil)
|
|
2435
3177
|
@name = name
|
|
2436
3178
|
@profile = profile
|
|
2437
3179
|
@steps = steps
|
|
@@ -2439,6 +3181,12 @@ module MockServer
|
|
|
2439
3181
|
@labels = labels
|
|
2440
3182
|
@max_requests = max_requests
|
|
2441
3183
|
@start_delay_millis = start_delay_millis
|
|
3184
|
+
@thresholds = thresholds
|
|
3185
|
+
@abort_on_fail = abort_on_fail
|
|
3186
|
+
@abort_grace_millis = abort_grace_millis
|
|
3187
|
+
@pacing = pacing
|
|
3188
|
+
@feeder = feeder
|
|
3189
|
+
@step_selection = step_selection
|
|
2442
3190
|
end
|
|
2443
3191
|
|
|
2444
3192
|
def to_h
|
|
@@ -2448,6 +3196,12 @@ module MockServer
|
|
|
2448
3196
|
'labels' => @labels,
|
|
2449
3197
|
'maxRequests' => @max_requests,
|
|
2450
3198
|
'startDelayMillis' => @start_delay_millis,
|
|
3199
|
+
'thresholds' => @thresholds.nil? ? nil : @thresholds.map(&:to_h),
|
|
3200
|
+
'abortOnFail' => @abort_on_fail,
|
|
3201
|
+
'abortGraceMillis' => @abort_grace_millis,
|
|
3202
|
+
'pacing' => @pacing&.to_h,
|
|
3203
|
+
'feeder' => @feeder&.to_h,
|
|
3204
|
+
'stepSelection' => @step_selection,
|
|
2451
3205
|
'profile' => @profile&.to_h,
|
|
2452
3206
|
'steps' => @steps&.map(&:to_h)
|
|
2453
3207
|
})
|
|
@@ -2457,12 +3211,19 @@ module MockServer
|
|
|
2457
3211
|
return nil if data.nil?
|
|
2458
3212
|
|
|
2459
3213
|
steps_data = data['steps']
|
|
3214
|
+
thresholds_data = data['thresholds']
|
|
2460
3215
|
new(
|
|
2461
3216
|
name: data['name'],
|
|
2462
3217
|
template_type: data['templateType'],
|
|
2463
3218
|
labels: data['labels'],
|
|
2464
3219
|
max_requests: data['maxRequests'],
|
|
2465
3220
|
start_delay_millis: data['startDelayMillis'],
|
|
3221
|
+
thresholds: thresholds_data ? thresholds_data.map { |t| LoadThreshold.from_hash(t) } : nil,
|
|
3222
|
+
abort_on_fail: data['abortOnFail'],
|
|
3223
|
+
abort_grace_millis: data['abortGraceMillis'],
|
|
3224
|
+
pacing: LoadPacing.from_hash(data['pacing']),
|
|
3225
|
+
feeder: LoadFeeder.from_hash(data['feeder']),
|
|
3226
|
+
step_selection: data['stepSelection'],
|
|
2466
3227
|
profile: LoadProfile.from_hash(data['profile']),
|
|
2467
3228
|
steps: steps_data ? steps_data.map { |s| LoadStep.from_hash(s) } : nil
|
|
2468
3229
|
)
|