soaspec 0.2.32 → 0.2.33

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -15
  3. data/.gitlab-ci.yml +62 -62
  4. data/.rspec +3 -3
  5. data/.rubocop.yml +2 -2
  6. data/CODE_OF_CONDUCT.md +74 -74
  7. data/ChangeLog +632 -625
  8. data/Dockerfile +7 -7
  9. data/Gemfile +8 -8
  10. data/LICENSE.txt +21 -21
  11. data/README.md +231 -231
  12. data/Rakefile +52 -52
  13. data/Todo.md +16 -16
  14. data/exe/soaspec +138 -138
  15. data/exe/xml_to_yaml_file +43 -43
  16. data/lib/soaspec.rb +106 -105
  17. data/lib/soaspec/baseline.rb +23 -0
  18. data/lib/soaspec/core_ext/hash.rb +44 -44
  19. data/lib/soaspec/cucumber/generic_steps.rb +94 -94
  20. data/lib/soaspec/demo.rb +6 -6
  21. data/lib/soaspec/errors.rb +24 -22
  22. data/lib/soaspec/exchange/exchange.rb +129 -129
  23. data/lib/soaspec/exchange/exchange_extractor.rb +90 -90
  24. data/lib/soaspec/exchange/exchange_properties.rb +28 -28
  25. data/lib/soaspec/exchange/exchange_repeater.rb +21 -21
  26. data/lib/soaspec/exchange/request_builder.rb +70 -70
  27. data/lib/soaspec/exchange/variable_storer.rb +24 -24
  28. data/lib/soaspec/exchange_handlers/exchange_handler.rb +98 -98
  29. data/lib/soaspec/exchange_handlers/exchange_handler_defaults.rb +61 -61
  30. data/lib/soaspec/exchange_handlers/handler_accessors.rb +132 -132
  31. data/lib/soaspec/exchange_handlers/request/rest_request.rb +59 -51
  32. data/lib/soaspec/exchange_handlers/request/soap_request.rb +41 -41
  33. data/lib/soaspec/exchange_handlers/response_extractor.rb +84 -84
  34. data/lib/soaspec/exchange_handlers/rest_exchanger_factory.rb +111 -111
  35. data/lib/soaspec/exchange_handlers/rest_handler.rb +307 -298
  36. data/lib/soaspec/exchange_handlers/rest_methods.rb +65 -65
  37. data/lib/soaspec/exchange_handlers/rest_parameters.rb +112 -112
  38. data/lib/soaspec/exchange_handlers/rest_parameters_defaults.rb +42 -42
  39. data/lib/soaspec/exchange_handlers/soap_handler.rb +241 -241
  40. data/lib/soaspec/exe_helpers.rb +94 -94
  41. data/lib/soaspec/generate_server.rb +48 -48
  42. data/lib/soaspec/generator/.rspec.erb +5 -5
  43. data/lib/soaspec/generator/.travis.yml.erb +5 -5
  44. data/lib/soaspec/generator/Gemfile.erb +8 -8
  45. data/lib/soaspec/generator/README.md.erb +29 -29
  46. data/lib/soaspec/generator/Rakefile.erb +19 -19
  47. data/lib/soaspec/generator/config/data/default.yml.erb +2 -2
  48. data/lib/soaspec/generator/css/bootstrap.css +6833 -6833
  49. data/lib/soaspec/generator/features/support/env.rb.erb +3 -3
  50. data/lib/soaspec/generator/generate_exchange.html.erb +47 -47
  51. data/lib/soaspec/generator/lib/blz_service.rb.erb +26 -26
  52. data/lib/soaspec/generator/lib/dynamic_class_content.rb.erb +12 -12
  53. data/lib/soaspec/generator/lib/new_rest_service.rb.erb +56 -56
  54. data/lib/soaspec/generator/lib/new_soap_service.rb.erb +29 -29
  55. data/lib/soaspec/generator/lib/package_service.rb.erb +2 -2
  56. data/lib/soaspec/generator/lib/shared_example.rb.erb +8 -8
  57. data/lib/soaspec/generator/spec/dynamic_soap_spec.rb.erb +12 -12
  58. data/lib/soaspec/generator/spec/rest_spec.rb.erb +9 -9
  59. data/lib/soaspec/generator/spec/soap_spec.rb.erb +51 -51
  60. data/lib/soaspec/generator/spec/spec_helper.rb.erb +23 -23
  61. data/lib/soaspec/generator/template/soap_template.xml +6 -6
  62. data/lib/soaspec/indifferent_hash.rb +9 -9
  63. data/lib/soaspec/interpreter.rb +70 -70
  64. data/lib/soaspec/matchers.rb +140 -118
  65. data/lib/soaspec/o_auth2.rb +142 -142
  66. data/lib/soaspec/soaspec_shared_examples.rb +26 -26
  67. data/lib/soaspec/spec_logger.rb +143 -143
  68. data/lib/soaspec/template_reader.rb +30 -30
  69. data/lib/soaspec/test_server/bank.wsdl +90 -90
  70. data/lib/soaspec/test_server/get_bank.rb +166 -166
  71. data/lib/soaspec/test_server/id_manager.rb +41 -41
  72. data/lib/soaspec/test_server/invoices.rb +29 -29
  73. data/lib/soaspec/test_server/namespace.xml +14 -14
  74. data/lib/soaspec/test_server/note.xml +5 -5
  75. data/lib/soaspec/test_server/puppy_service.rb +21 -21
  76. data/lib/soaspec/test_server/test_attribute.rb +14 -14
  77. data/lib/soaspec/test_server/test_namespace.rb +14 -14
  78. data/lib/soaspec/version.rb +6 -6
  79. data/lib/soaspec/virtual_server.rb +190 -176
  80. data/lib/soaspec/wait.rb +43 -43
  81. data/lib/soaspec/wsdl_generator.rb +215 -215
  82. data/soaspec.gemspec +58 -58
  83. data/test.wsdl +116 -116
  84. data/test.xml +10 -10
  85. data/test_wsdl.rb +43 -43
  86. metadata +4 -3
