jetstream_bridge 4.0.3 → 4.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,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'securerandom'
4
+ require_relative 'test_helpers/mock_nats'
4
5
 
5
6
  module JetstreamBridge
6
7
  # Test helpers for easier testing of JetStream Bridge integrations
@@ -34,13 +35,50 @@ module JetstreamBridge
34
35
  #
35
36
  module TestHelpers
36
37
  class << self
37
- # Enable test mode with in-memory event capture
38
+ # Auto-configure test helpers when RSpec is detected
38
39
  #
40
+ # This method is called automatically when test_helpers.rb is required.
41
+ # It sets up RSpec configuration to enable test mode for tests tagged with :jetstream.
42
+ #
43
+ # @return [void]
44
+ def auto_configure!
45
+ return unless defined?(RSpec)
46
+ return if @configured
47
+
48
+ RSpec.configure do |config|
49
+ config.include JetstreamBridge::TestHelpers
50
+ config.include JetstreamBridge::TestHelpers::Matchers
51
+
52
+ config.before(:each, :jetstream) do
53
+ JetstreamBridge::TestHelpers.enable_test_mode!
54
+ end
55
+
56
+ config.after(:each, :jetstream) do
57
+ JetstreamBridge::TestHelpers.reset_test_mode!
58
+ end
59
+ end
60
+
61
+ @configured = true
62
+ end
63
+
64
+ # Check if auto-configuration has been applied
65
+ #
66
+ # @return [Boolean]
67
+ def configured?
68
+ @configured ||= false
69
+ end
70
+
71
+ # Enable test mode with in-memory event capture and mock NATS connection
72
+ #
73
+ # @param use_mock_nats [Boolean] Whether to use mock NATS connection (default: true)
39
74
  # @return [void]
40
- def enable_test_mode!
75
+ def enable_test_mode!(use_mock_nats: true)
41
76
  @test_mode = true
42
77
  @published_events = []
43
78
  @consumed_events = []
79
+ @mock_nats_enabled = use_mock_nats
80
+
81
+ setup_mock_nats if use_mock_nats
44
82
  end
45
83
 
46
84
  # Reset test mode and clear captured events
@@ -50,6 +88,44 @@ module JetstreamBridge
50
88
  @test_mode = false
51
89
  @published_events = []
52
90
  @consumed_events = []
91
+
92
+ teardown_mock_nats if @mock_nats_enabled
93
+ @mock_nats_enabled = false
94
+ end
95
+
96
+ # Setup mock NATS connection
97
+ #
98
+ # @return [void]
99
+ def setup_mock_nats
100
+ MockNats.reset!
101
+ @mock_connection = MockNats.create_mock_connection
102
+ @mock_connection.connect
103
+
104
+ # Store the mock for Connection to use
105
+ JetstreamBridge.instance_variable_set(:@mock_nats_client, @mock_connection)
106
+ end
107
+
108
+ # Teardown mock NATS connection
109
+ #
110
+ # @return [void]
111
+ def teardown_mock_nats
112
+ MockNats.reset!
113
+ @mock_connection = nil
114
+ return unless JetstreamBridge.instance_variable_defined?(:@mock_nats_client)
115
+
116
+ JetstreamBridge.remove_instance_variable(:@mock_nats_client)
117
+ end
118
+
119
+ # Get the current mock connection
120
+ #
121
+ # @return [MockNats::MockConnection, nil]
122
+ attr_reader :mock_connection
123
+
124
+ # Get the mock storage for direct access in tests
125
+ #
126
+ # @return [MockNats::InMemoryStorage]
127
+ def mock_storage
128
+ MockNats.storage
53
129
  end
54
130
 
55
131
  # Check if test mode is enabled
@@ -271,5 +347,148 @@ module JetstreamBridge
271
347
  end
272
348
  end
273
349
  end
