bug_bunny 3.1.0 → 3.1.2

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,18 +1,23 @@
1
- # lib/bug_bunny/session.rb
1
+ # frozen_string_literal: true
2
2
 
3
3
  module BugBunny
4
4
  # Clase interna que encapsula una unidad de trabajo sobre una conexión RabbitMQ.
5
5
  #
6
- # Gestiona el ciclo de vida de un `Bunny::Channel` implementando:
7
- # 1. Carga Perezosa (Lazy Loading): El canal solo se abre al usarse.
8
- # 2. Resiliencia: Intenta recuperar la conexión TCP si está cerrada.
6
+ # Implementa la lógica de "Configuración en Cascada" para Exchanges y Colas,
7
+ # gestionando el ciclo de vida de un `Bunny::Channel` con resiliencia y carga perezosa.
9
8
  #
10
9
  # @api private
11
10
  class Session
12
- # Opciones por defecto (Mantenemos las que tenías en tu repo)
11
+ # @!group Opciones por Defecto (Nivel 1: Gema)
12
+
13
+ # Opciones predeterminadas de la gema para Exchanges.
13
14
  DEFAULT_EXCHANGE_OPTIONS = { durable: false, auto_delete: false }.freeze
15
+
16
+ # Opciones predeterminadas de la gema para Colas.
14
17
  DEFAULT_QUEUE_OPTIONS = { exclusive: false, durable: false, auto_delete: true }.freeze
15
18
 
19
+ # @!endgroup
20
+
16
21
  # @return [Bunny::Session] La conexión TCP subyacente.
17
22
  attr_reader :connection
18
23
 
@@ -42,30 +47,50 @@ module BugBunny
42
47
  @channel
43
48
  end
44
49
 
45
- # Factory method para declarar o recuperar un Exchange.
46
- # Usa el método robusto `channel` internamente.
50
+ # Factory method para declarar o recuperar un Exchange aplicando la cascada de configuración.
51
+ #
52
+ # Jerarquía de fusión:
53
+ # 1. Defaults de la gema (`DEFAULT_EXCHANGE_OPTIONS`)
54
+ # 2. Configuración global (`BugBunny.configuration.exchange_options`)
55
+ # 3. Opciones específicas pasadas como argumento (`opts`)
47
56
  #
48
57
  # @param name [String, nil] Nombre del exchange.
49
- # @param type [String, Symbol] Tipo de exchange.
50
- # @param opts [Hash] Opciones adicionales.
58
+ # @param type [String, Symbol] Tipo de exchange ('direct', 'topic', 'fanout').
59
+ # @param opts [Hash] Opciones específicas de infraestructura para este intercambio.
60
+ # @return [Bunny::Exchange] El objeto exchange de Bunny configurado.
51
61
  def exchange(name: nil, type: 'direct', opts: {})
52
62
  return channel.default_exchange if name.nil? || name.empty?
53
63
 
54
- merged_opts = DEFAULT_EXCHANGE_OPTIONS.merge(opts)
55
- # public_send permite llamar a :topic, :direct, etc. dinámicamente
64
+ # Aplicación de la lógica de fusión en cascada
65
+ merged_opts = DEFAULT_EXCHANGE_OPTIONS
66
+ .merge(BugBunny.configuration.exchange_options || {})
67
+ .merge(opts)
68
+
69
+ # public_send permite llamar a :topic, :direct, etc. dinámicamente según el tipo
56
70
  channel.public_send(type, name, merged_opts)
57
71
  end
58
72
 
59
- # Factory method para declarar o recuperar una Cola.
60
- # Usa el método robusto `channel` internamente.
73
+ # Factory method para declarar o recuperar una Cola aplicando la cascada de configuración.
74
+ #
75
+ # Jerarquía de fusión:
76
+ # 1. Defaults de la gema (`DEFAULT_QUEUE_OPTIONS`)
77
+ # 2. Configuración global (`BugBunny.configuration.queue_options`)
78
+ # 3. Opciones específicas pasadas como argumento (`opts`)
61
79
  #
62
80
  # @param name [String] Nombre de la cola.
63
- # @param opts [Hash] Opciones adicionales.
81
+ # @param opts [Hash] Opciones específicas de infraestructura para esta cola.
82
+ # @return [Bunny::Queue] El objeto cola de Bunny configurado.
64
83
  def queue(name, opts = {})