@@ -1,298 +1,307 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'exchange_handler'
4
- require_relative 'rest_parameters'
5
- require_relative 'rest_parameters_defaults'
6
- require_relative 'rest_exchanger_factory'
7
- require_relative '../core_ext/hash'
8
- require_relative '../errors'
9
- require_relative 'handler_accessors'
10
- require_relative '../interpreter'
11
- require_relative 'response_extractor'
12
- require_relative 'request/rest_request'
13
- require 'json'
14
- require 'jsonpath'
15
- require 'nori'
16
- require 'erb'
17
-
18
- module Soaspec
19
- # Wraps around Savon client defining default values dependent on the soap request
20
- class RestHandler < ExchangeHandler
21
- include ResponseExtractor
22
- extend Soaspec::RestParameters
23
- include Soaspec::RestParametersDefaults
24
- extend Soaspec::RestExchangeFactory
25
-
26
- # User used in making API calls
27
- attr_accessor :api_username
28
-
29
- # Setup object to handle communicating with a particular SOAP WSDL
30
- # @param [Hash] options Options defining REST request. base_url, default_hash
31
- def initialize(name = self.class.to_s, options = {})
32
- raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value
33
-
34
- if name.is_a?(Hash) && options == {} # If name is not set, use first parameter as the options hash
35
- options = name
36
- name = self.class.to_s
37
- end
38
- super
39
- set_remove_keys(options, %i[api_username default_hash template_name])
40
- @init_options = options
41
- init_merge_options # Call this to verify any issues with options on creating object
42
- end
43
-
44
- # @return [Boolean] Whether REST method should have a payload
45
- def payload?(overall_params)
46
- case overall_params[:method]
47
- when :post, :patch, :put
48
- true
49
- else
50
- false
51
- end
52
- end
53
-
54
- # @todo Use this in actually making the request
55
- # @return [RestRequest] Parameters used in making a request
56
- def request_parameters(override_parameters)
57
- overall_params = interpret_parameters(override_parameters)
58
- request = { overall: overall_params, options: init_merge_options }
59
- request[:body] = post_data(overall_params) if payload?(overall_params)
60
- RestRequest.new(overall_params, init_merge_options, payload?(overall_params) ? post_data(overall_params) : nil)
61
- end
62
-
63
- # Interpret REST parameters given provided parameters and adding defaults, making
64
- # transformations
65
- #
66
- # @param [Hash] request_parameters Parameters used in making a request
67
- # @return [Hash] Request parameters merged with default values
68
- def interpret_parameters(request_parameters)
69
- request_parameters[:params] ||= {}
70
- request_parameters[:method] ||= :post
71
- request_parameters[:suburl] = request_parameters[:suburl].to_s if request_parameters[:suburl]
72
- # Use q for query parameters. Nested :params is ugly, long and unclear
73
- request_parameters[:params][:params] = request_parameters[:q] if request_parameters[:q]
74
- request_parameters
75
- end
76
-
77
- # Override this with 'after_response' within class definition to perform an action
78
- # after response is retrieved
79
- # @param [RestClient::Response] _response Response to interpret to perform after block
80
- def after_response(_response, _self); end
81
-
82
- # Used in together with Exchange request that passes such override parameters
83
- # @param [Hash] override_parameters Params to characterize REST request
84
- # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
85
- # @option override_parameters [String] suburl URL appended to base_url of class
86
- # @option override_parameters [Hash] :q Query for REST
87
- # @option override_parameters [Symbol] :method REST method (:get, :post, :patch, etc)
88
- # Following are for the body of the request
89
- # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
90
- # @option override_parameters [String] :payload String to be passed directly in request body
91
- # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
92
- # @return [RestClient::Response] Response from making request
93
- def make_request(override_parameters)
94
- @merged_options ||= init_merge_options # TODO: Is this var needed? Can method be passed to resource creation?
95
- test_values = interpret_parameters override_parameters
96
- # In order for ERB to be calculated at correct time, the first time request is made, the resource should be created
97
- @resource ||= RestClient::Resource.new(ERB.new(base_url_value).result(binding), @merged_options)
98
- @resource_used = test_values[:suburl] ? @resource[test_values[:suburl]] : @resource
99
-
100
- self.exception = nil # Remove any previously stored exception
101
- begin
102
- response = case test_values[:method]
103
- when :post, :patch, :put
104
- Soaspec::SpecLogger.info("request body: #{post_data(test_values)}")
105
- @resource_used.send(test_values[:method].to_s, post_data(test_values), test_values[:params])
106
- else # :get, :delete
107
- @resource_used.send(test_values[:method].to_s, test_values[:params])
108
- end
109
- rescue RestClient::Exception => e
110
- self.exception = e
111
- raise e unless e.respond_to? :response
112
-
113
- response = e.response
114
- end
115
- Soaspec::SpecLogger.info("response: \n headers: #{response&.headers}\n body: #{response}\n")
116
- after_response(response, self)
117
- response
118
- end
119
-
120
- # Add values to here when extending this class to have default REST options.
121
- # See rest client resource at https://github.com/rest-client/rest-client for details
122
- # It's easier to set headers via 'headers' accessor rather than here
123
- # @return [Hash] Options adding to & overriding defaults
124
- def rest_resource_options
125
- {}
126
- end
127
-
128
- # Perform ERB on each header value
129
- # @return [Hash] Hash from 'rest_client_headers' passed through ERB
130
- def parse_headers
131
- Hash[rest_client_headers.map do |header_name, header_value|
132
- raise ArgumentError, "Header '#{header_name}' is null. Headers are #{rest_client_headers}" if header_value.nil?
133
-
134
- [header_name, ERB.new(header_value).result(binding)]
135
- end]
136
- end
137
-
138
- # Initialize value of merged options
139
- # @return [Hash] Hash of merged options
140
- def init_merge_options
141
- options = rest_resource_options
142
- options.merge! basic_auth_params if respond_to? :basic_auth_params
143
- options[:headers] ||= {}
144
- options[:headers].merge! parse_headers
145
- options[:headers][:authorization] ||= ERB.new('Bearer <%= access_token %>').result(binding) if Soaspec.auto_oauth && respond_to?(:access_token)
146
- options.merge(@init_options)
147
- end
148
-
149
- # @param [Hash] format Format of expected result.
150
- # @return [Object] Generic body to be displayed in error messages
151
- def response_body(response, format: :hash)
152
- extract_hash response
153
- end
154
-
155
- # @return [Boolean] Whether response body includes String
156
- def include_in_body?(response, expected)
157
- response.body.include? expected
158
- end
159
-
160
- # @@return [Boolean] Whether the request found the desired value or not
161
- def found?(response)
162
- status_code_for(response) != 404
163
- end
164
-
165
- # @return [Boolean] Whether response contains expected value
166
- def include_value?(response, expected)
167
- extract_hash(response).include_value? expected
168
- end
169
-
170
- # @return [Boolean] Whether response body contains expected key
171
- def include_key?(response, expected)
172
- value_from_path(response, expected)
173
- end
174
-
175
- # @return [Integer] HTTP Status code for response
176
- def status_code_for(response)
177
- response.code
178
- end
179
-
180
- # Returns the value at the provided xpath
181
- # @param [RestClient::Response] response
182
- # @param [String] xpath Path to find elements from
183
- # @param [String] attribute Attribute to find path for
184
- # @return [Enumerable] Value inside element found through Xpath
185
- def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
186
- raise ArgumentError unless response && xpath
187
- raise "Can't perform XPATH if response is not XML" unless Interpreter.response_type_for(response) == :xml
188
-
189
- xpath = prefix_xpath(xpath, attribute)
190
- temp_doc = Nokogiri.parse(response.body).dup
191
- if strip_namespaces? && !xpath.include?(':')
192
- temp_doc.remove_namespaces!
193
- temp_doc.xpath(xpath)
194
- else
195
- temp_doc.xpath(xpath, temp_doc.collect_namespaces)
196
- end
197
- end
198
-
199
- # @return [Enumerable] List of values matching JSON path
200
- def json_path_values_for(response, path, attribute: nil)
201
- raise 'JSON does not support attributes' if attribute
202
-
203
- JsonPath.on(response.body, path)
204
- end
205
-
206
- # Calculate all JSON path values based on rules. ',', pascal_case
207
- # @param [RestClient::Response] response Response from API
208
- # @param [Object] path Xpath, JSONPath or other path identifying how to find element
209
- # @param [String] attribute Generic attribute to find. Will override path
210
- # @param [Boolean] not_empty Whether to fail if result is empty
211
- # @return [Array] Paths to check as first and matching values (List of values matching JSON Path) as second
212
- def calculated_json_path_matches(path, response, attribute, not_empty: false)
213
- path = add_pascal_path(path)
214
- paths_to_check = path.split(',')
215
- paths_to_check = paths_to_check.map { |path_to_check| prefix_json_path(path_to_check) }
216
- matching_values = paths_to_check.collect do |path_to_check|
217
- json_path_values_for(response, path_to_check, attribute: attribute)
218
- end.reject(&:empty?)
219
- raise NoElementAtPath, "No value at JSONPath '#{paths_to_check}' in '#{response.body}'" if matching_values.empty? && not_empty
220
-
221
- matching_values.first
222
- end
223
-
224
- # Based on a exchange, return the value at the provided xpath
225
- # If the path does not begin with a '/', a '//' is added to it
226
- # @param [RestClient::Response] response Response from API
227
- # @param [Object] path Xpath, JSONPath or other path identifying how to find element
228
- # @param [String] attribute Generic attribute to find. Will override path
229
- # @return [String] Value at Xpath
230
- def value_from_path(response, path, attribute: nil)
231
- path = path.to_s
232
- case Interpreter.response_type_for(response)
233
- when :xml
234
- result = xpath_elements_for(response: response, xpath: path, attribute: attribute).first
235
- raise NoElementAtPath, "No value at Xpath '#{prefix_xpath(path, attribute)}' in '#{response.body}'" unless result
236
- return result.inner_text if attribute.nil?
237
-
238
- return result.attributes[attribute].inner_text
239
- when :json
240
- matching_values = calculated_json_path_matches(path, response, attribute, not_empty: true)
241
- matching_values.first
242
- else # Assume this is a String
243
- raise NoElementAtPath, 'Response is empty' if response.to_s.empty?
244
-
245
- response.to_s[/#{path}/] # Perform regular expression using path if not XML nor JSON
246
- end
247
- end
248
-
249
- # @return [Enumerable] List of values returned from path
250
- def values_from_path(response, path, attribute: nil)
251
- path = path.to_s
252
- case Interpreter.response_type_for(response)
253
- when :xml
254
- xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
255
- when :json
256
- result = calculated_json_path_matches(path, response, attribute)
257
- result || []
258
- # json_path_values_for(response, path, attribute: attribute)
259
- else
260
- raise "Unable to interpret type of #{response.body}"
261
- end
262
- end
263
-
264
- # @return [RestClient::Request] Request of API call. Either intended request or actual request
265
- def request(response)
266
- return 'Request not yet sent' if response.nil?
267
-
268
- response.request
269
- end
270
-
271
- # Work out data to send based upon payload, template_name, or body
272
- # @return [String] Payload to send in REST request
273
- def post_data(test_values)
274
- data = if @request_option == :hash && !test_values[:payload]
275
- test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s
276
- elsif @request_option == :template
277
- test_values = test_values[:body].dup if test_values[:body]
278
- test_values = IndifferentHash.new(test_values) # Allow test_values to be either Symbol or String
279
- Soaspec::TemplateReader.new.render_body(template_name, binding)
280
- else
281
- test_values[:payload]
282
- end
283
- # Soaspec::SpecLogger.info "Request Empty for '#{@request_option}'" if data.strip.empty?
284
- data
285
- end
286
-
287
- # @param [Hash] override_hash Values to override default hash with
288
- # @return [Hash] Hash used in REST request based on data conversion
289
- def hash_used_in_request(override_hash)
290
- request = override_hash ? @default_hash.merge(override_hash) : @default_hash
291
- if pascal_keys?
292
- request.map { |k, v| [convert_to_pascal_case(k.to_s), v] }.to_h
293
- else
294
- request
295
- end
296
- end
297
- end
298
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'exchange_handler'
4
+ require_relative 'rest_parameters'
5
+ require_relative 'rest_parameters_defaults'
6
+ require_relative 'rest_exchanger_factory'
7
+ require_relative '../core_ext/hash'
8
+ require_relative '../errors'
9
+ require_relative 'handler_accessors'
10
+ require_relative '../interpreter'
11
+ require_relative 'response_extractor'
12
+ require_relative 'request/rest_request'
13
+ require 'json'
14
+ require 'jsonpath'
15
+ require 'nori'
16
+ require 'erb'
17
+
18
+ module Soaspec
19
+ # Wraps around Savon client defining default values dependent on the soap request
20
+ class RestHandler < ExchangeHandler
21
+ include ResponseExtractor
22
+ extend Soaspec::RestParameters
23
+ include Soaspec::RestParametersDefaults
24
+ extend Soaspec::RestExchangeFactory
25
+
26
+ # User used in making API calls
27
+ attr_accessor :api_username
28
+
29
+ # Setup object to handle communicating with a particular SOAP WSDL
30
+ # @param [Hash] options Options defining REST request. base_url, default_hash
31
+ def initialize(name = self.class.to_s, options = {})
32
+ raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value
33
+
34
+ if name.is_a?(Hash) && options == {} # If name is not set, use first parameter as the options hash
35
+ options = name
36
+ name = self.class.to_s
37
+ end
38
+ super
39
+ set_remove_keys(options, %i[api_username default_hash template_name])
40
+ @init_options = options
41
+ init_merge_options # Call this to verify any issues with options on creating object
42
+ end
43
+
44
+ # @return [Boolean] Whether REST method should have a payload
45
+ def payload?(overall_params)
46
+ case overall_params[:method]
47
+ when :post, :patch, :put
48
+ true
49
+ else
50
+ false
51
+ end
52
+ end
53
+
54
+ # @todo Use this in actually making the request
55
+ # At the moment this is just for one to manually view the parameters
56
+ # that will be used in making a request
57
+ # @return [RestRequest] Parameters used in making a request
58
+ def request_parameters(override_parameters)
59
+ overall_params = interpret_parameters(override_parameters)
60
+ request = { overall: overall_params, options: init_merge_options }
61
+ request[:body] = post_data(overall_params) if payload?(overall_params)
62
+ RestRequest.new(overall_params, init_merge_options, payload?(overall_params) ? post_data(overall_params) : nil)
63
+ end
64
+
65
+ # Interpret REST parameters given provided parameters and adding defaults, making
66
+ # transformations
67
+ #
68
+ # @param [Hash] request_parameters Parameters used in making a request
69
+ # @return [Hash] Request parameters merged with default values
70
+ def interpret_parameters(request_parameters)
71
+ request_parameters[:params] ||= {}
72
+ request_parameters[:method] ||= :post
73
+ suburl = request_parameters[:suburl]
74
+ if suburl
75
+ if suburl.is_a? Array
76
+ request_parameters[:suburl] = suburl.collect(&:to_s).join('/')
77
+ else
78
+ request_parameters[:suburl] = suburl.to_s
79
+ end
80
+ end
81
+ # Use q for query parameters. Nested :params is ugly, long and unclear
82
+ request_parameters[:params][:params] = request_parameters[:q] if request_parameters[:q]
83
+ request_parameters
84
+ end
85
+
86
+ # Override this with 'after_response' within class definition to perform an action
87
+ # after response is retrieved
88
+ # @param [RestClient::Response] _response Response to interpret to perform after block
89
+ def after_response(_response, _self); end
90
+
91
+ # Used in together with Exchange request that passes such override parameters
92
+ # @param [Hash] override_parameters Params to characterize REST request
93
+ # @option override_parameters [Hash] :params Extra parameters (E.g. headers)
94
+ # @option override_parameters [String] suburl URL appended to base_url of class
95
+ # @option override_parameters [Hash] :q Query for REST
96
+ # @option override_parameters [Symbol] :method REST method (:get, :post, :patch, etc)
97
+ # Following are for the body of the request
98
+ # @option override_parameters [Hash] :body Hash to be converted to JSON in request body
99
+ # @option override_parameters [String] :payload String to be passed directly in request body
100
+ # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body
101
+ # @return [RestClient::Response] Response from making request
102
+ def make_request(override_parameters)
103
+ @merged_options ||= init_merge_options # TODO: Is this var needed? Can method be passed to resource creation?
104
+ test_values = interpret_parameters override_parameters
105
+ # In order for ERB to be calculated at correct time, the first time request is made, the resource should be created
106
+ @resource ||= RestClient::Resource.new(ERB.new(base_url_value).result(binding), @merged_options)
107
+ @resource_used = test_values[:suburl] ? @resource[test_values[:suburl]] : @resource
108
+
109
+ self.exception = nil # Remove any previously stored exception
110
+ begin
111
+ response = case test_values[:method]
112
+ when :post, :patch, :put
113
+ Soaspec::SpecLogger.info("request body: #{post_data(test_values)}")
114
+ @resource_used.send(test_values[:method].to_s, post_data(test_values), test_values[:params])
115
+ else # :get, :delete
116
+ @resource_used.send(test_values[:method].to_s, test_values[:params])
117
+ end
118
+ rescue RestClient::Exception => e
119
+ self.exception = e
120
+ raise e unless e.respond_to? :response
121
+
122
+ response = e.response
123
+ end
124
+ Soaspec::SpecLogger.info("response: \n headers: #{response&.headers}\n body: #{response}\n")
125
+ after_response(response, self)
126
+ response
127
+ end
128
+
129
+ # Add values to here when extending this class to have default REST options.
130
+ # See rest client resource at https://github.com/rest-client/rest-client for details
131
+ # It's easier to set headers via 'headers' accessor rather than here
132
+ # @return [Hash] Options adding to & overriding defaults
133
+ def rest_resource_options
134
+ {}
135
+ end
136
+
137
+ # Perform ERB on each header value
138
+ # @return [Hash] Hash from 'rest_client_headers' passed through ERB
139
+ def parse_headers
140
+ Hash[rest_client_headers.map do |header_name, header_value|
141
+ raise ArgumentError, "Header '#{header_name}' is null. Headers are #{rest_client_headers}" if header_value.nil?
142
+
143
+ [header_name, ERB.new(header_value).result(binding)]
144
+ end]
145
+ end
146
+
147
+ # Initialize value of merged options
148
+ # @return [Hash] Hash of merged options
149
+ def init_merge_options
150
+ options = rest_resource_options
151
+ options.merge! basic_auth_params if respond_to? :basic_auth_params
152
+ options[:headers] ||= {}
153
+ options[:headers].merge! parse_headers
154
+ options[:headers][:authorization] ||= ERB.new('Bearer <%= access_token %>').result(binding) if Soaspec.auto_oauth && respond_to?(:access_token)
155
+ options.merge(@init_options)
156
+ end
157
+
158
+ # @param [Hash] format Format of expected result.
159
+ # @return [Object] Generic body to be displayed in error messages
160
+ def response_body(response, format: :hash)
161
+ extract_hash response
162
+ end
163
+
164
+ # @return [Boolean] Whether response body includes String
165
+ def include_in_body?(response, expected)
166
+ response.body.include? expected
167
+ end
168
+
169
+ # @@return [Boolean] Whether the request found the desired value or not
170
+ def found?(response)
171
+ status_code_for(response) != 404
172
+ end
173
+
174
+ # @return [Boolean] Whether response contains expected value
175
+ def include_value?(response, expected)
176
+ extract_hash(response).include_value? expected
177
+ end
178
+
179
+ # @return [Boolean] Whether response body contains expected key
180
+ def include_key?(response, expected)
181
+ value_from_path(response, expected)
182
+ end
183
+
184
+ # @return [Integer] HTTP Status code for response
185
+ def status_code_for(response)
186
+ response.code
187
+ end
188
+
189
+ # Returns the value at the provided xpath
190
+ # @param [RestClient::Response] response
191
+ # @param [String] xpath Path to find elements from
192
+ # @param [String] attribute Attribute to find path for
193
+ # @return [Enumerable] Value inside element found through Xpath
194
+ def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
195
+ raise ArgumentError unless response && xpath
196
+ raise "Can't perform XPATH if response is not XML" unless Interpreter.response_type_for(response) == :xml
197
+
198
+ xpath = prefix_xpath(xpath, attribute)
199
+ temp_doc = Nokogiri.parse(response.body).dup
200
+ if strip_namespaces? && !xpath.include?(':')
201
+ temp_doc.remove_namespaces!
202
+ temp_doc.xpath(xpath)
203
+ else
204
+ temp_doc.xpath(xpath, temp_doc.collect_namespaces)
205
+ end
206
+ end
207
+
208
+ # @return [Enumerable] List of values matching JSON path
209
+ def json_path_values_for(response, path, attribute: nil)
210
+ raise 'JSON does not support attributes' if attribute
211
+
212
+ JsonPath.on(response.body, path)
213
+ end
214
+
215
+ # Calculate all JSON path values based on rules. ',', pascal_case
216
+ # @param [RestClient::Response] response Response from API
217
+ # @param [Object] path Xpath, JSONPath or other path identifying how to find element
218
+ # @param [String] attribute Generic attribute to find. Will override path
219
+ # @param [Boolean] not_empty Whether to fail if result is empty
220
+ # @return [Array] Paths to check as first and matching values (List of values matching JSON Path) as second
221
+ def calculated_json_path_matches(path, response, attribute, not_empty: false)
222
+ path = add_pascal_path(path)
223
+ paths_to_check = path.split(',')
224
+ paths_to_check = paths_to_check.map { |path_to_check| prefix_json_path(path_to_check) }
225
+ matching_values = paths_to_check.collect do |path_to_check|
226
+ json_path_values_for(response, path_to_check, attribute: attribute)
227
+ end.reject(&:empty?)
228
+ raise NoElementAtPath, "No value at JSONPath '#{paths_to_check}' in '#{response.body}'" if matching_values.empty? && not_empty
229
+
230
+ matching_values.first
231
+ end
232
+
233
+ # Based on a exchange, return the value at the provided xpath
234
+ # If the path does not begin with a '/', a '//' is added to it
235
+ # @param [RestClient::Response] response Response from API
236
+ # @param [Object] path Xpath, JSONPath or other path identifying how to find element
237
+ # @param [String] attribute Generic attribute to find. Will override path
238
+ # @return [String] Value at Xpath
239
+ def value_from_path(response, path, attribute: nil)
240
+ path = path.to_s
241
+ case Interpreter.response_type_for(response)
242
+ when :xml
243
+ result = xpath_elements_for(response: response, xpath: path, attribute: attribute).first
244
+ raise NoElementAtPath, "No value at Xpath '#{prefix_xpath(path, attribute)}' in '#{response.body}'" unless result
245
+ return result.inner_text if attribute.nil?
246
+
247
+ return result.attributes[attribute].inner_text
248
+ when :json
249
+ matching_values = calculated_json_path_matches(path, response, attribute, not_empty: true)
250
+ matching_values.first
251
+ else # Assume this is a String
252
+ raise NoElementAtPath, 'Response is empty' if response.to_s.empty?
253
+
254
+ response.to_s[/#{path}/] # Perform regular expression using path if not XML nor JSON
255
+ end
256
+ end
257
+
258
+ # @return [Enumerable] List of values returned from path
259
+ def values_from_path(response, path, attribute: nil)
260
+ path = path.to_s
261
+ case Interpreter.response_type_for(response)
262
+ when :xml
263
+ xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
264
+ when :json
265
+ result = calculated_json_path_matches(path, response, attribute)
266
+ result || []
267
+ # json_path_values_for(response, path, attribute: attribute)
268
+ else
269
+ raise "Unable to interpret type of #{response.body}"
270
+ end
271
+ end
272
+
273
+ # @return [RestClient::Request] Request of API call. Either intended request or actual request
274
+ def request(response)
275
+ return 'Request not yet sent' if response.nil?
276
+
277
+ response.request
278
+ end
279
+
280
+ # Work out data to send based upon payload, template_name, or body
281
+ # @return [String] Payload to send in REST request
282
+ def post_data(test_values)
283
+ data = if @request_option == :hash && !test_values[:payload]
284
+ test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s
285
+ elsif @request_option == :template
286
+ test_values = test_values[:body].dup if test_values[:body]
287
+ test_values = IndifferentHash.new(test_values) # Allow test_values to be either Symbol or String
288
+ Soaspec::TemplateReader.new.render_body(template_name, binding)
289
+ else
290
+ test_values[:payload]
291
+ end
292
+ # Soaspec::SpecLogger.info "Request Empty for '#{@request_option}'" if data.strip.empty?
293
+ data
294
+ end
295
+
296
+ # @param [Hash] override_hash Values to override default hash with
297
+ # @return [Hash] Hash used in REST request based on data conversion
298
+ def hash_used_in_request(override_hash)
299
+ request = override_hash ? @default_hash.merge(override_hash) : @default_hash
300
+ if pascal_keys?
301
+ request.map { |k, v| [convert_to_pascal_case(k.to_s), v] }.to_h
302
+ else
303
+ request
304
+ end
305
+ end
306
+ end
307
+ end