350
+
351
+ # Test fixtures for common event scenarios
352
+ #
353
+ # @example Create a user.created event
354
+ # event = JetstreamBridge::TestHelpers::Fixtures.user_created_event(id: 123)
355
+ #
356
+ module Fixtures
357
+ # Build a user.created event
358
+ #
359
+ # @param attrs [Hash] Event attributes
360
+ # @option attrs [Integer] :id User ID
361
+ # @option attrs [String] :email User email
362
+ # @option attrs [String] :name User name
363
+ # @option attrs [Hash] :payload Additional payload data
364
+ # @return [Models::Event] Event object
365
+ def self.user_created_event(attrs = {})
366
+ build_jetstream_event(
367
+ event_type: 'user.created',
368
+ payload: {
369
+ id: attrs[:id] || 1,
370
+ email: attrs[:email] || 'test@example.com',
371
+ name: attrs[:name] || 'Test User'
372
+ }.merge(attrs[:payload] || {})
373
+ )
374
+ end
375
+
376
+ # Build multiple sample events
377
+ #
378
+ # @param count [Integer] Number of events to create
379
+ # @param type [String] Event type
380
+ # @return [Array<Models::Event>] Array of event objects
381
+ def self.sample_events(count = 3, type: 'test.event')
382
+ Array.new(count) do |i|
383
+ build_jetstream_event(
384
+ event_type: type,
385
+ payload: { id: i + 1, sequence: i }
386
+ )
387
+ end
388
+ end
389
+
390
+ # Build a generic event with custom attributes
391
+ #
392
+ # @param event_type [String] Event type
393
+ # @param payload [Hash] Event payload
394
+ # @param attrs [Hash] Additional event attributes
395
+ # @return [Models::Event] Event object
396
+ def self.event(event_type:, payload: {}, **attrs)
397
+ build_jetstream_event(
398
+ event_type: event_type,
399
+ payload: payload,
400
+ **attrs
401
+ )
402
+ end
403
+
404
+ # Helper method to build events
405
+ # @private
406
+ def self.build_jetstream_event(event_type:, payload:, event_id: nil, trace_id: nil, occurred_at: nil, **metadata)
407
+ # Delegate to main module method to avoid duplication
408
+ TestHelpers.build_jetstream_event(
409
+ event_type: event_type,
410
+ payload: payload,
411
+ event_id: event_id,
412
+ trace_id: trace_id,
413
+ occurred_at: occurred_at,
414
+ **metadata
415
+ )
416
+ end
417
+ end
418
+
419
+ # Integration test helpers for end-to-end testing
420
+ module IntegrationHelpers
421
+ # Publish an event and wait for it to appear in mock storage
422
+ #
423
+ # @param event_attrs [Hash] Event attributes to publish
424
+ # @param timeout [Integer] Maximum seconds to wait
425
+ # @return [Models::PublishResult] Publish result
426
+ # @raise [Timeout::Error] If event doesn't appear within timeout
427
+ def publish_and_wait(event_attrs, timeout: 1)
428
+ result = JetstreamBridge.publish(**event_attrs)
429
+
430
+ deadline = Time.now + timeout
431
+ until Time.now > deadline
432
+ storage = JetstreamBridge::TestHelpers.mock_storage
433
+ break if storage.messages.any? { |m| m[:header]['nats-msg-id'] == result.event_id }
434
+
435
+ sleep 0.01
436
+ end
437
+
438
+ result
439
+ end
440
+
441
+ # Consume events from mock storage
442
+ #
443
+ # @param batch_size [Integer] Number of events to consume
444
+ # @yield [event] Block to handle each event
445
+ # @return [Array<Hash>] Consumed events
446
+ def consume_events(batch_size: 10, &handler)
447
+ storage = JetstreamBridge::TestHelpers.mock_storage
448
+ messages = storage.messages.first(batch_size)
449
+
450
+ messages.each do |msg|
451
+ event = JetstreamBridge::Models::Event.from_nats_message(
452
+ OpenStruct.new(
453
+ subject: msg[:subject],
454
+ data: msg[:data],
455
+ header: msg[:header],
456
+ metadata: OpenStruct.new(
457
+ sequence: OpenStruct.new(stream: msg[:sequence]),
458
+ num_delivered: msg[:delivery_count],
459
+ stream: 'test-stream',
460
+ consumer: 'test-consumer'
461
+ )
462
+ )
463
+ )
464
+
465
+ handler&.call(event)
466
+ JetstreamBridge::TestHelpers.record_consumed_event(event.to_h)
467
+ end
468
+
469
+ JetstreamBridge::TestHelpers.consumed_events
470
+ end
471
+
472
+ # Wait for a specific number of messages in mock storage
473
+ #
474
+ # @param count [Integer] Expected message count
475
+ # @param timeout [Integer] Maximum seconds to wait
476
+ # @return [Boolean] true if count reached, false if timeout
477
+ def wait_for_messages(count, timeout: 2)
478
+ deadline = Time.now + timeout
479
+ storage = JetstreamBridge::TestHelpers.mock_storage
480
+
481
+ until Time.now > deadline
482
+ return true if storage.messages.size >= count
483
+
484
+ sleep 0.01
485
+ end
486
+
487
+ false
488
+ end
489
+ end
274
490
  end
