lib/soaspec/exchange_handlers/rest_handler.rb in soaspec-0.1.12 vs lib/soaspec/exchange_handlers/rest_handler.rb in soaspec-0.1.13

- old
+ new

@@ -12,22 +12,64 @@ module Soaspec # Wraps around Savon client defining default values dependent on the soap request class RestHandler < ExchangeHandler extend Soaspec::RestAccessors + include Soaspec::RestAccessorsDefaults # User used in making API calls attr_accessor :api_username - # Set through following method. Base URL in REST requests. - def base_url_value - nil + # Setup object to handle communicating with a particular SOAP WSDL + # @param [Hash] options Options defining REST request. base_url, default_hash + 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, use first parameter as the options hash + options = name + name = self.class.to_s + end + super + set_remove_keys(options, %i[api_username default_hash template_name]) + @init_options = options end - # Headers used in RestClient - def rest_client_headers - {} + # Used in together with Exchange request that passes such override parameters + # @param [Hash] override_parameters Params to characterize REST request + # @option override_parameters [Hash] :params Extra parameters (E.g. headers) + # @option override_parameters [String] suburl URL appended to base_url of class + # @option override_parameters [Hash] :q Query for REST + # @option override_parameters [Symbol] :method REST method (:get, :post, :patch, etc) + # Following are for the body of the request + # @option override_parameters [Hash] :body Hash to be converted to JSON in request body + # @option override_parameters [String] :payload String to be passed directly in request body + # @option override_parameters [String] :template_name Path to file to be read via ERB and passed in request body + 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 + Soaspec::SpecLogger.info("request body: #{post_data(test_values)}") + @resource_used.send(test_values[:method].to_s, post_data(test_values), test_values[:params]) + else # :get, :delete + @resource_used.send(test_values[:method].to_s, test_values[:params]) + end + rescue RestClient::ExceptionWithResponse => e + response = e.response + end + Soaspec::SpecLogger.info(["response_headers: #{response.headers}", "response_body: #{response}"]) + response end # Add values to here when extending this class to have default REST options. # See rest client resource at https://github.com/rest-client/rest-client for details # It's easier to set headers via 'headers' accessor rather than here @@ -41,39 +83,16 @@ # @return [Hash] Hash from 'rest_client_headers' passed through ERB def parse_headers Hash[rest_client_headers.map { |k, header| [k, ERB.new(header).result(binding)] }] end - # Setup object to handle communicating with a particular SOAP WSDL - # @param [Hash] options Options defining SOAP request. WSDL, authentication - 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, :api_username) - set_remove_key(options, :default_hash) - set_remove_key(options, :template_name) - @init_options = options - end - # Convert snakecase to PascalCase 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 - # Whether to convert each key in the request to PascalCase - # It will also auto convert simple XPath, JSONPath where '//' or '..' not specified - # @return Whether to convert to PascalCase - def pascal_keys? - false - end - # Initialize value of merged options # @return [Hash] Hash of merged options def init_merge_options options = rest_resource_options options[:headers] ||= {} @@ -82,44 +101,10 @@ options[:headers][:authorization] ||= ERB.new('Bearer <%= access_token %>').result(binding) end options.merge(@init_options) end - # Used in together with Exchange request that passes such override parameters - # @param [Hash] override_parameters Params to characterize REST request - # @param_value [params] Extra parameters (E.g. headers) - # @param_value [suburl] URL appended to base_url of class - # @param_value [q] Query for REST - # @param_value [method] REST method (:get, :post, etc) - 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 - Soaspec::SpecLogger.info("request body: #{post_data(test_values)}") - @resource_used.send(test_values[:method].to_s, post_data(test_values), test_values[:params]) - else # :get, :delete - @resource_used.send(test_values[:method].to_s, test_values[:params]) - end - rescue RestClient::ExceptionWithResponse => e - response = e.response - end - Soaspec::SpecLogger.info('response_headers: ' + response.headers.to_s) - Soaspec::SpecLogger.info('response_body: ' + response.to_s) - response - end - # @param [Hash] format Format of expected result. # @return [Object] Generic body to be displayed in error messages def response_body(response, format: :hash) extract_hash response end @@ -132,29 +117,10 @@ # @@return [Boolean] Whether the request found the desired value or not def found?(response) status_code_for(response) != 404 end - # Convert XML or JSON response into a Hash - # @param [String] response Response as a String (either in XML or JSON) - # @return [Hash] - 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 - # @return [Boolean] Whether response contains expected value def include_value?(response, expected) extract_hash(response).include_value? expected end @@ -166,29 +132,10 @@ # @return [Integer] HTTP Status code for response def status_code_for(response) response.code end - # Override this to specify elements that must be present in the response - # Will be used in 'success_scenarios' shared examples - # @return [Array] Array of symbols specifying element names - def mandatory_elements - [] - end - - # Override this to specify xpath results that must be present in the response - # Will be used in 'success_scenarios' shared examples - # @return [Hash] Hash of 'xpath' => 'expected value' pairs - def mandatory_xpath_values - {} - end - - # Attributes set at the root XML element of SOAP request - def root_attributes - nil - end - # Returns the value at the provided xpath # @param [RestClient::Response] response # @param [String] xpath # @return [Enumerable] Value inside element found through Xpath def xpath_elements_for(response: nil, xpath: nil, attribute: nil) @@ -257,10 +204,30 @@ else raise "Unable to interpret type of #{response.body}" end end + # TODO: This and 'to_hash' method should be merged + # Convert XML or JSON response into a Hash + # @param [String] response Response as a String (either in XML or JSON) + # @return [Hash] + 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 produced ' + 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 + # @return [Hash] Hash representing response body def to_hash(response) case Interpreter.response_type_for(response) when :xml parser = Nori.new(strip_namespaces: strip_namespaces?, convert_tags_to: ->(tag) { tag.snakecase.to_sym }) @@ -270,19 +237,19 @@ else raise "Unable to interpret type of #{response.body}" end end - # Request of API call. Either intended request or actual request + # @response [RestClient::Request] Request of API call. Either intended request or actual request def request(response) return 'Request not yet sent' if response.nil? response.request end private - # Work out data to send based upon payload, template_name + # Work out data to send based upon payload, template_name, or body # @return [String] Payload to send in REST request def post_data(test_values) data = if test_values[:body] test_values[:payload] = JSON.generate(hash_used_in_request(test_values[:body])).to_s elsif @request_option == :template @@ -323,8 +290,7 @@ new(exchange_params) Exchange.new(params[:name], method: rest_method.to_sym, **params) end end end - end -end \ No newline at end of file +end