bug_bunny 3.1.0 → 3.1.1

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.
@@ -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.1
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
data/bin_suite.rb DELETED
@@ -1,106 +0,0 @@
1
- # bin_suite.rb
2
- require_relative 'test_helper'
3
- require_relative 'test_resource' # Cargamos la clase TestUser
4
-
5
- # Cliente "Raw" para pruebas manuales
6
- raw_client = BugBunny.new(pool: TEST_POOL)
7
-
8
- def assert(condition, msg)
9
- if condition
10
- puts " ✅ PASS: #{msg}"
11
- else
12
- puts " ❌ FAIL: #{msg}"
13
- end
14
- end
15
-
16
- puts "\n🔎 --- INICIANDO SUITE DE PRUEBAS BUG BUNNY ---"
17
-
18
- # ---------------------------------------------------------
19
- # TEST 1: RPC Manual (Raw Client)
20
- # ---------------------------------------------------------
21
- puts "\n[1] Probando RPC Manual (Client#request)..."
22
- begin
23
- # Notar la routing key: test_user.ping
24
- response = raw_client.request('test_user/ping', exchange: 'test_exchange', exchange_type: 'topic', routing_key: 'test_user.ping')
25
- assert(response['body']['message'] == 'Pong!', "Respuesta RPC recibida correctamente")
26
- rescue => e
27
- assert(false, "Error RPC: #{e.class} - #{e.message}")
28
- end
29
-
30
- # ---------------------------------------------------------
31
- # TEST 2: Resource Finder (ORM)
32
- # ---------------------------------------------------------
33
- puts "\n[2] Probando BugBunny::Resource (Estilo Rails)..."
34
-
35
- # YA NO NECESITAS with_scope
36
- puts " -> Buscando usuario ID 123..."
37
- user = TestUser.find(123)
38
-
39
- if user
40
- assert(user.is_a?(TestUser), "El objeto retornado es un TestUser")
41
- assert(user.name == "Gabriel", "El nombre cargó correctamente")
42
- assert(user.persisted?, "El objeto figura como persistido")
43
- else
44
- assert(false, "No se encontró el usuario (Check worker logs)")
45
- end
46
-
47
- # ---------------------------------------------------------
48
- # TEST 3: Resource Create (ORM)
49
- # ---------------------------------------------------------
50
- puts "\n[3] Probando Resource Creation..."
51
- puts " -> Creando usuario nuevo..."
52
- new_user = TestUser.create(name: "Nuevo User", email: "new@test.com")
53
- if new_user.persisted?
54
- assert(new_user.persisted?, "El usuario se guardó y recibió ID")
55
- assert(new_user.id.present?, "Tiene ID asignado por el worker (#{new_user.id})")
56
- else
57
- assert(false, "Fallo al crear usuario: #{new_user.errors.full_messages}")
58
- end
59
-
60
- # ---------------------------------------------------------
61
- # TEST 4: Validaciones Locales
62
- # ---------------------------------------------------------
63
- puts "\n[4] Probando Validaciones Locales..."
64
- invalid_user = TestUser.new(email: "sin_nombre@test.com")
65
- assert(invalid_user.valid? == false, "Usuario sin nombre es inválido")
66
- assert(invalid_user.errors[:name].any?, "Tiene error en el campo :name")
67
-
68
- # ---------------------------------------------------------
69
- # TEST 5: Probando Configuración Dinámica (.with)...
70
- # ---------------------------------------------------------
71
- puts "\n[5] Probando Configuración Dinámica (.with)..."
72
-
73
- # Probamos cambiar el routing key prefix temporalmente
74
- begin
75
- # Forzamos una routing key que no existe
76
- puts " -> Intentando ruta incorrecta (esperando timeout)..."
77
- TestUser.with(routing_key: 'ruta.incorrecta').find(123)
78
- assert(false, "Debería haber fallado por timeout")
79
- rescue BugBunny::RequestTimeout, BugBunny::ClientError
80
- # Nota: Dependiendo de tu config, puede dar Timeout o 501 si llega a un worker default
81
- puts " ✅ PASS: El override funcionó (timeout o error esperado en ruta incorrecta)"
82
- end
83
-
84
- # Probamos que vuelve a la normalidad
85
- user = TestUser.find(123)
86
- assert(user.present?, " ✅ PASS: La configuración volvió a la normalidad")
87
-
88
- # ---------------------------------------------------------
89
- # TEST 6: Filtrado Complejo (Query String Nested - FIX Rack)
90
- # ---------------------------------------------------------
91
- puts "\n[6] Probando Resource.where con filtros anidados (Fix Rack)..."
92
-
93
- begin
94
- # Esto fallaba antes (generaba string feo en la URL: {:active=>true})
95
- # Al usar Rack, esto genera: ?q[active]=true&q[roles][]=admin
96
- # No necesitamos que el worker responda algo real, solo que el request SALGA sin explotar URI.
97
- TestUser.where(q: { active: true, roles: ['admin'] })
98
- puts " ✅ PASS: .where generó la query anidada correctamente sin errores de URI."
99
- rescue URI::InvalidURIError => e
100
- assert(false, "❌ FAIL: URI Inválida (El fix de Rack no funcionó): #{e.message}")
101
- rescue => e
102
- # Si falla por conexión o 404 está bien, lo importante es que no falle al serializar
103
- puts " ✅ PASS: El request se envió correctamente (aunque el worker responda: #{e.class}). Serialización OK."
104
- end
105
-
106
- puts "\n🏁 SUITE FINALIZADA"
data/bin_worker.rb DELETED
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # bin_worker.rb
4
- require 'bundler/setup'
5
- require 'bug_bunny'
6
- require_relative 'test_controller' # Cargamos los controladores
7
-
8
- puts '🐰 WORKER INICIADO (Exchange: Topic)...'
9
-
10
- # Configuración básica
11
- BugBunny.configure do |config|
12
- config.logger = Logger.new($stdout)
13
- config.logger.level = Logger::DEBUG
14
- end
15
-
16
- # Iniciar el Consumidor
17
- # Escucha en la cola 'bug_bunny_queue', atada al exchange 'test_exchange' con routing key '#' (todo)
18
- connection = BugBunny.create_connection
19
-
20
- BugBunny::Consumer.subscribe(
21
- connection: connection,
22
- exchange_name: 'test_exchange',
23
- exchange_type: 'topic',
24
- queue_name: 'bug_bunny_test_queue',
25
- routing_key: '#' # Wildcard para recibir todo en este test
26
- )
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../test_helper'
4
- require 'connection_pool'
5
-
6
- class FireAndForgetTest < Minitest::Test
7
- def setup
8
- skip "RabbitMQ no disponible" unless TestHelper.rabbitmq_available?
9
-
10
- # 1. Configuración
11
- BugBunny.configure do |c|
12
- c.host = 'localhost'
13
- c.username = 'wisproMQ'
14
- c.password = 'wisproMQ'
15
- c.port = 5672
16
- c.logger = Logger.new(nil)
17
- end
18
-
19
- @pool = ConnectionPool.new(size: 1, timeout: 5) { BugBunny.create_connection }
20
-
21
- # 2. Infraestructura de Test
22
- @queue_name = 'test_fire_queue'
23
- @exchange_name = 'test_fire_exchange'
24
- @routing_key = 'logs.error'
25
-
26
- # Usamos una Queue de Ruby para pasar el mensaje del Consumer al Test
27
- @message_bucket = Queue.new
28
-
29
- # 3. Consumidor "Espía"
30
- @conn_consumer = BugBunny.create_connection
31
- @consumer_thread = Thread.new do
32
- ch = @conn_consumer.create_channel
33
- # IMPORTANTE: Aquí declaramos el exchange como TOPIC
34
- x = ch.topic(@exchange_name)
35
- q = ch.queue(@queue_name).bind(x, routing_key: @routing_key)
36
-
37
- q.subscribe(block: true) do |delivery_info, properties, body|
38
- @message_bucket << {
39
- body: body,
40
- routing_key: delivery_info.routing_key
41
- }
42
- end
43
- end
44
- sleep 0.5 # Wait boot
45
- end
46
-
47
- def teardown
48
- return unless @conn_consumer
49
- @conn_consumer.close
50
- @consumer_thread.kill
51
- end
52
-
53
- def test_publish_directly
54
- client = BugBunny::Client.new(pool: @pool)
55
- payload = { system: 'payment', error: 'timeout' }
56
-
57
- # 1. Disparamos (Fire)
58
- client.publish(
59
- 'logs/error',
60
- body: payload,
61
- exchange: @exchange_name,
62
- exchange_type: 'topic',
63
- routing_key: @routing_key
64
- )
65
-
66
- # 2. Verificamos asíncronamente
67
- received = nil
68
- Timeout.timeout(2) do
69
- received = @message_bucket.pop
70
- end
71
-
72
- # 3. Aserciones
73
- assert_equal payload.to_json, received[:body]
74
- assert_equal @routing_key, received[:routing_key]
75
- end
76
- end