bug_bunny 4.18.0 → 5.0.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/AGENTS.md +43 -0
- data/CHANGELOG.md +31 -0
- data/CLAUDE.md +14 -4
- data/README.md +0 -1
- data/docs/config/configuracion.md +172 -0
- data/docs/consumed/rabbitmq.md +102 -0
- data/docs/errors/errors.md +196 -0
- data/docs/release/release.md +108 -0
- data/docs/test/test.md +110 -0
- data/lib/bug_bunny/exception.rb +33 -7
- data/lib/bug_bunny/middleware/raise_error.rb +97 -43
- data/lib/bug_bunny/version.rb +1 -1
- data/skill/SKILL.md +7 -1
- data/skill/references/consumer.md +1 -1
- data/skill/references/errores.md +36 -7
- data/skill/references/routing.md +1 -1
- data/skills.yml +5 -21
- data/spec/unit/raise_error_spec.rb +176 -0
- metadata +9 -3
|
@@ -55,5 +55,181 @@ RSpec.describe BugBunny::Middleware::RaiseError do
|
|
|
55
55
|
expect { middleware.on_complete(response) }.to raise_error(BugBunny::NotFound)
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
# Alcance issue #52: status + raw_response como materia prima en TODAS las
|
|
60
|
+
# clases de error, no solo en 422.
|
|
61
|
+
describe 'raw_response and status on every error class' do
|
|
62
|
+
def captured_error(response)
|
|
63
|
+
middleware.on_complete(response)
|
|
64
|
+
nil
|
|
65
|
+
rescue BugBunny::Error => e
|
|
66
|
+
e
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
400 => BugBunny::BadRequest,
|
|
71
|
+
409 => BugBunny::Conflict,
|
|
72
|
+
500 => BugBunny::InternalServerError
|
|
73
|
+
}.each do |status, klass|
|
|
74
|
+
context "when status is #{status}" do
|
|
75
|
+
let(:body) { { 'error' => 'boom' } }
|
|
76
|
+
let(:error) { captured_error('status' => status, 'body' => body) }
|
|
77
|
+
|
|
78
|
+
it "raises #{klass} with status and raw_response populated" do
|
|
79
|
+
expect(error).to be_a(klass)
|
|
80
|
+
expect(error.status).to eq(status)
|
|
81
|
+
expect(error.raw_response).to eq(body)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
context 'when status is 404 (NotFound)' do
|
|
87
|
+
let(:body) { { 'error' => 'missing' } }
|
|
88
|
+
let(:error) { captured_error('status' => 404, 'body' => body) }
|
|
89
|
+
|
|
90
|
+
it 'populates status and raw_response' do
|
|
91
|
+
expect(error.status).to eq(404)
|
|
92
|
+
expect(error.raw_response).to eq(body)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context 'when status is 422 (UnprocessableEntity)' do
|
|
97
|
+
let(:body) { { 'errors' => { 'name' => ['blank'] } } }
|
|
98
|
+
let(:error) { captured_error('status' => 422, 'body' => body) }
|
|
99
|
+
|
|
100
|
+
it 'keeps raw_response/error_messages and adds status from base' do
|
|
101
|
+
expect(error).to be_a(BugBunny::UnprocessableEntity)
|
|
102
|
+
expect(error.status).to eq(422)
|
|
103
|
+
expect(error.raw_response).to eq(body)
|
|
104
|
+
expect(error.error_messages).to eq('name' => ['blank'])
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'when status is unmapped (418)' do
|
|
109
|
+
let(:body) { { 'error' => 'teapot' } }
|
|
110
|
+
let(:error) { captured_error('status' => 418, 'body' => body) }
|
|
111
|
+
|
|
112
|
+
it 'populates status and raw_response on the generic ClientError' do
|
|
113
|
+
expect(error).to be_a(BugBunny::ClientError)
|
|
114
|
+
expect(error.status).to eq(418)
|
|
115
|
+
expect(error.raw_response).to eq(body)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Alcance issue #52: hardening de format_error_message contra el envelope
|
|
121
|
+
# anidado para no volcar un Hash#inspect en .message.
|
|
122
|
+
describe 'message hardening against the nested canonical envelope' do
|
|
123
|
+
def captured_error(response)
|
|
124
|
+
middleware.on_complete(response)
|
|
125
|
+
nil
|
|
126
|
+
rescue BugBunny::Error => e
|
|
127
|
+
e
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context 'when error is the nested canonical envelope { error: { message } }' do
|
|
131
|
+
let(:body) { { 'error' => { 'code' => 'shortname_taken', 'message' => 'shortname already taken', 'details' => {} } } }
|
|
132
|
+
let(:error) { captured_error('status' => 409, 'body' => body) }
|
|
133
|
+
|
|
134
|
+
it 'extracts the human message and does not dump the Hash' do
|
|
135
|
+
expect(error.message).to eq('shortname already taken')
|
|
136
|
+
expect(error.message).not_to include('=>')
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context 'when the nested envelope lacks a usable message' do
|
|
141
|
+
let(:body) { { 'error' => { 'code' => 'x', 'details' => {} } } }
|
|
142
|
+
let(:error) { captured_error('status' => 409, 'body' => body) }
|
|
143
|
+
|
|
144
|
+
it 'falls back to JSON instead of Hash#inspect' do
|
|
145
|
+
expect(error.message).to eq(body.to_json)
|
|
146
|
+
expect(error.message).not_to include('=>')
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
context 'when error is the flat historical shape { error: string, detail: string }' do
|
|
151
|
+
let(:body) { { 'error' => 'boom', 'detail' => 'because reasons' } }
|
|
152
|
+
let(:error) { captured_error('status' => 400, 'body' => body) }
|
|
153
|
+
|
|
154
|
+
it 'keeps concatenating error and detail (backward compatible)' do
|
|
155
|
+
expect(error.message).to eq('boom - because reasons')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context 'when error is an empty string (backward-compat edge case)' do
|
|
160
|
+
let(:body) { { 'error' => '', 'detail' => 'x' } }
|
|
161
|
+
let(:error) { captured_error('status' => 400, 'body' => body) }
|
|
162
|
+
|
|
163
|
+
it 'preserves the historical concatenation instead of dumping JSON' do
|
|
164
|
+
expect(error.message).to eq(' - x')
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Alcance issue #52: casos borde y paths no cubiertos arriba.
|
|
170
|
+
describe 'edge cases and uncovered paths' do
|
|
171
|
+
def captured_error(response)
|
|
172
|
+
middleware.on_complete(response)
|
|
173
|
+
nil
|
|
174
|
+
rescue BugBunny::Error => e
|
|
175
|
+
e
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
context 'when 5xx carries a serialized remote exception (bug_bunny_exception)' do
|
|
179
|
+
let(:body) do
|
|
180
|
+
{ 'bug_bunny_exception' => { 'class' => 'ActiveRecord::RecordNotFound',
|
|
181
|
+
'message' => 'User not found',
|
|
182
|
+
'backtrace' => ['app.rb:1'] } }
|
|
183
|
+
end
|
|
184
|
+
let(:error) { captured_error('status' => 500, 'body' => body) }
|
|
185
|
+
|
|
186
|
+
it 'raises RemoteError with status and raw_response populated' do
|
|
187
|
+
expect(error).to be_a(BugBunny::RemoteError)
|
|
188
|
+
expect(error.status).to eq(500)
|
|
189
|
+
expect(error.raw_response).to eq(body)
|
|
190
|
+
expect(error.original_class).to eq('ActiveRecord::RecordNotFound')
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context 'when 422 carries the nested envelope (consumer parses from raw_response)' do
|
|
195
|
+
let(:body) { { 'error' => { 'code' => 'taken', 'message' => 'shortname already taken', 'details' => {} } } }
|
|
196
|
+
let(:error) { captured_error('status' => 422, 'body' => body) }
|
|
197
|
+
|
|
198
|
+
it 'keeps raw_response intact for the service boundary' do
|
|
199
|
+
expect(error).to be_a(BugBunny::UnprocessableEntity)
|
|
200
|
+
expect(error.status).to eq(422)
|
|
201
|
+
expect(error.raw_response).to eq(body)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context 'when status is 406 (no-arg constructor)' do
|
|
206
|
+
let(:body) { { 'error' => 'nope' } }
|
|
207
|
+
let(:error) { captured_error('status' => 406, 'body' => body) }
|
|
208
|
+
|
|
209
|
+
it 'still populates status and raw_response' do
|
|
210
|
+
expect(error).to be_a(BugBunny::NotAcceptable)
|
|
211
|
+
expect(error.status).to eq(406)
|
|
212
|
+
expect(error.raw_response).to eq(body)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
context 'when status is 408 (no-arg constructor)' do
|
|
217
|
+
let(:error) { captured_error('status' => 408, 'body' => nil) }
|
|
218
|
+
|
|
219
|
+
it 'still populates status' do
|
|
220
|
+
expect(error).to be_a(BugBunny::RequestTimeout)
|
|
221
|
+
expect(error.status).to eq(408)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
context 'when body is not a Hash nor a String (e.g. Array)' do
|
|
226
|
+
let(:body) { [1, 2, 3] }
|
|
227
|
+
let(:error) { captured_error('status' => 400, 'body' => body) }
|
|
228
|
+
|
|
229
|
+
it 'falls back to JSON without raising in the formatter' do
|
|
230
|
+
expect(error.message).to eq(body.to_json)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
58
234
|
end
|
|
59
235
|
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:
|
|
4
|
+
version: 5.0.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-
|
|
11
|
+
date: 2026-07-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bunny
|
|
@@ -229,12 +229,18 @@ executables: []
|
|
|
229
229
|
extensions: []
|
|
230
230
|
extra_rdoc_files: []
|
|
231
231
|
files:
|
|
232
|
+
- AGENTS.md
|
|
232
233
|
- CHANGELOG.md
|
|
233
234
|
- CLAUDE.md
|
|
234
235
|
- README.md
|
|
235
236
|
- Rakefile
|
|
236
237
|
- docs/behavior/behavior.md
|
|
238
|
+
- docs/config/configuracion.md
|
|
239
|
+
- docs/consumed/rabbitmq.md
|
|
240
|
+
- docs/errors/errors.md
|
|
237
241
|
- docs/glossary/glossary.md
|
|
242
|
+
- docs/release/release.md
|
|
243
|
+
- docs/test/test.md
|
|
238
244
|
- initializer_example.rb
|
|
239
245
|
- lib/bug_bunny.rb
|
|
240
246
|
- lib/bug_bunny/client.rb
|
|
@@ -307,7 +313,7 @@ metadata:
|
|
|
307
313
|
homepage_uri: https://github.com/gedera/bug_bunny
|
|
308
314
|
source_code_uri: https://github.com/gedera/bug_bunny
|
|
309
315
|
changelog_uri: https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md
|
|
310
|
-
documentation_uri: https://github.com/gedera/bug_bunny/blob/
|
|
316
|
+
documentation_uri: https://github.com/gedera/bug_bunny/blob/v5.0.0/skill
|
|
311
317
|
post_install_message:
|
|
312
318
|
rdoc_options: []
|
|
313
319
|
require_paths:
|