65
- channel.queue(name.to_s, DEFAULT_QUEUE_OPTIONS.merge(opts))
84
+ # Aplicación de la lógica de fusión en cascada
85
+ merged_opts = DEFAULT_QUEUE_OPTIONS
86
+ .merge(BugBunny.configuration.queue_options || {})
87
+ .merge(opts)
88
+
89
+ channel.queue(name.to_s, merged_opts)
66
90
  end
67
91
 
68
92
  # Cierra el canal asociado a esta sesión de forma segura.
93
+ # @return [void]
69
94
  def close
70
95
  @channel&.close if @channel&.open?
71
96
  @channel = nil
@@ -73,8 +98,10 @@ module BugBunny
73
98
 
74
99
  private
75
100
 
76
- # Crea y configura un nuevo canal.
101
+ # Crea y configura un nuevo canal con las preferencias globales.
77
102
  # Asume que la conexión ya ha sido verificada por `ensure_connection!`.
103
+ #
104
+ # @raise [BugBunny::CommunicationError] Si falla la creación del canal.
78
105
  def create_channel!
79
106
  @channel = @connection.create_channel
80
107
 
@@ -90,13 +117,15 @@ module BugBunny
90
117
 
91
118
  # Garantiza que la conexión TCP esté abierta.
92
119
  # Si está cerrada, intenta reconectarla (Reconexión Transparente).
120
+ #
121
+ # @raise [BugBunny::CommunicationError] Si falla la reconexión.
93
122
  def ensure_connection!
94
123
  return if @connection.open?
95
124
 
96
- BugBunny.configuration.logger.warn("[BugBunny] Connection lost. Attempting to reconnect...")
125
+ BugBunny.configuration.logger.warn("[BugBunny::Session] ⚠️ Connection lost. Attempting to reconnect...")
97
126
  @connection.start
98
127
  rescue StandardError => e
99
- BugBunny.configuration.logger.error("[BugBunny] Critical connection failure: #{e.message}")
128
+ BugBunny.configuration.logger.error("[BugBunny::Session] Critical connection failure: #{e.message}")
100
129
  raise BugBunny::CommunicationError, "Could not reconnect to RabbitMQ: #{e.message}"
101
130
  end
102
131
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BugBunny
4
- VERSION = "3.1.0"
4
+ VERSION = "3.1.2"
5
5
  end
data/lib/bug_bunny.rb CHANGED
@@ -75,7 +75,7 @@ module BugBunny
75
75
 
76
76
  @global_connection.close if @global_connection.open?
77
77
  @global_connection = nil
78
- configuration.logger.info('[BugBunny] Global connection closed.')
78
+ configuration.logger.info('[BugBunny] 🔌 Global connection closed.')
79
79
  end
80
80
 
