soaspec 0.2.32 → 0.2.33

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 +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