soaspec 0.2.33 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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 +643 -632
  8. data/Dockerfile +7 -7
  9. data/Gemfile +8 -8
  10. data/LICENSE.txt +21 -21
  11. data/README.md +253 -231
  12. data/Rakefile +52 -52
  13. data/Todo.md +16 -16
  14. data/exe/soaspec +140 -138
  15. data/exe/xml_to_yaml_file +43 -43
  16. data/lib/soaspec.rb +118 -106
  17. data/lib/soaspec/baseline.rb +82 -22
  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 -24
  22. data/lib/soaspec/exchange/exchange.rb +131 -129
  23. data/lib/soaspec/exchange/exchange_extractor.rb +105 -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 +108 -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 +77 -59
  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 -307
  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 +20 -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 +136 -140
  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 +193 -190
  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 +3 -3
@@ -1,307 +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
- # 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
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