bug_bunny 4.13.0 → 4.16.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +51 -4
- data/lib/bug_bunny/client.rb +37 -0
- data/lib/bug_bunny/configuration.rb +14 -0
- data/lib/bug_bunny/exception.rb +61 -0
- data/lib/bug_bunny/producer.rb +154 -7
- data/lib/bug_bunny/request.rb +5 -1
- data/lib/bug_bunny/session.rb +98 -3
- data/lib/bug_bunny/version.rb +1 -1
- data/skill/SKILL.md +73 -7
- data/skill/references/client-middleware.md +66 -15
- data/skill/references/errores.md +7 -0
- data/spec/integration/publisher_confirms_spec.rb +304 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/support/integration_helper.rb +8 -1
- data/spec/unit/client_session_pool_spec.rb +72 -0
- data/spec/unit/configuration_spec.rb +12 -0
- data/spec/unit/producer_spec.rb +266 -0
- data/spec/unit/request_spec.rb +16 -0
- data/spec/unit/session_spec.rb +72 -0
- metadata +4 -3
data/spec/unit/producer_spec.rb
CHANGED
|
@@ -95,6 +95,31 @@ RSpec.describe BugBunny::Producer do
|
|
|
95
95
|
messaging_destination_name: 'events_x'
|
|
96
96
|
)
|
|
97
97
|
end
|
|
98
|
+
|
|
99
|
+
it 'producer.published incluye duration_s y campos de routing' do
|
|
100
|
+
request = BugBunny::Request.new('users')
|
|
101
|
+
request.exchange = 'users_x'
|
|
102
|
+
request.method = :post
|
|
103
|
+
request.correlation_id = 'corr-1'
|
|
104
|
+
|
|
105
|
+
fake_exchange = double('exchange')
|
|
106
|
+
allow(session).to receive(:exchange).and_return(fake_exchange)
|
|
107
|
+
allow(fake_exchange).to receive(:publish)
|
|
108
|
+
|
|
109
|
+
producer.fire(request)
|
|
110
|
+
|
|
111
|
+
published_event = logged_events.find { |e| e[:event] == 'producer.published' }
|
|
112
|
+
expect(published_event).not_to be_nil
|
|
113
|
+
expect(published_event[:level]).to eq(:info)
|
|
114
|
+
expect(published_event[:kwargs]).to include(
|
|
115
|
+
method: 'POST',
|
|
116
|
+
path: 'users',
|
|
117
|
+
routing_key: 'users',
|
|
118
|
+
messaging_message_id: 'corr-1'
|
|
119
|
+
)
|
|
120
|
+
expect(published_event[:kwargs][:duration_s]).to be_a(Numeric)
|
|
121
|
+
expect(published_event[:kwargs][:duration_s]).to be >= 0
|
|
122
|
+
end
|
|
98
123
|
end
|
|
99
124
|
|
|
100
125
|
describe '#rpc — log events incluyen campos OTel' do
|
|
@@ -182,6 +207,28 @@ RSpec.describe BugBunny::Producer do
|
|
|
182
207
|
messaging_message_id: 'corr-reply-test'
|
|
183
208
|
)
|
|
184
209
|
end
|
|
210
|
+
|
|
211
|
+
it 'producer.rpc_response_received incluye duration_s total' do
|
|
212
|
+
request = BugBunny::Request.new('users')
|
|
213
|
+
request.exchange = 'users_x'
|
|
214
|
+
request.method = :get
|
|
215
|
+
|
|
216
|
+
allow(rpc_producer).to receive(:ensure_reply_listener!)
|
|
217
|
+
|
|
218
|
+
ivar = Concurrent::IVar.new
|
|
219
|
+
allow(Concurrent::IVar).to receive(:new).and_return(ivar)
|
|
220
|
+
|
|
221
|
+
request.correlation_id = 'corr-dur'
|
|
222
|
+
rpc_producer.instance_variable_get(:@pending_requests)['corr-dur'] = ivar
|
|
223
|
+
|
|
224
|
+
Thread.new { ivar.set({ body: '{"ok":true}', headers: {} }) }.join(0.1)
|
|
225
|
+
|
|
226
|
+
rpc_producer.rpc(request)
|
|
227
|
+
|
|
228
|
+
response_event = logged_events.find { |e| e[:event] == 'producer.rpc_response_received' }
|
|
229
|
+
expect(response_event[:kwargs][:duration_s]).to be_a(Numeric)
|
|
230
|
+
expect(response_event[:kwargs][:duration_s]).to be >= 0
|
|
231
|
+
end
|
|
185
232
|
end
|
|
186
233
|
end
|
|
187
234
|
|
|
@@ -201,6 +248,12 @@ RSpec.describe BugBunny::Producer do
|
|
|
201
248
|
s = instance_double(BugBunny::Session)
|
|
202
249
|
allow(s).to receive(:exchange).and_return(fake_exchange)
|
|
203
250
|
allow(s).to receive(:channel).and_return(mock_channel)
|
|
251
|
+
# Return registry stubs: tests que activan `mandatory: true` con `return_raise`
|
|
252
|
+
# default invocan estos hooks. Devolvemos un event/slot vacío (no return).
|
|
253
|
+
allow(s).to receive(:register_return_listener) do |_cid|
|
|
254
|
+
[Concurrent::Event.new, { event: Concurrent::Event.new, info: nil }]
|
|
255
|
+
end
|
|
256
|
+
allow(s).to receive(:unregister_return_listener)
|
|
204
257
|
s
|
|
205
258
|
end
|
|
206
259
|
|
|
@@ -252,6 +305,18 @@ RSpec.describe BugBunny::Producer do
|
|
|
252
305
|
expect(nack_event).to be_nil
|
|
253
306
|
end
|
|
254
307
|
|
|
308
|
+
it 'logea producer.confirmed con publish_duration_s, confirm_duration_s y duration_s total' do
|
|
309
|
+
confirmed_producer.confirmed(build_request)
|
|
310
|
+
|
|
311
|
+
ev = logged_events.find { |e| e[:event] == 'producer.confirmed' }
|
|
312
|
+
expect(ev).not_to be_nil
|
|
313
|
+
expect(ev[:level]).to eq(:info)
|
|
314
|
+
expect(ev[:kwargs]).to include(method: 'POST', path: 'acct.start', routing_key: 'acct.start')
|
|
315
|
+
expect(ev[:kwargs][:publish_duration_s]).to be_a(Numeric)
|
|
316
|
+
expect(ev[:kwargs][:confirm_duration_s]).to be_a(Numeric)
|
|
317
|
+
expect(ev[:kwargs][:duration_s]).to be_a(Numeric)
|
|
318
|
+
end
|
|
319
|
+
|
|
255
320
|
context 'cuando el broker NACKea (wait_for_confirms devuelve false)' do
|
|
256
321
|
before do
|
|
257
322
|
allow(mock_channel).to receive(:wait_for_confirms).and_return(false)
|
|
@@ -325,5 +390,206 @@ RSpec.describe BugBunny::Producer do
|
|
|
325
390
|
expect { confirmed_producer.confirmed(build_request) }
|
|
326
391
|
.to raise_error(BugBunny::CommunicationError, 'chan dead')
|
|
327
392
|
end
|
|
393
|
+
|
|
394
|
+
describe 'return_raise (basic.return + mandatory)' do
|
|
395
|
+
# Dispara el `basic.return` desde el reader-thread simulado. El stub de
|
|
396
|
+
# wait_for_confirms invoca este lambda *antes* de retornar true, simulando
|
|
397
|
+
# el orden AMQP wire: return precede al ack.
|
|
398
|
+
def stub_return_during_wait(producer, return_info)
|
|
399
|
+
allow(mock_channel).to receive(:wait_for_confirms) do
|
|
400
|
+
props = double('properties', correlation_id: producer_pending_cid(producer))
|
|
401
|
+
producer.instance_variable_get(:@session)
|
|
402
|
+
.send(:handle_broker_return, return_info, props, 'payload')
|
|
403
|
+
true
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def producer_pending_cid(producer)
|
|
408
|
+
session = producer.instance_variable_get(:@session)
|
|
409
|
+
registry = session.instance_variable_get(:@pending_returns)
|
|
410
|
+
cids = []
|
|
411
|
+
registry.each_pair { |cid, _slot| cids << cid }
|
|
412
|
+
cids.first
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
let(:return_info) do
|
|
416
|
+
Struct.new(:reply_code, :reply_text, :exchange, :routing_key)
|
|
417
|
+
.new(312, 'NO_ROUTE', 'acct_x', 'acct.unbound')
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
let(:real_session) do
|
|
421
|
+
s = BugBunny::Session.new(BunnyMocks::FakeConnection.new(true, mock_channel))
|
|
422
|
+
allow(s).to receive(:channel).and_return(mock_channel)
|
|
423
|
+
allow(s).to receive(:exchange).and_return(fake_exchange)
|
|
424
|
+
s
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
let(:return_producer) do
|
|
428
|
+
p = described_class.new(real_session)
|
|
429
|
+
allow(p).to receive(:safe_log) do |level, event, **kwargs|
|
|
430
|
+
logged_events << { level: level, event: event, kwargs: kwargs }
|
|
431
|
+
end
|
|
432
|
+
p
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
before do
|
|
436
|
+
BugBunny.configuration.on_return = nil
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
after do
|
|
440
|
+
BugBunny.configuration.on_return = nil
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def build_mandatory_request
|
|
444
|
+
req = BugBunny::Request.new('acct.start')
|
|
445
|
+
req.exchange = 'acct_x'
|
|
446
|
+
req.method = :post
|
|
447
|
+
req.body = { tenant: 42 }
|
|
448
|
+
req.mandatory = true
|
|
449
|
+
req
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
it 'levanta PublishUnroutable cuando llega basic.return + ack y mandatory:true (default)' do
|
|
453
|
+
stub_return_during_wait(return_producer, return_info)
|
|
454
|
+
|
|
455
|
+
req = build_mandatory_request
|
|
456
|
+
|
|
457
|
+
expect { return_producer.confirmed(req) }.to raise_error(BugBunny::PublishUnroutable) do |err|
|
|
458
|
+
expect(err.path).to eq('acct.start')
|
|
459
|
+
expect(err.exchange).to eq('acct_x')
|
|
460
|
+
expect(err.routing_key).to eq('acct.unbound')
|
|
461
|
+
expect(err.reply_code).to eq(312)
|
|
462
|
+
expect(err.reply_text).to eq('NO_ROUTE')
|
|
463
|
+
expect(err.correlation_id).to eq(req.correlation_id)
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
it 'logea producer.publish_unroutable antes de levantar' do
|
|
468
|
+
stub_return_during_wait(return_producer, return_info)
|
|
469
|
+
|
|
470
|
+
expect { return_producer.confirmed(build_mandatory_request) }
|
|
471
|
+
.to raise_error(BugBunny::PublishUnroutable)
|
|
472
|
+
|
|
473
|
+
ev = logged_events.find { |e| e[:event] == 'producer.publish_unroutable' }
|
|
474
|
+
expect(ev).not_to be_nil
|
|
475
|
+
expect(ev[:level]).to eq(:warn)
|
|
476
|
+
expect(ev[:kwargs]).to include(
|
|
477
|
+
path: 'acct.start',
|
|
478
|
+
exchange: 'acct_x',
|
|
479
|
+
routing_key: 'acct.unbound',
|
|
480
|
+
reply_code: 312
|
|
481
|
+
)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
it 'auto-asigna correlation_id cuando falta y return_raise está activo' do
|
|
485
|
+
stub_return_during_wait(return_producer, return_info)
|
|
486
|
+
req = build_mandatory_request
|
|
487
|
+
expect(req.correlation_id).to be_nil
|
|
488
|
+
|
|
489
|
+
expect { return_producer.confirmed(req) }.to raise_error(BugBunny::PublishUnroutable)
|
|
490
|
+
expect(req.correlation_id).to be_a(String)
|
|
491
|
+
expect(req.correlation_id).not_to be_empty
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
it 'limpia el listener del registry tras un return (no leak)' do
|
|
495
|
+
stub_return_during_wait(return_producer, return_info)
|
|
496
|
+
req = build_mandatory_request
|
|
497
|
+
|
|
498
|
+
expect { return_producer.confirmed(req) }.to raise_error(BugBunny::PublishUnroutable)
|
|
499
|
+
|
|
500
|
+
registry = real_session.instance_variable_get(:@pending_returns)
|
|
501
|
+
expect(registry.size).to eq(0)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
it 'limpia el listener del registry tras un ack normal sin return' do
|
|
505
|
+
# mock_channel.wait_for_confirms default ya devuelve true sin disparar return
|
|
506
|
+
req = build_mandatory_request
|
|
507
|
+
result = return_producer.confirmed(req)
|
|
508
|
+
|
|
509
|
+
expect(result).to eq('status' => 202, 'body' => nil)
|
|
510
|
+
registry = real_session.instance_variable_get(:@pending_returns)
|
|
511
|
+
expect(registry.size).to eq(0)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
it 'limpia el listener del registry tras timeout en wait_for_confirms' do
|
|
515
|
+
allow(mock_channel).to receive(:wait_for_confirms) {
|
|
516
|
+
sleep 1
|
|
517
|
+
true
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
req = build_mandatory_request
|
|
521
|
+
req.confirm_timeout = 0.05
|
|
522
|
+
|
|
523
|
+
expect { return_producer.confirmed(req) }.to raise_error(BugBunny::RequestTimeout)
|
|
524
|
+
registry = real_session.instance_variable_get(:@pending_returns)
|
|
525
|
+
expect(registry.size).to eq(0)
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
it 'no levanta cuando el request override `return_raise: false`' do
|
|
529
|
+
# No stub de return — el listener no se registra siquiera.
|
|
530
|
+
req = build_mandatory_request
|
|
531
|
+
req.return_raise = false
|
|
532
|
+
|
|
533
|
+
result = return_producer.confirmed(req)
|
|
534
|
+
expect(result).to eq('status' => 202, 'body' => nil)
|
|
535
|
+
# Sin listener registrado (flag off) → no hubo set up
|
|
536
|
+
registry = real_session.instance_variable_get(:@pending_returns)
|
|
537
|
+
expect(registry.size).to eq(0)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
it 'no levanta cuando la config global tiene return_raise=false (aunque mandatory esté on)' do
|
|
541
|
+
allow(BugBunny.configuration).to receive(:return_raise).and_return(false)
|
|
542
|
+
|
|
543
|
+
req = build_mandatory_request
|
|
544
|
+
result = return_producer.confirmed(req)
|
|
545
|
+
|
|
546
|
+
expect(result).to eq('status' => 202, 'body' => nil)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
it 'override per-request gana sobre la config global' do
|
|
550
|
+
allow(BugBunny.configuration).to receive(:return_raise).and_return(false)
|
|
551
|
+
stub_return_during_wait(return_producer, return_info)
|
|
552
|
+
|
|
553
|
+
req = build_mandatory_request
|
|
554
|
+
req.return_raise = true
|
|
555
|
+
|
|
556
|
+
expect { return_producer.confirmed(req) }.to raise_error(BugBunny::PublishUnroutable)
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
it 'flag inerte cuando mandatory:false aunque return_raise=true' do
|
|
560
|
+
req = BugBunny::Request.new('acct.start')
|
|
561
|
+
req.exchange = 'acct_x'
|
|
562
|
+
req.method = :post
|
|
563
|
+
req.body = { tenant: 42 }
|
|
564
|
+
req.mandatory = false
|
|
565
|
+
req.return_raise = true
|
|
566
|
+
|
|
567
|
+
result = return_producer.confirmed(req)
|
|
568
|
+
expect(result).to eq('status' => 202, 'body' => nil)
|
|
569
|
+
registry = real_session.instance_variable_get(:@pending_returns)
|
|
570
|
+
expect(registry.size).to eq(0)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
it 'invoca el callback global on_return antes de levantar PublishUnroutable' do
|
|
574
|
+
captured = nil
|
|
575
|
+
BugBunny.configuration.on_return = lambda { |ri, _props, _body|
|
|
576
|
+
captured = { rk: ri.routing_key }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
stub_return_during_wait(return_producer, return_info)
|
|
580
|
+
|
|
581
|
+
expect { return_producer.confirmed(build_mandatory_request) }
|
|
582
|
+
.to raise_error(BugBunny::PublishUnroutable)
|
|
583
|
+
expect(captured).to eq(rk: 'acct.unbound')
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
it 'levanta PublishUnroutable aunque el user_cb on_return explote' do
|
|
587
|
+
BugBunny.configuration.on_return = ->(_, _, _) { raise 'boom in user cb' }
|
|
588
|
+
stub_return_during_wait(return_producer, return_info)
|
|
589
|
+
|
|
590
|
+
expect { return_producer.confirmed(build_mandatory_request) }
|
|
591
|
+
.to raise_error(BugBunny::PublishUnroutable)
|
|
592
|
+
end
|
|
593
|
+
end
|
|
328
594
|
end
|
|
329
595
|
end
|
data/spec/unit/request_spec.rb
CHANGED
|
@@ -76,5 +76,21 @@ RSpec.describe BugBunny::Request do
|
|
|
76
76
|
expect(req.mandatory).to be(true)
|
|
77
77
|
expect(req.confirm_timeout).to eq(0.5)
|
|
78
78
|
end
|
|
79
|
+
|
|
80
|
+
it 'tiene return_raise=nil por defecto (delega a config global)' do
|
|
81
|
+
req = described_class.new('foo')
|
|
82
|
+
|
|
83
|
+
expect(req.return_raise).to be_nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'permite asignar return_raise' do
|
|
87
|
+
req = described_class.new('foo')
|
|
88
|
+
|
|
89
|
+
req.return_raise = false
|
|
90
|
+
expect(req.return_raise).to be(false)
|
|
91
|
+
|
|
92
|
+
req.return_raise = true
|
|
93
|
+
expect(req.return_raise).to be(true)
|
|
94
|
+
end
|
|
79
95
|
end
|
|
80
96
|
end
|
data/spec/unit/session_spec.rb
CHANGED
|
@@ -151,6 +151,78 @@ RSpec.describe BugBunny::Session do
|
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
+
describe '#register_return_listener / #unregister_return_listener' do
|
|
155
|
+
let(:properties_for) do
|
|
156
|
+
->(cid) { double('properties', correlation_id: cid) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
let(:return_info) do
|
|
160
|
+
Struct.new(:reply_code, :reply_text, :exchange, :routing_key)
|
|
161
|
+
.new(312, 'NO_ROUTE', 'evt_x', 'rk')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
before do
|
|
165
|
+
BugBunny.configuration.on_return = nil
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
after do
|
|
169
|
+
BugBunny.configuration.on_return = nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'devuelve un Concurrent::Event y un slot que se setean al disparar el return' do
|
|
173
|
+
event, slot = session.register_return_listener('corr-1')
|
|
174
|
+
|
|
175
|
+
expect(event).to be_a(Concurrent::Event)
|
|
176
|
+
expect(slot[:info]).to be_nil
|
|
177
|
+
|
|
178
|
+
session.send(:handle_broker_return, return_info, properties_for.call('corr-1'), 'payload')
|
|
179
|
+
|
|
180
|
+
expect(event.set?).to be(true)
|
|
181
|
+
expect(slot[:info]).to eq(return_info)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'no toca otros listeners cuando llega un return de otro correlation_id' do
|
|
185
|
+
event_a, slot_a = session.register_return_listener('corr-A')
|
|
186
|
+
event_b, slot_b = session.register_return_listener('corr-B')
|
|
187
|
+
|
|
188
|
+
session.send(:handle_broker_return, return_info, properties_for.call('corr-A'), 'p')
|
|
189
|
+
|
|
190
|
+
expect(event_a.set?).to be(true)
|
|
191
|
+
expect(slot_a[:info]).to eq(return_info)
|
|
192
|
+
expect(event_b.set?).to be(false)
|
|
193
|
+
expect(slot_b[:info]).to be_nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'ignora returns sin correlation_id en properties' do
|
|
197
|
+
event, slot = session.register_return_listener('corr-1')
|
|
198
|
+
props = double('properties', correlation_id: nil)
|
|
199
|
+
|
|
200
|
+
expect { session.send(:handle_broker_return, return_info, props, 'p') }.not_to raise_error
|
|
201
|
+
expect(event.set?).to be(false)
|
|
202
|
+
expect(slot[:info]).to be_nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it '#unregister_return_listener limpia el slot del registry' do
|
|
206
|
+
session.register_return_listener('corr-1')
|
|
207
|
+
session.unregister_return_listener('corr-1')
|
|
208
|
+
|
|
209
|
+
registry = session.instance_variable_get(:@pending_returns)
|
|
210
|
+
expect(registry['corr-1']).to be_nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'setea el event ANTES de invocar el callback global on_return (resiliencia a user_cb que explota)' do
|
|
214
|
+
BugBunny.configuration.on_return = ->(_, _, _) { raise 'boom in user callback' }
|
|
215
|
+
|
|
216
|
+
event, slot = session.register_return_listener('corr-1')
|
|
217
|
+
|
|
218
|
+
expect { session.send(:handle_broker_return, return_info, properties_for.call('corr-1'), 'p') }
|
|
219
|
+
.not_to raise_error
|
|
220
|
+
|
|
221
|
+
expect(event.set?).to be(true)
|
|
222
|
+
expect(slot[:info]).to eq(return_info)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
154
226
|
describe '#close' do
|
|
155
227
|
it 'cierra el canal y lo nilifica' do
|
|
156
228
|
session.channel
|
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: 4.
|
|
4
|
+
version: 4.16.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-05-
|
|
11
|
+
date: 2026-05-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bunny
|
|
@@ -275,6 +275,7 @@ files:
|
|
|
275
275
|
- spec/integration/controller_spec.rb
|
|
276
276
|
- spec/integration/error_handling_spec.rb
|
|
277
277
|
- spec/integration/infrastructure_spec.rb
|
|
278
|
+
- spec/integration/publisher_confirms_spec.rb
|
|
278
279
|
- spec/integration/resource_spec.rb
|
|
279
280
|
- spec/spec_helper.rb
|
|
280
281
|
- spec/support/bunny_mocks.rb
|
|
@@ -303,7 +304,7 @@ metadata:
|
|
|
303
304
|
homepage_uri: https://github.com/gedera/bug_bunny
|
|
304
305
|
source_code_uri: https://github.com/gedera/bug_bunny
|
|
305
306
|
changelog_uri: https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md
|
|
306
|
-
documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.
|
|
307
|
+
documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.16.0/skill
|
|
307
308
|
post_install_message:
|
|
308
309
|
rdoc_options: []
|
|
309
310
|
require_paths:
|