module Soaspec # Help with tasks common to soaspec executables module ExeHelpers require 'fileutils' # @param [String] filename Name of the file to create # @param [String] content Content to place inside file def create_file(filename: nil, content: nil, ignore_if_present: false) #filename = options[:filename] raise 'Need to pass filename' unless filename #content = options[:content] raise 'Need to pass contents to insert into file' unless content if File.exist? filename old_content = File.read(filename) if old_content != content && !ignore_if_present warn "!! #{filename} already exists and differs from template" end else File.open(filename, 'w') { |f| f.puts content } puts 'Created: ' + filename end end def create_folder(folder) if File.exist? folder unless File.directory? folder warn "!! #{folder} already exists and is not a directory" end else FileUtils.mkdir folder puts "Created folder: #{folder}/" end end def rake_content <<-RAKE # The list of task for a Rake file can be seen with `rake -T` require 'rspec/core/rake_task' # See See https://relishapp.com/rspec/rspec-core/docs/command-line/rake-task for details # This runs `rspec` command with the following options. Type `rake spec` to run this task RSpec::Core::RakeTask.new(:spec) do |t| t.pattern = "spec/*_spec.rb" # Run all specs in 'spec' folder ending in '_spec' # Next line shows output on the screen, Junit xml report and an HTML report t.rspec_opts = "--format documentation --format RspecJunitFormatter --out logs/spec.xml --format html --out logs/spec.html" t.fail_on_error = false end task :default => :spec # This runs the 'spec' task by default when no task is mentioned. E.g., if only `rake` is typed RAKE end def rake_virtual_content <<-EOF # The list of task for a Rake file can be seen with `rake -T` require 'rspec/core/rake_task' # See See https://relishapp.com/rspec/rspec-core/docs/command-line/rake-task for details # This runs `rspec` command with the following options. Type `rake spec` to run this task RSpec::Core::RakeTask.new(:run_spec) do |t| t.pattern = "spec/*_spec.rb" # Run all specs in 'spec' folder ending in '_spec' # Next line shows output on the screen, Junit xml report and an HTML report t.rspec_opts = "--format documentation --format RspecJunitFormatter --out logs/spec.xml --format html --out logs/spec.html" t.fail_on_error = false end task :spec => %w[start_test_server run_spec] task :default => :spec # This runs the 'spec' task by default when no task is mentioned. E.g., if only `rake` is typed desc 'Start virtual web service' task :start_test_server do ENV['test_server_pid'] = Process.spawn('ruby', 'spec/test_server.rb', err: %w[logs/test_server.log w]).to_s puts 'Running test server at pid ' + ENV['test_server_pid'] end EOF end def gem_content <<-EOF source 'https://rubygems.org' gem 'data_magic' gem 'require_all' gem 'rspec_junit_formatter' gem 'rake' gem 'soaspec' gem 'sinatra' EOF end def spec_helper_content <<-EOF require 'soaspec' require 'require_all' require_all 'lib' require 'data_magic' include DataMagic # Used as example of loading data smartly. Use 'data_for' method to load yml data RSpec.configure do |config| # This will make backtrace much shorter by removing many lines from rspec failure message config.backtrace_exclusion_patterns = [ /rspec/ ] # Close test server after all RSpec tests have run config.after(:suite) do Process.kill(:QUIT, ENV['test_server_pid'].to_i) if ENV['test_server_pid'] # && Process.wait - causes failure end end EOF end # @return [String] Content of README.md to aid someone getting started with this using this gem def readme_content <<-EOF # Installation Run `bundle install` to install the gems mentioned in the Gemfile. To avoid conflict with gems on the machine globally it may be better to specify gem location with: `bundle install --path ~/.gem` # Running tests On the command line type: `bundle exec rake spec` or simply `bundle exec rake` # Structure ## Tests Tests are within the 'spec' folder and end in '_spec'. Setup and teardown for tests is in 'spec/spec_helper' ## Templates These are the base requests with ERB inside them to create smartly changing requests accoring to the test.yml ## Data Data used in the test is stored in `config/data` folder. The `data_magic` gem is used by default to read files here. ## Libaries Libaries to be installed are in 'Gemfile'. Specific gem versions can be specified here and enforeced with `bundle exec rake` ## Reports Reports are shown in the 'logs' folder. By default Rake produces a junit, an html report, and a `traffic.log` file with the API request and responses in it EOF end def test_server_content <<-FILEEOF # Used to run virtual web service on localhost. This makes tests more reliable and faster require 'sinatra' require 'nokogiri' require 'erb' # Representing a GetBank SOAP service class GetBank def self.success_response_template <<-EOF Deutsche Bank DEUTDEMMXXX <%= soap_action %> München <%= @test_id %> EOF end def self.error_response_template <<-EOF soapenv:Receiver org.apache.axis2.databinding.ADBException: Unexpected subelement getBank org.apache.axis2.AxisFault: org.apache.axis2.databinding.ADBException: Unexpected subelement getBank at org.apache.axis2.AxisFault.makeFault(AxisFault.java:417) at com.thomas_bayer.blz.BLZServiceMessageReceiverInOut.fromOM(BLZServiceMessageReceiverInOut.java:124) at com.thomas_bayer.blz.BLZServiceMessageReceiverInOut.invokeBusinessLogic(BLZServiceMessageReceiverInOut.java:43) at org.apache.axis2.receivers.AbstractInOutSyncMessageReceiver.invokeBusinessLogic(AbstractInOutSyncMessageReceiver.java:42) at org.apache.axis2.receivers.AbstractMessageReceiver.receive(AbstractMessageReceiver.java:96) at org.apache.axis2.engine.AxisEngine.receive(AxisEngine.java:145) at org.apache.axis2.transport.http.HTTPTransportUtils.processHTTPPostRequest(HTTPTransportUtils.java:275) at org.apache.axis2.transport.http.AxisServlet.doPost(AxisServlet.java:120) at javax.servlet.http.HttpServlet.service(HttpServlet.java:646) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:683) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.Exception: org.apache.axis2.databinding.ADBException: Unexpected subelement getBank at com.thomas_bayer.adb.GetBankType$Factory.parse(GetBankType.java:423) at com.thomas_bayer.adb.GetBank$Factory.parse(GetBank.java:304) at com.thomas_bayer.blz.BLZServiceMessageReceiverInOut.fromOM(BLZServiceMessageReceiverInOut.java:117) ... 25 more Caused by: org.apache.axis2.databinding.ADBException: Unexpected subelement getBank at com.thomas_bayer.adb.GetBankType$Factory.parse(GetBankType.java:410) ... 27 more EOF end def self.response_for(request) request_body = request.body soap_action = request.env['HTTP_SOAPACTION'].strip doc = Nokogiri::XML(request_body) blz_element = doc.xpath('//tns:blz').first return 500, error_response_template unless blz_element @test_id = blz_element.inner_text ERB.new(success_response_template).result(binding) end end # This is the one being hit post '/BLZService' do GetBank.response_for request end FILEEOF end def weather_web_service <<-WEATH_WEB require 'soaspec' # This class is not part of the gem. It's an example of a class you can make # to describe your APIs. Usually this would exist in the 'lib' directory # Common configuration for the Savon client should go here class BLZService < Soaspec::SoapHandler # Add to or override default Savon client options def savon_options { # wsdl: 'http://www.thomas-bayer.com/axis2/services/BLZService?wsdl' # External wsdl: File.join('spec', 'test_data', 'wsdl', 'get_bank.wsdl') } end # # Specifying that get_weather_result must be present in the SOAP response mandatory_elements [:plz] # Example of xpath value that must be true for all success scenarios mandatory_xpath_values 'ns1:bezeichnung' => 'Deutsche Bank' # Example of setting an attribute on the root XML element root_attributes 'Version' => '1' # TODO: Create test on request for this end WEATH_WEB end def soap_spec_content <<-SOAP_SPEC require 'spec_helper' Soaspec.strip_namespaces = true # This allows namespace not to be used. Be careful with this id = '70070010' # BLZService.new(template_name: 'soap_template') Use this instead of default_hash to use template approach context 'Test Examples' do context BLZService.new('Get Bank', operation: :get_bank, default_hash: { blz: id }) do describe Exchange.new(:default) do it { is_expected.to contain_value id } it { is_expected.to include_in_body id } it_behaves_like 'success scenario' after(:all) { described_class.store(:title, 'bezeichnung') } end describe Exchange.new(:xpath_eg, blz: 100000) do its(['plz']) { is_expected.to eq '100000' } it { is_expected.to have_xpath_value '//ns1:bezeichnung' => 'Deutsche Bank' } context 'Handle retrieving stored value' do it { is_expected.to have_xpath_value 'bezeichnung' => described_class.retrieve(:title) } end end describe Exchange.new(:yaml_eg, data_for(:small_id)) do it_behaves_like 'success scenario' end # Retry for success more for web services that intermittently fail describe Exchange.new(:short_hand_xpath).retry_for_success do # Be careful. If you call a method that does not use namespaces, calling one that does may not find the element its(['ns1:bezeichnung']) { is_expected.to eq 'Deutsche Bank' } # '//' is not required at the beginning end describe Exchange.new('Check existence of elements') do it { is_expected.to have_element_at_xpath '//ns1:bezeichnung' } it { is_expected.not_to have_element_at_xpath '//ns1:bezeichnung_pretend' } end end end error_example = BLZService.new('Error example') error_example.operation = :get_bank error_example.default_hash = {} context 'Error Examples' do context error_example do describe Exchange.new(:no_blz_error) do it_behaves_like 'error scenario' end end end SOAP_SPEC end def shared_examples_content <<-SHARE_EG require 'rspec' shared_examples_for 'error scenario' do it 'does not have status code of 200' do expect(described_class.status_code).not_to eq 200 end end SHARE_EG end def soap_template_content <<-SOAP_TEMP <%= test_values[:blz] || '70070010' %> SOAP_TEMP end def default_yaml_content <<-DEF_YAML small_id: blz: 100 DEF_YAML end def test_wsdl_content < BLZService TEST_WSDL end end end