Class: Soaspec::RestHandler

Inherits:
ExchangeHandler show all
Extended by:
RestAccessors
Defined in:
lib/soaspec/exchange_handlers/rest_handler.rb

Overview

Wraps around Savon client defining default values dependent on the soap request

Instance Attribute Summary collapse

Attributes inherited from ExchangeHandler

#template_name

Instance Method Summary collapse

Methods included from RestAccessors

base_url, headers, oauth2, oauth2_file, pascal_keys

Methods inherited from ExchangeHandler

#convert_to_lower?, #default_hash=, #elements, #expected_mandatory_elements, #expected_mandatory_json_values, #expected_mandatory_xpath_values, #set_remove_key, #store, #to_s, #use

Methods included from HandlerAccessors

#attribute, #convert_to_lower, #element, #mandatory_json_values

Constructor Details

#initialize(name = self.class.to_s, options = {}) ⇒ RestHandler

Setup object to handle communicating with a particular SOAP WSDL

Parameters:

  • options (Hash) (defaults to: {})

    Options defining SOAP request. WSDL, authentication



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 137

def initialize(name = self.class.to_s, options = {})
  raise "Base URL not set! Please set in class with 'base_url' method" unless base_url_value
  @default_hash = {}
  if name.is_a?(Hash) && options == {} # If name is not set
    options = name
    name = self.class.to_s
  end
  super
  set_remove_key(options, :default_hash)
  @init_options = options
end

Instance Attribute Details

#api_usernameObject

User used in making API calls



108
109
110
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 108

def api_username
  @api_username
end

#clientObject

Savon client used to make SOAP calls



104
105
106
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 104

def client
  @client
end

#operationObject

SOAP Operation to use by default



106
107
108
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 106

def operation
  @operation
end

Instance Method Details

#base_url_valueObject

Set through following method. Base URL in REST requests.



111
112
113
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 111

def base_url_value
  nil
end

#convert_to_pascal_case(key) ⇒ Object

Convert snakecase to PascalCase



150
151
152
153
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 150

def convert_to_pascal_case(key)
  return key if /[[:upper:]]/ =~ key[0] # If first character already capital, don't do conversion
  key.split('_').map(&:capitalize).join
end

#extract_hash(response) ⇒ Hash

Convert XML or JSON response into a Hash

Parameters:

  • response (String)

    Response as a String (either in XML or JSON)

Returns:



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 234

def extract_hash(response)
  raise ArgumentError("Empty Body. Can't assert on it") if response.body.empty?
  case Interpreter.response_type_for response
  when :json
    converted = JSON.parse(response.body)
    return converted.transform_keys_to_symbols if converted.is_a? Hash
    return converted.map!(&:transform_keys_to_symbols) if converted.is_a? Array
    raise 'Incorrect Type prodcued ' + converted.class
  when :xml
    parser = Nori.new(convert_tags_to: lambda { |tag| tag.snakecase.to_sym })
    parser.parse(response.body)
  else
    raise "Neither XML nor JSON detected. It is #{type}. Don't know how to parse It is #{response.body}"
  end
end

#found?(response) ⇒ Boolean

Whether the request found the desired value or not

Returns:

  • (Boolean)


227
228
229
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 227

def found?(response)
  status_code_for(response) != 404
end

#hash_used_in_request(override_hash) ⇒ Hash

Returns:



163
164
165
166
167
168
169
170
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 163

def hash_used_in_request(override_hash)
  request = @default_hash.merge(override_hash)
  if pascal_keys?
    request.map { |k, v| [convert_to_pascal_case(k.to_s), v] }.to_h
  else
    request
  end
end

#include_in_body?(response, expected) ⇒ Boolean

Returns:

  • (Boolean)


222
223
224
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 222

def include_in_body?(response, expected)
  response.body.include? expected
end

#include_key?(response, expected) ⇒ Boolean

Returns Whether response body contains expected key.

Returns:

  • (Boolean)

    Whether response body contains expected key



256
257
258
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 256

def include_key?(response, expected)
  value_from_path(response, expected)
end

#include_value?(response, expected) ⇒ Boolean

Returns Whether response contains expected value.

Returns:

  • (Boolean)

    Whether response contains expected value



251
252
253
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 251

def include_value?(response, expected)
  extract_hash(response).include_value? expected
end

#init_merge_optionsObject

Initialize value of merged options



173
174
175
176
177
178
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 173

def init_merge_options
  options = rest_resource_options
  options[:headers] ||= {}
  options[:headers].merge! parse_headers
  options.merge(@init_options)
end

#make_request(override_parameters) ⇒ Object

Used in together with Exchange request that passes such override parameters

Parameters:

  • override_parameters (Hash)

    Params to characterize REST request



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 185

def make_request(override_parameters)
  @merged_options ||= init_merge_options
  test_values = override_parameters
  test_values[:params] ||= {}
  test_values[:method] ||= :post
  test_values[:suburl] = test_values[:suburl].to_s if test_values[:suburl]
  test_values[:params][:params] = test_values[:q] if test_values[:q] # Use q for query parameters. Nested :params is ugly and long

  # In order for ERB to be calculated at correct time, the first time request is made, the resource should be created
  @resource ||= RestClient::Resource.new(ERB.new(base_url_value).result(binding), @merged_options)

  @resource_used = test_values[:suburl] ? @resource[test_values[:suburl]] : @resource

  begin
  response = case test_values[:method]
             when :post, :patch, :put
               unless test_values[:payload]
                 test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s if test_values[:body]
               end
               @resource_used.send(test_values[:method].to_s, test_values[:payload], test_values[:params])
             else
               @resource_used.send(test_values[:method].to_s, test_values[:params])
             end
  rescue RestClient::ExceptionWithResponse => e
    response = e.response
  end
  Soaspec::SpecLogger.add_to('response_headers: ' + response.headers.to_s)
  Soaspec::SpecLogger.add_to('response_body: ' + response.to_s)
  response
