bugsnag-maze-runner 6.27.0

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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/bin/bugsnag-print-load-paths +6 -0
  3. data/bin/download-logs +76 -0
  4. data/bin/maze-runner +136 -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 +50 -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 +342 -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 +75 -0
  25. data/lib/features/steps/payload_steps.rb +234 -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 +107 -0
  29. data/lib/features/steps/runner_steps.rb +406 -0
  30. data/lib/features/steps/session_tracking_steps.rb +116 -0
  31. data/lib/features/steps/value_steps.rb +119 -0
  32. data/lib/features/support/env.rb +7 -0
  33. data/lib/features/support/internal_hooks.rb +260 -0
  34. data/lib/maze/appium_server.rb +112 -0
  35. data/lib/maze/assertions/request_set_assertions.rb +97 -0
  36. data/lib/maze/aws/sam.rb +112 -0
  37. data/lib/maze/bitbar_devices.rb +84 -0
  38. data/lib/maze/bitbar_utils.rb +112 -0
  39. data/lib/maze/browser_stack_devices.rb +160 -0
  40. data/lib/maze/browser_stack_utils.rb +164 -0
  41. data/lib/maze/browsers_bs.yml +220 -0
  42. data/lib/maze/browsers_cbt.yml +100 -0
  43. data/lib/maze/bugsnag_config.rb +42 -0
  44. data/lib/maze/capabilities.rb +126 -0
  45. data/lib/maze/checks/assert_check.rb +91 -0
  46. data/lib/maze/checks/noop_check.rb +34 -0
  47. data/lib/maze/compare.rb +161 -0
  48. data/lib/maze/configuration.rb +174 -0
  49. data/lib/maze/docker.rb +108 -0
  50. data/lib/maze/document_server.rb +46 -0
  51. data/lib/maze/driver/appium.rb +217 -0
  52. data/lib/maze/driver/browser.rb +138 -0
  53. data/lib/maze/driver/resilient_appium.rb +51 -0
  54. data/lib/maze/errors.rb +20 -0
  55. data/lib/maze/helper.rb +118 -0
  56. data/lib/maze/hooks/appium_hooks.rb +216 -0
  57. data/lib/maze/hooks/browser_hooks.rb +68 -0
  58. data/lib/maze/hooks/command_hooks.rb +9 -0
  59. data/lib/maze/hooks/hooks.rb +61 -0
  60. data/lib/maze/interactive_cli.rb +173 -0
  61. data/lib/maze/logger.rb +73 -0
  62. data/lib/maze/macos_utils.rb +14 -0
  63. data/lib/maze/network.rb +49 -0
  64. data/lib/maze/option/parser.rb +245 -0
  65. data/lib/maze/option/processor.rb +143 -0
  66. data/lib/maze/option/validator.rb +184 -0
  67. data/lib/maze/option.rb +64 -0
  68. data/lib/maze/plugins/bugsnag_reporting_plugin.rb +49 -0
  69. data/lib/maze/plugins/cucumber_report_plugin.rb +101 -0
  70. data/lib/maze/plugins/global_retry_plugin.rb +38 -0
  71. data/lib/maze/proxy.rb +114 -0
  72. data/lib/maze/request_list.rb +82 -0
  73. data/lib/maze/retry_handler.rb +76 -0
  74. data/lib/maze/runner.rb +149 -0
  75. data/lib/maze/sauce_labs_utils.rb +96 -0
  76. data/lib/maze/server.rb +207 -0
  77. data/lib/maze/servlets/base_servlet.rb +22 -0
  78. data/lib/maze/servlets/command_servlet.rb +44 -0
  79. data/lib/maze/servlets/log_servlet.rb +64 -0
  80. data/lib/maze/servlets/reflective_servlet.rb +69 -0
  81. data/lib/maze/servlets/servlet.rb +160 -0
  82. data/lib/maze/smart_bear_utils.rb +71 -0
  83. data/lib/maze/store.rb +15 -0
  84. data/lib/maze/terminating_server.rb +129 -0
  85. data/lib/maze/timers.rb +51 -0
  86. data/lib/maze/wait.rb +35 -0
  87. data/lib/maze.rb +27 -0
  88. metadata +371 -0
