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,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
|