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,142 @@
1
+ require 'test/unit'
2
+ require 'open-uri'
3
+ require 'json'
4
+ require 'cgi'
5
+
6
+ # @!group Multipart request assertion steps
7
+
8
+ # Verifies a request contains the correct Content-Type header and some contents
9
+ # for a multipart/form-data request
10
+ #
11
+ # @param request [Hash] The payload to test
12
+ def valid_multipart_form_data?(request)
13
+ content_regex = Regexp.new('^multipart\\/form-data; boundary=[\\h-]+$')
14
+ content_header = request[:request]['Content-Type']
15
+ Maze.check.match(content_regex, content_header)
16
+ Maze.check.true(
17
+ request[:body].size.positive?,
18
+ "Multipart request payload contained #{request[:body].size} fields"
19
+ )
20
+ end
21
+
22
+ # Verifies that any type of request contains multipart form-data
23
+ #
24
+ # @step_input request_type [String] The type of request (error, session, build, etc)
25
+ Then('the {word} request is valid multipart form-data') do |request_type|
26
+ list = Maze::Server.list_for request_type
27
+ valid_multipart_form_data?(list.current)
28
+ end
29
+
30
+ # Verifies all requests of a given type contain multipart form-data
31
+ #
32
+ # @step_input request_type [String] The type of request (error, session, build, etc)
33
+ Then('all {word} requests are valid multipart form-data') do |request_type|
34
+ list = Maze::Server.list_for request_type
35
+ list.all.all? { |request| valid_multipart_form_data?(request) }
36
+ end
37
+
38
+ # Tests the number of fields a given type of multipart request contains.
39
+ #
40
+ # @step_input request_type [String] The type of request (error, session, build, etc)
41
+ # @step_input part_count [Integer] The number of expected fields
42
+ Then('the {word} multipart request has {int} fields') do |request_type, part_count|
43
+ list = Maze::Server.list_for request_type
44
+ parts = list.current[:body]
45
+ Maze.check.equal(part_count, parts.size)
46
+ end
47
+
48
+ # Tests a given type of multipart request has at least one field.
49
+ #
50
+ # @step_input request_type [String] The type of request (error, session, build, etc)
51
+ Then('the {word} multipart request has a non-empty body') do |request_type|
52
+ list = Maze::Server.list_for request_type
53
+ parts = list.current[:body]
54
+ Maze.check.true(parts.size.positive?, "Multipart request payload contained #{parts.size} fields")
55
+ end
56
+
57
+ # Takes a hashmap and parses all fields into strings or hashes depending on their format
58
+ # Used to convert a multipart/form-data request into a JSON comparable hash
59
+ #
60
+ # @param body [Hash] The multipart/form-data hash to parse
61
+ #
62
+ # @return [Hash] The result of parsing hash fields to strings/JSON hashes
63
+ def parse_multipart_body(body)
64
+ body.each_with_object({}) do |(k, v), out|
65
+ out[k] = JSON.parse(v.to_s)
66
+ rescue JSON::ParserError
67
+ out[k] = v.to_s
68
+ end
69
+ end
70
+
71
+ # Tests that a given type of multipart payload body does not match a JSON file.
72
+ # JSON formatted multipart fields will be parsed into hashes.
73
+ #
74
+ # @step_input request_type [String] The type of request (error, session, build, etc)
75
+ # @step_input json_path [String] Path to a JSON file relative to maze-runner root
76
+ Then('the {word} multipart body does not match the JSON file in {string}') do |request_type, json_path|
77
+ Maze.check.true(File.exist?(json_path), "'#{json_path}' does not exist")
78
+ payload_list = Maze::Server.list_for request_type
79
+ raw_payload_value = payload_list.current[:body]
80
+ payload_value = parse_multipart_body(raw_payload_value)
81
+ expected_value = JSON.parse(open(json_path, &:read))
82
+ result = Maze::Compare.value(expected_value, payload_value)
83
+ Maze.check.false(result.equal?, "Payload:\n#{payload_value}\nExpected:#{expected_value}")
84
+ end
85
+
86
+ # Tests that a given type of multipart payload body matches a JSON fixture.
87
+ # JSON formatted multipart fields will be parsed into hashes.
88
+ #
89
+ # @step_input request_type [String] The type of request (error, session, build, etc)
90
+ # @step_input json_path [String] Path to a JSON file relative to maze-runner root
91
+ Then('the {word} multipart body matches the JSON file in {string}') do |request_type, json_path|
92
+ Maze.check.true(File.exist?(json_path), "'#{json_path}' does not exist")
93
+ payload_list = Maze::Server.list_for request_type
94
+ raw_payload_value = payload_list.current[:body]
95
+ payload_value = parse_multipart_body(raw_payload_value)
96
+ expected_value = JSON.parse(open(json_path, &:read))
97
+ result = Maze::Compare.value(expected_value, payload_value)
98
+ Maze.check.true(result.equal?, "The payload field '#{result.keypath}' does not match the fixture:\n #{result.reasons.join('\n')}")
99
+ end
100
+
101
+ # Tests that a given type of multipart field matches a JSON fixture.
102
+ # The field will be parsed into a hash.
103
+ #
104
+ # @step_input request_type [String] The type of request (error, session, build, etc)
105
+ # @step_input field_path [String] Path to the tested element
106
+ # @step_input json_path [String] Path to a JSON file relative to maze-runner root
107
+ Then('the {word} multipart field {string} matches the JSON file in {string}') do |request_type, field_path, json_path|
108
+ Maze.check.true(File.exist?(json_path), "'#{json_path}' does not exist")
109
+ payload_list = Maze::Server.list_for request_type
110
+ payload_value = JSON.parse(payload_list.current[:body][field_path].to_s)
111
+ expected_value = JSON.parse(open(json_path, &:read))
112
+ result = Maze::Compare.value(expected_value, payload_value)
113
+ Maze.check.true(result.equal?, "The multipart field '#{result.keypath}' does not match the fixture:\n #{result.reasons.join('\n')}")
114
+ end
115
+
116
+ # Tests that a multipart request field exists and is not null.
117
+ #
118
+ # @step_input request_type [String] The type of request (error, session, build, etc)
119
+ # @step_input part_key [String] The key to the multipart element
120
+ Then('the field {string} for multipart {word} is not null') do |part_key, request_type|
121
+ parts = Maze::Server.list_for(request_type).current[:body]
122
+ Maze.check.not_nil(parts[part_key], "The field '#{part_key}' should not be null")
123
+ end
124
+
125
+ # Tests that a multipart request field exists and is null.
126
+ #
127
+ # @step_input part_key [String] The key to the multipart element
128
+ # @step_input request_type [String] The type of request (error, session, build, etc)
129
+ Then('the field {string} for multipart {word} is null') do |part_key, request_type|
130
+ parts = Maze::Server.list_for(request_type).current[:body]
131
+ Maze.check.nil(parts[part_key], "The field '#{part_key}' should be null")
132
+ end
133
+
134
+ # Tests that a multipart request field equals a string.
135
+ #
136
+ # @step_input part_key [String] The key to the multipart element
137
+ # @step_input request_type [String] The type of request (error, session, build, etc)
138
+ # @step_input expected_value [String] The string to match against
139
+ Then('the field {string} for multipart {word} equals {string}') do |part_key, request_type, expected_value|
140
+ parts = Maze::Server.list_for(request_type).current[:body]
141
+ Maze.check.equal(parts[part_key], expected_value)
142
+ end
@@ -0,0 +1,75 @@
1
+ # @!group Network steps
2
+
3
+ # Checks that a port on a given host is open and ready for connections.
4
+ #
5
+ # @step_input host [String] The host to check
6
+ # @step_input port [String] The port to check
7
+ When('I wait for the host {string} to open port {string}') do |host, port|
8
+ Maze::Network.wait_for_port(host, port)
9
+ end
10
+
11
+ # Sets the HTTP status code to be used for all subsequent requests
12
+ #
13
+ # @step_input status_code [Integer] The status code to return
14
+ When('I set the HTTP status code to {int}') do |status_code|
15
+ Maze::Server.status_code = status_code
16
+ end
17
+
18
+ # Sets the HTTP status code to be used for the next request
19
+ #
20
+ # @step_input status_code [Integer] The status code to return
21
+ When('I set the HTTP status code for the next request to {int}') do |status_code|
22
+ Maze::Server.reset_status_code = true
23
+ Maze::Server.status_code = status_code
24
+ end
25
+
26
+ # Sets the response delay to be used for all subsequent requests
27
+ #
28
+ # @step_input response_delay_ms [Integer] The delay in milliseconds
29
+ When('I set the response delay to {int} milliseconds') do |response_delay_ms|
30
+ Maze::Server.response_delay_ms = response_delay_ms
31
+ end
32
+
33
+ # Sets the response delay to be used for the next request
34
+ #
35
+ # @step_input delay [Integer] The delay in milliseconds
36
+ When('I set the response delay for the next request to {int} milliseconds') do |delay|
37
+ Maze::Server.reset_response_delay = true
38
+ Maze::Server.response_delay_ms = delay
39
+ end
40
+
41
+ # Attempts to open a URL.
42
+ #
43
+ # @step_input url [String] The URL to open.
44
+ When('I open the URL {string}') do |url|
45
+ begin
46
+ open(url, &:read)
47
+ rescue OpenURI::HTTPError
48
+ $logger.debug $!.inspect
49
+ end
50
+ end
51
+
52
+ # Starts the terminating server to cancel requests received.
53
+ When('I start the terminating server') do
54
+ Maze::TerminatingServer.start
55
+ end
56
+
57
+ # Sets the response message on the terminating server
58
+ When('I set the terminated response message to {string}') do |response_message|
59
+ Maze::TerminatingServer.response = response_message
60
+ end
61
+
62
+ # Sets the maximum allowable amount of data received to the terminating server
63
+ #
64
+ # @step_input max_length [Integer] The number of bytes receivable
65
+ When('I set the terminating server data threshold to {int} bytes') do |max_length|
66
+ Maze::TerminatingServer.max_received_size = max_length
67
+ end
68
+
69
+ # Check if a certain number of connections have been received by the terminating server
70
+ #
71
+ # @step_input request_count [Integer] The number of desired requests
72
+ Then('the terminating server has received {int} requests') do |request_count|
73
+ Maze.check.equal(request_count, Maze::TerminatingServer.received_request_count,
74
+ "#{request_count} terminated requests expected, #{Maze::TerminatingServer.received_request_count} received")
75
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!group Payload steps
4
+
5
+ # Tests the payload body does not match a JSON fixture.
6
+ #
7
+ # @step_input request_type [String] The type of request (error, session, build, etc)
8
+ # @step_input fixture_path [String] Path to a JSON fixture
9
+ Then('the {word} payload body does not match the JSON fixture in {string}') do |request_type, fixture_path|
10
+ payload_value = Maze::Server.list_for(request_type).current[:body]
11
+ expected_value = JSON.parse(open(fixture_path, &:read))
12
+ result = Maze::Compare.value(expected_value, payload_value)
13
+ Maze.check.false(result.equal?, "Payload:\n#{payload_value}\nExpected:#{expected_value}")
14
+ end
15
+
16
+ # Test the payload body matches a JSON fixture.
17
+ #
18
+ # @step_input request_type [String] The type of request (error, session, build, etc)
19
+ # @step_input fixture_path [String] Path to a JSON fixture
20
+ Then('the {word} payload body matches the JSON fixture in {string}') do |request_type, fixture_path|
21
+ payload_value = Maze::Server.list_for(request_type).current[:body]
22
+ expected_value = JSON.parse(open(fixture_path, &:read))
23
+ result = Maze::Compare.value(expected_value, payload_value)
24
+ Maze.check.true(result.equal?,
25
+ "The payload field '#{result.keypath}' does not match the fixture:\n #{result.reasons.join('\n')}")
26
+ end
27
+
28
+ # Test that a payload element matches a JSON fixture.
29
+ #
30
+ # @step_input request_type [String] The type of request (error, session, build, etc)
31
+ # @step_input field_path [String] Path to the tested element
32
+ # @step_input fixture_path [String] Path to a JSON fixture
33
+ Then('the {word} payload field {string} matches the JSON fixture in {string}') \
34
+ do |request_type, field_path, fixture_path|
35
+ list = Maze::Server.list_for(request_type)
36
+ payload_value = Maze::Helper.read_key_path(list.current[:body], field_path)
37
+ expected_value = JSON.parse(open(fixture_path, &:read))
38
+ result = Maze::Compare.value(expected_value, payload_value)
39
+ Maze.check.true(result.equal?,
40
+ "The payload field '#{result.keypath}' does not match the fixture:\n #{result.reasons.join('\n')}")
41
+ end
42
+
43
+ # Tests that a request element is true.
44
+ #
45
+ # @step_input request_type [String] The type of request (error, session, build, etc)
46
+ # @step_input field_path [String] Path to the tested element
47
+ Then('the {word} payload field {string} is true') do |request_type, field_path|
48
+ list = Maze::Server.list_for(request_type)
49
+ Maze.check.true(Maze::Helper.read_key_path(list.current[:body], field_path))
50
+ end
51
+
52
+ # Tests that a request element is false.
53
+ #
54
+ # @step_input request_type [String] The type of request (error, session, build, etc)
55
+ # @step_input field_path [String] Path to the tested element
56
+ Then('the {word} payload field {string} is false') do |request_type, field_path|
57
+ list = Maze::Server.list_for(request_type)
58
+ Maze.check.false(Maze::Helper.read_key_path(list.current[:body], field_path))
59
+ end
60
+
61
+ # Tests that a request element is null.
62
+ #
63
+ # @step_input request_type [String] The type of request (error, session, build, etc)
64
+ # @step_input field_path [String] Path to the tested element
65
+ Then('the {word} payload field {string} is null') do |request_type, field_path|
66
+ list = Maze::Server.list_for(request_type)
67
+ Maze.check.nil(Maze::Helper.read_key_path(list.current[:body], field_path))
68
+ end
69
+
70
+ # Tests that a request element is not null.
71
+ #
72
+ # @step_input request_type [String] The type of request (error, session, build, etc)
73
+ # @step_input field_path [String] Path to the tested element
74
+ Then('the {word} payload field {string} is not null') do |request_type, field_path|
75
+ list = Maze::Server.list_for(request_type)
76
+ Maze.check.not_nil(Maze::Helper.read_key_path(list.current[:body], field_path))
77
+ end
78
+
79
+ # Tests that a payload element equals an integer.
80
+ #
81
+ # @step_input request_type [String] The type of request (error, session, build, etc)
82
+ # @step_input field_path [String] Path to the tested element
83
+ # @step_input int_value [Integer] The value to test against
84
+ Then('the {word} payload field {string} equals {int}') do |request_type, field_path, int_value|
85
+ Maze.check.equal(int_value,
86
+ Maze::Helper.read_key_path(Maze::Server.list_for(request_type).current[:body], field_path))
87
+ end
88
+
89
+ # Tests the payload field value against an environment variable.
90
+ #
91
+ # @step_input request_type [String] The type of request (error, session, build, etc)
92
+ # @step_input field_path [String] The payload element to test
93
+ # @step_input env_var [String] The environment variable to test against
94
+ Then('the {word} payload field {string} equals the environment variable {string}') \
95
+ do |request_type, field_path, env_var|
96
+ environment_value = ENV[env_var]
97
+ Maze.check.false(environment_value.nil?, "The environment variable #{env_var} must not be nil")
98
+ list = Maze::Server.list_for(request_type)
99
+ value = Maze::Helper.read_key_path(list.current[:body], field_path)
100
+
101
+ Maze.check.equal(environment_value, value)
102
+ end
103
+
104
+ # Tests a payload field contains a number larger than a value.
105
+ #
106
+ # @step_input request_type [String] The type of request (error, session, build, etc)
107
+ # @step_input field_path [String] The payload element to test
108
+ # @step_input int_value [Integer] The value to compare against
109
+ Then('the {word} payload field {string} is greater than {int}') do |request_type, field_path, int_value|
110
+ list = Maze::Server.list_for(request_type)
111
+ value = Maze::Helper.read_key_path(list.current[:body], field_path)
112
+ Maze.check.kind_of Integer, value
113
+ Maze.check.operator(value, :>, int_value, "The payload field '#{field_path}' (#{value}) is not greater than '#{int_value}'")
114
+ end
115
+
116
+ # Tests a payload field contains a number smaller than a value.
117
+ #
118
+ # @step_input request_type [String] The type of request (error, session, build, etc)
119
+ # @step_input field_path [String] The payload element to test
120
+ # @step_input int_value [Integer] The value to compare against
121
+ Then('the {word} payload field {string} is less than {int}') do |request_type, field_path, int_value|
122
+ list = Maze::Server.list_for(request_type)
123
+ value = Maze::Helper.read_key_path(list.current[:body], field_path)
124
+ Maze.check.kind_of Integer, value
125
+ fail_message = "The #{request_type} payload field '#{field_path}' (#{value}) is not less than '#{int_value}'"
126
+ Maze.check.operator(value, :<, int_value, fail_message)
127
+ end
128
+
129
+ # Tests a payload field equals a string.
130
+ #
131
+ # @step_input request_type [String] The type of request (error, session, build, etc)
132
+ # @step_input field_path [String] The payload element to test
133
+ # @step_input string_value [String] The string to test against
134
+ Then('the {word} payload field {string} equals {string}') do |request_type, field_path, string_value|
135
+ list = Maze::Server.list_for(request_type)
136
+ Maze.check.equal(string_value, Maze::Helper.read_key_path(list.current[:body], field_path))
137
+ end
138
+
139
+ # Tests a payload field starts with a string.
140
+ #
141
+ # @step_input request_type [String] The type of request (error, session, build, etc)
142
+ # @step_input field_path [String] The payload element to test
143
+ # @step_input string_value [String] The string to test against
144
+ Then('the {word} payload field {string} starts with {string}') do |request_type, field_path, string_value|
145
+ list = Maze::Server.list_for(request_type)
146
+ value = Maze::Helper.read_key_path(list.current[:body], field_path)
147
+ Maze.check.kind_of String, value
148
+ Maze.check.true(
149
+ value.start_with?(string_value),
150
+ "Field '#{field_path}' value ('#{value}') does not start with '#{string_value}'"
151
+ )
152
+ end
153
+
154
+ # Tests a payload field ends with a string.
155
+ #
156
+ # @step_input request_type [String] The type of request (error, session, build, etc)
157
+ # @step_input field_path [String] The payload element to test
158
+ # @step_input string_value [String] The string to test against
159
+ Then('the {word} payload field {string} ends with {string}') do |request_type, field_path, string_value|
160
+ list = Maze::Server.list_for(request_type)
161
+ value = Maze::Helper.read_key_path(list.current[:body], field_path)
162
+ Maze.check.kind_of String, value
163
+ Maze.check.true(
164
+ value.end_with?(string_value),
165
+ "Field '#{field_path}' value ('#{value}') does not end with '#{string_value}'"
166
+ )
167
+ end
168
+
169
+ # Tests a payload field is an array with a specific element count.
170
+ #
171
+ # @step_input request_type [String] The type of request (error, session, build, etc)
172
+ # @step_input field [String] The payload element to test
173
+ # @step_input count [Integer] The value expected
174
+ Then('the {word} payload field {string} is an array with {int} elements') do |request_type, field, count|
175
+ list = Maze::Server.list_for(request_type)
176
+ value = Maze::Helper.read_key_path(list.current[:body], field)
177
+ Maze.check.kind_of Array, value
178
+ Maze.check.equal(count, value.length)
179
+ end
180
+
181
+ # Tests a payload field is an array with at least one element.
182
+ #
183
+ # @step_input request_type [String] The type of request (error, session, build, etc)
184
+ # @step_input field [String] The payload element to test
185
+ Then('the {word} payload field {string} is a non-empty array') do |request_type, field|
186
+ list = Maze::Server.list_for(request_type)
187
+ value = Maze::Helper.read_key_path(list.current[:body], field)
188
+ Maze.check.kind_of Array, value
189
+ Maze.check.true(value.length.positive?, "the field '#{field}' must be a non-empty array")
190
+ end
191
+
192
+ # Tests a payload field matches a regex.
193
+ #
194
+ # @step_input request_type [String] The type of request (error, session, build, etc)
195
+ # @step_input field [String] The payload element to test
196
+ # @step_input regex [String] The regex to test against
197
+ Then('the {word} payload field {string} matches the regex {string}') do |request_type, field, regex_string|
198
+ regex = Regexp.new(regex_string)
199
+ list = Maze::Server.list_for(request_type)
200
+ value = Maze::Helper.read_key_path(list.current[:body], field)
201
+ Maze.check.match(regex, value)
202
+ end
203
+
204
+ # Tests a payload field is a numeric timestamp.
205
+ #
206
+ # @step_input request_type [String] The type of request (error, session, build, etc)
207
+ # @step_input field [String] The payload element to test
208
+ Then('the {word} payload field {string} is a parsable timestamp in seconds') do |request_type, field|
209
+ list = Maze::Server.list_for(request_type)
210
+ value = Maze::Helper.read_key_path(list.current[:body], field)
211
+ begin
212
+ int = value.to_i
213
+ parsed_time = Time.at(int)
214
+ rescue StandardError
215
+ parsed_time = nil
216
+ end
217
+ Maze.check.not_nil(parsed_time)
218
+ end
219
+
220
+ # Tests that every element in an array contains a specified key-value pair.
221
+ #
222
+ # @step_input request_type [String] The type of request (error, session, build, etc)
223
+ # @step_input key_path [String] The path to the tested array
224
+ # @step_input element_key_path [String] The key for the expected element inside the array
225
+ Then('each element in {word} payload field {string} has {string}') do |request_type, key_path, element_key_path|
226
+ list = Maze::Server.list_for(request_type)
227
+ value = Maze::Helper.read_key_path(list.current[:body], key_path)
228
+ Maze.check.kind_of Array, value
229
+ value.each do |element|
230
+ Maze.check.not_nil(Maze::Helper.read_key_path(element, element_key_path),
231
+ "Each element in '#{key_path}' must have '#{element_key_path}'")
232
+ end
233
+ end
234
+
@@ -0,0 +1,34 @@
1
+ # @!group Proxy steps
2
+
3
+ # Starts an HTTP proxy server.
4
+ #
5
+ Then('I start an http proxy') do
6
+ Maze::Proxy.instance.start :Http
7
+ end
8
+
9
+ # Starts an authenticated HTTP proxy server.
10
+ #
11
+ Then('I start an authenticated http proxy') do
12
+ Maze::Proxy.instance.start :Http, true
13
+ end
14
+
15
+ # Starts an HTTPS proxy server.
16
+ #
17
+ Then('I start an https proxy') do
18
+ Maze::Proxy.instance.start :Https
19
+ end
20
+
21
+ # Starts an authenticated HTTPS proxy server.
22
+ #
23
+ Then('I start an authenticated https proxy') do
24
+ Maze::Proxy.instance.start :Https, true
25
+ end
26
+
27
+ # Test the proxy server handled a request for a host.
28
+ #
29
+ # @step_input host [String] Destination host to check
30
+ Then('the proxy handled a request for {string}') do |host|
31
+ Maze.check.true(Maze::Proxy.instance.handled_host?(host), "The proxy did not handle a request for #{host}")
32
+ end
33
+
34
+ # @!endgroup
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!group Query parameter steps
4
+
5
+ # Tests that a query parameter matches a string.
6
+ #
7
+ # @step_input request_type [String] The type of request (error, session, build, etc)
8
+ # @step_input parameter_name [String] The parameter to test
9
+ # @step_input parameter_value [String] The expected value
10
+ Then('the {word} {string} query parameter equals {string}') do |request_type, parameter_name, parameter_value|
11
+ Maze.check.equal(parameter_value,
12
+ Maze::Helper.parse_querystring(Maze::Server.list_for(request_type).current)[parameter_name][0])
13
+ end
14
+
15
+ # Tests that a query parameter is present and not null.
16
+ #
17
+ # @step_input request_type [String] The type of request (error, session, build, etc)
18
+ # @step_input parameter_name [String] The parameter to test
19
+ Then('the {word} {string} query parameter is not null') do |request_type, parameter_name|
20
+ Maze.check.not_nil(Maze::Helper.parse_querystring(Maze::Server.list_for(request_type).current)[parameter_name][0],
21
+ "The '#{parameter_name}' query parameter should not be null")
22
+ end
23
+
24
+ # Tests that a query parameter is a timestamp.
25
+ #
26
+ # @step_input request_type [String] The type of request (error, session, build, etc)
27
+ # @step_input parameter_name [String] The parameter to test
28
+ Then('the {word} {string} query parameter is a timestamp') do |request_type, parameter_name|
29
+ param = Maze::Helper.parse_querystring(Maze::Server.list_for(request_type).current)[parameter_name][0]
30
+ Maze.check.match(TIMESTAMP_REGEX, param)
31
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+ require 'open-uri'
5
+ require 'json'
6
+ require 'cgi'
7
+ require_relative '../../maze/wait'
8
+
9
+ # @!group Request assertion steps
10
+
11
+ def assert_received_requests(request_count, list, list_name)
12
+ timeout = Maze.config.receive_requests_wait
13
+ wait = Maze::Wait.new(timeout: timeout)
14
+
15
+ received = wait.until { list.size >= request_count }
16
+
17
+ unless received
18
+ raise Test::Unit::AssertionFailedError.new <<-MESSAGE
19
+ Expected #{request_count} #{list_name} but received #{list.size} within the #{timeout}s timeout.
20
+ This could indicate that:
21
+ - Bugsnag crashed with a fatal error.
22
+ - Bugsnag did not make the requests that it should have done.
23
+ - The requests were made, but not deemed to be valid (e.g. missing integrity header).
24
+ - The requests made were prevented from being received due to a network or other infrastructure issue.
25
+ Please check the Maze Runner and device logs to confirm.)
26
+ MESSAGE
27
+ end
28
+
29
+ Maze.check.equal(request_count, list.size, "#{list.size} #{list_name} received")
30
+ end
31
+
32
+ #
33
+ # Error request assertions
34
+ #
35
+ # Shortcut to waiting to receive a single request of the given type
36
+ #
37
+ # @step_input request_type [String] The type of request (error, session, build, etc)
38
+ Then('I wait to receive a(n) {word}') do |request_type|
39
+ step "I wait to receive 1 #{request_type}"
40
+ end
41
+
42
+ # Continually checks to see if the required amount of requests have been received,
43
+ # timing out according to @see Maze.config.receive_requests_wait.
44
+ # If all expected requests are received and have the Bugsnag-Sent-At header, they
45
+ # will be sorted by the header.
46
+ #
47
+ # @step_input request_type [String] The type of request (error, session, build, etc)
48
+ # @step_input request_count [Integer] The amount of requests expected
49
+ Then('I wait to receive {int} {word}') do |request_count, request_type|
50
+ list = Maze::Server.list_for(request_type)
51
+ assert_received_requests request_count, list, request_type
52
+ list.sort_by_sent_at! request_count
53
+ end
54
+
55
+ # Verify that at least a certain amount of requests have been received
56
+ # This step is only intended for use in stress tests
57
+ #
58
+ # @step_input min_received [Integer] The minimum amount of requests required to pass
59
+ # @step_input request_type [String] The type of request (error, session, build, etc)
60
+ Then('I have received at least {int} {word}') do |min_received, request_type|
61
+ list = Maze::Server.list_for(request_type)
62
+ Maze.check.operator(list.size, :>=, min_received, "Actually received #{list.size} #{request_type} requests")
63
+ end
64
+
65
+ # Assert that the test Server hasn't received any requests - of a specific, or any, type.
66
+ #
67
+ # @step_input request_type [String] The type of request ('error', 'session', build, etc), or 'requests' to assert on all
68
+ # request types.
69
+ Then('I should receive no {word}') do |request_type|
70
+ sleep Maze.config.receive_no_requests_wait
71
+ if request_type == 'requests'
72
+ # Assert that the test Server hasn't received any requests at all.
73
+ Maze.check.equal(0, Maze::Server.errors.size, "#{Maze::Server.errors.size} errors received")
74
+ Maze.check.equal(0, Maze::Server.sessions.size, "#{Maze::Server.sessions.size} sessions received")
75
+ else
76
+ list = Maze::Server.list_for(request_type)
77
+ Maze.check.equal(0, list.size, "#{list.size} #{request_type} received")
78
+ end
79
+ end
80
+
81
+ # Moves to the next request
82
+ #
83
+ # @step_input request_type [String] The type of request (error, session, build, etc)
84
+ Then('I discard the oldest {word}') do |request_type|
85
+ raise "No #{request_type} to discard" if Maze::Server.list_for(request_type).current.nil?
86
+
87
+ Maze::Server.list_for(request_type).next
88
+ end
89
+
90
+ Then('the received errors match:') do |table|
91
+ # Checks that each request matches one of the event fields
92
+ requests = Maze::Server.errors.remaining
93
+ match_count = 0
94
+
95
+ # iterate through each row in the table. exactly 1 request should match each row.
96
+ table.hashes.each do |row|
97
+ requests.each do |request|
98
+ # Skip if no body.events in this request
99
+ next if (!request.key? :body) || (!request[:body].key? 'events')
100
+
101
+ events = request[:body]['events']
102
+ Maze.check.equal(1, events.length, 'Expected exactly one event per request')
103
+ match_count += 1 if Maze::Assertions::RequestSetAssertions.request_matches_row(events[0], row)
104
+ end
105
+ end
106
+ Maze.check.equal(requests.size, match_count, 'Unexpected number of requests matched the received payloads')
107
+ end