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,377 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Testing
5
+ # Thread-safe store for captured events, logs, errors, and metrics during tests
6
+ #
7
+ # This class is used internally by the testing helpers to store all
8
+ # captured data from stubbed SDK calls.
9
+ class EventStore
10
+ def initialize
11
+ @mutex = Mutex.new
12
+ @events = []
13
+ @metrics = []
14
+ @logs = []
15
+ @errors = []
16
+ @error_messages = []
17
+ @traces = []
18
+ @alerts = []
19
+ @notifications = []
20
+ @triggers = []
21
+ end
22
+
23
+ # === Events (Flux) ===
24
+
25
+ def record_event(name, properties = {})
26
+ @mutex.synchronize do
27
+ @events << {
28
+ name: name.to_s,
29
+ properties: properties,
30
+ timestamp: Time.now.utc
31
+ }
32
+ end
33
+ end
34
+
35
+ def events
36
+ @mutex.synchronize { @events.dup }
37
+ end
38
+
39
+ def events_named(name)
40
+ @mutex.synchronize do
41
+ @events.select { |e| e[:name] == name.to_s }
42
+ end
43
+ end
44
+
45
+ def event_tracked?(name, properties = nil)
46
+ @mutex.synchronize do
47
+ @events.any? do |event|
48
+ next false unless event[:name] == name.to_s
49
+ next true if properties.nil?
50
+
51
+ properties_match?(event[:properties], properties)
52
+ end
53
+ end
54
+ end
55
+
56
+ def last_event
57
+ @mutex.synchronize { @events.last }
58
+ end
59
+
60
+ def clear_events!
61
+ @mutex.synchronize { @events.clear }
62
+ end
63
+
64
+ # === Metrics (Flux) ===
65
+
66
+ def record_metric(type, name, value, opts = {})
67
+ @mutex.synchronize do
68
+ @metrics << {
69
+ type: type.to_sym,
70
+ name: name.to_s,
71
+ value: value,
72
+ tags: opts[:tags] || {},
73
+ timestamp: Time.now.utc
74
+ }
75
+ end
76
+ end
77
+
78
+ def metrics
79
+ @mutex.synchronize { @metrics.dup }
80
+ end
81
+
82
+ def metrics_named(name)
83
+ @mutex.synchronize do
84
+ @metrics.select { |m| m[:name] == name.to_s }
85
+ end
86
+ end
87
+
88
+ def metric_recorded?(type, name, value: nil, tags: nil)
89
+ @mutex.synchronize do
90
+ @metrics.any? do |metric|
91
+ next false unless metric[:type] == type.to_sym
92
+ next false unless metric[:name] == name.to_s
93
+ next false if value && metric[:value] != value
94
+ next false if tags && !properties_match?(metric[:tags], tags)
95
+
96
+ true
97
+ end
98
+ end
99
+ end
100
+
101
+ def clear_metrics!
102
+ @mutex.synchronize { @metrics.clear }
103
+ end
104
+
105
+ # === Logs (Recall) ===
106
+
107
+ def record_log(level, message, data = {})
108
+ @mutex.synchronize do
109
+ @logs << {
110
+ level: level.to_sym,
111
+ message: message.to_s,
112
+ data: data,
113
+ timestamp: Time.now.utc
114
+ }
115
+ end
116
+ end
117
+
118
+ def logs
119
+ @mutex.synchronize { @logs.dup }
120
+ end
121
+
122
+ def logs_at_level(level)
123
+ @mutex.synchronize do
124
+ @logs.select { |l| l[:level] == level.to_sym }
125
+ end
126
+ end
127
+
128
+ def logged?(level, message = nil, data = nil)
129
+ @mutex.synchronize do
130
+ @logs.any? do |log|
131
+ next false unless log[:level] == level.to_sym
132
+ next true if message.nil?
133
+
134
+ message_matches = case message
135
+ when Regexp
136
+ log[:message].match?(message)
137
+ else
138
+ log[:message].include?(message.to_s)
139
+ end
140
+
141
+ next false unless message_matches
142
+ next true if data.nil?
143
+
144
+ properties_match?(log[:data], data)
145
+ end
146
+ end
147
+ end
148
+
149
+ def clear_logs!
150
+ @mutex.synchronize { @logs.clear }
151
+ end
152
+
153
+ # === Errors (Reflex) ===
154
+
155
+ def record_error(exception, context = {})
156
+ @mutex.synchronize do
157
+ @errors << {
158
+ exception: exception,
159
+ error_class: exception.class.name,
160
+ message: exception.message,
161
+ backtrace: exception.backtrace,
162
+ context: context,
163
+ timestamp: Time.now.utc
164
+ }
165
+ end
166
+ end
167
+
168
+ def record_error_message(message, level, context = {})
169
+ @mutex.synchronize do
170
+ @error_messages << {
171
+ message: message.to_s,
172
+ level: level.to_sym,
173
+ context: context,
174
+ timestamp: Time.now.utc
175
+ }
176
+ end
177
+ end
178
+
179
+ def errors
180
+ @mutex.synchronize { @errors.dup }
181
+ end
182
+
183
+ def error_messages
184
+ @mutex.synchronize { @error_messages.dup }
185
+ end
186
+
187
+ def error_captured?(error_class = nil, message: nil, context: nil)
188
+ @mutex.synchronize do
189
+ @errors.any? do |error|
190
+ if error_class
191
+ next false unless error[:error_class] == error_class.to_s ||
192
+ (error_class.is_a?(Class) && error[:exception].is_a?(error_class))
193
+ end
194
+
195
+ if message
196
+ message_matches = case message
197
+ when Regexp
198
+ error[:message].match?(message)
199
+ else
200
+ error[:message].include?(message.to_s)
201
+ end
202
+ next false unless message_matches
203
+ end
204
+
205
+ next false if context && !properties_match?(error[:context], context)
206
+
207
+ true
208
+ end
209
+ end
210
+ end
211
+
212
+ def last_error
213
+ @mutex.synchronize { @errors.last }
214
+ end
215
+
216
+ def clear_errors!
217
+ @mutex.synchronize do
218
+ @errors.clear
219
+ @error_messages.clear
220
+ end
221
+ end
222
+
223
+ # === Traces (Pulse) ===
224
+
225
+ def record_trace(name, opts = {})
226
+ @mutex.synchronize do
227
+ @traces << {
228
+ name: name.to_s,
229
+ options: opts,
230
+ timestamp: Time.now.utc
231
+ }
232
+ end
233
+ end
234
+
235
+ def traces
236
+ @mutex.synchronize { @traces.dup }
237
+ end
238
+
239
+ def trace_recorded?(name, opts = nil)
240
+ @mutex.synchronize do
241
+ @traces.any? do |trace|
242
+ next false unless trace[:name] == name.to_s
243
+ next true if opts.nil?
244
+
245
+ properties_match?(trace[:options], opts)
246
+ end
247
+ end
248
+ end
249
+
250
+ def clear_traces!
251
+ @mutex.synchronize { @traces.clear }
252
+ end
253
+
254
+ # === Alerts (Signal) ===
255
+
256
+ def record_alert(name, message, severity, channels, data)
257
+ @mutex.synchronize do
258
+ @alerts << {
259
+ name: name.to_s,
260
+ message: message.to_s,
261
+ severity: severity.to_sym,
262
+ channels: channels,
263
+ data: data,
264
+ timestamp: Time.now.utc
265
+ }
266
+ end
267
+ end
268
+
269
+ def alerts
270
+ @mutex.synchronize { @alerts.dup }
271
+ end
272
+
273
+ def alert_sent?(name, message: nil, severity: nil)
274
+ @mutex.synchronize do
275
+ @alerts.any? do |alert|
276
+ next false unless alert[:name] == name.to_s
277
+ next false if message && !alert[:message].include?(message.to_s)
278
+ next false if severity && alert[:severity] != severity.to_sym
279
+
280
+ true
281
+ end
282
+ end
283
+ end
284
+
285
+ def clear_alerts!
286
+ @mutex.synchronize { @alerts.clear }
287
+ end
288
+
289
+ # === Notifications (Signal) ===
290
+
291
+ def record_notification(channel, message, title, data)
292
+ @mutex.synchronize do
293
+ @notifications << {
294
+ channel: Array(channel).map(&:to_s),
295
+ message: message.to_s,
296
+ title: title,
297
+ data: data,
298
+ timestamp: Time.now.utc
299
+ }
300
+ end
301
+ end
302
+
303
+ def notifications
304
+ @mutex.synchronize { @notifications.dup }
305
+ end
306
+
307
+ def clear_notifications!
308
+ @mutex.synchronize { @notifications.clear }
309
+ end
310
+
311
+ # === Triggers (Signal) ===
312
+
313
+ def record_trigger(rule_name, context)
314
+ @mutex.synchronize do
315
+ @triggers << {
316
+ rule_name: rule_name.to_s,
317
+ context: context,
318
+ timestamp: Time.now.utc
319
+ }
320
+ end
321
+ end
322
+
323
+ def triggers
324
+ @mutex.synchronize { @triggers.dup }
325
+ end
326
+
327
+ def clear_triggers!
328
+ @mutex.synchronize { @triggers.clear }
329
+ end
330
+
331
+ # === General ===
332
+
333
+ def clear!
334
+ @mutex.synchronize do
335
+ @events.clear
336
+ @metrics.clear
337
+ @logs.clear
338
+ @errors.clear
339
+ @error_messages.clear
340
+ @traces.clear
341
+ @alerts.clear
342
+ @notifications.clear
343
+ @triggers.clear
344
+ end
345
+ end
346
+
347
+ def empty?
348
+ @mutex.synchronize do
349
+ @events.empty? &&
350
+ @metrics.empty? &&
351
+ @logs.empty? &&
352
+ @errors.empty? &&
353
+ @error_messages.empty? &&
354
+ @traces.empty? &&
355
+ @alerts.empty? &&
356
+ @notifications.empty? &&
357
+ @triggers.empty?
358
+ end
359
+ end
360
+
361
+ private
362
+
363
+ def properties_match?(actual, expected)
364
+ expected.all? do |key, value|
365
+ actual_value = actual[key] || actual[key.to_s] || actual[key.to_sym]
366
+
367
+ case value
368
+ when Regexp
369
+ actual_value.to_s.match?(value)
370
+ else
371
+ actual_value == value
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end