soaspec 0.2.23 → 0.2.24

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