brainzlab 0.1.11 → 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 +210 -3
- 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 +245 -109
- data/lib/brainzlab/devtools/assets/devtools.js +40 -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,650 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Testing
|
|
5
|
+
# Test helpers for RSpec and Minitest
|
|
6
|
+
#
|
|
7
|
+
# Include this module in your test helper to gain access to
|
|
8
|
+
# BrainzLab testing utilities.
|
|
9
|
+
#
|
|
10
|
+
# @example RSpec configuration
|
|
11
|
+
# RSpec.configure do |config|
|
|
12
|
+
# config.include BrainzLab::Testing::Helpers
|
|
13
|
+
#
|
|
14
|
+
# config.before(:each) do
|
|
15
|
+
# stub_brainzlab!
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# config.after(:each) do
|
|
19
|
+
# clear_brainzlab_events!
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Minitest configuration
|
|
24
|
+
# class ActiveSupport::TestCase
|
|
25
|
+
# include BrainzLab::Testing::Helpers
|
|
26
|
+
#
|
|
27
|
+
# setup do
|
|
28
|
+
# stub_brainzlab!
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# teardown do
|
|
32
|
+
# clear_brainzlab_events!
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
module Helpers
|
|
37
|
+
# Stub all BrainzLab SDK calls to prevent real API requests
|
|
38
|
+
# and enable event capturing for assertions.
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# it 'tracks user signup' do
|
|
42
|
+
# stub_brainzlab!
|
|
43
|
+
# UserService.new.register(email: 'test@example.com')
|
|
44
|
+
# expect(brainzlab_events).to include(hash_including(name: 'user.signup'))
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
def stub_brainzlab!
|
|
48
|
+
BrainzLab::Testing.enable!
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Restore original BrainzLab SDK behavior
|
|
52
|
+
#
|
|
53
|
+
# @note This is typically called automatically if you're using
|
|
54
|
+
# clear_brainzlab_events! in your teardown, which also restores state.
|
|
55
|
+
def unstub_brainzlab!
|
|
56
|
+
BrainzLab::Testing.disable!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Clear all captured events, logs, errors, and metrics
|
|
60
|
+
#
|
|
61
|
+
# Call this in your test teardown or between test scenarios
|
|
62
|
+
# to ensure a clean slate.
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# after(:each) do
|
|
66
|
+
# clear_brainzlab_events!
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
def clear_brainzlab_events!
|
|
70
|
+
BrainzLab::Testing.event_store.clear!
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Access all captured Flux events
|
|
74
|
+
#
|
|
75
|
+
# @return [Array<Hash>] Array of captured events
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# brainzlab_events
|
|
79
|
+
# # => [{ name: 'user.signup', properties: { user_id: 1 }, timestamp: ... }]
|
|
80
|
+
#
|
|
81
|
+
def brainzlab_events
|
|
82
|
+
BrainzLab::Testing.event_store.events
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Access events filtered by name
|
|
86
|
+
#
|
|
87
|
+
# @param name [String, Symbol] Event name to filter by
|
|
88
|
+
# @return [Array<Hash>] Matching events
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# brainzlab_events_named('user.signup')
|
|
92
|
+
# # => [{ name: 'user.signup', properties: { user_id: 1 }, timestamp: ... }]
|
|
93
|
+
#
|
|
94
|
+
def brainzlab_events_named(name)
|
|
95
|
+
BrainzLab::Testing.event_store.events_named(name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Access all captured Flux metrics
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Hash>] Array of captured metrics
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# brainzlab_metrics
|
|
104
|
+
# # => [{ type: :increment, name: 'orders.count', value: 1, tags: {} }]
|
|
105
|
+
#
|
|
106
|
+
def brainzlab_metrics
|
|
107
|
+
BrainzLab::Testing.event_store.metrics
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Access all captured Recall logs
|
|
111
|
+
#
|
|
112
|
+
# @return [Array<Hash>] Array of captured log entries
|
|
113
|
+
#
|
|
114
|
+
# @example
|
|
115
|
+
# brainzlab_logs
|
|
116
|
+
# # => [{ level: :info, message: 'User created', data: { user_id: 1 } }]
|
|
117
|
+
#
|
|
118
|
+
def brainzlab_logs
|
|
119
|
+
BrainzLab::Testing.event_store.logs
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Access logs filtered by level
|
|
123
|
+
#
|
|
124
|
+
# @param level [Symbol] Log level (:debug, :info, :warn, :error, :fatal)
|
|
125
|
+
# @return [Array<Hash>] Matching log entries
|
|
126
|
+
#
|
|
127
|
+
def brainzlab_logs_at_level(level)
|
|
128
|
+
BrainzLab::Testing.event_store.logs_at_level(level)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Access all captured Reflex errors
|
|
132
|
+
#
|
|
133
|
+
# @return [Array<Hash>] Array of captured errors
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# brainzlab_errors
|
|
137
|
+
# # => [{ exception: #<RuntimeError>, error_class: 'RuntimeError', message: 'Oops' }]
|
|
138
|
+
#
|
|
139
|
+
def brainzlab_errors
|
|
140
|
+
BrainzLab::Testing.event_store.errors
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Access all captured Pulse traces
|
|
144
|
+
#
|
|
145
|
+
# @return [Array<Hash>] Array of captured traces
|
|
146
|
+
#
|
|
147
|
+
def brainzlab_traces
|
|
148
|
+
BrainzLab::Testing.event_store.traces
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Access all captured Signal alerts
|
|
152
|
+
#
|
|
153
|
+
# @return [Array<Hash>] Array of captured alerts
|
|
154
|
+
#
|
|
155
|
+
def brainzlab_alerts
|
|
156
|
+
BrainzLab::Testing.event_store.alerts
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Access all captured Signal notifications
|
|
160
|
+
#
|
|
161
|
+
# @return [Array<Hash>] Array of captured notifications
|
|
162
|
+
#
|
|
163
|
+
def brainzlab_notifications
|
|
164
|
+
BrainzLab::Testing.event_store.notifications
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Check if a specific event was tracked
|
|
168
|
+
#
|
|
169
|
+
# @param name [String, Symbol] Event name
|
|
170
|
+
# @param properties [Hash, nil] Optional properties to match
|
|
171
|
+
# @return [Boolean]
|
|
172
|
+
#
|
|
173
|
+
# @example
|
|
174
|
+
# brainzlab_event_tracked?('user.signup')
|
|
175
|
+
# brainzlab_event_tracked?('user.signup', user_id: 1)
|
|
176
|
+
#
|
|
177
|
+
def brainzlab_event_tracked?(name, properties = nil)
|
|
178
|
+
BrainzLab::Testing.event_store.event_tracked?(name, properties)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Check if a specific metric was recorded
|
|
182
|
+
#
|
|
183
|
+
# @param type [Symbol] Metric type (:gauge, :increment, :distribution, etc.)
|
|
184
|
+
# @param name [String, Symbol] Metric name
|
|
185
|
+
# @param value [Numeric, nil] Optional value to match
|
|
186
|
+
# @param tags [Hash, nil] Optional tags to match
|
|
187
|
+
# @return [Boolean]
|
|
188
|
+
#
|
|
189
|
+
def brainzlab_metric_recorded?(type, name, value: nil, tags: nil)
|
|
190
|
+
BrainzLab::Testing.event_store.metric_recorded?(type, name, value: value, tags: tags)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Check if a specific log message was recorded
|
|
194
|
+
#
|
|
195
|
+
# @param level [Symbol] Log level
|
|
196
|
+
# @param message [String, Regexp, nil] Optional message to match
|
|
197
|
+
# @param data [Hash, nil] Optional data to match
|
|
198
|
+
# @return [Boolean]
|
|
199
|
+
#
|
|
200
|
+
def brainzlab_logged?(level, message = nil, data = nil)
|
|
201
|
+
BrainzLab::Testing.event_store.logged?(level, message, data)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Check if a specific error was captured
|
|
205
|
+
#
|
|
206
|
+
# @param error_class [Class, String, nil] Error class to match
|
|
207
|
+
# @param message [String, Regexp, nil] Optional message to match
|
|
208
|
+
# @param context [Hash, nil] Optional context to match
|
|
209
|
+
# @return [Boolean]
|
|
210
|
+
#
|
|
211
|
+
def brainzlab_error_captured?(error_class = nil, message: nil, context: nil)
|
|
212
|
+
BrainzLab::Testing.event_store.error_captured?(error_class, message: message, context: context)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Check if a specific trace was recorded
|
|
216
|
+
#
|
|
217
|
+
# @param name [String, Symbol] Trace name
|
|
218
|
+
# @param opts [Hash, nil] Optional options to match
|
|
219
|
+
# @return [Boolean]
|
|
220
|
+
#
|
|
221
|
+
def brainzlab_trace_recorded?(name, opts = nil)
|
|
222
|
+
BrainzLab::Testing.event_store.trace_recorded?(name, opts)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Check if a specific alert was sent
|
|
226
|
+
#
|
|
227
|
+
# @param name [String, Symbol] Alert name
|
|
228
|
+
# @param message [String, nil] Optional message to match
|
|
229
|
+
# @param severity [Symbol, nil] Optional severity to match
|
|
230
|
+
# @return [Boolean]
|
|
231
|
+
#
|
|
232
|
+
def brainzlab_alert_sent?(name, message: nil, severity: nil)
|
|
233
|
+
BrainzLab::Testing.event_store.alert_sent?(name, message: message, severity: severity)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Get the last captured event
|
|
237
|
+
#
|
|
238
|
+
# @return [Hash, nil] The last event or nil
|
|
239
|
+
#
|
|
240
|
+
def last_brainzlab_event
|
|
241
|
+
BrainzLab::Testing.event_store.last_event
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Get the last captured error
|
|
245
|
+
#
|
|
246
|
+
# @return [Hash, nil] The last error or nil
|
|
247
|
+
#
|
|
248
|
+
def last_brainzlab_error
|
|
249
|
+
BrainzLab::Testing.event_store.last_error
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Create an event expectation builder for fluent assertions
|
|
253
|
+
#
|
|
254
|
+
# @param name [String, Symbol] Event name to expect
|
|
255
|
+
# @return [EventExpectation] Expectation builder
|
|
256
|
+
#
|
|
257
|
+
# @example RSpec usage
|
|
258
|
+
# expect_brainzlab_event('user.signup').with(user_id: 1)
|
|
259
|
+
# expect_brainzlab_event('order.completed').with(order_id: 42, total: 99.99)
|
|
260
|
+
#
|
|
261
|
+
def expect_brainzlab_event(name)
|
|
262
|
+
EventExpectation.new(name, BrainzLab::Testing.event_store)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Create an error expectation builder for fluent assertions
|
|
266
|
+
#
|
|
267
|
+
# @param error_class [Class, String] Error class to expect
|
|
268
|
+
# @return [ErrorExpectation] Expectation builder
|
|
269
|
+
#
|
|
270
|
+
# @example RSpec usage
|
|
271
|
+
# expect_brainzlab_error(RuntimeError).with_message(/something went wrong/i)
|
|
272
|
+
#
|
|
273
|
+
def expect_brainzlab_error(error_class)
|
|
274
|
+
ErrorExpectation.new(error_class, BrainzLab::Testing.event_store)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Create a log expectation builder for fluent assertions
|
|
278
|
+
#
|
|
279
|
+
# @param level [Symbol] Log level to expect
|
|
280
|
+
# @return [LogExpectation] Expectation builder
|
|
281
|
+
#
|
|
282
|
+
# @example RSpec usage
|
|
283
|
+
# expect_brainzlab_log(:info).with_message('User created')
|
|
284
|
+
#
|
|
285
|
+
def expect_brainzlab_log(level)
|
|
286
|
+
LogExpectation.new(level, BrainzLab::Testing.event_store)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Create a metric expectation builder for fluent assertions
|
|
290
|
+
#
|
|
291
|
+
# @param type [Symbol] Metric type (:gauge, :increment, :distribution, etc.)
|
|
292
|
+
# @param name [String, Symbol] Metric name
|
|
293
|
+
# @return [MetricExpectation] Expectation builder
|
|
294
|
+
#
|
|
295
|
+
# @example RSpec usage
|
|
296
|
+
# expect_brainzlab_metric(:increment, 'orders.count').with_value(1)
|
|
297
|
+
#
|
|
298
|
+
def expect_brainzlab_metric(type, name)
|
|
299
|
+
MetricExpectation.new(type, name, BrainzLab::Testing.event_store)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Create a trace expectation builder for fluent assertions
|
|
303
|
+
#
|
|
304
|
+
# @param name [String, Symbol] Trace name to expect
|
|
305
|
+
# @return [TraceExpectation] Expectation builder
|
|
306
|
+
#
|
|
307
|
+
# @example RSpec usage
|
|
308
|
+
# expect_brainzlab_trace('db.query')
|
|
309
|
+
#
|
|
310
|
+
def expect_brainzlab_trace(name)
|
|
311
|
+
TraceExpectation.new(name, BrainzLab::Testing.event_store)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Create an alert expectation builder for fluent assertions
|
|
315
|
+
#
|
|
316
|
+
# @param name [String, Symbol] Alert name to expect
|
|
317
|
+
# @return [AlertExpectation] Expectation builder
|
|
318
|
+
#
|
|
319
|
+
# @example RSpec usage
|
|
320
|
+
# expect_brainzlab_alert('high_error_rate').with_severity(:critical)
|
|
321
|
+
#
|
|
322
|
+
def expect_brainzlab_alert(name)
|
|
323
|
+
AlertExpectation.new(name, BrainzLab::Testing.event_store)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Fluent expectation builder for events
|
|
328
|
+
class EventExpectation
|
|
329
|
+
def initialize(name, store)
|
|
330
|
+
@name = name.to_s
|
|
331
|
+
@store = store
|
|
332
|
+
@expected_properties = {}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Specify expected properties
|
|
336
|
+
#
|
|
337
|
+
# @param properties [Hash] Properties to match
|
|
338
|
+
# @return [self]
|
|
339
|
+
#
|
|
340
|
+
def with(properties = {})
|
|
341
|
+
@expected_properties = properties
|
|
342
|
+
self
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Check if the expectation is satisfied
|
|
346
|
+
#
|
|
347
|
+
# @return [Boolean]
|
|
348
|
+
#
|
|
349
|
+
def satisfied?
|
|
350
|
+
@store.event_tracked?(@name, @expected_properties.empty? ? nil : @expected_properties)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Alias for satisfied? (for RSpec matchers)
|
|
354
|
+
alias matches? satisfied?
|
|
355
|
+
|
|
356
|
+
# Get matching events
|
|
357
|
+
#
|
|
358
|
+
# @return [Array<Hash>]
|
|
359
|
+
#
|
|
360
|
+
def matching_events
|
|
361
|
+
events = @store.events_named(@name)
|
|
362
|
+
return events if @expected_properties.empty?
|
|
363
|
+
|
|
364
|
+
events.select { |e| properties_match?(e[:properties], @expected_properties) }
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Failure message for RSpec
|
|
368
|
+
def failure_message
|
|
369
|
+
if @expected_properties.empty?
|
|
370
|
+
"expected event '#{@name}' to be tracked, but it wasn't"
|
|
371
|
+
else
|
|
372
|
+
"expected event '#{@name}' with properties #{@expected_properties.inspect} to be tracked, " \
|
|
373
|
+
"but got: #{@store.events_named(@name).map { |e| e[:properties] }.inspect}"
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Negative failure message for RSpec
|
|
378
|
+
def failure_message_when_negated
|
|
379
|
+
if @expected_properties.empty?
|
|
380
|
+
"expected event '#{@name}' not to be tracked, but it was"
|
|
381
|
+
else
|
|
382
|
+
"expected event '#{@name}' with properties #{@expected_properties.inspect} not to be tracked, but it was"
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
private
|
|
387
|
+
|
|
388
|
+
def properties_match?(actual, expected)
|
|
389
|
+
expected.all? do |key, value|
|
|
390
|
+
actual_value = actual[key] || actual[key.to_s] || actual[key.to_sym]
|
|
391
|
+
case value
|
|
392
|
+
when Regexp
|
|
393
|
+
actual_value.to_s.match?(value)
|
|
394
|
+
else
|
|
395
|
+
actual_value == value
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Fluent expectation builder for errors
|
|
402
|
+
class ErrorExpectation
|
|
403
|
+
def initialize(error_class, store)
|
|
404
|
+
@error_class = error_class
|
|
405
|
+
@store = store
|
|
406
|
+
@expected_message = nil
|
|
407
|
+
@expected_context = nil
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Specify expected message
|
|
411
|
+
#
|
|
412
|
+
# @param message [String, Regexp] Message to match
|
|
413
|
+
# @return [self]
|
|
414
|
+
#
|
|
415
|
+
def with_message(message)
|
|
416
|
+
@expected_message = message
|
|
417
|
+
self
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Specify expected context
|
|
421
|
+
#
|
|
422
|
+
# @param context [Hash] Context to match
|
|
423
|
+
# @return [self]
|
|
424
|
+
#
|
|
425
|
+
def with_context(context)
|
|
426
|
+
@expected_context = context
|
|
427
|
+
self
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Check if the expectation is satisfied
|
|
431
|
+
#
|
|
432
|
+
# @return [Boolean]
|
|
433
|
+
#
|
|
434
|
+
def satisfied?
|
|
435
|
+
@store.error_captured?(@error_class, message: @expected_message, context: @expected_context)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
alias matches? satisfied?
|
|
439
|
+
|
|
440
|
+
def failure_message
|
|
441
|
+
parts = ["expected error #{@error_class} to be captured"]
|
|
442
|
+
parts << "with message matching #{@expected_message.inspect}" if @expected_message
|
|
443
|
+
parts << "with context #{@expected_context.inspect}" if @expected_context
|
|
444
|
+
parts << ", but got: #{@store.errors.map { |e| { class: e[:error_class], message: e[:message] } }.inspect}"
|
|
445
|
+
parts.join
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def failure_message_when_negated
|
|
449
|
+
"expected error #{@error_class} not to be captured, but it was"
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Fluent expectation builder for logs
|
|
454
|
+
class LogExpectation
|
|
455
|
+
def initialize(level, store)
|
|
456
|
+
@level = level.to_sym
|
|
457
|
+
@store = store
|
|
458
|
+
@expected_message = nil
|
|
459
|
+
@expected_data = nil
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# Specify expected message
|
|
463
|
+
#
|
|
464
|
+
# @param message [String, Regexp] Message to match
|
|
465
|
+
# @return [self]
|
|
466
|
+
#
|
|
467
|
+
def with_message(message)
|
|
468
|
+
@expected_message = message
|
|
469
|
+
self
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# Specify expected data
|
|
473
|
+
#
|
|
474
|
+
# @param data [Hash] Data to match
|
|
475
|
+
# @return [self]
|
|
476
|
+
#
|
|
477
|
+
def with_data(data)
|
|
478
|
+
@expected_data = data
|
|
479
|
+
self
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Check if the expectation is satisfied
|
|
483
|
+
#
|
|
484
|
+
# @return [Boolean]
|
|
485
|
+
#
|
|
486
|
+
def satisfied?
|
|
487
|
+
@store.logged?(@level, @expected_message, @expected_data)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
alias matches? satisfied?
|
|
491
|
+
|
|
492
|
+
def failure_message
|
|
493
|
+
parts = ["expected log at level :#{@level}"]
|
|
494
|
+
parts << "with message matching #{@expected_message.inspect}" if @expected_message
|
|
495
|
+
parts << "with data #{@expected_data.inspect}" if @expected_data
|
|
496
|
+
parts << ", but got: #{@store.logs_at_level(@level).map { |l| { message: l[:message], data: l[:data] } }.inspect}"
|
|
497
|
+
parts.join
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def failure_message_when_negated
|
|
501
|
+
"expected no log at level :#{@level} to be recorded, but it was"
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Fluent expectation builder for metrics
|
|
506
|
+
class MetricExpectation
|
|
507
|
+
def initialize(type, name, store)
|
|
508
|
+
@type = type.to_sym
|
|
509
|
+
@name = name.to_s
|
|
510
|
+
@store = store
|
|
511
|
+
@expected_value = nil
|
|
512
|
+
@expected_tags = nil
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Specify expected value
|
|
516
|
+
#
|
|
517
|
+
# @param value [Numeric] Value to match
|
|
518
|
+
# @return [self]
|
|
519
|
+
#
|
|
520
|
+
def with_value(value)
|
|
521
|
+
@expected_value = value
|
|
522
|
+
self
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Specify expected tags
|
|
526
|
+
#
|
|
527
|
+
# @param tags [Hash] Tags to match
|
|
528
|
+
# @return [self]
|
|
529
|
+
#
|
|
530
|
+
def with_tags(tags)
|
|
531
|
+
@expected_tags = tags
|
|
532
|
+
self
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Check if the expectation is satisfied
|
|
536
|
+
#
|
|
537
|
+
# @return [Boolean]
|
|
538
|
+
#
|
|
539
|
+
def satisfied?
|
|
540
|
+
@store.metric_recorded?(@type, @name, value: @expected_value, tags: @expected_tags)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
alias matches? satisfied?
|
|
544
|
+
|
|
545
|
+
def failure_message
|
|
546
|
+
parts = ["expected metric #{@type}('#{@name}')"]
|
|
547
|
+
parts << "with value #{@expected_value.inspect}" if @expected_value
|
|
548
|
+
parts << "with tags #{@expected_tags.inspect}" if @expected_tags
|
|
549
|
+
parts << ", but got: #{@store.metrics_named(@name).inspect}"
|
|
550
|
+
parts.join
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def failure_message_when_negated
|
|
554
|
+
"expected metric #{@type}('#{@name}') not to be recorded, but it was"
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# Fluent expectation builder for traces
|
|
559
|
+
class TraceExpectation
|
|
560
|
+
def initialize(name, store)
|
|
561
|
+
@name = name.to_s
|
|
562
|
+
@store = store
|
|
563
|
+
@expected_opts = nil
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# Specify expected options
|
|
567
|
+
#
|
|
568
|
+
# @param opts [Hash] Options to match
|
|
569
|
+
# @return [self]
|
|
570
|
+
#
|
|
571
|
+
def with(opts)
|
|
572
|
+
@expected_opts = opts
|
|
573
|
+
self
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# Check if the expectation is satisfied
|
|
577
|
+
#
|
|
578
|
+
# @return [Boolean]
|
|
579
|
+
#
|
|
580
|
+
def satisfied?
|
|
581
|
+
@store.trace_recorded?(@name, @expected_opts)
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
alias matches? satisfied?
|
|
585
|
+
|
|
586
|
+
def failure_message
|
|
587
|
+
parts = ["expected trace '#{@name}'"]
|
|
588
|
+
parts << "with options #{@expected_opts.inspect}" if @expected_opts
|
|
589
|
+
parts << " to be recorded, but it wasn't"
|
|
590
|
+
parts.join
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def failure_message_when_negated
|
|
594
|
+
"expected trace '#{@name}' not to be recorded, but it was"
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# Fluent expectation builder for alerts
|
|
599
|
+
class AlertExpectation
|
|
600
|
+
def initialize(name, store)
|
|
601
|
+
@name = name.to_s
|
|
602
|
+
@store = store
|
|
603
|
+
@expected_message = nil
|
|
604
|
+
@expected_severity = nil
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Specify expected message
|
|
608
|
+
#
|
|
609
|
+
# @param message [String] Message to match
|
|
610
|
+
# @return [self]
|
|
611
|
+
#
|
|
612
|
+
def with_message(message)
|
|
613
|
+
@expected_message = message
|
|
614
|
+
self
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# Specify expected severity
|
|
618
|
+
#
|
|
619
|
+
# @param severity [Symbol] Severity to match
|
|
620
|
+
# @return [self]
|
|
621
|
+
#
|
|
622
|
+
def with_severity(severity)
|
|
623
|
+
@expected_severity = severity
|
|
624
|
+
self
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Check if the expectation is satisfied
|
|
628
|
+
#
|
|
629
|
+
# @return [Boolean]
|
|
630
|
+
#
|
|
631
|
+
def satisfied?
|
|
632
|
+
@store.alert_sent?(@name, message: @expected_message, severity: @expected_severity)
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
alias matches? satisfied?
|
|
636
|
+
|
|
637
|
+
def failure_message
|
|
638
|
+
parts = ["expected alert '#{@name}'"]
|
|
639
|
+
parts << "with message '#{@expected_message}'" if @expected_message
|
|
640
|
+
parts << "with severity :#{@expected_severity}" if @expected_severity
|
|
641
|
+
parts << " to be sent, but it wasn't"
|
|
642
|
+
parts.join
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def failure_message_when_negated
|
|
646
|
+
"expected alert '#{@name}' not to be sent, but it was"
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|