81
81
  # @api private
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ # --- CLASES DE PRUEBA (Namespace Aislado) ---
6
+ module InfraTest
7
+ class PingController < BugBunny::Controller
8
+ # Agregamos SHOW para soportar el .find del test
9
+ def show
10
+ render status: 200, json: { id: params[:id], message: 'pong', namespace: 'InfraTest' }
11
+ end
12
+
13
+ def index
14
+ render status: 200, json: { message: 'pong_index', namespace: 'InfraTest' }
15
+ end
16
+ end
17
+ end
18
+
19
+ class InfraResource < BugBunny::Resource
20
+ self.resource_name = 'ping'
21
+ self.exchange = 'test_infra_exchange'
22
+ self.exchange_type = 'topic'
23
+ end
24
+
25
+ # --- SUITE DE INFRAESTRUCTURA ---
26
+ class InfrastructureTest < Minitest::Test
27
+ include IntegrationHelper
28
+
29
+ def setup
30
+ skip "RabbitMQ no disponible" unless IntegrationHelper.rabbitmq_available?
31
+ @queue = "test_infra_queue_#{SecureRandom.hex(4)}"
32
+ @exchange = "test_infra_exchange"
33
+ end
34
+
35
+ def test_00_worker_lifecycle
36
+ with_running_worker(queue: @queue, exchange: @exchange) do
37
+ assert true, "El worker levantó y cedió el control al bloque"
38
+ end
39
+ end
40
+
41
+ def test_01_dynamic_namespace_resolution
42
+ BugBunny.configure do |c|
43
+ c.controller_namespace = 'InfraTest'
44
+ end
45
+
46
+ with_running_worker(queue: @queue, exchange: @exchange) do
47
+ # Enviamos GET ping/123 -> InfraTest::PingController#show
48
+ resource = InfraResource.find('123')
49
+
50
+ # Verificamos que volvió el objeto construido
51
+ assert_equal '123', resource.id
52
+ assert_equal 'InfraTest', resource.namespace
53
+ assert_equal 'pong', resource.message
54
+ end
55
+
56
+ ensure
57
+ BugBunny.configure do |c|
58
+ c.controller_namespace = 'Rabbit::Controllers'
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+ require 'timeout'
5
+
6
+ module ManualTest
7
+ class EchoController < BugBunny::Controller
8
+ def index
9
+ render status: 200, json: {
10
+ received: params[:message],
11
+ type: headers[:type],
12
+ via: 'ManualTest::EchoController'
13
+ }
14
+ end
15
+ end
16
+ end
17
+
18
+ class ManualClientTest < Minitest::Test
19
+ include IntegrationHelper
20
+
21
+ def setup
22
+ skip "RabbitMQ no disponible" unless IntegrationHelper.rabbitmq_available?
23
+
24
+ # 1. NOMBRES ÚNICOS: Evitamos colisiones entre tests de diferentes tipos (Direct/Fanout)
25
+ @queue = "test_manual_q_#{SecureRandom.hex(4)}"
26
+ @exchange = "test_manual_x_#{SecureRandom.hex(4)}"
27
+
28
+ @client = BugBunny::Client.new(pool: TEST_POOL)
29
+ BugBunny.configure { |c| c.controller_namespace = 'ManualTest' }
30
+ end
31
+
32
+ def teardown
33
+ BugBunny.configure { |c| c.controller_namespace = 'Rabbit::Controllers' }
34
+ end
35
+
36
+ # ==========================================
37
+ # GRUPO 1: PUBLICACIÓN ASÍNCRONA (PUBLISH)
38
+ # ==========================================
39
+
40
+ def test_publish_topic
41
+ puts "\n -> [Manual] Publish (Topic)..."
42
+ with_spy_worker(queue: @queue, exchange: @exchange, exchange_type: 'topic', routing_key: 'logs.#') do |messages|
43
+
44
+ @client.publish('logs.error', exchange: @exchange, exchange_type: 'topic', body: { a: 1 })
45
+
46
+ msg = wait_for_message(messages)
47
+ assert_equal 'logs.error', msg[:routing_key]
48
+ end
49
+ end
50
+
51
+ def test_publish_direct
52
+ puts " -> [Manual] Publish (Direct)..."
53
+ with_spy_worker(queue: @queue, exchange: @exchange, exchange_type: 'direct', routing_key: 'alert') do |messages|
54
+
55
+ @client.publish('alert', exchange: @exchange, exchange_type: 'direct', body: { a: 1 })
56
+
57
+ msg = wait_for_message(messages)
58
+ assert_equal 'alert', msg[:routing_key]
59
+ end
60
+ end
61
+
62
+ def test_publish_fanout
63
+ puts " -> [Manual] Publish (Fanout)..."
64
+ with_spy_worker(queue: @queue, exchange: @exchange, exchange_type: 'fanout', routing_key: '') do |messages|
65
+
66
+ @client.publish('ignored.key', exchange: @exchange, exchange_type: 'fanout', body: { a: 1 })
67
+
68
+ msg = wait_for_message(messages)
69
+ assert_equal 'ignored.key', msg[:routing_key]
70
+ end
71
+ end
72
+
73
+ # ==========================================
74
+ # GRUPO 2: RPC SÍNCRONO (REQUEST)
75
+ # ==========================================
76
+
77
+ def test_request_topic
78
+ puts " -> [Manual] RPC (Topic)..."
79
+ with_running_worker(queue: @queue, exchange: @exchange, exchange_type: 'topic', routing_key: 'echo') do
80
+
81
+ response = @client.request('echo',
82
+ method: :get, exchange: @exchange, exchange_type: 'topic',
83
+ body: { message: 'topic_rpc' }
84
+ )
85
+ assert_equal 'topic_rpc', response['body']['received']
86
+ end
87
+ end
88
+
89
+ def test_request_direct
90
+ puts " -> [Manual] RPC (Direct)..."
91
+ direct_key = 'rpc.direct'
92
+
93
+ with_running_worker(queue: @queue, exchange: @exchange, exchange_type: 'direct', routing_key: direct_key) do
94
+
95
+ response = @client.request('echo',
96
+ method: :get,
97
+ routing_key: direct_key,
98
+ exchange: @exchange,
99
+ exchange_type: 'direct',
100
+ body: { message: 'direct_rpc' }
101
+ )
102
+
103
+ assert_equal 200, response['status']
104
+ assert_equal 'direct_rpc', response['body']['received']
105
+ end
106
+ end
107
+
108
+ def test_request_fanout
109
+ puts " -> [Manual] RPC (Fanout)..."
110
+ with_running_worker(queue: @queue, exchange: @exchange, exchange_type: 'fanout', routing_key: '') do
111
+
112
+ response = @client.request('echo',
113
+ method: :get,
114
+ routing_key: 'random.ignored',
115
+ exchange: @exchange,
116
+ exchange_type: 'fanout',
117
+ body: { message: 'fanout_rpc' }
118
+ )
119
+
120
+ assert_equal 200, response['status']
121
+ assert_equal 'fanout_rpc', response['body']['received']
122
+ end
123
+ end
124
+
125
+ # ==========================================
126
+ # GRUPO 3: OPCIONES DE INFRAESTRUCTURA (CASCADA NIVEL 3)
127
+ # ==========================================
128
+
129
+ def test_publish_with_custom_exchange_options
130
+ puts " -> [Manual] Publish (Custom Options Nivel 3)..."
131
+ custom_exchange = "custom_opts_x_#{SecureRandom.hex(4)}"
132
+
133
+ # 1. Pre-creamos el exchange exigiendo que sea DURABLE (contrario a la config global)
134
+ conn = BugBunny.create_connection
135
+ ch = conn.create_channel
136
+ ch.topic(custom_exchange, durable: true, auto_delete: true)
137
+
138
+ begin
139
+ # 2. El cliente publica inyectando opciones dinámicas.
140
+ # Si esto no funcionara, RabbitMQ nos tiraría PRECONDITION_FAILED
141
+ # porque la configuración global (Nivel 2) dice durable: false.
142
+ @client.publish('logs',
143
+ exchange: custom_exchange,
144
+ exchange_type: 'topic',
145
+ exchange_options: { durable: true, auto_delete: true }, # Nivel 3 sobrescribe Nivel 2
146
+ body: { test: 'options' }
147
+ )
148
+
149
+ # Si la ejecución llega aquí, significa que la Cascada funcionó perfecto.
150
+ assert true
151
+ ensure
152
+ ch&.exchange_delete(custom_exchange) rescue nil
153
+ conn&.close
154
+ end
155
+ end
156
+
157
+ def test_request_with_custom_exchange_options
158
+ puts " -> [Manual] RPC (Custom Options Nivel 3)..."
159
+ custom_exchange = "custom_opts_rpc_x_#{SecureRandom.hex(4)}"
160
+
161
+ # 1. Exigimos DURABLE
162
+ conn = BugBunny.create_connection
163
+ ch = conn.create_channel
164
+ ch.direct(custom_exchange, durable: true, auto_delete: true)
165
+
166
+ # 2. Levantamos un worker usando esas configuraciones nativas
167
+ worker_thread = Thread.new do
168
+ q = ch.queue('', exclusive: true)
169
+ q.bind(custom_exchange, routing_key: 'custom.rpc')
170
+ q.subscribe(block: true) do |delivery, props, _body|
171
+ # Respuesta manual
172
+ ch.default_exchange.publish('{"status":200, "body":"ok"}', routing_key: props.reply_to, correlation_id: props.correlation_id)
173
+ end
174
+ end
175
+ sleep 0.5
176
+
177
+ begin
178
+ # 3. El cliente hace el request inyectando las opciones
179
+ response = @client.request('test',
180
+ exchange: custom_exchange,
181
+ exchange_type: 'direct',
182
+ routing_key: 'custom.rpc',
183
+ exchange_options: { durable: true, auto_delete: true }, # Nivel 3
184
+ body: { req: 'data' }
185
+ )
186
+
187
+ assert_equal 200, response['status']
188
+ assert_equal 'ok', response['body']
189
+ ensure
190
+ worker_thread&.kill
191
+ ch&.exchange_delete(custom_exchange) rescue nil
192
+ conn&.close
193
+ end
194
+ end
195
+
196
+ private
197
+
198
+ def wait_for_message(queue, timeout_sec = 2)
199
+ Timeout.timeout(timeout_sec) { queue.pop }
200
+ rescue Timeout::Error
201
+ flunk "Timeout: No llegó el mensaje al Worker en #{timeout_sec}s"
202
+ end
203
+ end
data/test/test_helper.rb CHANGED
@@ -1,24 +1,109 @@
1
1
  # frozen_string_literal: true
