soaspec 0.0.19 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +13 -13
- data/.rspec +3 -3
- data/.travis.yml +5 -5
- data/CODE_OF_CONDUCT.md +74 -74
- data/ChangeLog +61 -55
- data/Gemfile +16 -16
- data/Gemfile.lock +115 -115
- data/LICENSE.txt +21 -21
- data/README.md +85 -66
- data/Rakefile +20 -20
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/config/data/default.yml +2 -2
- data/exe/soaspec-init +254 -252
- data/exe/xml_to_yaml_file +63 -63
- data/lib/soaspec.rb +61 -48
- data/lib/soaspec/basic_soap_handler.rb +132 -122
- data/lib/soaspec/exchange.rb +56 -56
- data/lib/soaspec/hash_methods.rb +71 -71
- data/lib/soaspec/matchers.rb +40 -39
- data/lib/soaspec/soaspec_shared_examples.rb +18 -18
- data/lib/soaspec/spec_logger.rb +17 -17
- data/lib/soaspec/tester.rb +30 -30
- data/lib/soaspec/version.rb +3 -3
- data/lib/soaspec/xpath_not_found.rb +7 -0
- data/soaspec.gemspec +34 -34
- data/template/soap_template.xml +9 -9
- metadata +4 -3
data/exe/xml_to_yaml_file
CHANGED
@@ -1,64 +1,64 @@
|
|
1
|
-
|
2
|
-
require 'xmlsimple'
|
3
|
-
require 'yaml'
|
4
|
-
require 'fileutils'
|
5
|
-
|
6
|
-
default_output_file = 'output.yml'
|
7
|
-
|
8
|
-
# Create file if not present. If present but different give warning
|
9
|
-
def create_file(options)
|
10
|
-
filename = options[:filename]
|
11
|
-
raise 'Need to pass filename' unless filename
|
12
|
-
content = options[:content]
|
13
|
-
raise 'Need to pass contents to insert into file' unless content
|
14
|
-
if File.exist? filename
|
15
|
-
old_content = File.read(filename)
|
16
|
-
if old_content != content
|
17
|
-
warn "!! #{filename} already exists and differs from template"
|
18
|
-
end
|
19
|
-
else
|
20
|
-
File.open(filename, 'w') do |f|
|
21
|
-
f.puts content
|
22
|
-
end
|
23
|
-
puts 'Created: ' + filename
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Convert key in camelcase to underscore separated (snakecase)
|
28
|
-
def underscore_key(key)
|
29
|
-
key.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
30
|
-
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
31
|
-
.tr('-', '_')
|
32
|
-
.gsub(/\s/, '_')
|
33
|
-
.gsub(/__+/, '_')
|
34
|
-
.downcase
|
35
|
-
end
|
36
|
-
|
37
|
-
# For all keys in a Hash, convert Camelcase to underscore separated
|
38
|
-
def convert_hash_keys(value)
|
39
|
-
case value
|
40
|
-
when Array
|
41
|
-
value.map { |v| convert_hash_keys(v) }
|
42
|
-
when Hash
|
43
|
-
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
|
44
|
-
else
|
45
|
-
value
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Remove arrays created as another string
|
50
|
-
def clean_up_yaml(yaml_string)
|
51
|
-
yaml_string = yaml_string.gsub(/\R+(\s*)-/, '').gsub(/{}/, "''") # Remove arrays, {} -> ''
|
52
|
-
# Insert new line where there are 2 ':' on 1 line. Issue from first gsub
|
53
|
-
yaml_string.gsub(/:(\s)(\w*):/){|s| s.insert(1, "\n")}
|
54
|
-
end
|
55
|
-
|
56
|
-
if ARGV[0]
|
57
|
-
warn "Using '#{default_output_file}' as default output file since no 2nd argument passed" unless ARGV[1]
|
58
|
-
hash = XmlSimple.xml_in(ARGV[0])
|
59
|
-
converted = convert_hash_keys hash
|
60
|
-
yaml_file = clean_up_yaml(converted.to_yaml)
|
61
|
-
create_file(filename: ARGV[1] || default_output_file, content: yaml_file)
|
62
|
-
else
|
63
|
-
puts 'usage: xml_to_yaml_file [input.xml] [output.yml] '
|
1
|
+
|
2
|
+
require 'xmlsimple'
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
default_output_file = 'output.yml'
|
7
|
+
|
8
|
+
# Create file if not present. If present but different give warning
|
9
|
+
def create_file(options)
|
10
|
+
filename = options[:filename]
|
11
|
+
raise 'Need to pass filename' unless filename
|
12
|
+
content = options[:content]
|
13
|
+
raise 'Need to pass contents to insert into file' unless content
|
14
|
+
if File.exist? filename
|
15
|
+
old_content = File.read(filename)
|
16
|
+
if old_content != content
|
17
|
+
warn "!! #{filename} already exists and differs from template"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
File.open(filename, 'w') do |f|
|
21
|
+
f.puts content
|
22
|
+
end
|
23
|
+
puts 'Created: ' + filename
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Convert key in camelcase to underscore separated (snakecase)
|
28
|
+
def underscore_key(key)
|
29
|
+
key.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
30
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
31
|
+
.tr('-', '_')
|
32
|
+
.gsub(/\s/, '_')
|
33
|
+
.gsub(/__+/, '_')
|
34
|
+
.downcase
|
35
|
+
end
|
36
|
+
|
37
|
+
# For all keys in a Hash, convert Camelcase to underscore separated
|
38
|
+
def convert_hash_keys(value)
|
39
|
+
case value
|
40
|
+
when Array
|
41
|
+
value.map { |v| convert_hash_keys(v) }
|
42
|
+
when Hash
|
43
|
+
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
|
44
|
+
else
|
45
|
+
value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Remove arrays created as another string
|
50
|
+
def clean_up_yaml(yaml_string)
|
51
|
+
yaml_string = yaml_string.gsub(/\R+(\s*)-/, '').gsub(/{}/, "''") # Remove arrays, {} -> ''
|
52
|
+
# Insert new line where there are 2 ':' on 1 line. Issue from first gsub
|
53
|
+
yaml_string.gsub(/:(\s)(\w*):/){|s| s.insert(1, "\n")}
|
54
|
+
end
|
55
|
+
|
56
|
+
if ARGV[0]
|
57
|
+
warn "Using '#{default_output_file}' as default output file since no 2nd argument passed" unless ARGV[1]
|
58
|
+
hash = XmlSimple.xml_in(ARGV[0])
|
59
|
+
converted = convert_hash_keys hash
|
60
|
+
yaml_file = clean_up_yaml(converted.to_yaml)
|
61
|
+
create_file(filename: ARGV[1] || default_output_file, content: yaml_file)
|
62
|
+
else
|
63
|
+
puts 'usage: xml_to_yaml_file [input.xml] [output.yml] '
|
64
64
|
end
|
data/lib/soaspec.rb
CHANGED
@@ -1,48 +1,61 @@
|
|
1
|
-
require 'rest-client' # REST
|
2
|
-
require 'erb' # Embedded ruby
|
3
|
-
require 'yaml' # Reading yaml
|
4
|
-
require 'rspec' # Testing framework
|
5
|
-
require 'rspec/its'
|
6
|
-
require 'savon' # SOAP
|
7
|
-
require 'nokogiri' # XPath
|
8
|
-
require 'date'
|
9
|
-
|
10
|
-
require 'soaspec/version'
|
11
|
-
require 'soaspec/basic_soap_handler'
|
12
|
-
require 'soaspec/tester'
|
13
|
-
require 'soaspec/exchange'
|
14
|
-
require 'soaspec/matchers'
|
15
|
-
require 'soaspec/soaspec_shared_examples'
|
16
|
-
require 'soaspec/hash_methods'
|
17
|
-
require 'soaspec/spec_logger'
|
18
|
-
|
19
|
-
# Gem for handling SOAP and REST api tests
|
20
|
-
module Soaspec
|
21
|
-
|
22
|
-
def self.hi
|
23
|
-
puts 'Hello world!'
|
24
|
-
end
|
25
|
-
|
26
|
-
# Represents Environment parameters used in Soaspec tests
|
27
|
-
module Environment
|
28
|
-
|
29
|
-
# Used so that exchange class knows what context it's in
|
30
|
-
def self.api_handler=(handler)
|
31
|
-
@api_handler = handler
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.api_handler
|
35
|
-
@api_handler
|
36
|
-
end
|
37
|
-
|
38
|
-
# Whether to transform strings to keys automatically
|
39
|
-
def self.always_use_keys=(use_keys)
|
40
|
-
@always_use_keys = use_keys
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.always_use_keys?
|
44
|
-
@always_use_keys || true
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
|
-
|
1
|
+
require 'rest-client' # REST
|
2
|
+
require 'erb' # Embedded ruby
|
3
|
+
require 'yaml' # Reading yaml
|
4
|
+
require 'rspec' # Testing framework
|
5
|
+
require 'rspec/its'
|
6
|
+
require 'savon' # SOAP
|
7
|
+
require 'nokogiri' # XPath
|
8
|
+
require 'date'
|
9
|
+
|
10
|
+
require 'soaspec/version'
|
11
|
+
require 'soaspec/basic_soap_handler'
|
12
|
+
require 'soaspec/tester'
|
13
|
+
require 'soaspec/exchange'
|
14
|
+
require 'soaspec/matchers'
|
15
|
+
require 'soaspec/soaspec_shared_examples'
|
16
|
+
require 'soaspec/hash_methods'
|
17
|
+
require 'soaspec/spec_logger'
|
18
|
+
|
19
|
+
# Gem for handling SOAP and REST api tests
|
20
|
+
module Soaspec
|
21
|
+
|
22
|
+
def self.hi
|
23
|
+
puts 'Hello world!'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Represents Environment parameters used in Soaspec tests
|
27
|
+
module Environment
|
28
|
+
|
29
|
+
# Used so that exchange class knows what context it's in
|
30
|
+
def self.api_handler=(handler)
|
31
|
+
@api_handler = handler
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.api_handler
|
35
|
+
@api_handler
|
36
|
+
end
|
37
|
+
|
38
|
+
# Whether to transform strings to keys automatically
|
39
|
+
def self.always_use_keys=(use_keys)
|
40
|
+
@always_use_keys = use_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.always_use_keys?
|
44
|
+
@always_use_keys || true
|
45
|
+
end
|
46
|
+
|
47
|
+
# Whether to remove namespaces from response in Xpath assertion automatically
|
48
|
+
# For why this may not be a good thing in general see
|
49
|
+
# http://tenderlovemaking.com/2009/04/23/namespaces-in-xml.html
|
50
|
+
# This will be overridden if xpath has a ':' in it
|
51
|
+
def self.strip_namespaces=(remove_namespaces_from_response)
|
52
|
+
@strip_namespaces = remove_namespaces_from_response
|
53
|
+
end
|
54
|
+
|
55
|
+
# Whether to remove namespaces in xpath assertion automatically
|
56
|
+
def self.strip_namespaces?
|
57
|
+
@strip_namespaces || false
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -1,123 +1,133 @@
|
|
1
|
-
|
2
|
-
require_relative 'tester'
|
3
|
-
require_relative 'hash_methods'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
#
|
50
|
-
|
51
|
-
|
52
|
-
options.merge
|
53
|
-
options.merge!
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
@
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
#
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
result
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
1
|
+
|
2
|
+
require_relative 'tester'
|
3
|
+
require_relative 'hash_methods'
|
4
|
+
require_relative 'xpath_not_found'
|
5
|
+
|
6
|
+
module Soaspec
|
7
|
+
# Wraps around Savon client defining default values dependent on the soap request
|
8
|
+
class BasicSoapHandler < Tester
|
9
|
+
# Savon client used to make SOAP calls
|
10
|
+
attr_accessor :client
|
11
|
+
# SOAP Operation to use by default
|
12
|
+
attr_accessor :operation
|
13
|
+
|
14
|
+
# Options to log xml request and response
|
15
|
+
def logging_options
|
16
|
+
{
|
17
|
+
log: true, # See request and response. (Put this in traffic file)
|
18
|
+
log_level: :debug,
|
19
|
+
logger: Soaspec::SpecLogger.create,
|
20
|
+
pretty_print_xml: true # Prints XML pretty
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Default Savon options. See http://savonrb.com/version2/globals.html for details
|
25
|
+
# @return [Hash] Default Savon options for all BasicSoapHandler
|
26
|
+
def default_options
|
27
|
+
{
|
28
|
+
ssl_verify_mode: :none, # Easier for testing. Not so secure
|
29
|
+
follow_redirects: true, # Necessary for many API calls
|
30
|
+
soap_version: 2, # use SOAP 1.2. You will get 415 error if this is incorrect
|
31
|
+
raise_errors: false # HTTP errors not cause failure as often negative test scenarios expect not 200 response
|
32
|
+
# Things could go wrong if not set properly
|
33
|
+
# env_namespace: :soap, # Change environment namespace
|
34
|
+
# namespace_identifier: :tst, # Change namespace element
|
35
|
+
# element_form_default: :qualified # Populate each element with namespace
|
36
|
+
# namespace: 'http://Extended_namespace.xsd' change root namespace
|
37
|
+
# basic_auth: 'user', 'password'
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add values to here when extending this class to have default Savon options.
|
42
|
+
# See http://savonrb.com/version2/globals.html for details
|
43
|
+
# @return [Hash] Savon options adding to & overriding defaults
|
44
|
+
def savon_options
|
45
|
+
{
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Setup object to handle communicating with a particular SOAP WSDL
|
50
|
+
# @param [Hash] specific_options Options defining SOAP request. WSDL, authentication, see http://savonrb.com/version2/globals.html for list of options
|
51
|
+
def initialize(name, specific_options = {})
|
52
|
+
options = default_options.merge logging_options
|
53
|
+
options.merge! savon_options
|
54
|
+
options.merge!(specific_options)
|
55
|
+
@client = Savon.client(options)
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
def name(name)
|
60
|
+
@test_values = {}
|
61
|
+
@test_name = name
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def override(request_parameters)
|
66
|
+
@test_values = request_parameters
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Used in together with Exchange request that passes such override parameters
|
71
|
+
def make_request(override_parameters)
|
72
|
+
test_values = override_parameters # Used in Erb
|
73
|
+
# Erb parses template file, executing Ruby code in `<% %>` blocks to work out final request
|
74
|
+
test_values = test_values.transform_keys_to_symbols if Soaspec::Environment.always_use_keys?
|
75
|
+
if @request_option == :template
|
76
|
+
request_body = File.read('template/' + template_name + '.xml')
|
77
|
+
render_body = ERB.new(request_body).result(binding)
|
78
|
+
@client.call(operation, xml: render_body) # Call the SOAP operation with the request XML provided
|
79
|
+
elsif @request_option == :hash
|
80
|
+
@client.call(operation, message: @default_hash.merge(test_values), attributes: root_attributes)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def default_hash=(hash)
|
85
|
+
@request_option = :hash
|
86
|
+
@default_hash = Soaspec::Environment.always_use_keys? ? hash.transform_keys_to_symbols : hash
|
87
|
+
end
|
88
|
+
|
89
|
+
def status_code_for(response)
|
90
|
+
response.http.code
|
91
|
+
end
|
92
|
+
|
93
|
+
# Override this to specify elements that must be present in the response
|
94
|
+
# Will be used in 'success_scenarios' shared examples
|
95
|
+
# @return [Array] Array of symbols specifying element names
|
96
|
+
def mandatory_elements
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Override this to specify xpath results that must be present in the response
|
101
|
+
# Will be used in 'success_scenarios' shared examples
|
102
|
+
# @return [Hash] Hash of 'xpath' => 'expected value' pairs
|
103
|
+
def mandatory_xpath_values
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Attributes set at the root XML element of SOAP request
|
108
|
+
def root_attributes
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the value at the provided xpath
|
113
|
+
def xpath_value_for(param)
|
114
|
+
result =
|
115
|
+
if Soaspec::Environment.strip_namespaces? && !param[:xpath].include?(':')
|
116
|
+
temp_doc = param[:exchange].response.doc
|
117
|
+
temp_doc.remove_namespaces!
|
118
|
+
temp_doc.xpath(param[:xpath]).first
|
119
|
+
else
|
120
|
+
puts 'no strip' + param[:xpath]
|
121
|
+
param[:exchange].response.xpath(param[:xpath]).first
|
122
|
+
end
|
123
|
+
raise NoElementAtXpath, "No value at Xpath '#{param[:xpath]}'" unless result
|
124
|
+
result.inner_text
|
125
|
+
end
|
126
|
+
|
127
|
+
def value_from_path(exchange, path)
|
128
|
+
path = '//' + path if path[0] != '/'
|
129
|
+
xpath_value_for(exchange: exchange, xpath: path)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
123
133
|
end
|