bugsnag-maze-runner 7.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/bin/bugsnag-print-load-paths +6 -0
  3. data/bin/download-logs +74 -0
  4. data/bin/maze-runner +174 -0
  5. data/bin/upload-app +56 -0
  6. data/lib/features/scripts/await-android-emulator.sh +11 -0
  7. data/lib/features/scripts/clear-android-app-data.sh +8 -0
  8. data/lib/features/scripts/force-stop-android-app.sh +8 -0
  9. data/lib/features/scripts/install-android-app.sh +15 -0
  10. data/lib/features/scripts/launch-android-app.sh +38 -0
  11. data/lib/features/scripts/launch-android-emulator.sh +15 -0
  12. data/lib/features/steps/android_steps.rb +51 -0
  13. data/lib/features/steps/app_automator_steps.rb +228 -0
  14. data/lib/features/steps/aws_sam_steps.rb +212 -0
  15. data/lib/features/steps/breadcrumb_steps.rb +80 -0
  16. data/lib/features/steps/browser_steps.rb +93 -0
  17. data/lib/features/steps/build_api_steps.rb +25 -0
  18. data/lib/features/steps/document_server_steps.rb +7 -0
  19. data/lib/features/steps/error_reporting_steps.rb +358 -0
  20. data/lib/features/steps/feature_flag_steps.rb +190 -0
  21. data/lib/features/steps/header_steps.rb +72 -0
  22. data/lib/features/steps/log_steps.rb +29 -0
  23. data/lib/features/steps/multipart_request_steps.rb +142 -0
  24. data/lib/features/steps/network_steps.rb +135 -0
  25. data/lib/features/steps/payload_steps.rb +257 -0
  26. data/lib/features/steps/proxy_steps.rb +34 -0
  27. data/lib/features/steps/query_parameter_steps.rb +31 -0
  28. data/lib/features/steps/request_assertion_steps.rb +186 -0
  29. data/lib/features/steps/runner_steps.rb +428 -0
  30. data/lib/features/steps/session_tracking_steps.rb +116 -0
  31. data/lib/features/steps/trace_steps.rb +206 -0
  32. data/lib/features/steps/value_steps.rb +119 -0
  33. data/lib/features/support/env.rb +7 -0
  34. data/lib/features/support/internal_hooks.rb +207 -0
  35. data/lib/maze/api/appium/file_manager.rb +29 -0
  36. data/lib/maze/appium_server.rb +112 -0
  37. data/lib/maze/assertions/request_set_assertions.rb +97 -0
  38. data/lib/maze/aws/sam.rb +112 -0
  39. data/lib/maze/aws_public_ip.rb +53 -0
  40. data/lib/maze/bugsnag_config.rb +42 -0
  41. data/lib/maze/checks/assert_check.rb +69 -0
  42. data/lib/maze/checks/noop_check.rb +34 -0
  43. data/lib/maze/client/appium/base_client.rb +131 -0
  44. data/lib/maze/client/appium/bb_client.rb +102 -0
  45. data/lib/maze/client/appium/bb_devices.rb +127 -0
  46. data/lib/maze/client/appium/bs_client.rb +91 -0
  47. data/lib/maze/client/appium/bs_devices.rb +141 -0
  48. data/lib/maze/client/appium/bs_legacy_client.rb +31 -0
  49. data/lib/maze/client/appium/local_client.rb +67 -0
  50. data/lib/maze/client/appium.rb +23 -0
  51. data/lib/maze/client/bb_api_client.rb +102 -0
  52. data/lib/maze/client/bb_client_utils.rb +181 -0
  53. data/lib/maze/client/bs_client_utils.rb +168 -0
  54. data/lib/maze/client/selenium/base_client.rb +15 -0
  55. data/lib/maze/client/selenium/bb_browsers.yml +188 -0
  56. data/lib/maze/client/selenium/bb_client.rb +38 -0
  57. data/lib/maze/client/selenium/bs_browsers.yml +257 -0
  58. data/lib/maze/client/selenium/bs_client.rb +89 -0
  59. data/lib/maze/client/selenium/local_client.rb +16 -0
  60. data/lib/maze/client/selenium.rb +16 -0
  61. data/lib/maze/compare.rb +161 -0
  62. data/lib/maze/configuration.rb +182 -0
  63. data/lib/maze/docker.rb +147 -0
  64. data/lib/maze/document_server.rb +46 -0
  65. data/lib/maze/driver/appium.rb +198 -0
  66. data/lib/maze/driver/browser.rb +124 -0
  67. data/lib/maze/errors.rb +52 -0
  68. data/lib/maze/generator.rb +55 -0
  69. data/lib/maze/helper.rb +122 -0
  70. data/lib/maze/hooks/appium_hooks.rb +55 -0
  71. data/lib/maze/hooks/browser_hooks.rb +15 -0
  72. data/lib/maze/hooks/command_hooks.rb +9 -0
  73. data/lib/maze/hooks/error_code_hook.rb +49 -0
  74. data/lib/maze/hooks/hooks.rb +61 -0
  75. data/lib/maze/http_request.rb +21 -0
  76. data/lib/maze/interactive_cli.rb +173 -0
  77. data/lib/maze/logger.rb +86 -0
  78. data/lib/maze/macos_utils.rb +14 -0
  79. data/lib/maze/maze_output.rb +88 -0
  80. data/lib/maze/network.rb +49 -0
  81. data/lib/maze/option/parser.rb +240 -0
  82. data/lib/maze/option/processor.rb +130 -0
  83. data/lib/maze/option/validator.rb +155 -0
  84. data/lib/maze/option.rb +62 -0
  85. data/lib/maze/plugins/bugsnag_reporting_plugin.rb +49 -0
  86. data/lib/maze/plugins/cucumber_report_plugin.rb +101 -0
  87. data/lib/maze/plugins/error_code_plugin.rb +21 -0
  88. data/lib/maze/plugins/global_retry_plugin.rb +38 -0
  89. data/lib/maze/proxy.rb +114 -0
  90. data/lib/maze/request_list.rb +87 -0
  91. data/lib/maze/request_repeater.rb +49 -0
  92. data/lib/maze/retry_handler.rb +67 -0
  93. data/lib/maze/runner.rb +149 -0
  94. data/lib/maze/schemas/OtelTraceSchema.json +390 -0
  95. data/lib/maze/schemas/trace_schema.rb +7 -0
  96. data/lib/maze/schemas/trace_validator.rb +98 -0
  97. data/lib/maze/server.rb +251 -0
  98. data/lib/maze/servlets/base_servlet.rb +27 -0
  99. data/lib/maze/servlets/command_servlet.rb +47 -0
  100. data/lib/maze/servlets/log_servlet.rb +64 -0
  101. data/lib/maze/servlets/reflective_servlet.rb +70 -0
  102. data/lib/maze/servlets/servlet.rb +199 -0
  103. data/lib/maze/servlets/temp.rb +0 -0
  104. data/lib/maze/servlets/trace_servlet.rb +13 -0
  105. data/lib/maze/store.rb +15 -0
  106. data/lib/maze/terminating_server.rb +129 -0
  107. data/lib/maze/timers.rb +51 -0
  108. data/lib/maze/wait.rb +35 -0
  109. data/lib/maze.rb +27 -0
  110. data/lib/utils/deep_merge.rb +17 -0
  111. data/lib/utils/selenium_money_patch.rb +17 -0
  112. metadata +451 -0