2
- require 'bundler/setup'
3
-
4
- $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
5
- require 'bug_bunny'
6
2
 
3
+ require 'bundler/setup'
7
4
  require 'minitest/autorun'
8
- require 'mocha/minitest'
9
- require 'logger'
5
+ # require 'minitest/reporters'
6
+ require 'bug_bunny'
7
+ require 'connection_pool'
8
+ require 'securerandom'
9
+ require 'socket'
10
10
 
11
11
  BugBunny.configure do |config|
12
- config.logger = Logger.new(nil)
13
- config.bunny_logger = Logger.new(nil)
12
+ config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
13
+ config.username = ENV.fetch('RABBITMQ_USER', 'guest')
14
+ config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
15
+ config.vhost = '/'
16
+ config.logger = Logger.new($stdout)
17
+ config.logger.level = Logger::WARN
18
+
19
+ # ========================================================
20
+ # LA MAGIA DE LA CASCADA (Nivel 2: Configuración Global)
21
+ # ========================================================
22
+ # Para los tests, queremos que los exchanges y queues sean efímeros
23
+ config.exchange_options = { durable: false, auto_delete: true }
24
+ config.queue_options = { exclusive: false, durable: false, auto_delete: true }
14
25
  end
15
26
 
16
- module TestHelper
27
+ TEST_POOL = ConnectionPool.new(size: 5, timeout: 5) { BugBunny.create_connection }
28
+ BugBunny::Resource.connection_pool = TEST_POOL
29
+
30
+ module IntegrationHelper
17
31
  def self.rabbitmq_available?