275
491
  end
492
+
493
+ # Auto-configure when loaded (only if RSpec is available)
494
+ JetstreamBridge::TestHelpers.auto_configure! if defined?(RSpec)
@@ -7,6 +7,12 @@ require_relative '../core/logging'
7
7
  module JetstreamBridge
8
8
  # Checks for overlapping subjects.
9
9
  class OverlapGuard
10
+ # Cache for stream metadata to reduce N+1 API calls
11
+ @cache_mutex = Mutex.new
12
+ @stream_cache = {}
13
+ @cache_expires_at = Time.at(0)
14
+ CACHE_TTL = 60 # seconds
15
+
10
16
  class << self
11
17
  # Raise if any desired subjects conflict with other streams.
12
18
  def check!(jts, target_name, new_subjects)
@@ -46,9 +52,52 @@ module JetstreamBridge
46
52
  end
47
53
 
48
54
  def list_streams_with_subjects(jts)
55
+ # Use cached data if available and fresh
56
+ @cache_mutex.synchronize do
57
+ now = Time.now
58
+ if now < @cache_expires_at && @stream_cache.key?(:data)
59
+ Logging.debug(
60
+ 'Using cached stream metadata',
61
+ tag: 'JetstreamBridge::OverlapGuard'
62
+ )
63
+ return @stream_cache[:data]
64
+ end
65
+
66
+ # Fetch fresh data
67
+ Logging.debug(
68
+ 'Fetching fresh stream metadata from NATS',
69
+ tag: 'JetstreamBridge::OverlapGuard'
70
+ )
71
+ result = fetch_streams_uncached(jts)
72
+ @stream_cache = { data: result }
73
+ @cache_expires_at = now + CACHE_TTL
74
+ result
75
+ end
76
+ rescue StandardError => e
77
+ Logging.warn(
78
+ "Failed to fetch stream metadata: #{e.class} #{e.message}",
79
+ tag: 'JetstreamBridge::OverlapGuard'
80
+ )
81
+ # Return cached data on error if available, otherwise empty array
82
+ @cache_mutex.synchronize { @stream_cache[:data] || [] }
83
+ end
84
+
85
+ # Clear the cache (useful for testing)
86
+ def clear_cache!
87
+ @cache_mutex.synchronize do
88
+ @stream_cache = {}
89
+ @cache_expires_at = Time.at(0)
90
+ end
91
+ end
92
+
93
+ # Fetch stream metadata without caching (for internal use)
94
+ def fetch_streams_uncached(jts)
49
95
  list_stream_names(jts).map do |name|
50
96
  info = jts.stream_info(name)
51
- { name: name, subjects: Array(info.config.subjects || []) }
97
+ # Handle both object-style and hash-style access for compatibility
98
+ config_data = info.config
99
+ subjects = config_data.respond_to?(:subjects) ? config_data.subjects : config_data[:subjects]
100
+ { name: name, subjects: Array(subjects || []) }
52
101
  end
53
102
  end
54
103
 
@@ -127,28 +127,36 @@ module JetstreamBridge
127
127
  private
128
128
 
129
129
  def ensure_update(jts, name, info, desired_subjects)
130
- existing = StreamSupport.normalize_subjects(info.config.subjects || [])
130
+ # Handle both object-style and hash-style access for compatibility
131
+ config_data = info.config
132
+ subjects = config_data.respond_to?(:subjects) ? config_data.subjects : config_data[:subjects]
133
+ existing = StreamSupport.normalize_subjects(subjects || [])
131
134
  to_add = StreamSupport.missing_subjects(existing, desired_subjects)
132
135
 
133
136
  # Retention is immutable; if different, skip all updates to avoid 10052 error.
134
- have_ret = info.config.retention.to_s.downcase
137
+ retention = config_data.respond_to?(:retention) ? config_data.retention : config_data[:retention]
138
+ have_ret = retention.to_s.downcase
135
139
  if have_ret != RETENTION
136
140
  StreamSupport.log_retention_mismatch(name, have: have_ret, want: RETENTION)
137
141
  return
138
142
  end
139
143
 
140
- add_subjects(jts, name, existing, to_add) if to_add.any?
144
+ # Add subjects if needed and return (logging is handled in add_subjects)
145
+ if to_add.any?
146
+ add_subjects(jts, name, existing, to_add)
147
+ return
148
+ end
141
149
 
