require_relative 'exchange_handler' require_relative '../hash_methods' require_relative '../not_found_errors' require_relative '../accessors' require_relative '../interpreter' module Soaspec # Accessors specific to SOAP handler module SoapAccessors def root_attributes(attributes) define_method('request_root_attributes') do attributes end end end # Wraps around Savon client defining default values dependent on the soap request class SoapHandler < ExchangeHandler extend Soaspec::Accessors extend Soaspec::SoapAccessors # Savon client used to make SOAP calls attr_accessor :client # SOAP Operation to use by default attr_accessor :operation # Attributes set at the root XML element of SOAP request def request_root_attributes nil end # Options to log xml request and response def logging_options { log: true, # See request and response. (Put this in traffic file) log_level: :debug, logger: Soaspec::SpecLogger.create, pretty_print_xml: true # Prints XML pretty } end # Default Savon options. See http://savonrb.com/version2/globals.html for details # @return [Hash] Default Savon options for all BasicSoapHandler def default_options { ssl_verify_mode: :none, # Easier for testing. Not so secure follow_redirects: true, # Necessary for many API calls soap_version: 2, # use SOAP 1.2. You will get 415 error if this is incorrect raise_errors: false # HTTP errors not cause failure as often negative test scenarios expect not 200 response # Things could go wrong if not set properly # env_namespace: :soap, # Change environment namespace # namespace_identifier: :tst, # Change namespace element # element_form_default: :qualified # Populate each element with namespace # namespace: 'http://Extended_namespace.xsd' change root namespace # basic_auth: 'user', 'password' } end # Add values to here when extending this class to have default Savon options. # See http://savonrb.com/version2/globals.html for details # @return [Hash] Savon options adding to & overriding defaults def savon_options { } end # Setup object to handle communicating with a particular SOAP WSDL # @param [Hash] options Options defining SOAP request. WSDL, authentication, see http://savonrb.com/version2/globals.html for list of options def initialize(name = self.class.to_s, options = {}) @default_hash = {} @request_option = :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, :operation) set_remove_key(options, :default_hash) set_remove_key(options, :template_name) merged_options = default_options.merge logging_options merged_options.merge! savon_options merged_options.merge!(options) @client = Savon.client(merged_options) end # Used in together with Exchange request that passes such override parameters def make_request(override_parameters) test_values = override_parameters # Used in Erb # Erb parses template file, executing Ruby code in `<% %>` blocks to work out final request test_values = test_values.transform_keys_to_symbols if Soaspec.always_use_keys? begin if @request_option == :template request_body = File.read('template/' + template_name + '.xml') render_body = ERB.new(request_body).result(binding) @client.call(operation, xml: render_body) # Call the SOAP operation with the request XML provided elsif @request_option == :hash @client.call(operation, message: @default_hash.merge(test_values), attributes: request_root_attributes) end rescue Savon::HTTPError => e e end end # Set the default hash representing data to be used in making a request # This will set the @request_option instance variable too def default_hash=(hash) @request_option = :hash @default_hash = Soaspec.always_use_keys? ? hash.transform_keys_to_symbols : hash end # @param [Hash] format Format of expected result # @return [Object] Generic body to be displayed in error messages def response_body(response, format: :hash) case format when :hash response.body when :raw response.body.to_xml else response.body end end # @return [Boolean] Whether the request found the desired value or not def found?(response) status_code_for(response) != 404 end # Response status code for response. '200' indicates a success # @param [Savon::Response] response # @return [Integer] Status code def status_code_for(response) response.http.code end # @return [Boolean] Whether response includes provided string within it def include_in_body?(response, expected) response.to_xml.to_s.include? expected end # @return [Boolean] Whether response body contains expected key def include_key?(response, expected) response.body.include_key? expected end # Returns the value at the provided xpath # @param [Exchange] exchange # @param [String] xpath # @return [String] Value inside element found through Xpath def xpath_value_for(exchange: nil, xpath: nil) raise ArgumentError unless exchange && xpath result = if Soaspec.strip_namespaces? && !xpath.include?(':') temp_doc = exchange.response.doc temp_doc.remove_namespaces! temp_doc.xpath(xpath).first else exchange.response.xpath(xpath).first end raise NoElementAtXpath, "No value at Xpath '#{xpath}'" unless result result.inner_text end # Based on a exchange, return the value at the provided xpath # If the path does not begin with a '/', a '//' is added to it # @param [Exchange] exchange # @param [String] path Xpath # @return [String] Value at Xpath def value_from_path(exchange, path) path = '//' + path if path[0] != '/' xpath_value_for(exchange: exchange, xpath: path) end # Whether any of the keys of the Body Hash include value def include_value?(response, expected_value) response.body.include_value?(expected_value) end end # Deprecated class name. Will be removed in the future class BasicSoapHandler < SoapHandler def initialize(name, specific_options = {}) super warn "'BasicSoapHandler' class is Deprecated. Please use 'SoapHandler' instead" end end end