soaspec 0.1.1 → 0.1.2

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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +15 -15
  3. data/.gitlab-ci.yml +31 -31
  4. data/.rspec +3 -3
  5. data/.rubocop.yml +2 -2
  6. data/CODE_OF_CONDUCT.md +74 -74
  7. data/ChangeLog +384 -384
  8. data/Gemfile +6 -6
  9. data/LICENSE.txt +21 -21
  10. data/README.md +85 -85
  11. data/Rakefile +24 -24
  12. data/Todo.md +6 -6
  13. data/exe/soaspec +119 -119
  14. data/exe/soaspec-virtual-server +103 -103
  15. data/exe/xml_to_yaml_file +60 -60
  16. data/lib/soaspec.rb +91 -91
  17. data/lib/soaspec/core_ext/hash.rb +83 -83
  18. data/lib/soaspec/exchange.rb +234 -234
  19. data/lib/soaspec/exchange_handlers/exchange_handler.rb +103 -103
  20. data/lib/soaspec/exchange_handlers/handler_accessors.rb +106 -106
  21. data/lib/soaspec/exchange_handlers/rest_accessors.rb +92 -92
  22. data/lib/soaspec/exchange_handlers/rest_handler.rb +311 -311
  23. data/lib/soaspec/exchange_handlers/rest_methods.rb +44 -44
  24. data/lib/soaspec/exchange_handlers/soap_handler.rb +236 -236
  25. data/lib/soaspec/exe_helpers.rb +56 -56
  26. data/lib/soaspec/generator/.rspec.erb +5 -5
  27. data/lib/soaspec/generator/.travis.yml.erb +5 -5
  28. data/lib/soaspec/generator/Gemfile.erb +8 -8
  29. data/lib/soaspec/generator/README.md.erb +29 -29
  30. data/lib/soaspec/generator/Rakefile.erb +19 -19
  31. data/lib/soaspec/generator/config/data/default.yml.erb +1 -1
  32. data/lib/soaspec/generator/lib/blz_service.rb.erb +26 -26
  33. data/lib/soaspec/generator/lib/dynamic_class_content.rb.erb +12 -12
  34. data/lib/soaspec/generator/lib/shared_example.rb.erb +8 -8
  35. data/lib/soaspec/generator/spec/dynamic_soap_spec.rb.erb +12 -12
  36. data/lib/soaspec/generator/spec/soap_spec.rb.erb +51 -51
  37. data/lib/soaspec/generator/spec/spec_helper.rb.erb +20 -20
  38. data/lib/soaspec/generator/template/soap_template.xml +6 -6
  39. data/lib/soaspec/interpreter.rb +40 -40
  40. data/lib/soaspec/matchers.rb +65 -65
  41. data/lib/soaspec/not_found_errors.rb +13 -13
  42. data/lib/soaspec/soaspec_shared_examples.rb +24 -24
  43. data/lib/soaspec/spec_logger.rb +27 -27
  44. data/lib/soaspec/test_server/bank.wsdl +90 -90
  45. data/lib/soaspec/test_server/get_bank.rb +160 -160
  46. data/lib/soaspec/test_server/invoices.rb +27 -27
  47. data/lib/soaspec/test_server/namespace.xml +14 -14
  48. data/lib/soaspec/test_server/note.xml +5 -5
  49. data/lib/soaspec/test_server/puppy_service.rb +20 -20
  50. data/lib/soaspec/test_server/test_attribute.rb +13 -13
  51. data/lib/soaspec/version.rb +2 -2
  52. data/lib/soaspec/wsdl_generator.rb +144 -144
  53. data/soaspec.gemspec +46 -45
  54. data/test.wsdl +116 -116
  55. data/test.xml +10 -10
  56. data/test_wsdl.rb +43 -43
  57. metadata +17 -3
