mockserver-client 1.0.8.pre → 6.0.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.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +2 -1
  3. data/README.md +79 -227
  4. data/lib/mockserver/client.rb +518 -0
  5. data/lib/mockserver/errors.rb +18 -0
  6. data/lib/mockserver/forward_chain_expectation.rb +117 -0
  7. data/lib/mockserver/models.rb +1507 -0
  8. data/lib/mockserver/version.rb +3 -3
  9. data/lib/mockserver/websocket_client.rb +353 -0
  10. data/lib/mockserver-client.rb +7 -16
  11. data/mockserver-client.gemspec +26 -27
  12. metadata +54 -206
  13. data/.gitignore +0 -21
  14. data/.rubocop.yml +0 -7
  15. data/Rakefile +0 -10
  16. data/bin/mockserver +0 -9
  17. data/lib/cli.rb +0 -146
  18. data/lib/mockserver/abstract_client.rb +0 -111
  19. data/lib/mockserver/mock_server_client.rb +0 -46
  20. data/lib/mockserver/model/array_of.rb +0 -85
  21. data/lib/mockserver/model/body.rb +0 -56
  22. data/lib/mockserver/model/cookie.rb +0 -36
  23. data/lib/mockserver/model/delay.rb +0 -34
  24. data/lib/mockserver/model/enum.rb +0 -47
  25. data/lib/mockserver/model/expectation.rb +0 -139
  26. data/lib/mockserver/model/forward.rb +0 -41
  27. data/lib/mockserver/model/header.rb +0 -43
  28. data/lib/mockserver/model/parameter.rb +0 -43
  29. data/lib/mockserver/model/request.rb +0 -81
  30. data/lib/mockserver/model/response.rb +0 -45
  31. data/lib/mockserver/model/times.rb +0 -61
  32. data/lib/mockserver/proxy_client.rb +0 -9
  33. data/lib/mockserver/utility_methods.rb +0 -59
  34. data/pom.xml +0 -118
  35. data/spec/fixtures/forward_mockserver.json +0 -7
  36. data/spec/fixtures/incorrect_login_response.json +0 -20
  37. data/spec/fixtures/post_login_request.json +0 -22
  38. data/spec/fixtures/register_expectation.json +0 -50
  39. data/spec/fixtures/retrieved_request.json +0 -22
  40. data/spec/fixtures/search_request.json +0 -6
  41. data/spec/fixtures/times_once.json +0 -6
  42. data/spec/integration/mock_client_integration_spec.rb +0 -82
  43. data/spec/mockserver/builder_spec.rb +0 -90
  44. data/spec/mockserver/mock_client_spec.rb +0 -80
  45. data/spec/mockserver/proxy_client_spec.rb +0 -38
  46. data/spec/spec_helper.rb +0 -61
