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.
@@ -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