soaspec 0.0.85 → 0.0.86

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