18
- socket = TCPSocket.new('localhost', 5672)
32
+ socket = TCPSocket.new(BugBunny.configuration.host, 5672)
19
33
  socket.close
20
34
  true
21
- rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
35
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
22
36
  false
23
37
  end
38
+
39
+ def with_running_worker(queue:, exchange:, exchange_type: 'topic', routing_key: '#')
40
+ conn = BugBunny.create_connection
41
+
42
+ worker_thread = Thread.new do
43
+ ch = conn.create_channel
44
+
45
+ x_opts = BugBunny.configuration.exchange_options || {}
46
+ q_opts = BugBunny.configuration.queue_options || {}
47
+
48
+ # FIX: Usamos la API de alto nivel (public_send) idéntica a BugBunny::Session
49
+ ch.public_send(exchange_type, exchange, x_opts)
50
+ ch.close
51
+
52
+ BugBunny::Consumer.subscribe(
53
+ connection: conn,
54
+ queue_name: queue,
55
+ exchange_name: exchange,
56
+ exchange_type: exchange_type,
57
+ routing_key: routing_key,
58
+ queue_opts: q_opts,
59
+ block: true
60
+ )
61
+ rescue => e
62
+ puts "❌ WORKER CRASHED: #{e.message}"
63
+ puts e.backtrace.join("\n")
64
+ end
65
+
66
+ sleep 0.5
67
+ yield
68
+ ensure
69
+ conn&.close
70
+ worker_thread&.kill
71
+ sleep 0.1
72
+ end
73
+
74
+ def with_spy_worker(queue:, exchange:, exchange_type: 'topic', routing_key: '#')
75
+ captured_messages = Thread::Queue.new
76
+ conn = BugBunny.create_connection
77
+
78
+ worker_thread = Thread.new do
79
+ ch = conn.create_channel
80
+
81
+ x_opts = BugBunny.configuration.exchange_options || {}
82
+ q_opts = BugBunny.configuration.queue_options || {}
83
+
84
+ # FIX DEFINITIVO: Ahora 'x' es un hermoso objeto Bunny::Exchange
85
+ # que la función .bind() entiende perfectamente.
86
+ x = ch.public_send(exchange_type, exchange, x_opts)
87
+ q = ch.queue(queue, q_opts)
88
+
89
+ # Bindeamos el objeto al queue
90
+ q.bind(x, routing_key: routing_key)
91
+
92
+ q.subscribe(block: true) do |delivery, props, body|
93
+ captured_messages << {
94
+ body: body,
95
+ routing_key: delivery.routing_key,
96
+ headers: props.headers
97
+ }
98
+ end
99
+ rescue => e
100
+ puts "SPY WORKER ERROR: #{e.message}"
101
+ end
102
+
103
+ sleep 0.5
104
+ yield(captured_messages)
105
+ ensure
106
+ conn&.close
107
+ worker_thread&.kill
108
+ end
24
109
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bug_bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - gabix
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-18 00:00:00.000000000 Z
11
+ date: 2026-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: '2.0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: minitest-reporters
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '1.6'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '1.6'
209
223
  description: BugBunny is a lightweight RPC framework for Ruby on Rails over RabbitMQ.