142
150
  # Storage can be updated; do it without passing retention.
143
- have_storage = info.config.storage.to_s.downcase
151
+ storage = config_data.respond_to?(:storage) ? config_data.storage : config_data[:storage]
152
+ have_storage = storage.to_s.downcase
144
153
  if have_storage != STORAGE
145
154
  apply_update(jts, name, existing, storage: STORAGE)
146
155
  StreamSupport.log_config_updated(name, storage: STORAGE)
147
156
  return
148
157
  end
149
158
 
150
- return if to_add.any?
151
-
159
+ # If we reach here, nothing was updated
152
160
  StreamSupport.log_already_covered(name)
153
161
  end
154
162
 
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '4.0.3'
7
+ VERSION = '4.1.0'
8
8
  end
@@ -31,9 +31,10 @@ require_relative 'jetstream_bridge/models/outbox_event'
31
31
  # - Built-in health checks and monitoring
32
32
  # - Middleware support for cross-cutting concerns
33
33
  # - Rails integration with generators and migrations
34
+ # - Graceful startup/shutdown lifecycle management
34
35
  #
35
36
  # @example Quick start
36
- # # Configure
37
+ # # Configure (automatically starts connection)
37
38
  # JetstreamBridge.configure do |config|
38
39
  # config.nats_urls = "nats://localhost:4222"
39
40
  # config.env = "development"
@@ -50,9 +51,13 @@ require_relative 'jetstream_bridge/models/outbox_event'
50
51
  # )
51
52
  #
52
53
  # # Consume events
53
- # JetstreamBridge.subscribe do |event|
54
+ # consumer = JetstreamBridge.subscribe do |event|
54
55
  # puts "Received: #{event.type} - #{event.payload.to_h}"
55
- # end.run!
56
+ # end
57
+ # consumer.run!
58
+ #
59
+ # # Graceful shutdown
60
+ # at_exit { JetstreamBridge.shutdown! }
56
61
  #
57
62
  # @see Publisher For publishing events
58
63
  # @see Consumer For consuming events
@@ -65,14 +70,52 @@ module JetstreamBridge
65
70
  @config ||= Config.new
66
71
  end
67
72
 
68
- def configure(overrides = {})
73
+ # Configure JetStream Bridge settings and establish connection
74
+ #
75
+ # This method sets configuration and immediately establishes a connection
76
+ # to NATS, providing fail-fast behavior during application startup.
77
+ # If NATS is unavailable, the application will fail to start.
78
+ #
79
+ # Set config.lazy_connect = true to defer connection until first use.
80
+ #
81
+ # @example Basic configuration
82
+ # JetstreamBridge.configure do |config|
83
+ # config.nats_urls = "nats://localhost:4222"
84
+ # config.app_name = "my_app"
85
+ # config.destination_app = "worker"
86
+ # end
87
+ #
88
+ # @example With hash overrides
89
+ # JetstreamBridge.configure(env: 'production', app_name: 'my_app')
90
+ #
91
+ # @example Lazy connection (defer until first use)
92
+ # JetstreamBridge.configure do |config|
93
+ # config.nats_urls = "nats://localhost:4222"
94
+ # config.lazy_connect = true
95
+ # end
96
+ #
97
+ # @param overrides [Hash] Configuration key-value pairs to set
98
+ # @yield [Config] Configuration object for block-based configuration
99
+ # @return [Config] The configured instance
100
+ # @raise [ConnectionError] If connection to NATS fails (unless lazy_connect is true)
101
+ def configure(overrides = {}, **extra_overrides)
102
+ # Merge extra keyword arguments into overrides hash
103
+ all_overrides = overrides.nil? ? extra_overrides : overrides.merge(extra_overrides)
104
+
69
105
  cfg = config
70
- overrides.each { |k, v| assign!(cfg, k, v) } unless overrides.nil? || overrides.empty?
106
+ all_overrides.each { |k, v| assign!(cfg, k, v) } unless all_overrides.empty?
71
107
  yield(cfg) if block_given?
108
+
109
+ # Establish connection immediately for fail-fast behavior (unless lazy_connect is true)
110
+ startup! unless cfg.lazy_connect
111
+
72
112
  cfg
73
113
  end
74
114
 
75
- # Configure with a preset
115
+ # Configure with a preset and establish connection
116
+ #
117
+ # This method applies a configuration preset and immediately establishes
118
+ # a connection to NATS, providing fail-fast behavior.
76
119
  #
77
120
  # @example
