brainzlab 0.1.10 → 0.1.12
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/README.md +220 -4
- data/lib/brainzlab/beacon/client.rb +21 -1
- data/lib/brainzlab/configuration.rb +51 -2
- data/lib/brainzlab/cortex/client.rb +21 -1
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +21 -1
- data/lib/brainzlab/development/logger.rb +150 -0
- data/lib/brainzlab/development/store.rb +121 -0
- data/lib/brainzlab/development.rb +72 -0
- data/lib/brainzlab/devtools/assets/devtools.css +326 -103
- data/lib/brainzlab/devtools/assets/devtools.js +79 -5
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +11 -0
- data/lib/brainzlab/errors.rb +490 -0
- data/lib/brainzlab/nerve/client.rb +21 -1
- data/lib/brainzlab/pulse/client.rb +66 -5
- data/lib/brainzlab/pulse.rb +17 -4
- data/lib/brainzlab/recall/client.rb +74 -6
- data/lib/brainzlab/recall.rb +19 -2
- data/lib/brainzlab/reflex/client.rb +66 -5
- data/lib/brainzlab/reflex.rb +40 -8
- data/lib/brainzlab/sentinel/client.rb +21 -1
- data/lib/brainzlab/synapse/client.rb +21 -1
- data/lib/brainzlab/testing/event_store.rb +377 -0
- data/lib/brainzlab/testing/helpers.rb +650 -0
- data/lib/brainzlab/testing/matchers.rb +391 -0
- data/lib/brainzlab/testing.rb +327 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +32 -3
- data/lib/brainzlab/vault/client.rb +21 -1
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +53 -6
- data/lib/brainzlab.rb +42 -0
- metadata +24 -1
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Testing
|
|
5
|
+
# Custom RSpec matchers for BrainzLab SDK
|
|
6
|
+
#
|
|
7
|
+
# These matchers provide a clean, expressive syntax for testing
|
|
8
|
+
# BrainzLab event tracking, error capture, logging, and metrics.
|
|
9
|
+
#
|
|
10
|
+
# @example Usage in RSpec
|
|
11
|
+
# RSpec.configure do |config|
|
|
12
|
+
# config.include BrainzLab::Testing::Matchers
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# describe UserService do
|
|
16
|
+
# it 'tracks user signup' do
|
|
17
|
+
# subject.register(email: 'test@example.com')
|
|
18
|
+
# expect('user.signup').to have_been_tracked.with(email: 'test@example.com')
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
module Matchers
|
|
23
|
+
# RSpec matcher for tracking events
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# expect('user.signup').to have_been_tracked
|
|
27
|
+
# expect('user.signup').to have_been_tracked.with(user_id: 1)
|
|
28
|
+
# expect('order.completed').to have_been_tracked.with(order_id: 42, total: 99.99)
|
|
29
|
+
#
|
|
30
|
+
def have_been_tracked
|
|
31
|
+
HaveBeenTrackedMatcher.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# RSpec matcher for capturing errors
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# expect(RuntimeError).to have_been_captured
|
|
38
|
+
# expect(RuntimeError).to have_been_captured.with_message(/something went wrong/i)
|
|
39
|
+
# expect('MyCustomError').to have_been_captured.with_context(user_id: 1)
|
|
40
|
+
#
|
|
41
|
+
def have_been_captured
|
|
42
|
+
HaveBeenCapturedMatcher.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# RSpec matcher for recording logs
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# expect(:info).to have_been_logged.with_message('User created')
|
|
49
|
+
# expect(:error).to have_been_logged.with_message(/failed/i).with_data(user_id: 1)
|
|
50
|
+
#
|
|
51
|
+
def have_been_logged
|
|
52
|
+
HaveBeenLoggedMatcher.new
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# RSpec matcher for recording metrics
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# expect(['increment', 'orders.count']).to have_been_recorded
|
|
59
|
+
# expect(['gauge', 'memory.usage']).to have_been_recorded.with_value(1024)
|
|
60
|
+
# expect(['distribution', 'response.time']).to have_been_recorded.with_tags(endpoint: '/api/users')
|
|
61
|
+
#
|
|
62
|
+
def have_been_recorded
|
|
63
|
+
HaveBeenRecordedMatcher.new
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# RSpec matcher for recording traces
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# expect('db.query').to have_been_traced
|
|
70
|
+
# expect('http.request').to have_been_traced.with(method: 'GET')
|
|
71
|
+
#
|
|
72
|
+
def have_been_traced
|
|
73
|
+
HaveBeenTracedMatcher.new
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# RSpec matcher for sending alerts
|
|
77
|
+
#
|
|
78
|
+
# @example
|
|
79
|
+
# expect('high_error_rate').to have_sent_alert
|
|
80
|
+
# expect('low_disk_space').to have_sent_alert.with_severity(:critical)
|
|
81
|
+
#
|
|
82
|
+
def have_sent_alert
|
|
83
|
+
HaveSentAlertMatcher.new
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Matcher for events
|
|
87
|
+
class HaveBeenTrackedMatcher
|
|
88
|
+
def initialize
|
|
89
|
+
@expected_properties = {}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def with(properties)
|
|
93
|
+
@expected_properties = properties
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def matches?(event_name)
|
|
98
|
+
@event_name = event_name.to_s
|
|
99
|
+
store = BrainzLab::Testing.event_store
|
|
100
|
+
|
|
101
|
+
if @expected_properties.empty?
|
|
102
|
+
store.event_tracked?(@event_name)
|
|
103
|
+
else
|
|
104
|
+
store.event_tracked?(@event_name, @expected_properties)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def failure_message
|
|
109
|
+
actual_events = BrainzLab::Testing.event_store.events_named(@event_name)
|
|
110
|
+
|
|
111
|
+
if actual_events.empty?
|
|
112
|
+
"expected event '#{@event_name}' to have been tracked, but no such event was found.\n" \
|
|
113
|
+
"Tracked events: #{BrainzLab::Testing.event_store.events.map { |e| e[:name] }.inspect}"
|
|
114
|
+
else
|
|
115
|
+
"expected event '#{@event_name}' to have been tracked with properties:\n" \
|
|
116
|
+
" #{@expected_properties.inspect}\n" \
|
|
117
|
+
"but was tracked with:\n" \
|
|
118
|
+
" #{actual_events.map { |e| e[:properties] }.inspect}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def failure_message_when_negated
|
|
123
|
+
if @expected_properties.empty?
|
|
124
|
+
"expected event '#{@event_name}' not to have been tracked, but it was"
|
|
125
|
+
else
|
|
126
|
+
"expected event '#{@event_name}' not to have been tracked with properties " \
|
|
127
|
+
"#{@expected_properties.inspect}, but it was"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def description
|
|
132
|
+
desc = "have tracked event '#{@event_name}'"
|
|
133
|
+
desc += " with properties #{@expected_properties.inspect}" unless @expected_properties.empty?
|
|
134
|
+
desc
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Matcher for errors
|
|
139
|
+
class HaveBeenCapturedMatcher
|
|
140
|
+
def initialize
|
|
141
|
+
@expected_message = nil
|
|
142
|
+
@expected_context = nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def with_message(message)
|
|
146
|
+
@expected_message = message
|
|
147
|
+
self
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def with_context(context)
|
|
151
|
+
@expected_context = context
|
|
152
|
+
self
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def matches?(error_class)
|
|
156
|
+
@error_class = error_class
|
|
157
|
+
BrainzLab::Testing.event_store.error_captured?(
|
|
158
|
+
@error_class,
|
|
159
|
+
message: @expected_message,
|
|
160
|
+
context: @expected_context
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def failure_message
|
|
165
|
+
actual_errors = BrainzLab::Testing.event_store.errors
|
|
166
|
+
|
|
167
|
+
if actual_errors.empty?
|
|
168
|
+
"expected error #{@error_class} to have been captured, but no errors were captured"
|
|
169
|
+
else
|
|
170
|
+
msg = "expected error #{@error_class} to have been captured"
|
|
171
|
+
msg += " with message matching #{@expected_message.inspect}" if @expected_message
|
|
172
|
+
msg += " with context #{@expected_context.inspect}" if @expected_context
|
|
173
|
+
msg + ", but captured errors were:\n #{actual_errors.map { |e| { class: e[:error_class], message: e[:message] } }.inspect}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def failure_message_when_negated
|
|
178
|
+
"expected error #{@error_class} not to have been captured, but it was"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def description
|
|
182
|
+
desc = "have captured error #{@error_class}"
|
|
183
|
+
desc += " with message #{@expected_message.inspect}" if @expected_message
|
|
184
|
+
desc += " with context #{@expected_context.inspect}" if @expected_context
|
|
185
|
+
desc
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Matcher for logs
|
|
190
|
+
class HaveBeenLoggedMatcher
|
|
191
|
+
def initialize
|
|
192
|
+
@expected_message = nil
|
|
193
|
+
@expected_data = nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def with_message(message)
|
|
197
|
+
@expected_message = message
|
|
198
|
+
self
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def with_data(data)
|
|
202
|
+
@expected_data = data
|
|
203
|
+
self
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def matches?(level)
|
|
207
|
+
@level = level.to_sym
|
|
208
|
+
BrainzLab::Testing.event_store.logged?(@level, @expected_message, @expected_data)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def failure_message
|
|
212
|
+
actual_logs = BrainzLab::Testing.event_store.logs_at_level(@level)
|
|
213
|
+
|
|
214
|
+
if actual_logs.empty?
|
|
215
|
+
"expected a log at level :#{@level} to have been recorded, but none were found.\n" \
|
|
216
|
+
"Available log levels: #{BrainzLab::Testing.event_store.logs.map { |l| l[:level] }.uniq.inspect}"
|
|
217
|
+
else
|
|
218
|
+
msg = "expected a log at level :#{@level}"
|
|
219
|
+
msg += " with message matching #{@expected_message.inspect}" if @expected_message
|
|
220
|
+
msg += " with data #{@expected_data.inspect}" if @expected_data
|
|
221
|
+
msg + ", but logged messages were:\n #{actual_logs.map { |l| { message: l[:message], data: l[:data] } }.inspect}"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def failure_message_when_negated
|
|
226
|
+
"expected no log at level :#{@level} to have been recorded, but it was"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def description
|
|
230
|
+
desc = "have logged at level :#{@level}"
|
|
231
|
+
desc += " with message #{@expected_message.inspect}" if @expected_message
|
|
232
|
+
desc += " with data #{@expected_data.inspect}" if @expected_data
|
|
233
|
+
desc
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Matcher for metrics
|
|
238
|
+
class HaveBeenRecordedMatcher
|
|
239
|
+
def initialize
|
|
240
|
+
@expected_value = nil
|
|
241
|
+
@expected_tags = nil
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def with_value(value)
|
|
245
|
+
@expected_value = value
|
|
246
|
+
self
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def with_tags(tags)
|
|
250
|
+
@expected_tags = tags
|
|
251
|
+
self
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def matches?(type_and_name)
|
|
255
|
+
@type, @name = type_and_name
|
|
256
|
+
@type = @type.to_sym
|
|
257
|
+
@name = @name.to_s
|
|
258
|
+
|
|
259
|
+
BrainzLab::Testing.event_store.metric_recorded?(
|
|
260
|
+
@type,
|
|
261
|
+
@name,
|
|
262
|
+
value: @expected_value,
|
|
263
|
+
tags: @expected_tags
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def failure_message
|
|
268
|
+
actual_metrics = BrainzLab::Testing.event_store.metrics_named(@name)
|
|
269
|
+
|
|
270
|
+
if actual_metrics.empty?
|
|
271
|
+
"expected metric #{@type}('#{@name}') to have been recorded, but no such metric was found.\n" \
|
|
272
|
+
"Recorded metrics: #{BrainzLab::Testing.event_store.metrics.map { |m| "#{m[:type]}('#{m[:name]}')" }.inspect}"
|
|
273
|
+
else
|
|
274
|
+
msg = "expected metric #{@type}('#{@name}')"
|
|
275
|
+
msg += " with value #{@expected_value.inspect}" if @expected_value
|
|
276
|
+
msg += " with tags #{@expected_tags.inspect}" if @expected_tags
|
|
277
|
+
msg + ", but recorded metrics were:\n #{actual_metrics.inspect}"
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def failure_message_when_negated
|
|
282
|
+
"expected metric #{@type}('#{@name}') not to have been recorded, but it was"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def description
|
|
286
|
+
desc = "have recorded metric #{@type}('#{@name}')"
|
|
287
|
+
desc += " with value #{@expected_value.inspect}" if @expected_value
|
|
288
|
+
desc += " with tags #{@expected_tags.inspect}" if @expected_tags
|
|
289
|
+
desc
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Matcher for traces
|
|
294
|
+
class HaveBeenTracedMatcher
|
|
295
|
+
def initialize
|
|
296
|
+
@expected_opts = nil
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def with(opts)
|
|
300
|
+
@expected_opts = opts
|
|
301
|
+
self
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def matches?(trace_name)
|
|
305
|
+
@trace_name = trace_name.to_s
|
|
306
|
+
BrainzLab::Testing.event_store.trace_recorded?(@trace_name, @expected_opts)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def failure_message
|
|
310
|
+
actual_traces = BrainzLab::Testing.event_store.traces
|
|
311
|
+
|
|
312
|
+
if actual_traces.empty?
|
|
313
|
+
"expected trace '#{@trace_name}' to have been recorded, but no traces were found"
|
|
314
|
+
else
|
|
315
|
+
msg = "expected trace '#{@trace_name}'"
|
|
316
|
+
msg += " with options #{@expected_opts.inspect}" if @expected_opts
|
|
317
|
+
msg + ", but recorded traces were:\n #{actual_traces.map { |t| t[:name] }.inspect}"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def failure_message_when_negated
|
|
322
|
+
"expected trace '#{@trace_name}' not to have been recorded, but it was"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def description
|
|
326
|
+
desc = "have traced '#{@trace_name}'"
|
|
327
|
+
desc += " with #{@expected_opts.inspect}" if @expected_opts
|
|
328
|
+
desc
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Matcher for alerts
|
|
333
|
+
class HaveSentAlertMatcher
|
|
334
|
+
def initialize
|
|
335
|
+
@expected_message = nil
|
|
336
|
+
@expected_severity = nil
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def with_message(message)
|
|
340
|
+
@expected_message = message
|
|
341
|
+
self
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def with_severity(severity)
|
|
345
|
+
@expected_severity = severity
|
|
346
|
+
self
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def matches?(alert_name)
|
|
350
|
+
@alert_name = alert_name.to_s
|
|
351
|
+
BrainzLab::Testing.event_store.alert_sent?(
|
|
352
|
+
@alert_name,
|
|
353
|
+
message: @expected_message,
|
|
354
|
+
severity: @expected_severity
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def failure_message
|
|
359
|
+
actual_alerts = BrainzLab::Testing.event_store.alerts
|
|
360
|
+
|
|
361
|
+
if actual_alerts.empty?
|
|
362
|
+
"expected alert '#{@alert_name}' to have been sent, but no alerts were found"
|
|
363
|
+
else
|
|
364
|
+
msg = "expected alert '#{@alert_name}'"
|
|
365
|
+
msg += " with message '#{@expected_message}'" if @expected_message
|
|
366
|
+
msg += " with severity :#{@expected_severity}" if @expected_severity
|
|
367
|
+
msg + ", but sent alerts were:\n #{actual_alerts.map { |a| { name: a[:name], severity: a[:severity] } }.inspect}"
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def failure_message_when_negated
|
|
372
|
+
"expected alert '#{@alert_name}' not to have been sent, but it was"
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def description
|
|
376
|
+
desc = "have sent alert '#{@alert_name}'"
|
|
377
|
+
desc += " with message '#{@expected_message}'" if @expected_message
|
|
378
|
+
desc += " with severity :#{@expected_severity}" if @expected_severity
|
|
379
|
+
desc
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Auto-register RSpec matchers if RSpec is loaded
|
|
387
|
+
if defined?(RSpec)
|
|
388
|
+
RSpec.configure do |config|
|
|
389
|
+
config.include BrainzLab::Testing::Matchers
|
|
390
|
+
end
|
|
391
|
+
end
|