@@ -0,0 +1,342 @@
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 equals an integer.
124
+ #
125
+ # @step_input field [String] The relative location of the value to test
126
+ # @step_input value [Integer] The integer to test against
127
+ Then('the event {string} equals {int}') do |field, value|
128
+ step "the error payload field \"events.0.#{field}\" equals #{value}"
129
+ end
130
+
131
+ # Tests whether a value in the first event entry starts with a string.
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} starts with {string}') do |field, string_value|
136
+ step "the error payload field \"events.0.#{field}\" starts with \"#{string_value}\""
137
+ end
138
+
139
+ # Tests whether a value in the first event entry ends with a string.
140
+ #
141
+ # @step_input field [String] The relative location of the value to test
142
+ # @step_input string_value [String] The string to match against
143
+ Then('the event {string} ends with {string}') do |field, string_value|
144
+ step "the error payload field \"events.0.#{field}\" ends with \"#{string_value}\""
145
+ end
146
+
147
+ # Tests whether a value in the first event entry matches a regex.
148
+ #
149
+ # @step_input field [String] The relative location of the value to test
150
+ # @step_input pattern [String] The regex to match against
151
+ Then('the event {string} matches {string}') do |field, pattern|
152
+ step "the error payload field \"events.0.#{field}\" matches the regex \"#{pattern}\""
153
+ end
154
+
155
+ # Tests whether a value in the first event entry is a timestamp.
156
+ #
157
+ # @step_input field [String] The relative location of the value to test
158
+ Then('the event {string} is a timestamp') do |field|
159
+ step "the error payload field \"events.0.#{field}\" matches the regex \"#{TIMESTAMP_REGEX}\""
160
+ end
161
+
162
+ # Tests whether a value in the first event entry is a numeric and parsable timestamp.
163
+ #
164
+ # @step_input field [String] The relative location of the value to test
165
+ Then('the event {string} is a parsable timestamp in seconds') do |field|
166
+ step "the error payload field \"events.0.#{field}\" is a parsable timestamp in seconds"
167
+ end
168
+
169
+ # Tests the Event field value against an environment variable.
170
+ #
171
+ # @step_input field [String] The payload element to check
172
+ # @step_input env_var [String] The environment variable to test against
173
+ Then('the event {string} equals the environment variable {string}') do |field, env_var|
174
+ step "the error payload field \"events.0.#{field}\" equals the environment variable \"#{env_var}\""
175
+ end
176
+
177
+ # Tests whether a value in the first event entry matches a JSON fixture.
178
+ #
179
+ # @step_input field [String] The relative location of the value to test
180
+ # @step_input fixture_path [String] The fixture to match against
181
+ Then('the event {string} matches the JSON fixture in {string}') do |field, fixture_path|
182
+ step "the error payload field \"events.0.#{field}\" matches the JSON fixture in \"#{fixture_path}\""
183
+ end
184
+
185
+ Then('the event {string} string is empty') do |keypath|
186
+ value = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], keypath)
187
+ Maze.check.true(value.nil? || value.empty?, "The #{keypath} is not empty: '#{value}'")
188
+ end
189
+
190
+ Then('the event {string} is greater than {int}') do |keypath, int|
191
+ value = Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.0.#{keypath}")
192
+ Maze.check.false(value.nil?, "The event #{keypath} is nil")
193
+ Maze.check.operator(value, :>, int)
194
+ end
195
+
196
+ # Tests whether a value in the first exception of the first event entry starts with a string.
197
+ #
198
+ # @step_input field [String] The relative location of the value to test
199
+ # @step_input string_value [String] The string to match against
200
+ Then('the exception {string} starts with {string}') do |field, string_value|
201
+ step "the error payload field \"events.0.exceptions.0.#{field}\" starts with \"#{string_value}\""
202
+ end
203
+
204
+ # Tests whether a value in the first exception of the first event entry ends with a string.
205
+ #
206
+ # @step_input field [String] The relative location of the value to test
207
+ # @step_input string_value [String] The string to match against
208
+ Then('the exception {string} ends with {string}') do |field, string_value|
209
+ step "the error payload field \"events.0.exceptions.0.#{field}\" ends with \"#{string_value}\""
210
+ end
211
+
212
+ # Tests whether a value in the first exception of the first event entry equals 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} equals {string}') do |field, string_value|
217
+ step "the error payload field \"events.0.exceptions.0.#{field}\" equals \"#{string_value}\""
218
+ end
219
+
220
+ # Tests whether a value in the first exception of the first event entry matches a regex.
221
+ #
222
+ # @step_input field [String] The relative location of the value to test
223
+ # @step_input pattern [String] The regex to match against
224
+ Then('the exception {string} matches {string}') do |field, pattern|
225
+ step "the error payload field \"events.0.exceptions.0.#{field}\" matches the regex \"#{pattern}\""
226
+ end
227
+
228
+ # Tests whether a element of a stack frame in the first exception of the first event equals an integer.
229
+ #
230
+ # @step_input key [String] The element of the stack frame to test
231
+ # @step_input num [Integer] The stack frame where the element is present
232
+ # @step_input value [Integer] The value to test against
233
+ Then('the {string} of stack frame {int} equals {int}') do |key, num, value|
234
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
235
+ step "the error payload field \"#{field}\" equals #{value}"
236
+ end
237
+
238
+ # Tests whether an element of a stack frame in the first exception of the first event matches a regex pattern.
239
+ #
240
+ # @step_input key [String] The element of the stack frame to test
241
+ # @step_input num [Integer] The stack frame where the element is present
242
+ # @step_input pattern [String] The regex to match against
243
+ Then('the {string} of stack frame {int} matches {string}') do |key, num, pattern|
244
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
245
+ step "the error payload field \"#{field}\" matches the regex \"#{pattern}\""
246
+ end
247
+
248
+ # Tests whether an element of a stack frame in the first exception of the first event equals a string.
249
+ #
250
+ # @step_input key [String] The element of the stack frame to test
251
+ # @step_input num [Integer] The stack frame where the element is present
252
+ # @step_input value [String] The value to test against
253
+ Then('the {string} of stack frame {int} equals {string}') do |key, num, value|
254
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
255
+ step "the error payload field \"#{field}\" equals \"#{value}\""
256
+ end
257
+
258
+ # Tests whether an element of a stack frame in the first exception of the first event starts with a string.
259
+ #
260
+ # @step_input key [String] The element of the stack frame to test
261
+ # @step_input num [Integer] The stack frame where the element is present
262
+ # @step_input value [String] The value to test against
263
+ Then('the {string} of stack frame {int} starts with {string}') do |key, num, value|
264
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
265
+ step "the error payload field \"#{field}\" starts with \"#{value}\""
266
+ end
267
+
268
+ # Tests whether an element of a stack frame in the first exception of the first event ends with a string.
269
+ #
270
+ # @step_input key [String] The element of the stack frame to test
271
+ # @step_input num [Integer] The stack frame where the element is present
272
+ # @step_input value [String] The value to test against
273
+ Then('the {string} of stack frame {int} ends with {string}') do |key, num, value|
274
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
275
+ step "the error payload field \"#{field}\" ends with \"#{value}\""
276
+ end
277
+
278
+ # Tests whether an element of a stack frame in the first exception of the first event matches a literal.
279
+ #
280
+ # @step_input key [String] The element of the stack frame to test
281
+ # @step_input num [Integer] The stack frame where the element is present
282
+ # @step_input literal [Enum] The literal to test against, one of: true, false, null, not null
283
+ Then(/^the "(.*)" of stack frame (\d*) is (true|false|null|not null)$/) do |key, num, literal|
284
+ field = "events.0.exceptions.0.stacktrace.#{num}.#{key}"
285
+ step "the error payload field \"#{field}\" is #{literal}"
286
+ end
287
+
288
+ # Tests whether a thread from the first event, identified by name, is the error reporting thread.
289
+ #
290
+ # @step_input thread_name [String] The name of the thread to test
291
+ Then('the thread with name {string} contains the error reporting flag') do |thread_name|
292
+ validate_error_reporting_thread('name', thread_name)
293
+ end
294
+
295
+ # Tests whether a thread from the first event, identified by an id, is the error reporting thread.
296
+ #
297
+ # @step_input thread_id [String] The id of the thread to test
298
+ Then('the thread with id {string} contains the error reporting flag') do |thread_id|
299
+ validate_error_reporting_thread('id', thread_id)
300
+ end
301
+
302
+ # Tests that a thread from the first event, identified by a particular key-value pair, is the error reporting thread.
303
+ #
304
+ # @param payload_key [String] The thread identifier key
305
+ # @param payload_value [Any] The thread identifier value
306
+ def validate_error_reporting_thread(payload_key, payload_value)
307
+ threads = Maze::Server.errors.current[:body]['events'].first['threads']
308
+ Maze.check.kind_of Array, threads
309
+ count = 0
310
+
311
+ threads.each do |thread|
312
+ count += 1 if thread[payload_key].to_s == payload_value && thread['errorReportingThread'] == true
313
+ end
314
+ Maze.check.equal(1, count)
315
+ end
316
+
317
+ # Tests whether an event has the correct attributes we'd expect for un/handled events
318
+ #
319
+ # @param event [Integer] The index of the event
320
+ # @param unhandled [Boolean] Whether the event is unhandled or handled
321
+ # @param severity [String] Optional. An overwritten severity to look for
322
+ def test_unhandled_state(event, unhandled, severity = nil)
323
+ expected_unhandled_state = unhandled ? 'true' : 'false'
324
+ expected_severity = if severity
325
+ severity
326
+ elsif unhandled
327
+ 'error'
328
+ else
329
+ 'warning'
330
+ end
331
+ steps %(
332
+ Then the error payload field "events.#{event}.unhandled" is #{expected_unhandled_state}
333
+ And the error payload field "events.#{event}.severity" equals "#{expected_severity}"
334
+ )
335
+
336
+ return if Maze::Helper.read_key_path(Maze::Server.errors.current[:body], "events.#{event}.session").nil?
337
+
338
+ session_field = unhandled ? 'unhandled' : 'handled'
339
+ steps %(
340
+ And the error payload field "events.#{event}.session.events.#{session_field}" is greater than 0
341
+ )
342
+ 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