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