mockserver-client 6.1.0 → 7.1.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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MockServer
4
- VERSION = '6.1.0'
4
+ VERSION = '7.1.0'
5
5
  end
@@ -8,6 +8,7 @@ require 'timeout'
8
8
 
9
9
  module MockServer
10
10
  WEB_SOCKET_CORRELATION_ID_HEADER_NAME = 'WebSocketCorrelationId'
11
+ BREAKPOINT_ID_HEADER_NAME = 'X-MockServer-BreakpointId'
11
12
  CLIENT_REGISTRATION_ID_HEADER = 'X-CLIENT-REGISTRATION-ID'
12
13
 
13
14
  WEBSOCKET_PATH = '/_mockserver_callback_websocket'
@@ -17,6 +18,8 @@ module MockServer
17
18
  TYPE_HTTP_REQUEST_AND_RESPONSE = 'org.mockserver.model.HttpRequestAndHttpResponse'
18
19
  TYPE_CLIENT_ID_DTO = 'org.mockserver.serialization.model.WebSocketClientIdDTO'
19
20
  TYPE_ERROR_DTO = 'org.mockserver.serialization.model.WebSocketErrorDTO'
21
+ TYPE_PAUSED_STREAM_FRAME_DTO = 'org.mockserver.serialization.model.PausedStreamFrameDTO'
22
+ TYPE_STREAM_FRAME_DECISION_DTO = 'org.mockserver.serialization.model.StreamFrameDecisionDTO'
20
23
 
21
24
  MAX_RECONNECT_ATTEMPTS = 3
22
25
  REGISTRATION_TIMEOUT = 10
@@ -32,6 +35,17 @@ module MockServer
32
35
  nil
33
36
  end
34
37
 
38
+ def self.extract_breakpoint_id(request)
39
+ return nil if request.headers.nil?
40
+
41
+ request.headers.each do |header|
42
+ if header.name == BREAKPOINT_ID_HEADER_NAME
43
+ return header.values.first if header.values && !header.values.empty?
44
+ end
45
+ end
46
+ nil
47
+ end
48
+
35
49
  def self.add_correlation_id_header(message, correlation_id)
36
50
  message.headers ||= []
37
51
  message.headers.each do |header|
@@ -94,6 +108,10 @@ module MockServer
94
108
  @logger.progname = 'MockServer::WebSocketClient'
95
109
  @logger.level = Logger::WARN
96
110
  @registration_queue = nil
111
+ # Per-breakpoint-id handlers for matcher-driven breakpoints
112
+ @breakpoint_request_handlers = {}
113
+ @breakpoint_response_handlers = {}
114
+ @breakpoint_stream_frame_handlers = {}
97
115
  end
98
116
 
99
117
  def connected?
@@ -124,6 +142,37 @@ module MockServer
124
142
  @forward_response_callback = response_fn
125
143
  end
126
144
 
145
+ # Register a REQUEST-phase breakpoint handler keyed by breakpoint id.
146
+ def set_breakpoint_request_handler(breakpoint_id, handler)
147
+ @breakpoint_request_handlers[breakpoint_id] = handler if breakpoint_id && handler
148
+ end
149
+
150
+ # Register a RESPONSE-phase breakpoint handler keyed by breakpoint id.
151
+ def set_breakpoint_response_handler(breakpoint_id, handler)
152
+ @breakpoint_response_handlers[breakpoint_id] = handler if breakpoint_id && handler
153
+ end
154
+
155
+ # Register a stream-frame breakpoint handler keyed by breakpoint id.
156
+ def set_breakpoint_stream_frame_handler(breakpoint_id, handler)
157
+ @breakpoint_stream_frame_handlers[breakpoint_id] = handler if breakpoint_id && handler
158
+ end
159
+
160
+ # Remove all handlers for the given breakpoint id.
161
+ def remove_breakpoint_handlers(breakpoint_id)
162
+ return unless breakpoint_id
163
+
164
+ @breakpoint_request_handlers.delete(breakpoint_id)
165
+ @breakpoint_response_handlers.delete(breakpoint_id)
166
+ @breakpoint_stream_frame_handlers.delete(breakpoint_id)
167
+ end
168
+
169
+ # Remove all breakpoint handlers.
170
+ def clear_breakpoint_handlers
171
+ @breakpoint_request_handlers.clear
172
+ @breakpoint_response_handlers.clear
173
+ @breakpoint_stream_frame_handlers.clear
174
+ end
175
+
127
176
  def listen
128
177
  @listen_thread = Thread.new { listen_loop }
129
178
  @listen_thread.abort_on_exception = false
@@ -270,8 +319,12 @@ module MockServer
270
319
  if msg_type == TYPE_HTTP_REQUEST
271
320
  request = HttpRequest.from_hash(JSON.parse(msg_value))
272
321
  correlation_id = MockServer.extract_correlation_id(request)
322
+ breakpoint_id = MockServer.extract_breakpoint_id(request)
273
323
 
274
- if @forward_callback
324
+ bp_handler = breakpoint_id ? @breakpoint_request_handlers[breakpoint_id] : nil
325
+ if bp_handler
326
+ handle_breakpoint_request(request, correlation_id, bp_handler)
327
+ elsif @forward_callback
275
328
  handle_forward_request(request, correlation_id)
276
329
  elsif @response_callback
277
330
  handle_response_request(request, correlation_id)
@@ -284,8 +337,12 @@ module MockServer
284
337
  if msg_type == TYPE_HTTP_REQUEST_AND_RESPONSE
