soaspec 0.2.33 → 0.3.1

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