@@ -0,0 +1,358 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!group Error reporting steps
4
+
5
+ # Verifies that generic elements of an error payload are present.
6
+ # APIKey fields and headers are tested against the '$api_key' global variable.
7
+ #
8
+ # @step_input version [String] The payload version expected
9
+ # @step_input name [String] The expected name of the notifier
10
+ Then('the error is valid for the error reporting API version {string} for the {string} notifier') do |version, name|
11
+ step "the error is valid for the error reporting API version \"#{version}\"" \
12
+ " for the \"#{name}\" notifier with the apiKey \"#{$api_key}\""
13
+ end
14
+
15
+ # Verifies that generic elements of an error payload are present.
16
+ #
17
+ # @step_input version [String] The payload version expected
18
+ # @step_input name [String] The expected name of the notifier
19
+ # @step_input api_key [String] The API key expected
20
+ Then('the error is valid for the error reporting API version {string}' \
21
+ ' for the {string} notifier with the apiKey {string}') do |payload_version, notifier_name, api_key|
22
+ steps %(
23
+ Then the error "Bugsnag-Api-Key" header equals "#{api_key}"
24
+ And the error payload field "apiKey" equals "#{api_key}"
25
+ And the error "Bugsnag-Payload-Version" header equals "#{payload_version}"
26
+ And the error payload contains the payloadVersion "#{payload_version}"
27
+ And the error "Content-Type" header equals "application/json"
28
+ And the error "Bugsnag-Sent-At" header is a timestamp
29
+ And the error Bugsnag-Integrity header is valid
30
+
31
+ And the error payload field "notifier.name" equals "#{notifier_name}"
32
+ And the error payload field "notifier.url" is not null
33
+ And the error payload field "notifier.version" is not null
34
+ And the error payload field "events" is a non-empty array
35
+
36
+ And each element in error payload field "events" has "severity"
37
+ And each element in error payload field "events" has "severityReason.type"
38
+ And each element in error payload field "events" has "unhandled"
39
+ And each element in error payload field "events" has "exceptions"
40
+ )
41
+ end
42
+
43
+
44
+ # Verifies that an event is correct for an unhandled error
45
+ # This checks various elements of the payload matching an unhandled error including:
46
+ # The unhandled flag
47
+ # Any attached session information
48
+ # Severity
49
+ #
50
+ # @param event [Integer] The event to verify
51
+ Then('event {int} is unhandled') do |event|
52
+ test_unhandled_state(event, true)
53
+ end
54
+
55
+ # Verifies that an event is correct for an unhandled error
56
+ # This checks various elements of the payload matching an unhandled error including:
57
+ # The unhandled flag
58
+ # Any attached session information
59
+ # Severity
60
+ #
61
+ # @param event [Integer] The event to verify
62
+ # @param severity [String] An expected severity different to the default "error"
63
+ Then('event {int} is unhandled with the severity {string}') do |event, severity|
64
+ test_unhandled_state(event, true, severity)
65
+ end
66
+
67
+ # Verifies that an event is correct for an handled error
68
+ # This checks various elements of the payload matching an unhandled error including:
69
+ # The unhandled flag
70
+ # Any attached session information
71
+ # Severity
72
+ #
73
+ # @param event [Integer] The event to verify
74
+ Then('event {int} is handled') do |event|
75
+ test_unhandled_state(event, false)
76
+ end
77
+
78
+ # Verifies that an event is correct for an handled error
79
+ # This checks various elements of the payload matching an unhandled error including:
80
+ # The unhandled flag
81
+ # Any attached session information
82
+ # Severity
83
+ #
84
+ # @param event [Integer] The event to verify
85
+ # @param severity [String] An expected severity different to the default "error"
86
+ Then('event {int} is handled with the severity {string}') do |event, severity|
87
+ test_unhandled_state(event, false, severity)
88
+ end
89
+
90
+ # Checks the payloadVersion is set correctly.
91
+ # For Javascript this should be in the events.
92
+ # For all other notifiers this should be a top-level key.
93
+ #
94
+ # @step_input payload_version [String] The payload version expected
95
+ Then('the error payload contains the payloadVersion {string}') do |payload_version|
96
+ body_version = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], 'payloadVersion')
97
+ body_set = payload_version == body_version
98
+ event_version = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], 'events.0.payloadVersion')
99
+ event_set = payload_version == event_version
100
+ Maze.check.true(
101
+ body_set || event_set,
102
+ "The payloadVersion was not the expected value of #{payload_version}. " \
103
+ "#{body_version} found in body, #{event_version} found in event"
104
+ )
105
+ end
106
+
107
+ # Tests whether a value in the first event entry matches a literal.
108
+ #
109
+ # @step_input field [String] The relative location of the value to test
110
+ # @step_input literal [Enum] The literal to test against, one of: true, false, null, not null
111
+ Then(/^the event "(.+)" is (true|false|null|not null)$/) do |field, literal|
112
+ step "the error payload field \"events.0.#{field}\" is #{literal}"
113
+ end
114
+
115
+ # Tests whether a value in the first event entry matches a string.
116
+ #
117
+ # @step_input field [String] The relative location of the value to test
118
+ # @step_input string_value [String] The string to match against
119
+ Then('the event {string} equals {string}') do |field, string_value|
120
+ step "the error payload field \"events.0.#{field}\" equals \"#{string_value}\""
121
+ end
122
+
123
+ # Tests whether a value in the first event entry matches a floating point value.
124
+ #
125
+ # @step_input field [String] The relative location of the value to test
126
+ # @step_input string_value [String] The string to match against
127
+ Then('the event {string} equals {float}') do |field, float_value|
128
+ step "the error payload field \"events.0.#{field}\" equals #{float_value}"
129
+ end
130
+
131
+ # Tests whether a value in the first event entry matches a floating point value, to a given number of decimal places.
132
+ #
133
+ # @step_input field [String] The relative location of the value to test
134
+ # @step_input string_value [String] The string to match against
135
+ Then('the event {string} equals {float} to {int} decimal place(s)') do |field, float_value, places|
136
+ step "the error payload field \"events.0.#{field}\" equals #{float_value} to #{places} decimal places"
137
+ end
138
+
139
+ # Tests whether a value in the first event entry equals an integer.
140
+ #
141
+ # @step_input field [String] The relative location of the value to test
142
+ # @step_input value [Integer] The integer to test against
143
+ Then('the event {string} equals {int}') do |field, value|
144
+ step "the error payload field \"events.0.#{field}\" equals #{value}"
145
+ end
146
+
147
+ # Tests whether a value in the first event entry starts with a string.
148
+ #
149
+ # @step_input field [String] The relative location of the value to test
150
+ # @step_input string_value [String] The string to match against
151
+ Then('the event {string} starts with {string}') do |field, string_value|
152
+ step "the error payload field \"events.0.#{field}\" starts with \"#{string_value}\""
153
+ end
154
+
155
+ # Tests whether a value in the first event entry ends with a string.
156
+ #
157
+ # @step_input field [String] The relative location of the value to test
158
+ # @step_input string_value [String] The string to match against
159
+ Then('the event {string} ends with {string}') do |field, string_value|
160
+ step "the error payload field \"events.0.#{field}\" ends with \"#{string_value}\""
161
+ end
162
+
163
+ # Tests whether a value in the first event entry matches a regex.
164
+ #
165
+ # @step_input field [String] The relative location of the value to test
166
+ # @step_input pattern [String] The regex to match against
167
+ Then('the event {string} matches {string}') do |field, pattern|
168
+ step "the error payload field \"events.0.#{field}\" matches the regex \"#{pattern}\""
169
+ end
170
+
171
+ # Tests whether a value in the first event entry is a timestamp.
172
+ #
173
+ # @step_input field [String] The relative location of the value to test
174
+ Then('the event {string} is a timestamp') do |field|
175
+ step "the error payload field \"events.0.#{field}\" matches the regex \"#{TIMESTAMP_REGEX}\""
176
+ end
177
+
178
+ # Tests whether a value in the first event entry is a numeric and parsable timestamp.
179
+ #
180
+ # @step_input field [String] The relative location of the value to test
181
+ Then('the event {string} is a parsable timestamp in seconds') do |field|
182
+ step "the error payload field \"events.0.#{field}\" is a parsable timestamp in seconds"
183
+ end
184
+
185
+ # Tests the Event field value against an environment variable.
186
+ #
187
+ # @step_input field [String] The payload element to check
188
+ # @step_input env_var [String] The environment variable to test against
189
+ Then('the event {string} equals the environment variable {string}') do |field, env_var|
190
+ step "the error payload field \"events.0.#{field}\" equals the environment variable \"#{env_var}\""
191
+ end
192
+
193
+ # Tests whether a value in the first event entry matches a JSON fixture.
194
+ #
195
+ # @step_input field [String] The relative location of the value to test
196
+ # @step_input fixture_path [String] The fixture to match against
197
+ Then('the event {string} matches the JSON fixture in {string}') do |field, fixture_path|
198
+ step "the error payload field \"events.0.#{field}\" matches the JSON fixture in \"#{fixture_path}\""
199
+ end
200
+
201
+ Then('the event {string} string is empty') do |keypath|
202
+ value = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], keypath)
203
+ Maze.check.true(value.nil? || value.empty?, "The #{keypath} is not empty: '#{value}'")
204
+ end
205
+
206
+ Then('the event {string} is greater than {int}') do |keypath, int|
207
+ value = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.0.#{keypath}")
208
+ Maze.check.false(value.nil?, "The event #{keypath} is nil")
209
+ Maze.check.operator(value, :>, int)
210
+ end
211
+
212
+ # Tests whether a value in the first exception of the first event entry starts with a string.
213
+ #
214
+ # @step_input field [String] The relative location of the value to test
215
+ # @step_input string_value [String] The string to match against
216
+ Then('the exception {string} starts with {string}') do |field, string_value|
217
+ step "the error payload field \"events.0.exceptions.0.#{field}\" starts with \"#{string_value}\""
218
+ end
219
+
220
+ # Tests whether a value in the first exception of the first event entry ends with a string.
221
+ #
222
+ # @step_input field [String] The relative location of the value to test
223
+ # @step_input string_value [String] The string to match against
224
+ Then('the exception {string} ends with {string}') do |field, string_value|
225
+ step "the error payload field \"events.0.exceptions.0.#{field}\" ends with \"#{string_value}\""
226
+ end
227
+
228
+ # Tests whether a value in the first exception of the first event entry equals a string.
229
+ #
230
+ # @step_input field [String] The relative location of the value to test
231
+ # @step_input string_value [String] The string to match against
232
+ Then('the exception {string} equals {string}') do |field, string_value|
233
+ step "the error payload field \"events.0.exceptions.0.#{field}\" equals \"#{string_value}\""
234
+ end
235
+
236
+ # Tests whether a value in the first exception of the first event entry matches a regex.
237
+ #
238
+ # @step_input field [String] The relative location of the value to test
239
+ # @step_input pattern [String] The regex to match against
240
+ Then('the exception {string} matches {string}') do |field, pattern|
241
+ step "the error payload field \"events.0.exceptions.0.#{field}\" matches the regex \"#{pattern}\""
242
+ end
243
+
244
+ # Tests whether a element of a stack frame in the first exception of the first event equals an integer.
245
+ #
246
+ # @step_input key [String] The element of the stack frame to test
247
+ # @step_input num [Integer] The stack frame where the element is present
248
+ # @step_input value [Integer] The value to test against
249
+ Then('the {string} of stack frame {int} equals {int}') do |key, num, value|
250
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
251
+ step "the error payload field \"#{field}\" equals #{value}"
252
+ end
253
+
254
+ # Tests whether an element of a stack frame in the first exception of the first event matches a regex pattern.
255
+ #
256
+ # @step_input key [String] The element of the stack frame to test
257
+ # @step_input num [Integer] The stack frame where the element is present
258
+ # @step_input pattern [String] The regex to match against
259
+ Then('the {string} of stack frame {int} matches {string}') do |key, num, pattern|
260
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
261
+ step "the error payload field \"#{field}\" matches the regex \"#{pattern}\""
262
+ end
263
+
264
+ # Tests whether an element of a stack frame in the first exception of the first event equals a string.
265
+ #
266
+ # @step_input key [String] The element of the stack frame to test
267
+ # @step_input num [Integer] The stack frame where the element is present
268
+ # @step_input value [String] The value to test against
269
+ Then('the {string} of stack frame {int} equals {string}') do |key, num, value|
270
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
271
+ step "the error payload field \"#{field}\" equals \"#{value}\""
272
+ end
273
+
274
+ # Tests whether an element of a stack frame in the first exception of the first event starts with a string.
275
+ #
276
+ # @step_input key [String] The element of the stack frame to test
277
+ # @step_input num [Integer] The stack frame where the element is present
278
+ # @step_input value [String] The value to test against
279
+ Then('the {string} of stack frame {int} starts with {string}') do |key, num, value|
280
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
281
+ step "the error payload field \"#{field}\" starts with \"#{value}\""
282
+ end
283
+
284
+ # Tests whether an element of a stack frame in the first exception of the first event ends with a string.
285
+ #
286
+ # @step_input key [String] The element of the stack frame to test
287
+ # @step_input num [Integer] The stack frame where the element is present
288
+ # @step_input value [String] The value to test against
289
+ Then('the {string} of stack frame {int} ends with {string}') do |key, num, value|
290
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
291
+ step "the error payload field \"#{field}\" ends with \"#{value}\""
292
+ end
293
+
294
+ # Tests whether an element of a stack frame in the first exception of the first event matches a literal.
295
+ #
296
+ # @step_input key [String] The element of the stack frame to test
297
+ # @step_input num [Integer] The stack frame where the element is present
298
+ # @step_input literal [Enum] The literal to test against, one of: true, false, null, not null
299
+ Then(/^the "(.*)" of stack frame (\d*) is (true|false|null|not null)$/) do |key, num, literal|
300
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
301
+ step "the error payload field \"#{field}\" is #{literal}"
302
+ end
303
+
304
+ # Tests whether a thread from the first event, identified by name, is the error reporting thread.
305
+ #
306
+ # @step_input thread_name [String] The name of the thread to test
307
+ Then('the thread with name {string} contains the error reporting flag') do |thread_name|
308
+ validate_error_reporting_thread('name', thread_name)
309
+ end
310
+
311
+ # Tests whether a thread from the first event, identified by an id, is the error reporting thread.
312
+ #
313
+ # @step_input thread_id [String] The id of the thread to test
314
+ Then('the thread with id {string} contains the error reporting flag') do |thread_id|
315
+ validate_error_reporting_thread('id', thread_id)
316
+ end
317
+
318
+ # Tests that a thread from the first event, identified by a particular key-value pair, is the error reporting thread.
319
+ #
320
+ # @param payload_key [String] The thread identifier key
321
+ # @param payload_value [Any] The thread identifier value
322
+ def validate_error_reporting_thread(payload_key, payload_value)
323
+ threads = Maze::Server.errors.current[:body]['events'].first['threads']
324
+ Maze.check.kind_of Array, threads
325
+ count = 0
326
+
327
+ threads.each do |thread|
328
+ count += 1 if thread[payload_key].to_s == payload_value && thread['errorReportingThread'] == true
329
+ end
330
+ Maze.check.equal(1, count)
331
+ end
332
+
333
+ # Tests whether an event has the correct attributes we'd expect for un/handled events
334
+ #
335
+ # @param event [Integer] The index of the event
336
+ # @param unhandled [Boolean] Whether the event is unhandled or handled
337
+ # @param severity [String] Optional. An overwritten severity to look for
338
+ def test_unhandled_state(event, unhandled, severity = nil)
339
+ expected_unhandled_state = unhandled ? 'true' : 'false'
340
+ expected_severity = if severity
341
+ severity
342
+ elsif unhandled
343
+ 'error'
344
+ else
345
+ 'warning'
346
+ end
347
+ steps %(
348
+ Then the error payload field "events.#{event}.unhandled" is #{expected_unhandled_state}
349
+ And the error payload field "events.#{event}.severity" equals "#{expected_severity}"
350
+ )
351
+
352
+ return if Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event}.session").nil?
353
+
354
+ session_field = unhandled ? 'unhandled' : 'handled'
355
+ steps %(
356
+ And the error payload field "events.#{event}.session.events.#{session_field}" is greater than 0
357
+ )
358
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!group Feature flag steps
4
+
5
+ # Verifies that there are no feature flags present in a given event
6
+ #
7
+ # @step_input event_id [Integer] The id of the event in the payloads array
8
+ Then('event {int} has no feature flags') do |event_id|
9
+ event = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event_id}")
10
+ Maze.check.false(has_feature_flags?(event), "Feature flags expected to be absent or empty, was #{event['featureFlags']}")
11
+ end
12
+
13
+ # Verifies that the are no feature flags present
14
+ Then('the event has no feature flags') do
15
+ steps %(
16
+ Then event 0 has no feature flags
17
+ )
18
+ end
19
+
20
+ # Verifies a feature flag with a specific variant is uniquely present in a given event
21
+ #
22
+ # @step_input event_id [Integer] The id of the event in the payloads array
23
+ # @step_input flag_name [String] The featureFlag value expected
24
+ # @step_input variant [String] The variant value expected
25
+ Then('event {int} contains the feature flag {string} with variant {string}') do |event_id, flag_name, variant|
26
+ event = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event_id}")
27
+ Maze.check.true(has_feature_flags?(event), "Expected feature flags were not present in event #{event_id}: #{event}")
28
+ feature_flags = event['featureFlags']
29
+ # Test for flag name uniqueness
30
+ Maze.check.true(
31
+ feature_flags.one? { |flag| flag['featureFlag'].eql?(flag_name) },
32
+ "Expected single flag with 'featureFlag' value: #{flag_name}. Present flags: #{feature_flags}"
33
+ )
34
+
35
+ flag = feature_flags.find { |flag| flag['featureFlag'].eql?(flag_name) }
36
+ # Test the variant value
37
+ Maze.check.true(
38
+ flag.has_key?('variant') && flag['variant'].eql?(variant),
39
+ "Feature flag: #{flag} did not have variant: #{variant}. All flags: #{feature_flags}"
40
+ )
41
+ end
42
+
43
+ # Verifies a feature flag with a specific variant is uniquely present
44
+ #
45
+ # @step_input flag_name [String] The featureFlag value expected
46
+ # @step_input variant [String] The variant value expected
47
+ Then('the event contains the feature flag {string} with variant {string}') do |flag_name, variant|
48
+ steps %(
49
+ Then event 0 contains the feature flag "#{flag_name}" with variant "#{variant}"
50
+ )
51
+ end
52
+
53
+ # Verifies a feature flag with no variant (either null or missing) is uniquely present in a given event
54
+ #
55
+ # @step_input event_id [Integer] The id of the event in the payloads array
56
+ # @step_input flag_name [String] The featureFlag value expected
57
+ Then('event {int} contains the feature flag {string} with no variant') do |event_id, flag_name|
58
+ event = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event_id}")
59
+ Maze.check.true(has_feature_flags?(event),
60
+ "Expected feature flags were not present in event #{event_id}: #{event}")
61
+ feature_flags = event['featureFlags']
62
+ # Test for flag name uniqueness
63
+ Maze.check.true(
64
+ feature_flags.one? { |flag| flag['featureFlag'].eql?(flag_name) },
65
+ "Expected single flag with 'featureFlag' value: #{flag_name}. All flags: #{feature_flags}"
66
+ )
67
+
68
+ flag = feature_flags.find { |flag| flag['featureFlag'].eql?(flag_name) }
69
+ # Test the variant value
70
+ Maze.check.false(
71
+ flag.has_key?('variant'),
72
+ "Feature flag: #{flag} expected to have no variant. All flags: #{feature_flags}"
73
+ )
74
+ end
75
+
76
+ # Verifies a feature flag with no variant (either null or missing) is uniquely present
77
+ #
78
+ # @step_input flag_name [String] The featureFlag value expected
79
+ Then('the event contains the feature flag {string} with no variant') do |flag_name|
80
+ steps %(
81
+ Then event 0 contains the feature flag "#{flag_name}" with no variant
82
+ )
83
+ end
84
+
85
+ # Verifies that a number of feature flags outlined in a table are all present and unique in the given event
86
+ #
87
+ # The DataTable used for this step should have `featureFlag` and `variant` columns, containing the appropriate
88
+ # values. For flags with a variant leave the `variant` column blank.
89
+ #
90
+ # Example:
91
+ # | featureFlag | variant |
92
+ # | my_flag_1 | var_1 |
93
+ # | my_flag_2 | var_2 |
94
+ # | my_flag_3 | | # Should not have a variant present
95
+ #
96
+ # @step_input event_id [Integer] The id of the event in the payloads array
97
+ # @step_input table [Cucumber::MultilineArgument::DataTable] Table of expected values
98
+ Then('event {int} contains the following feature flags:') do |event_id, table|
99
+ event = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event_id}")
100
+ verify_feature_flags_with_table(event, table)
101
+ end
102
+
103
+ # Verifies that a number of feature flags outlined in a table are all present and unique
104
+ #
105
+ # See above for data table details
106
+ #
107
+ # @step_input table [Cucumber::MultilineArgument::DataTable] Table of expected values
108
+ Then('the event contains the following feature flags:') do |table|
109
+ event = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], 'events.0')
110
+ verify_feature_flags_with_table(event, table)
111
+ end
112
+
113
+ # Verifies a feature flag a specific name is not present, regardless of variant, for a given event
114
+ #
115
+ # @step_input event_id [Integer] The id of the event in the payloads array
116
+ # @step_input flag_name [String] The featureFlag value not expected
117
+ Then('event {int} does not contain the feature flag {string}') do |event_id, flag_name|
118
+ event = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event_id}")
119
+ Maze.check.true(
120
+ has_feature_flags?(event),
121
+ "Expected feature flags were not present in event #{event_id}: #{event}"
122
+ )
123
+ feature_flags = event['featureFlags']
124
+ Maze.check.true(
125
+ feature_flags.none? { |flag| flag['featureFlag'].eql?(flag_name) },
126
+ "Expected to not find feature flag #{flag_name}. All flags: #{feature_flags}"
127
+ )
128
+ end
129
+
130
+ # Verifies a feature flag a specific name is not present, regardless of variant
131
+ #
132
+ # @step_input flag_name [String] The featureFlag value not expected
133
+ Then('the event does not contain the feature flag {string}') do |flag_name|
134
+ steps %(
135
+ Then event 0 does not contain the feature flag "#{flag_name}"
136
+ )
137
+ end
138
+
139
+ def verify_feature_flags_with_table(event, table)
140
+ Maze.check.true(
141
+ has_feature_flags?(event),
142
+ "Expected feature flags were not present in event: #{event}"
143
+ )
144
+ feature_flags = event['featureFlags']
145
+
146
+ expected_features = table.hashes
147
+ Maze.check.true(
148
+ feature_flags.size == expected_features.size,
149
+ "Expected #{expected_features.size} features,
150
+ found #{feature_flags}"
151
+ )
152
+ expected_features.each do |expected|
153
+ flag_name = expected['featureFlag']
154
+ variant = expected['variant']
155
+ # Test for flag name uniqueness
156
+ Maze.check.true(
157
+ feature_flags.one? { |flag| flag['featureFlag'].eql?(flag_name) },
158
+ "Expected single flag with 'featureFlag' value: #{flag_name}. Present flags: #{feature_flags}"
159
+ )
160
+ flag = feature_flags.find { |flag| flag['featureFlag'].eql?(flag_name) }
161
+ # Test the variant value
162
+ if variant.nil? || expected['variant'].empty?
163
+ Maze.check.false(
164
+ flag.has_key?('variant'),
165
+ "Feature flag: #{flag} expected to have no variant. All flags: #{feature_flags}"
166
+ )
167
+ else
168
+ Maze.check.true(
169
+ flag.has_key?('variant') && flag['variant'].eql?(variant),
170
+ "Feature flag: #{flag} did not have variant: #{variant}. All flags: #{feature_flags}"
171
+ )
172
+ end
173
+ end
174
+ end
175
+
176
+ def has_feature_flags?(event)
177
+ if event.has_key?('featureFlags')
178
+ Maze.check.false(
179
+ event['featureFlags'].nil?,
180
+ 'The feature flags key was present, but null'
181
+ )
182
+ Maze.check.true(
183
+ event['featureFlags'].is_a?(Array),
184
+ "The feature flags key was present, but the value: #{event['featureFlags']} must be an array"
185
+ )
186
+ !event['featureFlags'].empty?
187
+ else
188
+ false
189
+ end
190
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!group Header steps
4
+
5
+ # Tests that a request header is not null
6
+ #
7
+ # @step_input request_type [String] The type of request (error, session, build, etc)
8
+ # @step_input header_name [String] The header to test
9
+ Then('the {word} {string} header is not null') do |request_type, header_name|
10
+ Maze.check.not_nil(Maze::Server.list_for(request_type).current[:request][header_name],
11
+ "The #{request_type} '#{header_name}' header should not be null")
12
+ end
13
+
14
+ # Tests that a request header is null
15
+ #
16
+ # @step_input request_type [String] The type of request (error, session, build, etc)
17
+ # @step_input header_name [String] The header to test
18
+ Then('the {word} {string} header is null') do |request_type, header_name|
19
+ request = Maze::Server.list_for(request_type).current[:request]
20
+
21
+ Maze.check.nil(request[header_name],
22
+ "The #{request_type} '#{header_name}' header should be null")
23
+ end
24
+
25
+ # Tests that request header equals a string
26
+ #
27
+ # @step_input request_type [String] The type of request (error, session, build, etc)
28
+ # @step_input header_name [String] The header to test
29
+ # @step_input header_value [String] The string it should match
30
+ Then('the {word} {string} header equals {string}') do |request_type, header_name, header_value|
31
+ Maze.check.not_nil(Maze::Server.list_for(request_type).current[:request][header_name],
32
+ "The #{request_type} '#{header_name}' header wasn't present in the request")
33
+ Maze.check.equal(header_value, Maze::Server.list_for(request_type).current[:request][header_name])
34
+ end
35
+
36
+ # Tests that a request header matches a regex
37
+ #
38
+ # @step_input request_type [String] The type of request (error, session, build, etc)
39
+ # @step_input header_name [String] The header to test
40
+ # @step_input regex_string [String] The regex to match with
41
+ Then('the {word} {string} header matches the regex {string}') do |request_type, header_name, regex_string|
42
+ regex = Regexp.new(regex_string)
43
+ value = Maze::Server.list_for(request_type).current[:request][header_name]
44
+ Maze.check.match(regex, value)
45
+ end
46
+
47
+ # Tests that a request header matches one of a list of strings
48
+ #
49
+ # @step_input request_type [String] The type of request (error, session, build, etc)
50
+ # @step_input header_name [String] The header to test
51
+ # @step_input header_values [DataTable] A parsed data table
52
+ Then('the {word} {string} header equals one of:') do |request_type, header_name, header_values|
53
+ Maze.check.include(header_values.raw.flatten, Maze::Server.list_for(request_type).current[:request][header_name])
54
+ end
55
+
56
+ # Tests that a request header is a timestamp.
57
+ #
58
+ # @step_input request_type [String] The type of request (error, session, build, etc)
59
+ # @step_input header_name [String] The header to test
60
+ Then('the {word} {string} header is a timestamp') do |request_type, header_name|
61
+ header = Maze::Server.list_for(request_type).current[:request][header_name]
62
+ Maze.check.match(TIMESTAMP_REGEX, header)
63
+ end
64
+
65
+ # Checks that the Bugsnag-Integrity header is a SHA1 or simple digest
66
+ #
67
+ # @step_input request_type [String] The type of request (error, session, build, etc)
68
+ When('the {word} Bugsnag-Integrity header is valid') do |request_type|
69
+ Maze.check.true(Maze::Helper.valid_bugsnag_integrity_header(Maze::Server.list_for(request_type).current),
70
+ 'Invalid Bugsnag-Integrity header detected')
71
+ end
72
+
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!group Log assertion steps
4
+
5
+ # Tests that a log request matches a given level and message.
6
+ #
7
+ # @step_input log_level [String] The expected log level
8
+ # @step_input message [String] The expected message
9
+ Then('the {string} level log message equals {string}') do |log_level, message|
10
+ request = Maze::Server.logs.current
11
+ Maze.check.not_nil(request, 'No log message to check')
12
+ log = request[:body]
13
+ Maze.check.equal(log_level, Maze::Helper.read_key_path(log, 'level'))
14
+ Maze.check.equal(message, Maze::Helper.read_key_path(log, 'message'))
15
+ end
16
+
17
+ # Tests that a log request matches a given level and message regex.
18
+ #
19
+ # @step_input log_level [String] The expected log level
20
+ # @step_input message_regex [String] Regex for the expected message
21
+ Then('the {string} level log message matches the regex {string}') do |log_level, message_regex|
22
+ request = Maze::Server.logs.current
23
+ Maze.check.not_nil(request, 'No log message to check')
24
+
25
+ log = request[:body]
26
+ Maze.check.equal(log_level, Maze::Helper.read_key_path(log, 'level'))
27
+ regex = Regexp.new(message_regex)
28
+ Maze.check.match(regex, Maze::Helper.read_key_path(log, 'message'))
29
+ end