285
338
  req_and_resp = HttpRequestAndHttpResponse.from_hash(JSON.parse(msg_value))
286
339
  correlation_id = MockServer.extract_correlation_id(req_and_resp.http_request)
340
+ breakpoint_id = MockServer.extract_breakpoint_id(req_and_resp.http_request)
287
341
 
288
- if @forward_response_callback
342
+ bp_handler = breakpoint_id ? @breakpoint_response_handlers[breakpoint_id] : nil
343
+ if bp_handler
344
+ handle_breakpoint_response(req_and_resp, correlation_id, bp_handler)
345
+ elsif @forward_response_callback
289
346
  handle_forward_response(req_and_resp, correlation_id)
290
347
  else
291
348
  @logger.warn("Received HttpRequestAndHttpResponse callback but no forward_response_callback registered")
@@ -293,6 +350,12 @@ module MockServer
293
350
  return
294
351
  end
295
352
 
353
+ if msg_type == TYPE_PAUSED_STREAM_FRAME_DTO
354
+ paused_frame = JSON.parse(msg_value)
355
+ handle_breakpoint_stream_frame(paused_frame)
356
+ return
357
+ end
358
+
296
359
  @logger.warn("Received unhandled WebSocket message type: #{msg_type}")
297
360
  end
298
361
 
@@ -349,5 +412,69 @@ module MockServer
349
412
  @ws.send(error_msg)
350
413
  end
351
414
  end
415
+
416
+ def handle_breakpoint_request(request, correlation_id, handler)
417
+ result = handler.call(request)
418
+ result = request if result.nil? # auto-continue
419
+
420
+ MockServer.add_correlation_id_header(result, correlation_id) if correlation_id
421
+
422
+ type_name = result.is_a?(HttpResponse) ? TYPE_HTTP_RESPONSE : TYPE_HTTP_REQUEST
423
+ msg = MockServer.build_ws_message(type_name, result.to_h)
424
+ @ws.send(msg)
425
+ rescue StandardError => exc
426
+ @logger.error("Error in breakpoint request handler, auto-continuing: #{exc.message}")
427
+ MockServer.add_correlation_id_header(request, correlation_id) if correlation_id
428
+ msg = MockServer.build_ws_message(TYPE_HTTP_REQUEST, request.to_h)
429
+ @ws.send(msg)
430
+ end
431
+
432
+ def handle_breakpoint_response(req_and_resp, correlation_id, handler)
433
+ result = handler.call(req_and_resp.http_request, req_and_resp.http_response)
434
+ result = req_and_resp.http_response if result.nil? # auto-continue
435
+
436
+ unless result.is_a?(HttpResponse)
437
+ raise CallbackError, "Breakpoint response handler must return HttpResponse, got #{result.class}"
438
+ end
439
+
440
+ MockServer.add_correlation_id_header(result, correlation_id) if correlation_id
441
+ msg = MockServer.build_ws_message(TYPE_HTTP_RESPONSE, result.to_h)
442
+ @ws.send(msg)
443
+ rescue StandardError => exc
444
+ @logger.error("Error in breakpoint response handler, auto-continuing: #{exc.message}")
445
+ resp = req_and_resp.http_response || HttpResponse.new
446
+ MockServer.add_correlation_id_header(resp, correlation_id) if correlation_id
447
+ msg = MockServer.build_ws_message(TYPE_HTTP_RESPONSE, resp.to_h)
448
+ @ws.send(msg)
449
+ end
450
+
451
+ def handle_breakpoint_stream_frame(paused_frame)
452
+ breakpoint_id = paused_frame['breakpointId']
453
+ correlation_id = paused_frame['correlationId'] || ''
454
+ handler = breakpoint_id ? @breakpoint_stream_frame_handlers[breakpoint_id] : nil
455
+
456
+ decision = if handler
457
+ begin
458
+ result = handler.call(paused_frame)
459
+ if result.nil?
460
+ { 'correlationId' => correlation_id, 'action' => 'CONTINUE' }
461
+ else
462
+ result['correlationId'] = correlation_id # ensure echoed
463
+ result
464
+ end
465
+ rescue StandardError => exc
466
+ @logger.error("Error in breakpoint stream frame handler, auto-continuing: #{exc.message}")
467
+ { 'correlationId' => correlation_id, 'action' => 'CONTINUE' }
468
+ end
469
+ else
470
+ { 'correlationId' => correlation_id, 'action' => 'CONTINUE' }
471
+ end
472
+
473
+ msg = JSON.generate({
474
+ 'type' => TYPE_STREAM_FRAME_DECISION_DTO,
475
+ 'value' => JSON.generate(decision)
476
+ })
477
+ @ws.send(msg)
478
+ end
352
479
  end
353
480
  end
@@ -6,3 +6,4 @@ require_relative 'mockserver/models'
6
6
  require_relative 'mockserver/websocket_client'
7
7
  require_relative 'mockserver/forward_chain_expectation'
8
8
  require_relative 'mockserver/client'
9
+ require_relative 'mockserver/binary_launcher'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mockserver-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 7.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Bloom
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-27 00:00:00.000000000 Z
11
+ date: 2026-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger
@@ -91,6 +91,7 @@ files:
91
91
  - Gemfile
92
92
  - README.md
93
93
  - lib/mockserver-client.rb
94
+ - lib/mockserver/binary_launcher.rb
94
95
  - lib/mockserver/client.rb
95
96
  - lib/mockserver/errors.rb
96
97
  - lib/mockserver/forward_chain_expectation.rb