78
121
  # JetstreamBridge.configure_for(:production) do |config|
@@ -84,6 +127,7 @@ module JetstreamBridge
84
127
  # @param preset [Symbol] Preset name (:development, :test, :production, etc.)
85
128
  # @yield [Config] Configuration object
86
129
  # @return [Config] Configured instance
130
+ # @raise [ConnectionError] If connection to NATS fails
87
131
  def configure_for(preset)
88
132
  configure do |cfg|
89
133
  cfg.apply_preset(preset)
@@ -93,6 +137,41 @@ module JetstreamBridge
93
137
 
94
138
  def reset!
95
139
  @config = nil
140
+ @connection_initialized = false
141
+ end
142
+
143
+ # Initialize the JetStream Bridge connection and topology
144
+ #
145
+ # This method is called automatically by `configure`, but can be called
146
+ # explicitly if needed. It's idempotent and safe to call multiple times.
147
+ #
148
+ # @return [void]
149
+ def startup!
150
+ return if @connection_initialized
151
+
152
+ Connection.connect!
153
+ @connection_initialized = true
154
+ Logging.info('JetStream Bridge started successfully', tag: 'JetstreamBridge')
155
+ end
156
+
157
+ # Gracefully shutdown the JetStream Bridge connection
158
+ #
159
+ # Closes the NATS connection and cleans up resources. Should be called
160
+ # during application shutdown (e.g., in at_exit or signal handlers).
161
+ #
162
+ # @return [void]
163
+ def shutdown!
164
+ return unless @connection_initialized
165
+
166
+ begin
167
+ nc = Connection.nc
168
+ nc&.close if nc&.connected?
169
+ Logging.info('JetStream Bridge shut down gracefully', tag: 'JetstreamBridge')
170
+ rescue StandardError => e
171
+ Logging.error("Error during shutdown: #{e.message}", tag: 'JetstreamBridge')
172
+ ensure
173
+ @connection_initialized = false
174
+ end
96
175
  end
97
176
 
98
177
  def use_outbox?
@@ -115,21 +194,56 @@ module JetstreamBridge
115
194
  Connection.jetstream
116
195
  end
117
196
 
118
- # Health check for monitoring and readiness probes
197
+ # Active health check for monitoring and readiness probes
198
+ #
199
+ # Performs actual operations to verify system health:
200
+ # - Checks NATS connection (active: calls account_info API)
201
+ # - Verifies stream exists and is accessible (active: queries stream info)
202
+ # - Tests NATS round-trip communication (active: RTT measurement)
119
203
  #
204
+ # Rate Limiting: To prevent abuse, uncached health checks are limited to once every 5 seconds.
205
+ # Cached results (within 30s TTL) bypass this limit via Connection.instance.connected?.
206
+ #
207
+ # @param skip_cache [Boolean] Force fresh health check, bypass connection cache (rate limited)
120
208
  # @return [Hash] Health status including NATS connection, stream, and version
121
- def health_check
209
+ # @raise [HealthCheckFailedError] If skip_cache requested too frequently
210
+ def health_check(skip_cache: false)
211
+ # Rate limit uncached requests to prevent abuse (max 1 per 5 seconds)
212
+ enforce_health_check_rate_limit! if skip_cache
213
+
214
+ start_time = Time.now
122
215
  conn_instance = Connection.instance
123
- connected = conn_instance.connected?
216
+
217
+ # Active check: calls @jts.account_info internally
218
+ # Pass skip_cache to force fresh check if requested
219
+ connected = conn_instance.connected?(skip_cache: skip_cache)
124
220
  connected_at = conn_instance.connected_at
221
+ connection_state = conn_instance.state
222
+ last_error = conn_instance.last_reconnect_error
223
+ last_error_at = conn_instance.last_reconnect_error_at
224
+
225
+ # Active check: queries actual stream from NATS server
226
+ stream_info = connected ? fetch_stream_info : { exists: false, name: config.stream_name }
227
+
228
+ # Active check: measure NATS round-trip time
229
+ rtt_ms = measure_nats_rtt if connected
125
230
 
126
- stream_info = fetch_stream_info if connected
231
+ health_check_duration_ms = ((Time.now - start_time) * 1000).round(2)
127
232
 