@@ -1,312 +1,312 @@
1
- require_relative 'exchange_handler'
2
- require_relative 'rest_accessors'
3
- require_relative '../core_ext/hash'
4
- require_relative '../not_found_errors'
5
- require_relative 'handler_accessors'
6
- require_relative '../interpreter'
7
- require 'json'
8
- require 'jsonpath'
9
- require 'nori'
10
- require 'erb'
11
-
12
- module Soaspec
13
-
14
- # Wraps around Savon client defining default values dependent on the soap request
15
- class RestHandler < ExchangeHandler
16
- extend Soaspec::RestAccessors
17
-
18
- # User used in making API calls
19
- attr_accessor :api_username
20
-
21
- # Set through following method. Base URL in REST requests.
22
- def base_url_value
23
- nil
24
- end
25
-
26
- # Headers used in RestClient
27
- def rest_client_headers
28
- {}
29
- end
30
-
31
- # Add values to here when extending this class to have default REST options.
32
- # See rest client resource at https://github.com/rest-client/rest-client for details
33
- # It's easier to set headers via 'headers' accessor rather than here
34
- # @return [Hash] Options adding to & overriding defaults
35
- def rest_resource_options
36
- {
37
- }
38
- end
39
-
40
- # Perform ERB on each header value
41
- # @return [Hash] Hash from 'rest_client_headers' passed through ERB
42
- def parse_headers
43
- Hash[rest_client_headers.map { |k, header| [k, ERB.new(header).result(binding)] }]
44
- end
45
-
46
- # Setup object to handle communicating with a particular SOAP WSDL
47
- # @param [Hash] options Options defining SOAP request. WSDL, authentication
48
- def initialize(name = self.class.to_s, options = {})
49
- raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value
50
- @default_hash = {}
51
- if name.is_a?(Hash) && options == {} # If name is not set
52
- options = name
53
- name = self.class.to_s
54
- end
55
- super
56
- set_remove_key(options, :api_username)
57
- set_remove_key(options, :default_hash)
58
- set_remove_key(options, :template_name)
59
- @init_options = options
60
- end
61
-
62
- # Convert snakecase to PascalCase
63
- def convert_to_pascal_case(key)
64
- return key if /[[:upper:]]/ =~ key[0] # If first character already capital, don't do conversion
65
- key.split('_').map(&:capitalize).join
66
- end
67
-
68
- # Whether to convert each key in the request to PascalCase
69
- # It will also auto convert simple XPath, JSONPath where '//' or '..' not specified
70
- # @return Whether to convert to PascalCase
71
- def pascal_keys?
72
- false
73
- end
74
-
75
- # Initialize value of merged options
76
- def init_merge_options
77
- options = rest_resource_options
78
- options[:headers] ||= {}
79
- options[:headers].merge! parse_headers
80
- options.merge(@init_options)
81
- end
82
-
83
- # Used in together with Exchange request that passes such override parameters
84
- # @param [Hash] override_parameters Params to characterize REST request
85
- # @param_value [params] Extra parameters (E.g. headers)
86
- # @param_value [suburl] URL appended to base_url of class
87
- # @param_value [q] Query for REST
88
- # @param_value [method] REST method (:get, :post, etc)
89
- def make_request(override_parameters)
90
- @merged_options ||= init_merge_options
91
- test_values = override_parameters
92
- test_values[:params] ||= {}
93
- test_values[:method] ||= :post
94
- test_values[:suburl] = test_values[:suburl].to_s if test_values[:suburl]
95
- test_values[:params][:params] = test_values[:q] if test_values[:q] # Use q for query parameters. Nested :params is ugly and long
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
-
99
- @resource_used = test_values[:suburl] ? @resource[test_values[:suburl]] : @resource
100
-
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::ExceptionWithResponse => e
110
- response = e.response
111
- end
112
- Soaspec::SpecLogger.info('response_headers: ' + response.headers.to_s)
113
- Soaspec::SpecLogger.info('response_body: ' + response.to_s)
114
- response
115
- end
116
-
117
- # @param [Hash] format Format of expected result.
118
- # @return [Object] Generic body to be displayed in error messages
119
- def response_body(response, format: :hash)
120
- extract_hash response
121
- end
122
-
123
- # @return [Boolean] Whether response body includes String
124
- def include_in_body?(response, expected)
125
- response.body.include? expected
126
- end
127
-
128
- # @@return [Boolean] Whether the request found the desired value or not
129
- def found?(response)
130
- status_code_for(response) != 404
131
- end
132
-
133
- # Convert XML or JSON response into a Hash
134
- # @param [String] response Response as a String (either in XML or JSON)
135
- # @return [Hash]
136
- def extract_hash(response)
137
- raise ArgumentError("Empty Body. Can't assert on it") if response.body.empty?
138
- case Interpreter.response_type_for response
139
- when :json
140
- converted = JSON.parse(response.body)
141
- return converted.transform_keys_to_symbols if converted.is_a? Hash
142
- return converted.map!(&:transform_keys_to_symbols) if converted.is_a? Array
143
- raise 'Incorrect Type prodcued ' + converted.class
144
- when :xml
145
- parser = Nori.new(convert_tags_to: lambda { |tag| tag.snakecase.to_sym })
146
- parser.parse(response.body)
147
- else
148
- raise "Neither XML nor JSON detected. It is #{type}. Don't know how to parse It is #{response.body}"
149
- end
150
- end
151
-
152
- # @return [Boolean] Whether response contains expected value
153
- def include_value?(response, expected)
154
- extract_hash(response).include_value? expected
155
- end
156
-
157
- # @return [Boolean] Whether response body contains expected key
158
- def include_key?(response, expected)
159
- value_from_path(response, expected)
160
- end
161
-
162
- # @return [Integer] HTTP Status code for response
163
- def status_code_for(response)
164
- response.code
165
- end
166
-
167
- # Override this to specify elements that must be present in the response
168
- # Will be used in 'success_scenarios' shared examples
169
- # @return [Array] Array of symbols specifying element names
170
- def mandatory_elements
171
- []
172
- end
173
-
174
- # Override this to specify xpath results that must be present in the response
175
- # Will be used in 'success_scenarios' shared examples
176
- # @return [Hash] Hash of 'xpath' => 'expected value' pairs
177
- def mandatory_xpath_values
178
- {}
179
- end
180
-
181
- # Attributes set at the root XML element of SOAP request
182
- def root_attributes
183
- nil
184
- end
185
-
186
- # Returns the value at the provided xpath
187
- # @param [RestClient::Response] response
188
- # @param [String] xpath
189
- # @return [Enumerable] Value inside element found through Xpath
190
- def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
191
- raise ArgumentError unless response && xpath
192
- raise "Can't perform XPATH if response is not XML" unless Interpreter.response_type_for(response) == :xml
193
- xpath = "//*[@#{attribute}]" unless attribute.nil?
194
- if xpath[0] != '/'
195
- xpath = convert_to_pascal_case(xpath) if pascal_keys?
196
- xpath = '//' + xpath
197
- end
198
- temp_doc = Nokogiri.parse(response.body).dup
199
- if strip_namespaces? && !xpath.include?(':')
200
- temp_doc.remove_namespaces!
201
- temp_doc.xpath(xpath)
202
- else
203
- temp_doc.xpath(xpath, temp_doc.collect_namespaces)
204
- end
205
- end
206
-
207
- # @return [Enumerable] List of values matching JSON path
208
- def json_path_values_for(response, path, attribute: nil)
209
- raise 'JSON does not support attributes' if attribute
210
- if path[0] != '$'
211
- path = convert_to_pascal_case(path) if pascal_keys?
212
- path = '$..' + path
213
- end
214
- JsonPath.on(response.body, path)
215
- end
216
-
217
- # Based on a exchange, return the value at the provided xpath
218
- # If the path does not begin with a '/', a '//' is added to it
219
- # @param [Response] response
220
- # @param [Object] path Xpath, JSONPath or other path identifying how to find element
221
- # @param [String] attribute Generic attribute to find. Will override path
222
- # @return [String] Value at Xpath
223
- def value_from_path(response, path, attribute: nil)
224
- path = path.to_s
225
- case Interpreter.response_type_for(response)
226
- when :xml
227
- result = xpath_elements_for(response: response, xpath: path, attribute: attribute).first
228
- raise NoElementAtPath, "No value at Xpath '#{path}'" unless result
229
- return result.inner_text if attribute.nil?
230
- return result.attributes[attribute].inner_text
231
- when :json
232
- matching_values = json_path_values_for(response, path, attribute: attribute)
233
- raise NoElementAtPath, "Element in #{response.body} not found with path '#{path}'" if matching_values.empty?
234
- matching_values.first
235
- when :hash
236
- response.dig(path.split('.')) # Use path as Hash dig expression separating params via '.' TODO: Unit test
237
- else
238
- response.to_s[/path/] # Perform regular expression using path if not XML nor JSON TODO: Unit test
239
- end
240
- end
241
-
242
- # @return [Enumerable] List of values returned from path
243
- def values_from_path(response, path, attribute: nil)
244
- path = path.to_s
245
- case Interpreter.response_type_for(response)
246
- when :xml
247
- xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
248
- when :json
249
- json_path_values_for(response, path, attribute: attribute)
250
- else
251
- raise "Unable to interpret type of #{response.body}"
252
- end
253
- end
254
-
255
- # @return [Hash] Hash representing response body
256
- def to_hash(response)
257
- case Interpreter.response_type_for(response)
258
- when :xml
259
- parser = Nori.new(strip_namespaces: strip_namespaces?, convert_tags_to: ->(tag) { tag.snakecase.to_sym })
260
- parser.parse(response.body.to_s)
261
- when :json
262
- JSON.parse(response.body.to_s)
263
- else
264
- raise "Unable to interpret type of #{response.body}"
265
- end
266
- end
267
-
268
- private
269
-
270
- # Work out data to send based upon payload, template_name
271
- # @return [String] Payload to send in REST request
272
- def post_data(test_values)
273
- if test_values[:body]
274
- test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s
275
- elsif @request_option == :template
276
- request_body = File.read('template/' + template_name)
277
- ERB.new(request_body).result(binding)
278
- else
279
- test_values[:payload]
280
- end
281
- end
282
-
283
- # @return [Hash] Hash used in REST request based on data conversion
284
- def hash_used_in_request(override_hash)
285
- request = @default_hash.merge(override_hash)
286
- if pascal_keys?
287
- request.map { |k, v| [convert_to_pascal_case(k.to_s), v] }.to_h
288
- else
289
- request
290
- end
291
- end
292
-
293
- # Convenience methods for once off usage of a REST request
294
- class << self
295
-
296
- methods = %w[post patch put get delete]
297
-
298
- methods.each do |rest_method|
299
- # Make REST Exchange within this Handler context
300
- # @param [Hash] params Exchange parameters
301
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
302
- define_method(rest_method) do |params|
303
- params ||= {}
304
- params[:name] ||= rest_method
305
- new(params[:name])
306
- Exchange.new(params[:name], method: rest_method.to_sym, **params)
307
- end
308
- end
309
- end
310
-
311
- end
1
+ require_relative 'exchange_handler'
2
+ require_relative 'rest_accessors'
3
+ require_relative '../core_ext/hash'
4
+ require_relative '../not_found_errors'
5
+ require_relative 'handler_accessors'
6
+ require_relative '../interpreter'
7
+ require 'json'
8
+ require 'jsonpath'
9
+ require 'nori'
10
+ require 'erb'
11
+
12
+ module Soaspec
13
+
14
+ # Wraps around Savon client defining default values dependent on the soap request
15
+ class RestHandler < ExchangeHandler
16
+ extend Soaspec::RestAccessors
17
+
18
+ # User used in making API calls
19
+ attr_accessor :api_username
20
+
21
+ # Set through following method. Base URL in REST requests.
22
+ def base_url_value
23
+ nil
24
+ end
25
+
26
+ # Headers used in RestClient
27
+ def rest_client_headers
28
+ {}
29
+ end
30
+
31
+ # Add values to here when extending this class to have default REST options.
32
+ # See rest client resource at https://github.com/rest-client/rest-client for details
33
+ # It's easier to set headers via 'headers' accessor rather than here
34
+ # @return [Hash] Options adding to & overriding defaults
35
+ def rest_resource_options
36
+ {
37
+ }
38
+ end
39
+
40
+ # Perform ERB on each header value
41
+ # @return [Hash] Hash from 'rest_client_headers' passed through ERB
42
+ def parse_headers
43
+ Hash[rest_client_headers.map { |k, header| [k, ERB.new(header).result(binding)] }]
44
+ end
45
+
46
+ # Setup object to handle communicating with a particular SOAP WSDL
47
+ # @param [Hash] options Options defining SOAP request. WSDL, authentication
48
+ def initialize(name = self.class.to_s, options = {})
49
+ raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value
50
+ @default_hash = {}
51
+ if name.is_a?(Hash) && options == {} # If name is not set
52
+ options = name
53
+ name = self.class.to_s
54
+ end
55
+ super
56
+ set_remove_key(options, :api_username)
57
+ set_remove_key(options, :default_hash)
58
+ set_remove_key(options, :template_name)
59
+ @init_options = options
60
+ end
61
+
62
+ # Convert snakecase to PascalCase
63
+ def convert_to_pascal_case(key)
64
+ return key if /[[:upper:]]/ =~ key[0] # If first character already capital, don't do conversion
65
+ key.split('_').map(&:capitalize).join
66
+ end
67
+
68
+ # Whether to convert each key in the request to PascalCase
69
+ # It will also auto convert simple XPath, JSONPath where '//' or '..' not specified
70
+ # @return Whether to convert to PascalCase
71
+ def pascal_keys?
72
+ false
73
+ end
74
+
75
+ # Initialize value of merged options
76
+ def init_merge_options
77
+ options = rest_resource_options
78
+ options[:headers] ||= {}
79
+ options[:headers].merge! parse_headers
80
+ options.merge(@init_options)
81
+ end
82
+
83
+ # Used in together with Exchange request that passes such override parameters
84
+ # @param [Hash] override_parameters Params to characterize REST request
85
+ # @param_value [params] Extra parameters (E.g. headers)
86
+ # @param_value [suburl] URL appended to base_url of class
87
+ # @param_value [q] Query for REST
88
+ # @param_value [method] REST method (:get, :post, etc)
89
+ def make_request(override_parameters)
90
+ @merged_options ||= init_merge_options
91
+ test_values = override_parameters
92
+ test_values[:params] ||= {}
93
+ test_values[:method] ||= :post
94
+ test_values[:suburl] = test_values[:suburl].to_s if test_values[:suburl]
95
+ test_values[:params][:params] = test_values[:q] if test_values[:q] # Use q for query parameters. Nested :params is ugly and long
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
+
99
+ @resource_used = test_values[:suburl] ? @resource[test_values[:suburl]] : @resource
100
+
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::ExceptionWithResponse => e
110
+ response = e.response
111
+ end
112
+ Soaspec::SpecLogger.info('response_headers: ' + response.headers.to_s)
113
+ Soaspec::SpecLogger.info('response_body: ' + response.to_s)
114
+ response
115
+ end
116
+
117
+ # @param [Hash] format Format of expected result.
118
+ # @return [Object] Generic body to be displayed in error messages
119
+ def response_body(response, format: :hash)
120
+ extract_hash response
121
+ end
122
+
123
+ # @return [Boolean] Whether response body includes String
124
+ def include_in_body?(response, expected)
125
+ response.body.include? expected
126
+ end
127
+
128
+ # @@return [Boolean] Whether the request found the desired value or not
129
+ def found?(response)
130
+ status_code_for(response) != 404
131
+ end
132
+
133
+ # Convert XML or JSON response into a Hash
134
+ # @param [String] response Response as a String (either in XML or JSON)
135
+ # @return [Hash]
136
+ def extract_hash(response)
137
+ raise ArgumentError("Empty Body. Can't assert on it") if response.body.empty?
138
+ case Interpreter.response_type_for response
139
+ when :json
140
+ converted = JSON.parse(response.body)
141
+ return converted.transform_keys_to_symbols if converted.is_a? Hash
142
+ return converted.map!(&:transform_keys_to_symbols) if converted.is_a? Array
143
+ raise 'Incorrect Type prodcued ' + converted.class
144
+ when :xml
145
+ parser = Nori.new(convert_tags_to: lambda { |tag| tag.snakecase.to_sym })
146
+ parser.parse(response.body)
147
+ else
148
+ raise "Neither XML nor JSON detected. It is #{type}. Don't know how to parse It is #{response.body}"
149
+ end
150
+ end
151
+
152
+ # @return [Boolean] Whether response contains expected value
153
+ def include_value?(response, expected)
154
+ extract_hash(response).include_value? expected
155
+ end
156
+
157
+ # @return [Boolean] Whether response body contains expected key
158
+ def include_key?(response, expected)
159
+ value_from_path(response, expected)
160
+ end
161
+
162
+ # @return [Integer] HTTP Status code for response
163
+ def status_code_for(response)
164
+ response.code
165
+ end
166
+
167
+ # Override this to specify elements that must be present in the response
168
+ # Will be used in 'success_scenarios' shared examples
169
+ # @return [Array] Array of symbols specifying element names
170
+ def mandatory_elements
171
+ []
172
+ end
173
+
174
+ # Override this to specify xpath results that must be present in the response
175
+ # Will be used in 'success_scenarios' shared examples
176
+ # @return [Hash] Hash of 'xpath' => 'expected value' pairs
177
+ def mandatory_xpath_values
178
+ {}
179
+ end
180
+
181
+ # Attributes set at the root XML element of SOAP request
182
+ def root_attributes
183
+ nil
184
+ end
185
+
186
+ # Returns the value at the provided xpath
187
+ # @param [RestClient::Response] response
188
+ # @param [String] xpath
189
+ # @return [Enumerable] Value inside element found through Xpath
190
+ def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
191
+ raise ArgumentError unless response && xpath
192
+ raise "Can't perform XPATH if response is not XML" unless Interpreter.response_type_for(response) == :xml
193
+ xpath = "//*[@#{attribute}]" unless attribute.nil?
194
+ if xpath[0] != '/'
195
+ xpath = convert_to_pascal_case(xpath) if pascal_keys?
196
+ xpath = '//' + xpath
197
+ end
198
+ temp_doc = Nokogiri.parse(response.body).dup
199
+ if strip_namespaces? && !xpath.include?(':')
200
+ temp_doc.remove_namespaces!
201
+ temp_doc.xpath(xpath)
202
+ else
203
+ temp_doc.xpath(xpath, temp_doc.collect_namespaces)
204
+ end
205
+ end
206
+
207
+ # @return [Enumerable] List of values matching JSON path
208
+ def json_path_values_for(response, path, attribute: nil)
209
+ raise 'JSON does not support attributes' if attribute
210
+ if path[0] != '$'
211
+ path = convert_to_pascal_case(path) if pascal_keys?
212
+ path = '$..' + path
213
+ end
214
+ JsonPath.on(response.body, path)
215
+ end
216
+
217
+ # Based on a exchange, return the value at the provided xpath
218
+ # If the path does not begin with a '/', a '//' is added to it
219
+ # @param [Response] response
220
+ # @param [Object] path Xpath, JSONPath or other path identifying how to find element
221
+ # @param [String] attribute Generic attribute to find. Will override path
222
+ # @return [String] Value at Xpath
223
+ def value_from_path(response, path, attribute: nil)
224
+ path = path.to_s
225
+ case Interpreter.response_type_for(response)
226
+ when :xml
227
+ result = xpath_elements_for(response: response, xpath: path, attribute: attribute).first
228
+ raise NoElementAtPath, "No value at Xpath '#{path}'" unless result
229
+ return result.inner_text if attribute.nil?
230
+ return result.attributes[attribute].inner_text
231
+ when :json
232
+ matching_values = json_path_values_for(response, path, attribute: attribute)
233
+ raise NoElementAtPath, "Element in #{response.body} not found with path '#{path}'" if matching_values.empty?
234
+ matching_values.first
235
+ when :hash
236
+ response.dig(path.split('.')) # Use path as Hash dig expression separating params via '.' TODO: Unit test
237
+ else
238
+ response.to_s[/path/] # Perform regular expression using path if not XML nor JSON TODO: Unit test
239
+ end
240
+ end
241
+
242
+ # @return [Enumerable] List of values returned from path
243
+ def values_from_path(response, path, attribute: nil)
244
+ path = path.to_s
245
+ case Interpreter.response_type_for(response)
246
+ when :xml
247
+ xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
248
+ when :json
249
+ json_path_values_for(response, path, attribute: attribute)
250
+ else
251
+ raise "Unable to interpret type of #{response.body}"
252
+ end
253
+ end
254
+
255
+ # @return [Hash] Hash representing response body
256
+ def to_hash(response)
257
+ case Interpreter.response_type_for(response)
258
+ when :xml
259
+ parser = Nori.new(strip_namespaces: strip_namespaces?, convert_tags_to: ->(tag) { tag.snakecase.to_sym })
260
+ parser.parse(response.body.to_s)
261
+ when :json
262
+ JSON.parse(response.body.to_s)
263
+ else
264
+ raise "Unable to interpret type of #{response.body}"
265
+ end
266
+ end
267
+
268
+ private
269
+
270
+ # Work out data to send based upon payload, template_name
271
+ # @return [String] Payload to send in REST request
272
+ def post_data(test_values)
273
+ if test_values[:body]
274
+ test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s
275
+ elsif @request_option == :template
276
+ request_body = File.read('template/' + template_name)
277
+ ERB.new(request_body).result(binding)
278
+ else
279
+ test_values[:payload]
280
+ end
281
+ end
282
+
283
+ # @return [Hash] Hash used in REST request based on data conversion
284
+ def hash_used_in_request(override_hash)
285
+ request = @default_hash.merge(override_hash)
286
+ if pascal_keys?
287
+ request.map { |k, v| [convert_to_pascal_case(k.to_s), v] }.to_h
288
+ else
289
+ request
290
+ end
291
+ end
292
+
293
+ # Convenience methods for once off usage of a REST request
294
+ class << self
295
+
296
+ methods = %w[post patch put get delete]
297
+
298
+ methods.each do |rest_method|
299
+ # Make REST Exchange within this Handler context
300
+ # @param [Hash] params Exchange parameters
301
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
302
+ define_method(rest_method) do |params|
303
+ params ||= {}
304
+ params[:name] ||= rest_method
305
+ new(params[:name])
306
+ Exchange.new(params[:name], method: rest_method.to_sym, **params)
307
+ end
308
+ end
309
+ end
310
+
311
+ end
312
312
  end