bugsnag-maze-runner 6.27.0

Sign up to get free protection for your applications and to get access to all the features.
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