128
233
  {
129
234
  healthy: connected && stream_info&.fetch(:exists, false),
130
- nats_connected: connected,
131
- connected_at: connected_at&.iso8601,
235
+ connection: {
236
+ state: connection_state,
237
+ connected: connected,
238
+ connected_at: connected_at&.iso8601,
239
+ last_error: last_error&.message,
240
+ last_error_at: last_error_at&.iso8601
241
+ },
132
242
  stream: stream_info,
243
+ performance: {
244
+ nats_rtt_ms: rtt_ms,
245
+ health_check_duration_ms: health_check_duration_ms
246
+ },
133
247
  config: {
134
248
  env: config.env,
135
249
  app_name: config.app_name,
@@ -143,6 +257,10 @@ module JetstreamBridge
143
257
  rescue StandardError => e
144
258
  {
145
259
  healthy: false,
260
+ connection: {
261
+ state: :failed,
262
+ connected: false
263
+ },
146
264
  error: "#{e.class}: #{e.message}"
147
265
  }
148
266
  end
@@ -278,14 +396,38 @@ module JetstreamBridge
278
396
 
279
397
  private
280
398
 
399
+ # Enforce rate limit on uncached health checks to prevent abuse
400
+ # Max 1 uncached request per 5 seconds per process
401
+ def enforce_health_check_rate_limit!
402
+ @health_check_mutex ||= Mutex.new
403
+ @health_check_mutex.synchronize do
404
+ now = Time.now
405
+ if @last_uncached_health_check
406
+ time_since = now - @last_uncached_health_check
407
+ if time_since < 5
408
+ raise HealthCheckFailedError,
409
+ "Health check rate limit exceeded. Please wait #{(5 - time_since).ceil} second(s)"
410
+ end
411
+ end
412
+ @last_uncached_health_check = now
413
+ end
414
+ end
415
+
281
416
  def fetch_stream_info
282
417
  jts = Connection.jetstream
283
418
  info = jts.stream_info(config.stream_name)
419
+
420
+ # Handle both object-style and hash-style access for compatibility
421
+ config_data = info.config
422
+ state_data = info.state
423
+ subjects = config_data.respond_to?(:subjects) ? config_data.subjects : config_data[:subjects]
424
+ messages = state_data.respond_to?(:messages) ? state_data.messages : state_data[:messages]
425
+
284
426
  {
285
427
  exists: true,
286
428
  name: config.stream_name,
287
- subjects: info.config.subjects,
288
- messages: info.state.messages
429
+ subjects: subjects,
430
+ messages: messages
289
431
  }
290
432
  rescue StandardError => e
291
433
  {
@@ -295,6 +437,20 @@ module JetstreamBridge
295
437
  }
296
438
  end
297
439
 
440
+ def measure_nats_rtt
441
+ # Measure round-trip time using NATS RTT method
442
+ nc = Connection.nc
443
+ start = Time.now
444
+ nc.rtt
445
+ ((Time.now - start) * 1000).round(2)
446
+ rescue StandardError => e
447
+ Logging.warn(
448
+ "Failed to measure NATS RTT: #{e.class} #{e.message}",
449
+ tag: 'JetstreamBridge'
450
+ )
451
+ nil
452
+ end
453
+
298
454
  def assign!(cfg, key, val)
299
455
  setter = :"#{key}="
300
456
  raise ArgumentError, "Unknown configuration option: #{key}" unless cfg.respond_to?(setter)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jetstream_bridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Attara
@@ -68,16 +68,22 @@ dependencies:
68
68
  name: nats-pure
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - "~>"
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.4.0
74
+ - - "<"
72
75
  - !ruby/object:Gem::Version
73
- version: '2.4'
76
+ version: '3.0'
74
77
  type: :runtime
75
78
  prerelease: false
76
79
  version_requirements: !ruby/object:Gem::Requirement
77
80
  requirements:
78
- - - "~>"
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 2.4.0
84
+ - - "<"
79
85
  - !ruby/object:Gem::Version
80
- version: '2.4'
86
+ version: '3.0'
81
87
  - !ruby/object:Gem::Dependency
82
88
  name: oj
83
89
  requirement: !ruby/object:Gem::Requirement
@@ -152,6 +158,7 @@ files:
152
158
  - lib/jetstream_bridge/railtie.rb
153
159
  - lib/jetstream_bridge/tasks/install.rake
154
160
  - lib/jetstream_bridge/test_helpers.rb
161
+ - lib/jetstream_bridge/test_helpers/mock_nats.rb
155
162
  - lib/jetstream_bridge/topology/overlap_guard.rb
156
163
  - lib/jetstream_bridge/topology/stream.rb
157
164
  - lib/jetstream_bridge/topology/subject_matcher.rb