@@ -0,0 +1,1507 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'set'
6
+
7
+ module MockServer
8
+ # Explicit mapping from Ruby snake_case field names to the camelCase
9
+ # keys expected by the MockServer JSON protocol.
10
+ FIELD_MAP = {
11
+ 'status_code' => 'statusCode',
12
+ 'reason_phrase' => 'reasonPhrase',
13
+ 'keep_alive' => 'keepAlive',
14
+ 'respond_before_body' => 'respondBeforeBody',
15
+ 'query_string_parameters' => 'queryStringParameters',
16
+ 'path_parameters' => 'pathParameters',
17
+ 'socket_address' => 'socketAddress',
18
+ 'time_unit' => 'timeUnit',
19
+ 'time_to_live' => 'timeToLive',
20
+ 'remaining_times' => 'remainingTimes',
21
+ 'close_socket' => 'closeSocket',
22
+ 'close_socket_delay' => 'closeSocketDelay',
23
+ 'suppress_content_length_header' => 'suppressContentLengthHeader',
24
+ 'content_length_header_override' => 'contentLengthHeaderOverride',
25
+ 'suppress_connection_header' => 'suppressConnectionHeader',
26
+ 'keep_alive_override' => 'keepAliveOverride',
27
+ 'connection_options' => 'connectionOptions',
28
+ 'callback_class' => 'callbackClass',
29
+ 'client_id' => 'clientId',
30
+ 'response_callback' => 'responseCallback',
31
+ 'drop_connection' => 'dropConnection',
32
+ 'response_bytes' => 'responseBytes',
33
+ 'http_request' => 'httpRequest',
34
+ 'http_response' => 'httpResponse',
35
+ 'http_response_template' => 'httpResponseTemplate',
36
+ 'http_response_class_callback' => 'httpResponseClassCallback',
37
+ 'http_response_object_callback' => 'httpResponseObjectCallback',
38
+ 'http_forward' => 'httpForward',
39
+ 'http_forward_template' => 'httpForwardTemplate',
40
+ 'http_forward_class_callback' => 'httpForwardClassCallback',
41
+ 'http_forward_object_callback' => 'httpForwardObjectCallback',
42
+ 'http_override_forwarded_request' => 'httpOverrideForwardedRequest',
43
+ 'http_error' => 'httpError',
44
+ 'http_sse_response' => 'httpSseResponse',
45
+ 'http_websocket_response' => 'httpWebSocketResponse',
46
+ 'template_type' => 'templateType',
47
+ 'base64_bytes' => 'base64Bytes',
48
+ 'not_body' => 'not',
49
+ 'content_type' => 'contentType',
50
+ 'at_least' => 'atLeast',
51
+ 'at_most' => 'atMost',
52
+ 'expectation_id' => 'expectationId',
53
+ 'expectation_ids' => 'expectationIds',
54
+ 'http_requests' => 'httpRequests',
55
+ 'spec_url_or_payload' => 'specUrlOrPayload',
56
+ 'operations_and_responses' => 'operationsAndResponses',
57
+ 'operation_id' => 'operationId',
58
+ 'request_modifier' => 'requestModifier',
59
+ 'response_modifier' => 'responseModifier',
60
+ 'maximum_number_of_request_to_return_in_verification_failure' => 'maximumNumberOfRequestToReturnInVerificationFailure',
61
+ 'base_path' => 'basePath',
62
+ 'id_field' => 'idField',
63
+ 'id_strategy' => 'idStrategy',
64
+ 'initial_data' => 'initialData'
65
+ }.freeze
66
+
67
+ REVERSE_FIELD_MAP = FIELD_MAP.invert.freeze
68
+
69
+ # Known Body type strings used to distinguish Body objects from plain hashes
70
+ # during deserialization.
71
+ BODY_TYPES = Set.new(%w[
72
+ STRING JSON REGEX XML BINARY JSON_SCHEMA JSON_PATH XPATH XML_SCHEMA JSON_RPC GRAPHQL
73
+ ]).freeze
74
+
75
+ # -------------------------------------------------------------------
76
+ # Helper functions
77
+ # -------------------------------------------------------------------
78
+
79
+ # @api private
80
+ def self.to_camel(snake_str)
81
+ return FIELD_MAP[snake_str] if FIELD_MAP.key?(snake_str)
82
+
83
+ parts = snake_str.split('_')
84
+ parts[0] + parts[1..].map(&:capitalize).join
85
+ end
86
+
87
+ # @api private
88
+ def self.from_camel(camel_str)
89
+ return REVERSE_FIELD_MAP[camel_str] if REVERSE_FIELD_MAP.key?(camel_str)
90
+
91
+ camel_str.gsub(/([A-Z])/) { "_#{$1.downcase}" }
92
+ end
93
+
94
+ # @api private
95
+ def self.strip_none(hash)
96
+ hash.reject { |_k, v| v.nil? }
97
+ end
98
+
99
+ # @api private
100
+ def self.serialize_value(value)
101
+ case value
102
+ when ->(v) { v.respond_to?(:to_h) && v.class.ancestors.any? { |a| a.to_s.start_with?('MockServer::') } }
103
+ value.to_h
104
+ when Array
105
+ value.map { |item| serialize_value(item) }
106
+ else
107
+ value
108
+ end
109
+ end
110
+
111
+ # @api private
112
+ def self.serialize_body(body)
113
+ return nil if body.nil?
114
+ return body if body.is_a?(String)
115
+ return body if body.is_a?(Hash)
116
+ return body.to_h if body.is_a?(Body)
117
+ return body.to_h if body.is_a?(JsonRpcBody)
118
+ return body.to_h if body.is_a?(GraphQLBody)
119
+
120
+ body
121
+ end
122
+
123
+ # @api private
124
+ def self.deserialize_body(data)
125
+ return nil if data.nil?
126
+ return data if data.is_a?(String)
127
+
128
+ if data.is_a?(Hash)
129
+ if data['type'] == 'JSON_RPC'
130
+ return JsonRpcBody.from_hash(data)
131
+ end
132
+ if data['type'] == 'GRAPHQL'
133
+ return GraphQLBody.from_hash(data)
134
+ end
135
+ return Body.from_hash(data) if BODY_TYPES.include?(data['type'])
136
+
137
+ return data
138
+ end
139
+ data
140
+ end
141
+
142
+ # @api private
143
+ def self.serialize_key_multi_values(items)
144
+ return nil if items.nil?
145
+
146
+ items.map(&:to_h)
147
+ end
148
+
149
+ # @api private
150
+ def self.deserialize_key_multi_values(data)
151
+ return nil if data.nil?
152
+
153
+ if data.is_a?(Hash)
154
+ return data.map { |k, v| KeyToMultiValue.new(name: k, values: v.is_a?(Array) ? v : [v]) }
155
+ end
156
+
157
+ data.map do |item|
158
+ if item.is_a?(Hash)
159
+ KeyToMultiValue.from_hash(item)
160
+ elsif item.is_a?(String)
161
+ KeyToMultiValue.new(name: item, values: [])
162
+ else
163
+ KeyToMultiValue.from_hash(item)
164
+ end
165
+ end
166
+ end
167
+
168
+ # -------------------------------------------------------------------
169
+ # Model classes
170
+ # -------------------------------------------------------------------
171
+
172
+ class DelayDistribution
173
+ attr_accessor :type, :min, :max, :median, :p99, :mean, :std_dev
174
+
175
+ def initialize(type: nil, min: nil, max: nil, median: nil, p99: nil, mean: nil, std_dev: nil)
176
+ @type = type
177
+ @min = min
178
+ @max = max
179
+ @median = median
180
+ @p99 = p99
181
+ @mean = mean
182
+ @std_dev = std_dev
183
+ end
184
+
185
+ def to_h
186
+ MockServer.strip_none({
187
+ 'type' => @type,
188
+ 'min' => @min,
189
+ 'max' => @max,
190
+ 'median' => @median,
191
+ 'p99' => @p99,
192
+ 'mean' => @mean,
193
+ 'stdDev' => @std_dev
194
+ })
195
+ end
196
+
197
+ def self.from_hash(data)
198
+ return nil if data.nil?
199
+
200
+ new(
201
+ type: data['type'],
202
+ min: data['min'],
203
+ max: data['max'],
204
+ median: data['median'],
205
+ p99: data['p99'],
206
+ mean: data['mean'],
207
+ std_dev: data['stdDev']
208
+ )
209
+ end
210
+ end
211
+
212
+ class Delay
213
+ attr_accessor :time_unit, :value, :distribution
214
+
215
+ def initialize(time_unit: 'MILLISECONDS', value: 0, distribution: nil)
216
+ @time_unit = time_unit
217
+ @value = value
218
+ @distribution = distribution
219
+ end
220
+
221
+ def to_h
222
+ MockServer.strip_none({
223
+ 'timeUnit' => @time_unit,
224
+ 'value' => @value,
225
+ 'distribution' => @distribution&.to_h
226
+ })
227
+ end
228
+
229
+ def self.from_hash(data)
230
+ return nil if data.nil?
231
+
232
+ dist_data = data['distribution']
233
+ new(
234
+ time_unit: data.fetch('timeUnit', 'MILLISECONDS'),
235
+ value: data.fetch('value', 0),
236
+ distribution: dist_data ? DelayDistribution.from_hash(dist_data) : nil
237
+ )
238
+ end
239
+ end
240
+
241
+ class Times
242
+ attr_accessor :remaining_times, :unlimited
243
+
244
+ def initialize(remaining_times: nil, unlimited: nil)
245
+ @remaining_times = remaining_times
246
+ @unlimited = unlimited
247
+ end
248
+
249
+ def to_h
250
+ MockServer.strip_none({
251
+ 'remainingTimes' => @remaining_times,
252
+ 'unlimited' => @unlimited
253
+ })
254
+ end
255
+
256
+ def self.from_hash(data)
257
+ return nil if data.nil?
258
+
259
+ new(
260
+ remaining_times: data['remainingTimes'],
261
+ unlimited: data['unlimited']
262
+ )
263
+ end
264
+
265
+ def self.unlimited
266
+ new(unlimited: true)
267
+ end
268
+
269
+ def self.exactly(count)
270
+ new(remaining_times: count, unlimited: false)
271
+ end
272
+ end
273
+
274
+ class TimeToLive
275
+ attr_accessor :time_unit, :time_to_live, :unlimited
276
+
277
+ def initialize(time_unit: nil, time_to_live: nil, unlimited: nil)
278
+ @time_unit = time_unit
279
+ @time_to_live = time_to_live
280
+ @unlimited = unlimited
281
+ end
282
+
283
+ def to_h
284
+ MockServer.strip_none({
285
+ 'timeUnit' => @time_unit,
286
+ 'timeToLive' => @time_to_live,
287
+ 'unlimited' => @unlimited
288
+ })
289
+ end
290
+
291
+ def self.from_hash(data)
292
+ return nil if data.nil?
293
+
294
+ new(
295
+ time_unit: data['timeUnit'],
296
+ time_to_live: data['timeToLive'],
297
+ unlimited: data['unlimited']
298
+ )
299
+ end
300
+
301
+ def self.unlimited
302
+ new(unlimited: true)
303
+ end
304
+
305
+ def self.exactly(time_to_live, time_unit)
306
+ new(
307
+ time_unit: time_unit,
308
+ time_to_live: time_to_live,
309
+ unlimited: false
310
+ )
311
+ end
312
+ end
313
+
314
+ class KeyToMultiValue
315
+ attr_accessor :name, :values
316
+
317
+ def initialize(name: '', values: [])
318
+ @name = name
319
+ @values = values
320
+ end
321
+
322
+ # name and values are always emitted (not stripped via strip_none) because
323
+ # the MockServer protocol requires both fields on every header/cookie/parameter.
324
+ def to_h
325
+ {
326
+ 'name' => @name,
327
+ 'values' => @values
328
+ }
329
+ end
330
+
331
+ def self.from_hash(data)
332
+ return nil if data.nil?
333
+
334
+ new(
335
+ name: data.fetch('name', ''),
336
+ values: data.fetch('values', [])
337
+ )
338
+ end
339
+ end
340
+
341
+ class Body
342
+ attr_accessor :type, :string, :json, :base64_bytes, :not_body, :content_type, :charset
343
+
344
+ def initialize(type: nil, string: nil, json: nil, base64_bytes: nil, not_body: nil, content_type: nil, charset: nil)
345
+ @type = type
346
+ @string = string
347
+ @json = json
348
+ @base64_bytes = base64_bytes
349
+ @not_body = not_body
350
+ @content_type = content_type
351
+ @charset = charset
352
+ end
353
+
354
+ def to_h
355
+ result = {}
356
+ result['type'] = @type unless @type.nil?
357
+ result['string'] = @string unless @string.nil?
358
+ result['json'] = @json unless @json.nil?
359
+ result['base64Bytes'] = @base64_bytes unless @base64_bytes.nil?
360
+ result['not'] = @not_body unless @not_body.nil?
361
+ result['contentType'] = @content_type unless @content_type.nil?
362
+ result['charset'] = @charset unless @charset.nil?
363
+ result
364
+ end
365
+
366
+ def self.from_hash(data)
367
+ return nil if data.nil?
368
+
369
+ new(
370
+ type: data['type'],
371
+ string: data['string'],
372
+ json: data['json'],
373
+ base64_bytes: data['base64Bytes'],
374
+ not_body: data['not'],
375
+ content_type: data['contentType'],
376
+ charset: data['charset']
377
+ )
378
+ end
379
+
380
+ def self.string(value)
381
+ new(type: 'STRING', string: value)
382
+ end
383
+
384
+ def self.json(value)
385
+ new(type: 'JSON', json: value)
386
+ end
387
+
388
+ def self.regex(value)
389
+ new(type: 'REGEX', string: value)
390
+ end
391
+
392
+ def self.exact(value)
393
+ new(type: 'STRING', string: value)
394
+ end
395
+
396
+ def self.xml(value)
397
+ new(type: 'XML', string: value)
398
+ end
399
+
400
+ def self.json_rpc(method_name, params_schema: nil)
401
+ JsonRpcBody.new(method_name: method_name, params_schema: params_schema)
402
+ end
403
+
404
+ def self.graphql(query, operation_name: nil, variables_schema: nil)
405
+ GraphQLBody.new(query: query, operation_name: operation_name, variables_schema: variables_schema)
406
+ end
407
+ end
408
+
409
+ class JsonRpcBody
410
+ attr_accessor :method_name, :params_schema, :not_body, :optional
411
+
412
+ def initialize(method_name:, params_schema: nil, not_body: false, optional: false)
413
+ @method_name = method_name
414
+ @params_schema = params_schema
415
+ @not_body = not_body
416
+ @optional = optional
417
+ end
418
+
419
+ def to_h
420
+ result = { 'type' => 'JSON_RPC', 'method' => @method_name }
421
+ result['paramsSchema'] = @params_schema if @params_schema
422
+ result['not'] = true if @not_body
423
+ result['optional'] = true if @optional
424
+ result
425
+ end
426
+
427
+ def self.from_hash(data)
428
+ return nil if data.nil?
429
+
430
+ new(
431
+ method_name: data['method'] || '',
432
+ params_schema: data['paramsSchema'],
433
+ not_body: data.fetch('not', false),
434
+ optional: data.fetch('optional', false)
435
+ )
436
+ end
437
+ end
438
+
439
+ class GraphQLBody
440
+ attr_accessor :query, :operation_name, :variables_schema, :not_body, :optional
441
+
442
+ def initialize(query:, operation_name: nil, variables_schema: nil, not_body: false, optional: false)
443
+ @query = query
444
+ @operation_name = operation_name
445
+ @variables_schema = variables_schema
446
+ @not_body = not_body
447
+ @optional = optional
448
+ end
449
+
450
+ def to_h
451
+ result = { 'type' => 'GRAPHQL', 'query' => @query }
452
+ result['operationName'] = @operation_name if @operation_name
453
+ result['variablesSchema'] = @variables_schema if @variables_schema
454
+ result['not'] = true if @not_body
455
+ result['optional'] = true if @optional
456
+ result
457
+ end
458
+
459
+ def self.from_hash(data)
460
+ return nil if data.nil?
461
+
462
+ new(
463
+ query: data['query'] || '',
464
+ operation_name: data['operationName'],
465
+ variables_schema: data['variablesSchema'],
466
+ not_body: data.fetch('not', false),
467
+ optional: data.fetch('optional', false)
468
+ )
469
+ end
470
+ end
471
+
472
+ class SocketAddress
473
+ attr_accessor :host, :port, :scheme
474
+
475
+ def initialize(host: nil, port: nil, scheme: nil)
476
+ @host = host
477
+ @port = port
478
+ @scheme = scheme
479
+ end
480
+
481
+ def to_h
482
+ MockServer.strip_none({
483
+ 'host' => @host,
484
+ 'port' => @port,
485
+ 'scheme' => @scheme
486
+ })
487
+ end
488
+
489
+ def self.from_hash(data)
490
+ return nil if data.nil?
491
+
492
+ new(
493
+ host: data['host'],
494
+ port: data['port'],
495
+ scheme: data['scheme']
496
+ )
497
+ end
498
+ end
499
+
500
+ class HttpRequest
501
+ attr_accessor :method, :path, :query_string_parameters, :headers,
502
+ :cookies, :body, :secure, :keep_alive, :respond_before_body,
503
+ :path_parameters, :socket_address
504
+
505
+ def initialize(method: nil, path: nil, query_string_parameters: nil, headers: nil,
506
+ cookies: nil, body: nil, secure: nil, keep_alive: nil,
507
+ respond_before_body: nil, path_parameters: nil, socket_address: nil)
508
+ @method = method
509
+ @path = path
510
+ @query_string_parameters = query_string_parameters
511
+ @headers = headers
512
+ @cookies = cookies
513
+ @body = body
514
+ @secure = secure
515
+ @keep_alive = keep_alive
516
+ @respond_before_body = respond_before_body
517
+ @path_parameters = path_parameters
518
+ @socket_address = socket_address
519
+ end
520
+
521
+ def to_h
522
+ MockServer.strip_none({
523
+ 'method' => @method,
524
+ 'path' => @path,
525
+ 'queryStringParameters' => MockServer.serialize_key_multi_values(@query_string_parameters),
526
+ 'headers' => MockServer.serialize_key_multi_values(@headers),
527
+ 'cookies' => MockServer.serialize_key_multi_values(@cookies),
528
+ 'body' => MockServer.serialize_body(@body),
529
+ 'secure' => @secure,
530
+ 'keepAlive' => @keep_alive,
531
+ 'respondBeforeBody' => @respond_before_body,
532
+ 'pathParameters' => MockServer.serialize_key_multi_values(@path_parameters),
533
+ 'socketAddress' => @socket_address&.to_h
534
+ })
535
+ end
536
+
537
+ def self.from_hash(data)
538
+ return nil if data.nil?
539
+
540
+ new(
541
+ method: data['method'],
542
+ path: data['path'],
543
+ query_string_parameters: MockServer.deserialize_key_multi_values(data['queryStringParameters']),
544
+ headers: MockServer.deserialize_key_multi_values(data['headers']),
545
+ cookies: MockServer.deserialize_key_multi_values(data['cookies']),
546
+ body: MockServer.deserialize_body(data['body']),
547
+ secure: data['secure'],
548
+ keep_alive: data['keepAlive'],
549
+ respond_before_body: data['respondBeforeBody'],
550
+ path_parameters: MockServer.deserialize_key_multi_values(data['pathParameters']),
551
+ socket_address: SocketAddress.from_hash(data['socketAddress'])
552
+ )
553
+ end
554
+
555
+ def self.request(path: nil)
556
+ new(path: path)
557
+ end
558
+
559
+ def with_method(method)
560
+ @method = method
561
+ self
562
+ end
563
+
564
+ def with_path(path)
565
+ @path = path
566
+ self
567
+ end
568
+
569
+ def with_header(name, *values)
570
+ @headers ||= []
571
+ @headers << KeyToMultiValue.new(name: name, values: values.flatten)
572
+ self
573
+ end
574
+
575
+ def with_query_param(name, *values)
576
+ @query_string_parameters ||= []
577
+ @query_string_parameters << KeyToMultiValue.new(name: name, values: values.flatten)
578
+ self
579
+ end
580
+
581
+ def with_cookie(name, value)
582
+ @cookies ||= []
583
+ @cookies << KeyToMultiValue.new(name: name, values: [value])
584
+ self
585
+ end
586
+
587
+ def with_body(body)
588
+ @body = body
589
+ self
590
+ end
591
+
592
+ def with_secure(secure)
593
+ @secure = secure
594
+ self
595
+ end
596
+
597
+ def with_keep_alive(keep_alive)
598
+ @keep_alive = keep_alive
599
+ self
600
+ end
601
+
602
+ def with_respond_before_body(respond_before_body)
603
+ @respond_before_body = respond_before_body
604
+ self
605
+ end
606
+ end
607
+
608
+ class ConnectionOptions
609
+ attr_accessor :close_socket, :close_socket_delay, :suppress_content_length_header,
610
+ :content_length_header_override, :suppress_connection_header,
611
+ :chunk_size, :chunk_delay, :keep_alive_override
612
+
613
+ def initialize(close_socket: nil, close_socket_delay: nil,
614
+ suppress_content_length_header: nil, content_length_header_override: nil,
615
+ suppress_connection_header: nil, chunk_size: nil, chunk_delay: nil,
616
+ keep_alive_override: nil)
617
+ @close_socket = close_socket
618
+ @close_socket_delay = close_socket_delay
619
+ @suppress_content_length_header = suppress_content_length_header
620
+ @content_length_header_override = content_length_header_override
621
+ @suppress_connection_header = suppress_connection_header
622
+ @chunk_size = chunk_size
623
+ @chunk_delay = chunk_delay
624
+ @keep_alive_override = keep_alive_override
625
+ end
626
+
627
+ def to_h
628
+ MockServer.strip_none({
629
+ 'closeSocket' => @close_socket,
630
+ 'closeSocketDelay' => @close_socket_delay&.to_h,
631
+ 'suppressContentLengthHeader' => @suppress_content_length_header,
632
+ 'contentLengthHeaderOverride' => @content_length_header_override,
633
+ 'suppressConnectionHeader' => @suppress_connection_header,
634
+ 'chunkSize' => @chunk_size,
635
+ 'chunkDelay' => @chunk_delay&.to_h,
636
+ 'keepAliveOverride' => @keep_alive_override
637
+ })
638
+ end
639
+
640
+ def self.from_hash(data)
641
+ return nil if data.nil?
642
+
643
+ new(
644
+ close_socket: data['closeSocket'],
645
+ close_socket_delay: Delay.from_hash(data['closeSocketDelay']),
646
+ suppress_content_length_header: data['suppressContentLengthHeader'],
647
+ content_length_header_override: data['contentLengthHeaderOverride'],
648
+ suppress_connection_header: data['suppressConnectionHeader'],
649
+ chunk_size: data['chunkSize'],
650
+ chunk_delay: Delay.from_hash(data['chunkDelay']),
651
+ keep_alive_override: data['keepAliveOverride']
652
+ )
653
+ end
654
+ end
655
+
656
+ class HttpResponse
657
+ attr_accessor :status_code, :reason_phrase, :headers, :cookies,
658
+ :body, :delay, :connection_options, :primary
659
+
660
+ def initialize(status_code: nil, reason_phrase: nil, headers: nil, cookies: nil,
661
+ body: nil, delay: nil, connection_options: nil, primary: nil)
662
+ @status_code = status_code
663
+ @reason_phrase = reason_phrase
664
+ @headers = headers
665
+ @cookies = cookies
666
+ @body = body
667
+ @delay = delay
668
+ @connection_options = connection_options
669
+ @primary = primary
670
+ end
671
+
672
+ def to_h
673
+ MockServer.strip_none({
674
+ 'statusCode' => @status_code,
675
+ 'reasonPhrase' => @reason_phrase,
676
+ 'headers' => MockServer.serialize_key_multi_values(@headers),
677
+ 'cookies' => MockServer.serialize_key_multi_values(@cookies),
678
+ 'body' => MockServer.serialize_body(@body),
679
+ 'delay' => @delay&.to_h,
680
+ 'connectionOptions' => @connection_options&.to_h,
681
+ 'primary' => @primary
682
+ })
683
+ end
684
+
685
+ def self.from_hash(data)
686
+ return nil if data.nil?
687
+
688
+ new(
689
+ status_code: data['statusCode'],
690
+ reason_phrase: data['reasonPhrase'],
691
+ headers: MockServer.deserialize_key_multi_values(data['headers']),
692
+ cookies: MockServer.deserialize_key_multi_values(data['cookies']),
693
+ body: MockServer.deserialize_body(data['body']),
694
+ delay: Delay.from_hash(data['delay']),
695
+ connection_options: ConnectionOptions.from_hash(data['connectionOptions']),
696
+ primary: data['primary']
697
+ )
698
+ end
699
+
700
+ def self.response(body: nil, status_code: nil)
701
+ resp = new
702
+ if body
703
+ resp.body = body
704
+ if status_code.nil?
705
+ resp.status_code = 200
706
+ resp.reason_phrase = 'OK'
707
+ else
708
+ resp.status_code = status_code
709
+ end
710
+ elsif status_code
711
+ resp.status_code = status_code
712
+ end
713
+ resp
714
+ end
715
+
716
+ def self.not_found_response
717
+ new(status_code: 404, reason_phrase: 'Not Found')
718
+ end
719
+
720
+ def with_status_code(status_code)
721
+ @status_code = status_code
722
+ self
723
+ end
724
+
725
+ def with_header(name, *values)
726
+ @headers ||= []
727
+ @headers << KeyToMultiValue.new(name: name, values: values.flatten)
728
+ self
729
+ end
730
+
731
+ def with_cookie(name, value)
732
+ @cookies ||= []
733
+ @cookies << KeyToMultiValue.new(name: name, values: [value])
734
+ self
735
+ end
736
+
737
+ def with_body(body)
738
+ @body = body
739
+ self
740
+ end
741
+
742
+ def with_delay(delay)
743
+ @delay = delay
744
+ self
745
+ end
746
+
747
+ def with_reason_phrase(reason_phrase)
748
+ @reason_phrase = reason_phrase
749
+ self
750
+ end
751
+ end
752
+
753
+ class HttpForward
754
+ attr_accessor :host, :port, :scheme, :delay, :primary
755
+
756
+ def initialize(host: nil, port: nil, scheme: nil, delay: nil, primary: nil)
757
+ @host = host
758
+ @port = port
759
+ @scheme = scheme
760
+ @delay = delay
761
+ @primary = primary
762
+ end
763
+
764
+ def to_h
765
+ MockServer.strip_none({
766
+ 'host' => @host,
767
+ 'port' => @port,
768
+ 'scheme' => @scheme,
769
+ 'delay' => @delay&.to_h,
770
+ 'primary' => @primary
771
+ })
772
+ end
773
+
774
+ def self.from_hash(data)
775
+ return nil if data.nil?
776
+
777
+ new(
778
+ host: data['host'],
779
+ port: data['port'],
780
+ scheme: data['scheme'],
781
+ delay: Delay.from_hash(data['delay']),
782
+ primary: data['primary']
783
+ )
784
+ end
785
+
786
+ def self.forward
787
+ new
788
+ end
789
+ end
790
+
791
+ class HttpTemplate
792
+ attr_accessor :template_type, :template, :delay, :primary
793
+
794
+ def initialize(template_type: 'JAVASCRIPT', template: nil, delay: nil, primary: nil)
795
+ @template_type = template_type
796
+ @template = template
797
+ @delay = delay
798
+ @primary = primary
799
+ end
800
+
801
+ def to_h
802
+ MockServer.strip_none({
803
+ 'templateType' => @template_type,
804
+ 'template' => @template,
805
+ 'delay' => @delay&.to_h,
806
+ 'primary' => @primary
807
+ })
808
+ end
809
+
810
+ def self.from_hash(data)
811
+ return nil if data.nil?
812
+
813
+ new(
814
+ template_type: data.fetch('templateType', 'JAVASCRIPT'),
815
+ template: data['template'],
816
+ delay: Delay.from_hash(data['delay']),
817
+ primary: data['primary']
818
+ )
819
+ end
820
+
821
+ def self.template(template_type, template = nil)
822
+ new(template_type: template_type, template: template)
823
+ end
824
+ end
825
+
826
+ class HttpClassCallback
827
+ attr_accessor :callback_class, :delay, :primary
828
+
829
+ def initialize(callback_class: nil, delay: nil, primary: nil)
830
+ @callback_class = callback_class
831
+ @delay = delay
832
+ @primary = primary
833
+ end
834
+
835
+ def to_h
836
+ MockServer.strip_none({
837
+ 'callbackClass' => @callback_class,
838
+ 'delay' => @delay&.to_h,
839
+ 'primary' => @primary
840
+ })
841
+ end
842
+
843
+ def self.from_hash(data)
844
+ return nil if data.nil?
845
+
846
+ new(
847
+ callback_class: data['callbackClass'],
848
+ delay: Delay.from_hash(data['delay']),
849
+ primary: data['primary']
850
+ )
851
+ end
852
+
853
+ def self.callback(callback_class: nil)
854
+ new(callback_class: callback_class)
855
+ end
856
+ end
857
+
858
+ class HttpObjectCallback
859
+ attr_accessor :client_id, :response_callback, :delay, :primary
860
+
861
+ def initialize(client_id: nil, response_callback: nil, delay: nil, primary: nil)
862
+ @client_id = client_id
863
+ @response_callback = response_callback
864
+ @delay = delay
865
+ @primary = primary
866
+ end
867
+
868
+ def to_h
869
+ MockServer.strip_none({
870
+ 'clientId' => @client_id,
871
+ 'responseCallback' => @response_callback,
872
+ 'delay' => @delay&.to_h,
873
+ 'primary' => @primary
874
+ })
875
+ end
876
+
877
+ def self.from_hash(data)
878
+ return nil if data.nil?
879
+
880
+ new(
881
+ client_id: data['clientId'],
882
+ response_callback: data['responseCallback'],
883
+ delay: Delay.from_hash(data['delay']),
884
+ primary: data['primary']
885
+ )
886
+ end
887
+ end
888
+
889
+ class HttpError
890
+ attr_accessor :drop_connection, :response_bytes, :delay, :primary
891
+
892
+ def initialize(drop_connection: nil, response_bytes: nil, delay: nil, primary: nil)
893
+ @drop_connection = drop_connection
894
+ @response_bytes = response_bytes
895
+ @delay = delay
896
+ @primary = primary
897
+ end
898
+
899
+ def to_h
900
+ MockServer.strip_none({
901
+ 'dropConnection' => @drop_connection,
902
+ 'responseBytes' => @response_bytes,
903
+ 'delay' => @delay&.to_h,
904
+ 'primary' => @primary
905
+ })
906
+ end
907
+
908
+ def self.from_hash(data)
909
+ return nil if data.nil?
910
+
911
+ new(
912
+ drop_connection: data['dropConnection'],
913
+ response_bytes: data['responseBytes'],
914
+ delay: Delay.from_hash(data['delay']),
915
+ primary: data['primary']
916
+ )
917
+ end
918
+
919
+ def self.error
920
+ new
921
+ end
922
+ end
923
+
924
+ class HttpOverrideForwardedRequest
925
+ attr_accessor :http_request, :http_response, :delay,
926
+ :request_modifier, :response_modifier, :primary
927
+
928
+ def initialize(http_request: nil, http_response: nil, delay: nil,
929
+ request_modifier: nil, response_modifier: nil, primary: nil)
930
+ @http_request = http_request
931
+ @http_response = http_response
932
+ @delay = delay
933
+ @request_modifier = request_modifier
934
+ @response_modifier = response_modifier
935
+ @primary = primary
936
+ end
937
+
938
+ def to_h
939
+ MockServer.strip_none({
940
+ 'httpRequest' => @http_request&.to_h,
941
+ 'httpResponse' => @http_response&.to_h,
942
+ 'delay' => @delay&.to_h,
943
+ 'requestModifier' => @request_modifier,
944
+ 'responseModifier' => @response_modifier,
945
+ 'primary' => @primary
946
+ })
947
+ end
948
+
949
+ def self.from_hash(data)
950
+ return nil if data.nil?
951
+
952
+ new(
953
+ http_request: HttpRequest.from_hash(data['httpRequest']),
954
+ http_response: HttpResponse.from_hash(data['httpResponse']),
955
+ delay: Delay.from_hash(data['delay']),
956
+ request_modifier: data['requestModifier'],
957
+ response_modifier: data['responseModifier'],
958
+ primary: data['primary']
959
+ )
960
+ end
961
+
962
+ def self.forward_overridden_request(request: nil)
963
+ new(http_request: request)
964
+ end
965
+ end
966
+
967
+ class HttpRequestAndHttpResponse
968
+ attr_accessor :http_request, :http_response
969
+
970
+ def initialize(http_request: nil, http_response: nil)
971
+ @http_request = http_request
972
+ @http_response = http_response
973
+ end
974
+
975
+ def to_h
976
+ MockServer.strip_none({
977
+ 'httpRequest' => @http_request&.to_h,
978
+ 'httpResponse' => @http_response&.to_h
979
+ })
980
+ end
981
+
982
+ def self.from_hash(data)
983
+ return nil if data.nil?
984
+
985
+ new(
986
+ http_request: HttpRequest.from_hash(data['httpRequest']),
987
+ http_response: HttpResponse.from_hash(data['httpResponse'])
988
+ )
989
+ end
990
+ end
991
+
992
+ class ExpectationId
993
+ attr_accessor :id
994
+
995
+ def initialize(id: '')
996
+ @id = id
997
+ end
998
+
999
+ def to_h
1000
+ { 'id' => @id }
1001
+ end
1002
+
1003
+ def self.from_hash(data)
1004
+ return nil if data.nil?
1005
+
1006
+ new(id: data.fetch('id', ''))
1007
+ end
1008
+ end
1009
+
1010
+ class SseEvent
1011
+ attr_accessor :event, :data, :id, :retry, :delay
1012
+
1013
+ def initialize(event: nil, data: nil, id: nil, retry_ms: nil, delay: nil)
1014
+ @event = event
1015
+ @data = data
1016
+ @id = id
1017
+ @retry = retry_ms
1018
+ @delay = delay
1019
+ end
1020
+
1021
+ def to_h
1022
+ result = {}
1023
+ result['event'] = @event unless @event.nil?
1024
+ result['data'] = @data unless @data.nil?
1025
+ result['id'] = @id unless @id.nil?
1026
+ result['retry'] = @retry unless @retry.nil?
1027
+ result['delay'] = @delay.to_h if @delay
1028
+ result
1029
+ end
1030
+
1031
+ def self.from_hash(data)
1032
+ return nil if data.nil?
1033
+
1034
+ new(
1035
+ event: data['event'],
1036
+ data: data['data'],
1037
+ id: data['id'],
1038
+ retry_ms: data['retry'],
1039
+ delay: Delay.from_hash(data['delay'])
1040
+ )
1041
+ end
1042
+ end
1043
+
1044
+ class HttpSseResponse
1045
+ attr_accessor :status_code, :headers, :events, :close_connection, :delay, :primary
1046
+
1047
+ def initialize(status_code: nil, headers: nil, events: nil, close_connection: nil, delay: nil, primary: nil)
1048
+ @status_code = status_code
1049
+ @headers = headers
1050
+ @events = events
1051
+ @close_connection = close_connection
1052
+ @delay = delay
1053
+ @primary = primary
1054
+ end
1055
+
1056
+ def to_h
1057
+ result = {}
1058
+ result['statusCode'] = @status_code unless @status_code.nil?
1059
+ result['headers'] = MockServer.serialize_key_multi_values(@headers) if @headers
1060
+ result['events'] = @events&.map(&:to_h) if @events
1061
+ result['closeConnection'] = @close_connection unless @close_connection.nil?
1062
+ result['delay'] = @delay.to_h if @delay
1063
+ result['primary'] = @primary unless @primary.nil?
1064
+ result
1065
+ end
1066
+
1067
+ def self.from_hash(data)
1068
+ return nil if data.nil?
1069
+
1070
+ events_data = data['events']
1071
+ events = events_data&.map { |e| SseEvent.from_hash(e) }
1072
+ new(
1073
+ status_code: data['statusCode'],
1074
+ headers: MockServer.deserialize_key_multi_values(data['headers']),
1075
+ events: events,
1076
+ close_connection: data['closeConnection'],
1077
+ delay: Delay.from_hash(data['delay']),
1078
+ primary: data['primary']
1079
+ )
1080
+ end
1081
+ end
1082
+
1083
+ class WebSocketMessage
1084
+ attr_accessor :text, :binary, :delay
1085
+
1086
+ def initialize(text: nil, binary: nil, delay: nil)
1087
+ @text = text
1088
+ @binary = binary
1089
+ @delay = delay
1090
+ end
1091
+
1092
+ def to_h
1093
+ result = {}
1094
+ result['text'] = @text unless @text.nil?
1095
+ result['binary'] = Base64.strict_encode64(@binary) unless @binary.nil?
1096
+ result['delay'] = @delay.to_h if @delay
1097
+ result
1098
+ end
1099
+
1100
+ def self.from_hash(data)
1101
+ return nil if data.nil?
1102
+
1103
+ binary_data = data['binary']
1104
+ binary = binary_data ? Base64.strict_decode64(binary_data) : nil
1105
+ new(
1106
+ text: data['text'],
1107
+ binary: binary,
1108
+ delay: Delay.from_hash(data['delay'])
1109
+ )
1110
+ end
1111
+ end
1112
+
1113
+ class HttpWebSocketResponse
1114
+ attr_accessor :subprotocol, :messages, :close_connection, :delay, :primary
1115
+
1116
+ def initialize(subprotocol: nil, messages: nil, close_connection: nil, delay: nil, primary: nil)
1117
+ @subprotocol = subprotocol
1118
+ @messages = messages
1119
+ @close_connection = close_connection
1120
+ @delay = delay
1121
+ @primary = primary
1122
+ end
1123
+
1124
+ def to_h
1125
+ result = {}
1126
+ result['subprotocol'] = @subprotocol unless @subprotocol.nil?
1127
+ result['messages'] = @messages&.map(&:to_h) if @messages
1128
+ result['closeConnection'] = @close_connection unless @close_connection.nil?
1129
+ result['delay'] = @delay.to_h if @delay
1130
+ result['primary'] = @primary unless @primary.nil?
1131
+ result
1132
+ end
1133
+
1134
+ def self.from_hash(data)
1135
+ return nil if data.nil?
1136
+
1137
+ messages_data = data['messages']
1138
+ messages = messages_data&.map { |m| WebSocketMessage.from_hash(m) }
1139
+ new(
1140
+ subprotocol: data['subprotocol'],
1141
+ messages: messages,
1142
+ close_connection: data['closeConnection'],
1143
+ delay: Delay.from_hash(data['delay']),
1144
+ primary: data['primary']
1145
+ )
1146
+ end
1147
+ end
1148
+
1149
+ class AfterAction
1150
+ attr_accessor :http_request, :http_class_callback, :http_object_callback, :delay
1151
+
1152
+ def initialize(http_request: nil, http_class_callback: nil, http_object_callback: nil, delay: nil)
1153
+ @http_request = http_request
1154
+ @http_class_callback = http_class_callback
1155
+ @http_object_callback = http_object_callback
1156
+ @delay = delay
1157
+ end
1158
+
1159
+ def to_h
1160
+ MockServer.strip_none({
1161
+ 'httpRequest' => @http_request&.to_h,
1162
+ 'httpClassCallback' => @http_class_callback&.to_h,
1163
+ 'httpObjectCallback' => @http_object_callback&.to_h,
1164
+ 'delay' => @delay&.to_h
1165
+ })
1166
+ end
1167
+
1168
+ def self.from_hash(data)
1169
+ return nil if data.nil?
1170
+
1171
+ new(
1172
+ http_request: HttpRequest.from_hash(data['httpRequest']),
1173
+ http_class_callback: HttpClassCallback.from_hash(data['httpClassCallback']),
1174
+ http_object_callback: HttpObjectCallback.from_hash(data['httpObjectCallback']),
1175
+ delay: Delay.from_hash(data['delay'])
1176
+ )
1177
+ end
1178
+ end
1179
+
1180
+ class Expectation
1181
+ attr_accessor :id, :priority, :percentage, :http_request, :http_response,
1182
+ :http_response_template, :http_response_class_callback,
1183
+ :http_response_object_callback, :http_forward,
1184
+ :http_forward_template, :http_forward_class_callback,
1185
+ :http_forward_object_callback, :http_override_forwarded_request,
1186
+ :http_error, :times, :time_to_live,
1187
+ :http_sse_response, :http_websocket_response, :after_actions,
1188
+ :http_responses, :response_mode,
1189
+ :scenario_name, :scenario_state, :new_scenario_state
1190
+
1191
+ def initialize(id: nil, priority: nil, percentage: nil, http_request: nil, http_response: nil,
1192
+ http_response_template: nil, http_response_class_callback: nil,
1193
+ http_response_object_callback: nil, http_forward: nil,
1194
+ http_forward_template: nil, http_forward_class_callback: nil,
1195
+ http_forward_object_callback: nil, http_override_forwarded_request: nil,
1196
+ http_error: nil, times: nil, time_to_live: nil,
1197
+ http_sse_response: nil, http_websocket_response: nil, after_actions: nil,
1198
+ http_responses: nil, response_mode: nil,
1199
+ scenario_name: nil, scenario_state: nil, new_scenario_state: nil)
1200
+ @id = id
1201
+ @priority = priority
1202
+ @percentage = percentage
1203
+ @http_request = http_request
1204
+ @http_response = http_response
1205
+ @http_response_template = http_response_template
1206
+ @http_response_class_callback = http_response_class_callback
1207
+ @http_response_object_callback = http_response_object_callback
1208
+ @http_forward = http_forward
1209
+ @http_forward_template = http_forward_template
1210
+ @http_forward_class_callback = http_forward_class_callback
1211
+ @http_forward_object_callback = http_forward_object_callback
1212
+ @http_override_forwarded_request = http_override_forwarded_request
1213
+ @http_error = http_error
1214
+ @times = times
1215
+ @time_to_live = time_to_live
1216
+ @http_sse_response = http_sse_response
1217
+ @http_websocket_response = http_websocket_response
1218
+ @after_actions = after_actions
1219
+ @http_responses = http_responses
1220
+ @response_mode = response_mode
1221
+ @scenario_name = scenario_name
1222
+ @scenario_state = scenario_state
1223
+ @new_scenario_state = new_scenario_state
1224
+ end
1225
+
1226
+ def to_h
1227
+ after_actions_h = nil
1228
+ if @after_actions.is_a?(Array)
1229
+ after_actions_h = @after_actions.map(&:to_h) unless @after_actions.empty?
1230
+ elsif @after_actions
1231
+ after_actions_h = @after_actions.to_h
1232
+ end
1233
+
1234
+ MockServer.strip_none({
1235
+ 'id' => @id,
1236
+ 'priority' => @priority,
1237
+ 'percentage' => @percentage,
1238
+ 'httpRequest' => @http_request&.to_h,
1239
+ 'httpResponse' => @http_response&.to_h,
1240
+ 'httpResponseTemplate' => @http_response_template&.to_h,
1241
+ 'httpResponseClassCallback' => @http_response_class_callback&.to_h,
1242
+ 'httpResponseObjectCallback' => @http_response_object_callback&.to_h,
1243
+ 'httpForward' => @http_forward&.to_h,
1244
+ 'httpForwardTemplate' => @http_forward_template&.to_h,
1245
+ 'httpForwardClassCallback' => @http_forward_class_callback&.to_h,
1246
+ 'httpForwardObjectCallback' => @http_forward_object_callback&.to_h,
1247
+ 'httpOverrideForwardedRequest' => @http_override_forwarded_request&.to_h,
1248
+ 'httpError' => @http_error&.to_h,
1249
+ 'httpSseResponse' => @http_sse_response&.to_h,
1250
+ 'httpWebSocketResponse' => @http_websocket_response&.to_h,
1251
+ 'afterActions' => after_actions_h,
1252
+ 'httpResponses' => @http_responses&.map(&:to_h),
1253
+ 'responseMode' => @response_mode,
1254
+ 'times' => @times&.to_h,
1255
+ 'timeToLive' => @time_to_live&.to_h,
1256
+ 'scenarioName' => @scenario_name,
1257
+ 'scenarioState' => @scenario_state,
1258
+ 'newScenarioState' => @new_scenario_state
1259
+ })
1260
+ end
1261
+
1262
+ def self.from_hash(data)
1263
+ return nil if data.nil?
1264
+
1265
+ after_actions_data = data['afterActions']
1266
+ after_actions = if after_actions_data.is_a?(Array)
1267
+ after_actions_data.map { |a| AfterAction.from_hash(a) }
1268
+ elsif after_actions_data
1269
+ AfterAction.from_hash(after_actions_data)
1270
+ end
1271
+
1272
+ new(
1273
+ id: data['id'],
1274
+ priority: data['priority'],
1275
+ percentage: data['percentage'],
1276
+ http_request: HttpRequest.from_hash(data['httpRequest']),
1277
+ http_response: HttpResponse.from_hash(data['httpResponse']),
1278
+ http_response_template: HttpTemplate.from_hash(data['httpResponseTemplate']),
1279
+ http_response_class_callback: HttpClassCallback.from_hash(data['httpResponseClassCallback']),
1280
+ http_response_object_callback: HttpObjectCallback.from_hash(data['httpResponseObjectCallback']),
1281
+ http_forward: HttpForward.from_hash(data['httpForward']),
1282
+ http_forward_template: HttpTemplate.from_hash(data['httpForwardTemplate']),
1283
+ http_forward_class_callback: HttpClassCallback.from_hash(data['httpForwardClassCallback']),
1284
+ http_forward_object_callback: HttpObjectCallback.from_hash(data['httpForwardObjectCallback']),
1285
+ http_override_forwarded_request: HttpOverrideForwardedRequest.from_hash(data['httpOverrideForwardedRequest']),
1286
+ http_error: HttpError.from_hash(data['httpError']),
1287
+ http_sse_response: HttpSseResponse.from_hash(data['httpSseResponse']),
1288
+ http_websocket_response: HttpWebSocketResponse.from_hash(data['httpWebSocketResponse']),
1289
+ after_actions: after_actions,
1290
+ http_responses: data['httpResponses']&.map { |r| HttpResponse.from_hash(r) },
1291
+ response_mode: data['responseMode'],
1292
+ times: Times.from_hash(data['times']),
1293
+ time_to_live: TimeToLive.from_hash(data['timeToLive']),
1294
+ scenario_name: data['scenarioName'],
1295
+ scenario_state: data['scenarioState'],
1296
+ new_scenario_state: data['newScenarioState']
1297
+ )
1298
+ end
1299
+ end
1300
+
1301
+ class OpenAPIDefinition
1302
+ attr_accessor :spec_url_or_payload, :operation_id
1303
+
1304
+ def initialize(spec_url_or_payload: nil, operation_id: nil)
1305
+ @spec_url_or_payload = spec_url_or_payload
1306
+ @operation_id = operation_id
1307
+ end
1308
+
1309
+ def to_h
1310
+ MockServer.strip_none({
1311
+ 'specUrlOrPayload' => @spec_url_or_payload,
1312
+ 'operationId' => @operation_id
1313
+ })
1314
+ end
1315
+
1316
+ def self.from_hash(data)
1317
+ return nil if data.nil?
1318
+
1319
+ new(
1320
+ spec_url_or_payload: data['specUrlOrPayload'],
1321
+ operation_id: data['operationId']
1322
+ )
1323
+ end
1324
+ end
1325
+
1326
+ class OpenAPIExpectation
1327
+ attr_accessor :spec_url_or_payload, :operations_and_responses
1328
+
1329
+ def initialize(spec_url_or_payload: nil, operations_and_responses: nil)
1330
+ @spec_url_or_payload = spec_url_or_payload
1331
+ @operations_and_responses = operations_and_responses
1332
+ end
1333
+
1334
+ def to_h
1335
+ MockServer.strip_none({
1336
+ 'specUrlOrPayload' => @spec_url_or_payload,
1337
+ 'operationsAndResponses' => @operations_and_responses
1338
+ })
1339
+ end
1340
+
1341
+ def self.from_hash(data)
1342
+ return nil if data.nil?
1343
+
1344
+ new(
1345
+ spec_url_or_payload: data['specUrlOrPayload'],
1346
+ operations_and_responses: data['operationsAndResponses']
1347
+ )
1348
+ end
1349
+ end
1350
+
1351
+ class VerificationTimes
1352
+ attr_accessor :at_least, :at_most
1353
+
1354
+ def initialize(at_least: nil, at_most: nil)
1355
+ @at_least = at_least
1356
+ @at_most = at_most
1357
+ end
1358
+
1359
+ def to_h
1360
+ MockServer.strip_none({
1361
+ 'atLeast' => @at_least,
1362
+ 'atMost' => @at_most
1363
+ })
1364
+ end
1365
+
1366
+ def self.from_hash(data)
1367
+ return nil if data.nil?
1368
+
1369
+ new(
1370
+ at_least: data['atLeast'],
1371
+ at_most: data['atMost']
1372
+ )
1373
+ end
1374
+
1375
+ def self.at_least(count)
1376
+ new(at_least: count)
1377
+ end
1378
+
1379
+ def self.at_most(count)
1380
+ new(at_most: count)
1381
+ end
1382
+
1383
+ def self.exactly(count)
1384
+ new(at_least: count, at_most: count)
1385
+ end
1386
+
1387
+ def self.once
1388
+ new(at_least: 1, at_most: 1)
1389
+ end
1390
+
1391
+ def self.between(at_least, at_most)
1392
+ new(at_least: at_least, at_most: at_most)
1393
+ end
1394
+ end
1395
+
1396
+ class Verification
1397
+ attr_accessor :http_request, :expectation_id, :times,
1398
+ :maximum_number_of_request_to_return_in_verification_failure
1399
+
1400
+ def initialize(http_request: nil, expectation_id: nil, times: nil,
1401
+ maximum_number_of_request_to_return_in_verification_failure: nil)
1402
+ @http_request = http_request
1403
+ @expectation_id = expectation_id
1404
+ @times = times
1405
+ @maximum_number_of_request_to_return_in_verification_failure = maximum_number_of_request_to_return_in_verification_failure
1406
+ end
1407
+
1408
+ def to_h
1409
+ MockServer.strip_none({
1410
+ 'httpRequest' => @http_request&.to_h,
1411
+ 'expectationId' => @expectation_id&.to_h,
1412
+ 'times' => @times&.to_h,
1413
+ 'maximumNumberOfRequestToReturnInVerificationFailure' => @maximum_number_of_request_to_return_in_verification_failure
1414
+ })
1415
+ end
1416
+
1417
+ def self.from_hash(data)
1418
+ return nil if data.nil?
1419
+
1420
+ new(
1421
+ http_request: HttpRequest.from_hash(data['httpRequest']),
1422
+ expectation_id: ExpectationId.from_hash(data['expectationId']),
1423
+ times: VerificationTimes.from_hash(data['times']),
1424
+ maximum_number_of_request_to_return_in_verification_failure: data['maximumNumberOfRequestToReturnInVerificationFailure']
1425
+ )
1426
+ end
1427
+ end
1428
+
1429
+ class VerificationSequence
1430
+ attr_accessor :http_requests, :expectation_ids
1431
+
1432
+ def initialize(http_requests: nil, expectation_ids: nil)
1433
+ @http_requests = http_requests
1434
+ @expectation_ids = expectation_ids
1435
+ end
1436
+
1437
+ def to_h
1438
+ MockServer.strip_none({
1439
+ 'httpRequests' => @http_requests&.map(&:to_h),
1440
+ 'expectationIds' => @expectation_ids&.map(&:to_h)
1441
+ })
1442
+ end
1443
+
1444
+ def self.from_hash(data)
1445
+ return nil if data.nil?
1446
+
1447
+ http_requests_data = data['httpRequests']
1448
+ expectation_ids_data = data['expectationIds']
1449
+ new(
1450
+ http_requests: http_requests_data&.map { |r| HttpRequest.from_hash(r) },
1451
+ expectation_ids: expectation_ids_data&.map { |e| ExpectationId.from_hash(e) }
1452
+ )
1453
+ end
1454
+ end
1455
+
1456
+ class Ports
1457
+ attr_accessor :ports
1458
+
1459
+ def initialize(ports: [])
1460
+ @ports = ports
1461
+ end
1462
+
1463
+ def to_h
1464
+ { 'ports' => @ports }
1465
+ end
1466
+
1467
+ def self.from_hash(data)
1468
+ return nil if data.nil?
1469
+
1470
+ new(ports: data.fetch('ports', []))
1471
+ end
1472
+ end
1473
+
1474
+ class CrudExpectationsDefinition
1475
+ attr_accessor :base_path, :id_field, :id_strategy, :initial_data
1476
+
1477
+ def initialize(base_path: '', id_field: 'id', id_strategy: 'AUTO_INCREMENT', initial_data: nil)
1478
+ @base_path = base_path
1479
+ @id_field = id_field
1480
+ @id_strategy = id_strategy
1481
+ @initial_data = initial_data
1482
+ end
1483
+
1484
+ def to_h
1485
+ MockServer.strip_none({
1486
+ 'basePath' => @base_path,
1487
+ 'idField' => @id_field,
1488
+ 'idStrategy' => @id_strategy,
1489
+ 'initialData' => @initial_data
1490
+ })
1491
+ end
1492
+
1493
+ def self.from_hash(data)
1494
+ return nil if data.nil?
1495
+
1496
+ new(
1497
+ base_path: data.fetch('basePath', ''),
1498
+ id_field: data.fetch('idField', 'id'),
1499
+ id_strategy: data.fetch('idStrategy', 'AUTO_INCREMENT'),
1500
+ initial_data: data['initialData']
1501
+ )
1502
+ end
1503
+ end
1504
+
1505
+ # Alias matching the Python client
1506
+ RequestDefinition = HttpRequest
1507
+ end