210
224
  It simulates a RESTful architecture with an intelligent router, Active Record-like
211
225
  resources, and middleware support.
@@ -218,9 +232,6 @@ files:
218
232
  - CHANGELOG.md
219
233
  - README.md
220
234
  - Rakefile
221
- - bin_client.rb
222
- - bin_suite.rb
223
- - bin_worker.rb
224
235
  - initializer_example.rb
225
236
  - lib/bug_bunny.rb
226
237
  - lib/bug_bunny/client.rb
@@ -241,18 +252,9 @@ files:
241
252
  - lib/generators/bug_bunny/install/install_generator.rb
242
253
  - lib/generators/bug_bunny/install/templates/initializer.rb
243
254
  - sig/bug_bunny.rbs
244
- - test/integration/fire_and_forget_test.rb
245
- - test/integration/rpc_flow_test.rb
255
+ - test/integration/infrastructure_test.rb
256
+ - test/integration/manual_client_test.rb
246
257
  - test/test_helper.rb
247
- - test/unit/configuration_test.rb
248
- - test/unit/consumer_test.rb
249
- - test/unit/controller_headers_test.rb
250
- - test/unit/hybrid_resource_test.rb
251
- - test/unit/middleware_test.rb
252
- - test/unit/resource_test.rb
253
- - test_controller.rb
254
- - test_helper.rb
255
- - test_resource.rb
256
258
  homepage: https://github.com/gedera/bug_bunny
257
259
  licenses:
258
260
  - MIT
data/bin_client.rb DELETED
@@ -1,51 +0,0 @@
1
- # bin_client.rb
2
- require_relative 'lib/bug_bunny'
3
- $stdout.sync = true # <--- Agrega esto
4
-
5
- # 1. Configuración
6
- BugBunny.configure do |config|
7
- config.host = 'localhost'
8
- config.username = 'wisproMQ'
9
- config.password = 'wisproMQ'
10
- config.vhost = 'sync.devel'
11
- config.logger = Logger.new(STDOUT)
12
- config.rpc_timeout = 5
13
- end
14
-
15
- # 2. Pool
16
- POOL = ConnectionPool.new(size: 2, timeout: 5) do
17
- BugBunny.create_connection
18
- end
19
-
20
- # 3. Cliente
21
- client = BugBunny.new(pool: POOL)
22
-
23
- # --- PRUEBA 1: Publish ---
24
- puts "\n[1] Enviando mensaje asíncrono (Publish)..."
25
-
26
- # AGREGADO: exchange_type: 'topic'
27
- client.publish('test/ping', exchange: 'test_exchange', exchange_type: 'topic', routing_key: 'test.ping') do |req|
28
- req.body = { msg: 'Hola, soy invisible' }
29
- end
30
-
31
- puts " -> Enviado."
32
- sleep 1
33
-
34
- # --- PRUEBA 2: RPC ---
35
- puts "\n[2] Enviando petición síncrona (Request)..."
36
-
37
- begin
38
- # AGREGADO: exchange_type: 'topic'
39
- response = client.request('test/123/ping', exchange: 'test_exchange', exchange_type: 'topic', routing_key: 'test.ping') do |req|
40
- req.body = { data: 'Importante' }
41
- req.timeout = 3
42
- req.headers['X-Source'] = 'Terminal'
43
- end
44
-
45
- puts " -> ✅ RESPUESTA RECIBIDA:"
46
- puts " Status: #{response['status']}"
47
- puts " Body: #{response['body']}"
48
-
49
- rescue BugBunny::RequestTimeout
50
- puts " -> ❌ Error: Timeout esperando respuesta."
51
- end