bug_bunny 3.0.6 → 3.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.
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+ require 'connection_pool'
5
+
6
+ # Controlador Dummy en memoria
7
+ module Rabbit
8
+ module Controllers
9
+ class IntegrationTest < BugBunny::Controller
10
+ def index
11
+ # CORRECCIÓN: Usar 'render' en lugar de retornar un hash implícito.
12
+ # Si solo retornas el valor, el Controller lo ignora y devuelve 204 (No Content).
13
+ render status: 200, json: { message: 'pong' }
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ class RpcFlowTest < Minitest::Test
20
+ def setup
21
+ skip "RabbitMQ no disponible" unless TestHelper.rabbitmq_available?
22
+
23
+ # 1. Configuración Real
24
+ BugBunny.configure do |c|
25
+ c.host = 'localhost'
26
+ c.username = 'wisproMQ'
27
+ c.password = 'wisproMQ'
28
+ c.port = 5672
29
+ c.logger = Logger.new(nil)
30
+ end
31
+
32
+ # 2. Pool para el Cliente
33
+ @pool = ConnectionPool.new(size: 1, timeout: 5) do
34
+ BugBunny.create_connection
35
+ end
36
+
37
+ # 3. Consumer en hilo separado
38
+ @conn_consumer = BugBunny.create_connection
39
+ @queue_name = 'test_integration_queue'
40
+ @exchange_name = 'test_integration_exchange'
41
+
42
+ @consumer_thread = Thread.new do
43
+ BugBunny::Consumer.subscribe(
44
+ connection: @conn_consumer,
45
+ queue_name: @queue_name,
46
+ exchange_name: @exchange_name,
47
+ routing_key: 'test_key',
48
+ block: true
49
+ )
50
+ end
51
+ sleep 0.5 # Esperar arranque
52
+ end
53
+
54
+ def teardown
55
+ return unless @conn_consumer
56
+ @conn_consumer.close
57
+ @consumer_thread.kill
58
+ end
59
+
60
+ def test_rpc_request_response
61
+ client = BugBunny::Client.new(pool: @pool)
62
+
63
+ # Usamos request() para RPC
64
+ response = client.request(
65
+ 'integration_test',
66
+ body: {},
67
+ exchange: @exchange_name,
68
+ routing_key: 'test_key'
69
+ )
70
+
71
+ # Ahora sí esperamos 200 OK
72
+ assert_equal 200, response['status']
73
+
74
+ body = response['body']
75
+ msg = body.is_a?(Hash) ? (body['message'] || body[:message]) : body
76
+ assert_equal 'pong', msg
77
+ end
78
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/setup'
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
5
+ require 'bug_bunny'
6
+
7
+ require 'minitest/autorun'
8
+ require 'mocha/minitest'
9
+ require 'logger'
10
+
11
+ BugBunny.configure do |config|
12
+ config.logger = Logger.new(nil)
13
+ config.bunny_logger = Logger.new(nil)
14
+ end
15
+
16
+ module TestHelper
17
+ def self.rabbitmq_available?
18
+ socket = TCPSocket.new('localhost', 5672)
19
+ socket.close
20
+ true
21
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
22
+ false
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class ConfigurationTest < Minitest::Test
6
+ def setup
7
+ BugBunny.configuration = nil # Resetear singleton
8
+ end
9
+
10
+ def test_default_values
11
+ config = BugBunny::Configuration.new
12
+
13
+ assert_equal '127.0.0.1', config.host
14
+ assert_equal 5672, config.port # Validamos el fix de la v3.0.6
15
+ assert_equal 'guest', config.username
16
+ assert_equal '/', config.vhost
17
+ assert config.automatically_recover
18
+ end
19
+
20
+ def test_configure_block
21
+ BugBunny.configure do |c|
22
+ c.host = 'rabbit.prod'
23
+ c.port = 1234
24
+ end
25
+
26
+ assert_equal 'rabbit.prod', BugBunny.configuration.host
27
+ assert_equal 1234, BugBunny.configuration.port
28
+ end
29
+
30
+ def test_url_generation
31
+ config = BugBunny::Configuration.new
32
+ config.host = 'host'
33
+ config.port = 5672
34
+ config.username = 'u'
35
+ config.password = 'p'
36
+
37
+ # CAMBIO: Agregamos el slash extra al final que genera la interpolación
38
+ assert_equal "amqp://u:p@host:5672//", config.url
39
+ end
40
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class ConsumerTest < Minitest::Test
6
+ def setup
7
+ @connection = mock('Bunny::Session')
8
+ @channel = mock('Bunny::Channel')
9
+
10
+ # Stubs para que Consumer.new no falle
11
+ @connection.stubs(:open?).returns(true)
12
+ @connection.stubs(:create_channel).returns(@channel)
13
+ @connection.stubs(:close)
14
+
15
+ # Stubs para Session y Channel
16
+ @channel.stubs(:confirm_select)
17
+ @channel.stubs(:prefetch)
18
+ @channel.stubs(:open?).returns(true)
19
+ @channel.stubs(:close)
20
+
21
+ @consumer = BugBunny::Consumer.new(@connection)
22
+ end
23
+
24
+ def test_router_dispatch
25
+ # Probamos la lógica interna del Router (método privado router_dispatch)
26
+
27
+ # Caso 1: POST /users -> Create
28
+ route_post = @consumer.send(:router_dispatch, 'POST', 'users')
29
+ assert_equal 'users', route_post[:controller]
30
+ assert_equal 'create', route_post[:action]
31
+
32
+ # Caso 2: GET /users/123 -> Show
33
+ route_show = @consumer.send(:router_dispatch, 'GET', 'users/123')
34
+ assert_equal 'users', route_show[:controller]
35
+ assert_equal 'show', route_show[:action]
36
+ assert_equal '123', route_show[:id]
37
+
38
+ # Caso 3: Custom Action (POST /users/1/promote)
39
+ route_custom = @consumer.send(:router_dispatch, 'POST', 'users/1/promote')
40
+ assert_equal 'users', route_custom[:controller]
41
+ assert_equal 'promote', route_custom[:action]
42
+ assert_equal '1', route_custom[:id]
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class ControllerHeadersTest < Minitest::Test
6
+ # Controlador Dummy para probar headers
7
+ class HeadersController < BugBunny::Controller
8
+ def echo_headers
9
+ # 1. LEER del Request
10
+ client_token = headers['X-Client-Token']
11
+
12
+ # 2. ESCRIBIR en el Response
13
+ response_headers['X-Server-Time'] = '123456789'
14
+ response_headers['X-Received-Token'] = client_token
15
+
16
+ render status: 200, json: { message: 'ok' }
17
+ end
18
+ end
19
+
20
+ def test_headers_flow
21
+ # Simulamos los headers que enviaría el Consumer al Controller
22
+ request_headers = {
23
+ action: 'echo_headers',
24
+ 'X-Client-Token' => 'secret_abc'
25
+ }
26
+
27
+ # Ejecutamos el pipeline del controlador
28
+ response = HeadersController.call(headers: request_headers, body: {})
29
+
30
+ # Verificamos la estructura de la respuesta
31
+ assert_equal 200, response[:status]
32
+
33
+ # Verificamos que los headers de respuesta estén presentes
34
+ assert_kind_of Hash, response[:headers]
35
+ assert_equal '123456789', response[:headers]['X-Server-Time']
36
+ assert_equal 'secret_abc', response[:headers]['X-Received-Token']
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class HybridResourceTest < Minitest::Test
6
+ # Definimos una clase temporal para el test
7
+ class Product < BugBunny::Resource
8
+ # Atributos explícitos (Tipados)
9
+ attribute :price, :decimal, default: 0.0
10
+ attribute :active, :boolean, default: false
11
+
12
+ # Validaciones mixtas
13
+ validates :price, numericality: { greater_than: 0 }
14
+ validates :name, presence: true # 'name' será dinámico
15
+ end
16
+
17
+ def test_hybrid_attributes_assignment
18
+ # Caso: Asignación mixta en initialize
19
+ p = Product.new(price: "10.5", name: "Laptop", active: "1")
20
+
21
+ # 1. Atributo Tipado (:decimal) -> Coerción automática
22
+ assert_kind_of BigDecimal, p.price
23
+ assert_equal 10.5, p.price
24
+
25
+ # 2. Atributo Tipado (:boolean) -> Coerción automática
26
+ assert_equal true, p.active
27
+
28
+ # 3. Atributo Dinámico (method_missing) -> String directo
29
+ assert_equal "Laptop", p.name
30
+ end
31
+
32
+ def test_hybrid_serialization
33
+ p = Product.new(price: 20.0, name: "Mouse")
34
+
35
+ # Simulamos serialización (lo que se enviaría a RabbitMQ)
36
+ payload = p.attributes_for_serialization
37
+
38
+ assert_equal 20.0, payload['price']
39
+ assert_equal "Mouse", payload['name']
40
+ assert_equal false, payload['active'] # Default value
41
+ end
42
+
43
+ def test_hybrid_dirty_tracking
44
+ p = Product.new(price: 10.0, name: "Old Name")
45
+ p.persisted = true
46
+ p.send(:clear_changes_information)
47
+
48
+ # Modificamos uno tipado y uno dinámico
49
+ p.price = 15.0
50
+ p.name = "New Name"
51
+
52
+ changes = p.changes_to_send
53
+
54
+ # Ambos deben aparecer en los cambios
55
+ assert_includes changes.keys, 'price'
56
+ assert_includes changes.keys, 'name'
57
+ assert_equal 15.0, changes['price']
58
+ assert_equal "New Name", changes['name']
59
+ end
60
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class MiddlewareTest < Minitest::Test
6
+ # Middleware dummy para rastrear ejecución
7
+ class TrackerMiddleware < BugBunny::Middleware::Base
8
+ def on_request(env)
9
+ env[:trace] << 'req_in'
10
+ end
11
+
12
+ def on_complete(response)
13
+ response[:trace] << 'res_out'
14
+ end
15
+ end
16
+
17
+ def setup
18
+ @stack = BugBunny::Middleware::Stack.new
19
+ end
20
+
21
+ def test_execution_order
22
+ # La "App" final (Producer)
23
+ final_app = ->(env) {
24
+ env[:trace] << 'executing'
25
+ { body: 'ok', trace: env[:trace] }
26
+ }
27
+
28
+ @stack.use TrackerMiddleware
29
+ chain = @stack.build(final_app)
30
+
31
+ env = { trace: [] }
32
+ response = chain.call(env)
33
+
34
+ # El orden debe ser: Entrada -> Ejecución -> Salida (Cebolla)
35
+ expected_trace = ['req_in', 'executing', 'res_out']
36
+ assert_equal expected_trace, response[:trace]
37
+ end
38
+
39
+ def test_json_response_parsing
40
+ # Simulamos una respuesta JSON cruda
41
+ app = ->(_) { { 'body' => '{"foo":"bar"}', 'status' => 200 } }
42
+
43
+ middleware = BugBunny::Middleware::JsonResponse.new(app)
44
+ response = middleware.call({})
45
+
46
+ # Debe convertirse en Hash
47
+ assert_kind_of Hash, response['body']
48
+ assert_equal 'bar', response['body']['foo']
49
+ end
50
+
51
+ def test_raise_error_handling
52
+ # Simulamos un error 404
53
+ app = ->(_) { { 'status' => 404, 'body' => 'Not Found' } }
54
+
55
+ middleware = BugBunny::Middleware::RaiseError.new(app)
56
+
57
+ assert_raises(BugBunny::NotFound) do
58
+ middleware.call({})
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class ConsumerTest < Minitest::Test
6
+ def setup
7
+ # CORRECCIÓN: Asegurar que existe configuración para evitar error en Session
8
+ BugBunny.configure do |c|
9
+ c.channel_prefetch = 1
10
+ end
11
+
12
+ @connection = mock('Bunny::Session')
13
+ @channel = mock('Bunny::Channel')
14
+
15
+ # Stubs para que Consumer.new no falle
16
+ @connection.stubs(:open?).returns(true)
17
+ @connection.stubs(:create_channel).returns(@channel)
18
+ @connection.stubs(:close)
19
+
20
+ # Stubs para Session y Channel
21
+ @channel.stubs(:confirm_select)
22
+ @channel.stubs(:prefetch)
23
+ @channel.stubs(:open?).returns(true)
24
+ @channel.stubs(:close)
25
+
26
+ @consumer = BugBunny::Consumer.new(@connection)
27
+ end
28
+
29
+ def test_router_dispatch
30
+ # Probamos la lógica interna del Router (método privado router_dispatch)
31
+
32
+ # Caso 1: POST /users -> Create
33
+ route_post = @consumer.send(:router_dispatch, 'POST', 'users')
34
+ assert_equal 'users', route_post[:controller]
35
+ assert_equal 'create', route_post[:action]
36
+
37
+ # Caso 2: GET /users/123 -> Show
38
+ route_show = @consumer.send(:router_dispatch, 'GET', 'users/123')
39
+ assert_equal 'users', route_show[:controller]
40
+ assert_equal 'show', route_show[:action]
41
+ assert_equal '123', route_show[:id]
42
+
43
+ # Caso 3: Custom Action (POST /users/1/promote)
44
+ route_custom = @consumer.send(:router_dispatch, 'POST', 'users/1/promote')
45
+ assert_equal 'users', route_custom[:controller]
46
+ assert_equal 'promote', route_custom[:action]
47
+ assert_equal '1', route_custom[:id]
48
+ end
49
+ 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.0.6
4
+ version: 3.1.0
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-17 00:00:00.000000000 Z
11
+ date: 2026-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -178,6 +178,34 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0.9'
181
+ - !ruby/object:Gem::Dependency
182
+ name: minitest
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '5.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '5.0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: mocha
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '2.0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '2.0'
181
209
  description: BugBunny is a lightweight RPC framework for Ruby on Rails over RabbitMQ.
182
210
  It simulates a RESTful architecture with an intelligent router, Active Record-like
183
211
  resources, and middleware support.
@@ -213,6 +241,15 @@ files:
213
241
  - lib/generators/bug_bunny/install/install_generator.rb
214
242
  - lib/generators/bug_bunny/install/templates/initializer.rb
215
243
  - sig/bug_bunny.rbs
244
+ - test/integration/fire_and_forget_test.rb
245
+ - test/integration/rpc_flow_test.rb
246
+ - 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
216
253
  - test_controller.rb
217
254
  - test_helper.rb
218
255
  - test_resource.rb