end

#mandatory_elementsArray

Override this to specify elements that must be present in the response Will be used in ‘success_scenarios’ shared examples

Returns:

  • (Array)

    Array of symbols specifying element names



268
269
270
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 268

def mandatory_elements
  []
end

#mandatory_xpath_valuesHash

Override this to specify xpath results that must be present in the response Will be used in ‘success_scenarios’ shared examples

Returns:

  • (Hash)

    Hash of ‘xpath’ => ‘expected value’ pairs



275
276
277
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 275

def mandatory_xpath_values
  {}
end

#parse_headersHash

Perform ERB on each header value

Returns:

  • (Hash)

    Hash from ‘rest_client_headers’ passed through ERB



131
132
133
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 131

def parse_headers
  Hash[rest_client_headers.map { |k, header| [k, ERB.new(header).result(binding)] }]
end

#pascal_keys?Boolean

Whether to convert each key in the request to PascalCase It will also auto convert simple XPath, JSONPath where ‘//’ or ‘..’ not specified

Returns:

  • (Boolean)

    Whether to convert to PascalCase



158
159
160
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 158

def pascal_keys?
  false
end

#response_body(response, _format: :hash) ⇒ Object

Returns Generic body to be displayed in error messages.

Parameters:

  • _format (Hash) (defaults to: :hash)

    Format of expected result. Ignored for this

Returns:

  • (Object)

    Generic body to be displayed in error messages



218
219
220
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 218

def response_body(response, _format: :hash)
  extract_hash response
end

#rest_client_headersObject

Headers used in RestClient



116
117
118
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 116

def rest_client_headers
  {}
end

#rest_resource_optionsHash

Add values to here when extending this class to have default REST options. See rest client resource at github.com/rest-client/rest-client for details It’s easier to set headers via ‘headers’ accessor rather than here

Returns:

  • (Hash)

    Options adding to & overriding defaults



124
125
126
127
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 124

def rest_resource_options
  {
  }
end

#root_attributesObject

Attributes set at the root XML element of SOAP request



280
281
282
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 280

def root_attributes
  nil
end

#status_code_for(response) ⇒ Integer

Returns HTTP Status code for response.

Returns:

  • (Integer)

    HTTP Status code for response



261
262
263
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 261

def status_code_for(response)
  response.code
end

#value_from_path(response, path, attribute: nil) ⇒ String

Based on a exchange, return the value at the provided xpath If the path does not begin with a ‘/’, a ‘//’ is added to it

Parameters:

  • response (Response)
  • path (Object)

    Xpath, JSONPath or other path identifying how to find element

  • attribute (String) (defaults to: nil)

    Generic attribute to find. Will override path

Returns:

  • (String)

    Value at Xpath



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 310

def value_from_path(response, path, attribute: nil)
  path = path.to_s

  case Interpreter.response_type_for(response)
  when :xml
    path = "//*[@#{attribute}]" unless attribute.nil?
    if path[0] != '/'
      path = convert_to_pascal_case(path) if pascal_keys?
      path = '//' + path
    end
    xpath_value_for(response: response, xpath: path, attribute: attribute)
  when :json
    raise 'JSON does not support attributes' if attribute
    if path[0] != '$'
      path = convert_to_pascal_case(path) if pascal_keys?
      path = '$..' + path
    end
    matching_values = JsonPath.on(response.body, path)
    raise NoElementAtPath, "Element in #{response.body} not found with path '#{path}'" if matching_values.empty?
    matching_values.first
  when :hash
    response.dig(path.split('.')) # Use path as Hash dig expression separating params via '.' TODO: Unit test
  else
    response.to_s[/path/] # Perform regular expression using path if not XML nor JSON TODO: Unit test
  end
end

#xpath_value_for(response: nil, xpath: nil, attribute: nil) ⇒ String

Returns the value at the provided xpath

Parameters:

  • response (RestClient::Response) (defaults to: nil)
  • xpath (String) (defaults to: nil)

Returns:

  • (String)

    Value inside element found through Xpath

Raises:

  • (ArgumentError)


288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/soaspec/exchange_handlers/rest_handler.rb', line 288

def xpath_value_for(response: nil, xpath: nil, attribute: nil)
  raise ArgumentError unless response && xpath
  raise "Can't perform XPATH if response is not XML" unless Interpreter.response_type_for(response) == :xml
  result =
    if Soaspec.strip_namespaces? && !xpath.include?(':')
      temp_doc = Nokogiri.parse response.body
      temp_doc.remove_namespaces!
      temp_doc.xpath(xpath).first
    else
      Nokogiri.parse(response.body).xpath(xpath).first
    end
  raise NoElementAtPath, "No value at Xpath '#{xpath}'" unless result
  return result.inner_text if attribute.nil?
  result.attributes[attribute].inner_text
end