soaspec 0.2.23 → 0.2.24

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -15
  3. data/.gitlab-ci.yml +33 -33
  4. data/.rspec +3 -3
  5. data/.rubocop.yml +2 -2
  6. data/CODE_OF_CONDUCT.md +74 -74
  7. data/ChangeLog +577 -573
  8. data/Gemfile +6 -6
  9. data/LICENSE.txt +21 -21
  10. data/README.md +230 -230
  11. data/Rakefile +42 -42
  12. data/Todo.md +15 -15
  13. data/exe/soaspec +123 -123
  14. data/exe/xml_to_yaml_file +42 -42
  15. data/lib/soaspec.rb +101 -101
  16. data/lib/soaspec/core_ext/hash.rb +35 -35
  17. data/lib/soaspec/cucumber/generic_steps.rb +85 -85
  18. data/lib/soaspec/demo.rb +4 -4
  19. data/lib/soaspec/exchange/exchange.rb +111 -111
  20. data/lib/soaspec/exchange/exchange_extractor.rb +83 -83
  21. data/lib/soaspec/exchange/exchange_properties.rb +26 -26
  22. data/lib/soaspec/exchange/exchange_repeater.rb +19 -19
  23. data/lib/soaspec/exchange/request_builder.rb +68 -68
  24. data/lib/soaspec/exchange/variable_storer.rb +22 -22
  25. data/lib/soaspec/exchange_handlers/exchange_handler.rb +126 -126
  26. data/lib/soaspec/exchange_handlers/handler_accessors.rb +130 -130
  27. data/lib/soaspec/exchange_handlers/response_extractor.rb +82 -82
  28. data/lib/soaspec/exchange_handlers/rest_exchanger_factory.rb +109 -109
  29. data/lib/soaspec/exchange_handlers/rest_handler.rb +259 -259
  30. data/lib/soaspec/exchange_handlers/rest_methods.rb +44 -44
  31. data/lib/soaspec/exchange_handlers/rest_parameters.rb +86 -86
  32. data/lib/soaspec/exchange_handlers/rest_parameters_defaults.rb +21 -21
  33. data/lib/soaspec/exchange_handlers/soap_handler.rb +235 -235
  34. data/lib/soaspec/exe_helpers.rb +92 -92
  35. data/lib/soaspec/generate_server.rb +37 -37
  36. data/lib/soaspec/generator/.rspec.erb +5 -5
  37. data/lib/soaspec/generator/.travis.yml.erb +5 -5
  38. data/lib/soaspec/generator/Gemfile.erb +8 -8
  39. data/lib/soaspec/generator/README.md.erb +29 -29
  40. data/lib/soaspec/generator/Rakefile.erb +19 -19
  41. data/lib/soaspec/generator/config/data/default.yml.erb +2 -2
  42. data/lib/soaspec/generator/css/bootstrap.css +6833 -6833
  43. data/lib/soaspec/generator/generate_exchange.html.erb +35 -35
  44. data/lib/soaspec/generator/lib/blz_service.rb.erb +26 -26
  45. data/lib/soaspec/generator/lib/dynamic_class_content.rb.erb +12 -12
  46. data/lib/soaspec/generator/lib/new_rest_service.rb.erb +51 -51
  47. data/lib/soaspec/generator/lib/new_soap_service.rb.erb +29 -29
  48. data/lib/soaspec/generator/lib/package_service.rb.erb +2 -2
  49. data/lib/soaspec/generator/lib/shared_example.rb.erb +8 -8
  50. data/lib/soaspec/generator/spec/dynamic_soap_spec.rb.erb +12 -12
  51. data/lib/soaspec/generator/spec/rest_spec.rb.erb +9 -9
  52. data/lib/soaspec/generator/spec/soap_spec.rb.erb +51 -51
  53. data/lib/soaspec/generator/spec/spec_helper.rb.erb +23 -23
  54. data/lib/soaspec/generator/template/soap_template.xml +6 -6
  55. data/lib/soaspec/indifferent_hash.rb +7 -7
  56. data/lib/soaspec/interpreter.rb +39 -39
  57. data/lib/soaspec/matchers.rb +114 -114
  58. data/lib/soaspec/not_found_errors.rb +13 -13
  59. data/lib/soaspec/o_auth2.rb +128 -128
  60. data/lib/soaspec/soaspec_shared_examples.rb +24 -24
  61. data/lib/soaspec/spec_logger.rb +121 -121
  62. data/lib/soaspec/template_reader.rb +28 -28
  63. data/lib/soaspec/test_server/bank.wsdl +90 -90
  64. data/lib/soaspec/test_server/get_bank.rb +164 -164
  65. data/lib/soaspec/test_server/id_manager.rb +39 -39
  66. data/lib/soaspec/test_server/invoices.rb +27 -27
  67. data/lib/soaspec/test_server/namespace.xml +14 -14
  68. data/lib/soaspec/test_server/note.xml +5 -5
  69. data/lib/soaspec/test_server/puppy_service.rb +19 -19
  70. data/lib/soaspec/test_server/test_attribute.rb +12 -12
  71. data/lib/soaspec/test_server/test_namespace.rb +12 -12
  72. data/lib/soaspec/version.rb +4 -3
  73. data/lib/soaspec/virtual_server.rb +174 -174
  74. data/lib/soaspec/wait.rb +41 -41
  75. data/lib/soaspec/wsdl_generator.rb +215 -215
  76. data/soaspec.gemspec +53 -53
  77. data/test.wsdl +116 -116
  78. data/test.xml +10 -10
  79. data/test_wsdl.rb +41 -41
  80. metadata +3 -4
@@ -1,44 +1,44 @@
1
- module Soaspec
2
- # Contains commonly used REST methods
3
- module RestMethods
4
- # Make REST Post Exchange
5
- # @param [String] name Name of test displayed
6
- # @param [Hash] params Exchange parameters
7
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
8
- def post(name, params = {})
9
- Exchange.new(name, method: :post, **params)
10
- end
11
-
12
- # Make REST Patch Exchange
13
- # @param [String] name Name of test displayed
14
- # @param [Hash] params Exchange parameters
15
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
16
- def patch(name, params = {})
17
- Exchange.new(name, method: :patch, **params)
18
- end
19
-
20
- # Make REST Put Exchange
21
- # @param [String] name Name of test displayed
22
- # @param [Hash] params Exchange parameters
23
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
24
- def put(name, params = {})
25
- Exchange.new(name, method: :put, **params)
26
- end
27
-
28
- # Make REST Get Exchange
29
- # @param [String] name Name of test displayed
30
- # @param [Hash] params Exchange parameters
31
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
32
- def get(name, params = {})
33
- Exchange.new(name, method: :get, **params)
34
- end
35
-
36
- # Make REST Delete Exchange
37
- # @param [String] name Name of test displayed
38
- # @param [Hash] params Exchange parameters
39
- # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
40
- def delete(name, params = {})
41
- Exchange.new(name, method: :delete, **params)
42
- end
43
- end
44
- end
1
+ module Soaspec
2
+ # Contains commonly used REST methods
3
+ module RestMethods
4
+ # Make REST Post Exchange
5
+ # @param [String] name Name of test displayed
6
+ # @param [Hash] params Exchange parameters
7
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
8
+ def post(name, params = {})
9
+ Exchange.new(name, method: :post, **params)
10
+ end
11
+
12
+ # Make REST Patch Exchange
13
+ # @param [String] name Name of test displayed
14
+ # @param [Hash] params Exchange parameters
15
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
16
+ def patch(name, params = {})
17
+ Exchange.new(name, method: :patch, **params)
18
+ end
19
+
20
+ # Make REST Put Exchange
21
+ # @param [String] name Name of test displayed
22
+ # @param [Hash] params Exchange parameters
23
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
24
+ def put(name, params = {})
25
+ Exchange.new(name, method: :put, **params)
26
+ end
27
+
28
+ # Make REST Get Exchange
29
+ # @param [String] name Name of test displayed
30
+ # @param [Hash] params Exchange parameters
31
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
32
+ def get(name, params = {})
33
+ Exchange.new(name, method: :get, **params)
34
+ end
35
+
36
+ # Make REST Delete Exchange
37
+ # @param [String] name Name of test displayed
38
+ # @param [Hash] params Exchange parameters
39
+ # @return [Exchange] Instance of Exchange class. Assertions are made by default on the response body
40
+ def delete(name, params = {})
41
+ Exchange.new(name, method: :delete, **params)
42
+ end
43
+ end
44
+ end
@@ -1,86 +1,86 @@
1
- module Soaspec
2
- # Methods to define parameters specific to REST handler
3
- module RestParameters
4
- # Defines method 'base_url_value' containing base URL used in REST requests
5
- # @param [String] url Base Url to use in REST requests. Suburl is appended to this
6
- def base_url(url)
7
- raise ArgumentError, "Base Url passed must be a String for #{self} but was #{url.class}" unless url.is_a?(String)
8
-
9
- define_method('base_url_value') { ERB.new(url).result(binding) }
10
- end
11
-
12
- # Will create access_token method based on passed parameters
13
- # @param [Hash] params OAuth 2 parameters
14
- # @option params [token_url] URL to retrieve OAuth token from. @Note this can be set globally instead of here
15
- # @option params [client_id] Client ID
16
- # @option params [client_secret] Client Secret
17
- # @option params [username] Username used in password grant
18
- # @option params [password] Password used in password grant
19
- # @option params [security_token] Security Token used in password grant
20
- def oauth2(params)
21
- # @!method oauth_obj Object to handle oauth2
22
- define_method('oauth_obj') { OAuth2.new(params, api_username) }
23
- # @!method access_token Retrieve OAuth2 access token
24
- define_method('access_token') { oauth_obj.access_token }
25
- # @!method instance_url Retrieve instance url from OAuth request
26
- define_method('instance_url') { oauth_obj.instance_url }
27
- end
28
-
29
- # Pass path to YAML file containing OAuth2 parameters
30
- # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
31
- def oauth2_file(path_to_filename)
32
- oauth_parameters = load_credentials_hash(path_to_filename)
33
- @client_id = oauth_parameters[:client_id] if oauth_parameters[:client_id]
34
- oauth2 oauth_parameters
35
- end
36
-
37
- # @return [String] Client id obtained from credentials file
38
- def client_id
39
- raise '@client_id is not set. Set by specifying credentials file with "oauth2_file FILENAME" before this is called' unless @client_id
40
-
41
- @client_id
42
- end
43
-
44
- # Define basic authentication
45
- # @param [String] user Username to use
46
- # @param [String] password Password to use
47
- def basic_auth(user: nil, password: nil)
48
- raise ArgumentError, "Must pass both 'user' and 'password' for #{self}" unless user && password
49
-
50
- define_method('basic_auth_params') do
51
- { user: user, password: password }
52
- end
53
- end
54
-
55
- # Pass path to YAML file containing Basic Auth parameters (i.e, both username and password)
56
- # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
57
- def basic_auth_file(path_to_filename)
58
- basic_auth load_credentials_hash(path_to_filename)
59
- end
60
-
61
- # @param [Hash] headers Hash of REST headers used in RestClient
62
- def headers(headers)
63
- define_method('rest_client_headers') { headers }
64
- end
65
-
66
- # Convert each key from snake_case to PascalCase
67
- def pascal_keys(set)
68
- define_method('pascal_keys?') { set }
69
- end
70
-
71
- private
72
-
73
- # Load credentials hash from a YAML using Soaspec.credentials_folder if set, adding '.yml' if not set
74
- # @return [Hash] Hash with credentials in it
75
- def load_credentials_hash(filename)
76
- raise ArgumentError, "Filename passed must be a String for #{self} but was #{filename.class}" unless filename.is_a?(String)
77
-
78
- full_path = Soaspec.credentials_folder ? File.join(Soaspec.credentials_folder, filename) : filename
79
- full_path += '.yml' unless full_path.end_with?('.yml') # Automatically add 'yml' extension
80
- file_hash = YAML.load_file(full_path)
81
- raise "File at #{full_path} is not a hash" unless file_hash.is_a? Hash
82
-
83
- file_hash.transform_keys_to_symbols
84
- end
85
- end
86
- end
1
+ module Soaspec
2
+ # Methods to define parameters specific to REST handler
3
+ module RestParameters
4
+ # Defines method 'base_url_value' containing base URL used in REST requests
5
+ # @param [String] url Base Url to use in REST requests. Suburl is appended to this
6
+ def base_url(url)
7
+ raise ArgumentError, "Base Url passed must be a String for #{self} but was #{url.class}" unless url.is_a?(String)
8
+
9
+ define_method('base_url_value') { ERB.new(url).result(binding) }
10
+ end
11
+
12
+ # Will create access_token method based on passed parameters
13
+ # @param [Hash] params OAuth 2 parameters
14
+ # @option params [token_url] URL to retrieve OAuth token from. @Note this can be set globally instead of here
15
+ # @option params [client_id] Client ID
16
+ # @option params [client_secret] Client Secret
17
+ # @option params [username] Username used in password grant
18
+ # @option params [password] Password used in password grant
19
+ # @option params [security_token] Security Token used in password grant
20
+ def oauth2(params)
21
+ # @!method oauth_obj Object to handle oauth2
22
+ define_method('oauth_obj') { OAuth2.new(params, api_username) }
23
+ # @!method access_token Retrieve OAuth2 access token
24
+ define_method('access_token') { oauth_obj.access_token }
25
+ # @!method instance_url Retrieve instance url from OAuth request
26
+ define_method('instance_url') { oauth_obj.instance_url }
27
+ end
28
+
29
+ # Pass path to YAML file containing OAuth2 parameters
30
+ # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
31
+ def oauth2_file(path_to_filename)
32
+ oauth_parameters = load_credentials_hash(path_to_filename)
33
+ @client_id = oauth_parameters[:client_id] if oauth_parameters[:client_id]
34
+ oauth2 oauth_parameters
35
+ end
36
+
37
+ # @return [String] Client id obtained from credentials file
38
+ def client_id
39
+ raise '@client_id is not set. Set by specifying credentials file with "oauth2_file FILENAME" before this is called' unless @client_id
40
+
41
+ @client_id
42
+ end
43
+
44
+ # Define basic authentication
45
+ # @param [String] user Username to use
46
+ # @param [String] password Password to use
47
+ def basic_auth(user: nil, password: nil)
48
+ raise ArgumentError, "Must pass both 'user' and 'password' for #{self}" unless user && password
49
+
50
+ define_method('basic_auth_params') do
51
+ { user: user, password: password }
52
+ end
53
+ end
54
+
55
+ # Pass path to YAML file containing Basic Auth parameters (i.e, both username and password)
56
+ # @param [String] path_to_filename Will have Soaspec.credentials_folder appended to it if set
57
+ def basic_auth_file(path_to_filename)
58
+ basic_auth load_credentials_hash(path_to_filename)
59
+ end
60
+
61
+ # @param [Hash] headers Hash of REST headers used in RestClient
62
+ def headers(headers)
63
+ define_method('rest_client_headers') { headers }
64
+ end
65
+
66
+ # Convert each key from snake_case to PascalCase
67
+ def pascal_keys(set)
68
+ define_method('pascal_keys?') { set }
69
+ end
70
+
71
+ private
72
+
73
+ # Load credentials hash from a YAML using Soaspec.credentials_folder if set, adding '.yml' if not set
74
+ # @return [Hash] Hash with credentials in it
75
+ def load_credentials_hash(filename)
76
+ raise ArgumentError, "Filename passed must be a String for #{self} but was #{filename.class}" unless filename.is_a?(String)
77
+
78
+ full_path = Soaspec.credentials_folder ? File.join(Soaspec.credentials_folder, filename) : filename
79
+ full_path += '.yml' unless full_path.end_with?('.yml') # Automatically add 'yml' extension
80
+ file_hash = YAML.load_file(full_path)
81
+ raise "File at #{full_path} is not a hash" unless file_hash.is_a? Hash
82
+
83
+ file_hash.transform_keys_to_symbols
84
+ end
85
+ end
86
+ end
@@ -1,21 +1,21 @@
1
- module Soaspec
2
- # Defaults for Soaspec RestParameters methods
3
- module RestParametersDefaults
4
- # Set through following method. Base URL in REST requests.
5
- def base_url_value
6
- nil
7
- end
8
-
9
- # Headers used in RestClient
10
- def rest_client_headers
11
- {}
12
- end
13
-
14
- # Whether to convert each key in the request to PascalCase
15
- # It will also auto convert simple XPath, JSONPath where '//' or '..' not specified
16
- # @return Whether to convert to PascalCase
17
- def pascal_keys?
18
- false
19
- end
20
- end
21
- end
1
+ module Soaspec
2
+ # Defaults for Soaspec RestParameters methods
3
+ module RestParametersDefaults
4
+ # Set through following method. Base URL in REST requests.
5
+ def base_url_value
6
+ nil
7
+ end
8
+
9
+ # Headers used in RestClient
10
+ def rest_client_headers
11
+ {}
12
+ end
13
+
14
+ # Whether to convert each key in the request to PascalCase
15
+ # It will also auto convert simple XPath, JSONPath where '//' or '..' not specified
16
+ # @return Whether to convert to PascalCase
17
+ def pascal_keys?
18
+ false
19
+ end
20
+ end
21
+ end
@@ -1,235 +1,235 @@
1
- require_relative 'exchange_handler'
2
- require_relative '../core_ext/hash'
3
- require_relative '../not_found_errors'
4
- require_relative 'handler_accessors'
5
- require_relative '../interpreter'
6
- require 'forwardable'
7
-
8
- module Soaspec
9
- # Accessors specific to SOAP handler
10
- module SoapAccessors
11
- # Define attributes set on root SOAP element
12
- def root_attributes(attributes)
13
- define_method('request_root_attributes') do
14
- attributes
15
- end
16
- end
17
- end
18
-
19
- # Wraps around Savon client defining default values dependent on the soap request
20
- class SoapHandler < ExchangeHandler
21
- extend Soaspec::SoapAccessors
22
- extend Forwardable
23
-
24
- delegate [:operations] => :client
25
-
26
- # Savon client used to make SOAP calls
27
- attr_accessor :client
28
- # SOAP Operation to use by default
29
- attr_accessor :operation
30
-
31
- # Attributes set at the root XML element of SOAP request
32
- def request_root_attributes
33
- nil
34
- end
35
-
36
- # Options to log xml request and response
37
- def logging_options
38
- {
39
- log: true, # See request and response. (Put this in traffic file)
40
- log_level: :debug,
41
- logger: Soaspec::SpecLogger.create,
42
- pretty_print_xml: true # Prints XML pretty
43
- }
44
- end
45
-
46
- # Default Savon options. See http://savonrb.com/version2/globals.html for details
47
- # @example Things could go wrong if not set properly
48
- # env_namespace: :soap, # Change environment namespace
49
- # namespace_identifier: :tst, # Change namespace element
50
- # element_form_default: :qualified # Populate each element with namespace
51
- # namespace: 'http://Extended_namespace.xsd' change root namespace
52
- # basic_auth: 'user', 'password'
53
- # @return [Hash] Default Savon options for all BasicSoapHandler
54
- def default_options
55
- {
56
- ssl_verify_mode: :none, # Easier for testing. Not so secure
57
- follow_redirects: true, # Necessary for many API calls
58
- soap_version: 2, # use SOAP 1.2. You will get 415 error if this is incorrect
59
- raise_errors: false # HTTP errors not cause failure as often negative test scenarios expect not 200 response
60
- }
61
- end
62
-
63
- # Add values to here when extending this class to have default Savon options.
64
- # See http://savonrb.com/version2/globals.html for details
65
- # @return [Hash] Savon options adding to & overriding defaults
66
- def savon_options
67
- {
68
- }
69
- end
70
-
71
- # Setup object to handle communicating with a particular SOAP WSDL
72
- # @param [String] name Name to describe handler. Used in calling 'to_s'
73
- # @param [Hash] options Options defining SOAP request. WSDL, authentication, see http://savonrb.com/version2/globals.html for list of options
74
- def initialize(name = self.class.to_s, options = {})
75
- if name.is_a?(Hash) && options == {} # If name is not set
76
- options = name
77
- name = self.class.to_s
78
- end
79
- super
80
- set_remove_keys(options, %i[operation default_hash template_name])
81
- merged_options = Soaspec::SpecLogger.log_api_traffic? ? default_options.merge(logging_options) : default_options
82
- merged_options.merge! savon_options
83
- merged_options.merge!(options)
84
- self.client = Savon.client(merged_options)
85
- end
86
-
87
- # Used in making request via hash or in template via Erb
88
- # @param [Hash] request_parameters Hash representing elements to send in request
89
- # If the :body key is set, this will be used as the request body
90
- def request_body_params(request_parameters)
91
- test_values = request_parameters[:body] || request_parameters
92
- test_values.transform_keys_to_symbols if Soaspec.always_use_keys?
93
- end
94
-
95
- # Used in together with Exchange request that passes such override parameters
96
- # @param [Hash] request_parameters Parameters used to overwrite defaults in request
97
- def make_request(request_parameters)
98
- test_values = request_body_params request_parameters
99
- # Call the SOAP operation with the request XML provided
100
- begin
101
- if @request_option == :template
102
- test_values = IndifferentHash.new(test_values) # Allow test_values to be either Symbol or String
103
- client.call(operation, xml: Soaspec::TemplateReader.new.render_body(template_name, binding))
104
- elsif @request_option == :hash
105
- client.call(operation, message: @default_hash.merge(test_values), attributes: request_root_attributes)
106
- end
107
- rescue Savon::HTTPError => soap_error
108
- soap_error
109
- end
110
- end
111
-
112
- # @param [Hash] format Format of expected result
113
- # @return [Object] Generic body to be displayed in error messages
114
- def response_body(response, format: :hash)
115
- case format
116
- when :hash
117
- response.body
118
- when :raw
119
- response.xml
120
- else
121
- response.body
122
- end
123
- end
124
-
125
- # @return [Boolean] Whether the request found the desired value or not
126
- def found?(response)
127
- status_code_for(response) != 404
128
- end
129
-
130
- # Response status code for response. '200' indicates a success
131
- # @param [Savon::Response] response
132
- # @return [Integer] Status code
133
- def status_code_for(response)
134
- response.http.code
135
- end
136
-
137
- # @return [Boolean] Whether response includes provided string within it
138
- def include_in_body?(response, expected)
139
- response.to_xml.to_s.include? expected
140
- end
141
-
142
- # @param [Symbol] expected
143
- # @return [Boolean] Whether response body contains expected key
144
- def include_key?(response, expected)
145
- body = response.body
146
- body.extend Hashie::Extensions::DeepFind
147
- !body.deep_find_all(expected).empty?
148
- end
149
-
150
- # Convert all XML nodes to lowercase
151
- # @param [Nokogiri::XML::Document] xml_doc Xml document to convert
152
- def convert_to_lower_case(xml_doc)
153
- xml_doc.traverse do |node|
154
- node.name = node.name.downcase if node.is_a?(Nokogiri::XML::Element)
155
- end
156
- end
157
-
158
- # Returns the value at the provided xpath
159
- # @param [Savon::Response] response
160
- # @param [String] xpath
161
- # @return [Enumerable] Elements found through Xpath
162
- def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
163
- raise ArgumentError('response and xpath must be passed to method') unless response && xpath
164
-
165
- xpath = "//*[@#{attribute}]" unless attribute.nil?
166
- xpath = '//' + xpath if xpath[0] != '/'
167
- temp_doc = response.doc.dup
168
- convert_to_lower_case(temp_doc) if convert_to_lower?
169
- if strip_namespaces? && !xpath.include?(':')
170
- temp_doc.remove_namespaces!
171
- temp_doc.xpath(xpath)
172
- else
173
- temp_doc.xpath(xpath, temp_doc.collect_namespaces)
174
- end
175
- end
176
-
177
- # Based on a exchange, return the value at the provided xpath
178
- # If the path does not begin with a '/', a '//' is added to it
179
- # @param [Savon::Response] response
180
- # @param [String] path Xpath
181
- # @param [String] attribute Generic attribute to find. Will override path
182
- # @return [String] Value at Xpath
183
- def value_from_path(response, path, attribute: nil)
184
- results = xpath_elements_for(response: response, xpath: path, attribute: attribute)
185
- raise NoElementAtPath, "No value at Xpath '#{path}' in XML #{response.doc}" if results.empty?
186
- return results.first.inner_text if attribute.nil?
187
-
188
- results.first.attributes[attribute].inner_text
189
- end
190
-
191
- # @return [Enumerable] List of values returned from path
192
- def values_from_path(response, path, attribute: nil)
193
- xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
194
- end
195
-
196
- # alias elements xpath_elements_for
197
-
198
- # @return [Boolean] Whether any of the keys of the Body Hash include value
199
- def include_value?(response, expected_value)
200
- response.body.include_value?(expected_value)
201
- end
202
-
203
- # Hash of response body
204
- def to_hash(response)
205
- response.body
206
- end
207
-
208
- # Convenience methods for once off usage of a SOAP request
209
- class << self
210
- # Implement undefined setter with []= for FactoryBot to use without needing to define params to set
211
- # @param [Object] method_name Name of method not defined
212
- # @param [Object] args Arguments passed to method
213
- # @param [Object] block
214
- def method_missing(method_name, *args, &block)
215
- tmp_class = new(method_name)
216
- operations = tmp_class.operations
217
- if operations.include? method_name
218
- tmp_class.operation = method_name
219
- exchange = Exchange.new(method_name, *args)
220
- exchange.exchange_handler = tmp_class
221
- yield exchange if block_given?
222
- exchange
223
- else
224
- super
225
- end
226
- end
227
-
228
- def respond_to_missing?(method_name, *args)
229
- tmp_class = new(args)
230
- operations = tmp_class.operations
231
- operations.include?(method_name) || super
232
- end
233
- end
234
- end
235
- end
1
+ require_relative 'exchange_handler'
2
+ require_relative '../core_ext/hash'
3
+ require_relative '../not_found_errors'
4
+ require_relative 'handler_accessors'
5
+ require_relative '../interpreter'
6
+ require 'forwardable'
7
+
8
+ module Soaspec
9
+ # Accessors specific to SOAP handler
10
+ module SoapAccessors
11
+ # Define attributes set on root SOAP element
12
+ def root_attributes(attributes)
13
+ define_method('request_root_attributes') do
14
+ attributes
15
+ end
16
+ end
17
+ end
18
+
19
+ # Wraps around Savon client defining default values dependent on the soap request
20
+ class SoapHandler < ExchangeHandler
21
+ extend Soaspec::SoapAccessors
22
+ extend Forwardable
23
+
24
+ delegate [:operations] => :client
25
+
26
+ # Savon client used to make SOAP calls
27
+ attr_accessor :client
28
+ # SOAP Operation to use by default
29
+ attr_accessor :operation
30
+
31
+ # Attributes set at the root XML element of SOAP request
32
+ def request_root_attributes
33
+ nil
34
+ end
35
+
36
+ # Options to log xml request and response
37
+ def logging_options
38
+ {
39
+ log: true, # See request and response. (Put this in traffic file)
40
+ log_level: :debug,
41
+ logger: Soaspec::SpecLogger.create,
42
+ pretty_print_xml: true # Prints XML pretty
43
+ }
44
+ end
45
+
46
+ # Default Savon options. See http://savonrb.com/version2/globals.html for details
47
+ # @example Things could go wrong if not set properly
48
+ # env_namespace: :soap, # Change environment namespace
49
+ # namespace_identifier: :tst, # Change namespace element
50
+ # element_form_default: :qualified # Populate each element with namespace
51
+ # namespace: 'http://Extended_namespace.xsd' change root namespace
52
+ # basic_auth: 'user', 'password'
53
+ # @return [Hash] Default Savon options for all BasicSoapHandler
54
+ def default_options
55
+ {
56
+ ssl_verify_mode: :none, # Easier for testing. Not so secure
57
+ follow_redirects: true, # Necessary for many API calls
58
+ soap_version: 2, # use SOAP 1.2. You will get 415 error if this is incorrect
59
+ raise_errors: false # HTTP errors not cause failure as often negative test scenarios expect not 200 response
60
+ }
61
+ end
62
+
63
+ # Add values to here when extending this class to have default Savon options.
64
+ # See http://savonrb.com/version2/globals.html for details
65
+ # @return [Hash] Savon options adding to & overriding defaults
66
+ def savon_options
67
+ {
68
+ }
69
+ end
70
+
71
+ # Setup object to handle communicating with a particular SOAP WSDL
72
+ # @param [String] name Name to describe handler. Used in calling 'to_s'
73
+ # @param [Hash] options Options defining SOAP request. WSDL, authentication, see http://savonrb.com/version2/globals.html for list of options
74
+ def initialize(name = self.class.to_s, options = {})
75
+ if name.is_a?(Hash) && options == {} # If name is not set
76
+ options = name
77
+ name = self.class.to_s
78
+ end
79
+ super
80
+ set_remove_keys(options, %i[operation default_hash template_name])
81
+ merged_options = Soaspec::SpecLogger.log_api_traffic? ? default_options.merge(logging_options) : default_options
82
+ merged_options.merge! savon_options
83
+ merged_options.merge!(options)
84
+ self.client = Savon.client(merged_options)
85
+ end
86
+
87
+ # Used in making request via hash or in template via Erb
88
+ # @param [Hash] request_parameters Hash representing elements to send in request
89
+ # If the :body key is set, this will be used as the request body
90
+ def request_body_params(request_parameters)
91
+ test_values = request_parameters[:body] || request_parameters
92
+ test_values.transform_keys_to_symbols if Soaspec.always_use_keys?
93
+ end
94
+
95
+ # Used in together with Exchange request that passes such override parameters
96
+ # @param [Hash] request_parameters Parameters used to overwrite defaults in request
97
+ def make_request(request_parameters)
98
+ test_values = request_body_params request_parameters
99
+ # Call the SOAP operation with the request XML provided
100
+ begin
101
+ if @request_option == :template
102
+ test_values = IndifferentHash.new(test_values) # Allow test_values to be either Symbol or String
103
+ client.call(operation, xml: Soaspec::TemplateReader.new.render_body(template_name, binding))
104
+ elsif @request_option == :hash
105
+ client.call(operation, message: @default_hash.merge(test_values), attributes: request_root_attributes)
106
+ end
107
+ rescue Savon::HTTPError => soap_error
108
+ soap_error
109
+ end
110
+ end
111
+
112
+ # @param [Hash] format Format of expected result
113
+ # @return [Object] Generic body to be displayed in error messages
114
+ def response_body(response, format: :hash)
115
+ case format
116
+ when :hash
117
+ response.body
118
+ when :raw
119
+ response.xml
120
+ else
121
+ response.body
122
+ end
123
+ end
124
+
125
+ # @return [Boolean] Whether the request found the desired value or not
126
+ def found?(response)
127
+ status_code_for(response) != 404
128
+ end
129
+
130
+ # Response status code for response. '200' indicates a success
131
+ # @param [Savon::Response] response
132
+ # @return [Integer] Status code
133
+ def status_code_for(response)
134
+ response.http.code
135
+ end
136
+
137
+ # @return [Boolean] Whether response includes provided string within it
138
+ def include_in_body?(response, expected)
139
+ response.to_xml.to_s.include? expected
140
+ end
141
+
142
+ # @param [Symbol] expected
143
+ # @return [Boolean] Whether response body contains expected key
144
+ def include_key?(response, expected)
145
+ body = response.body
146
+ body.extend Hashie::Extensions::DeepFind
147
+ !body.deep_find_all(expected).empty?
148
+ end
149
+
150
+ # Convert all XML nodes to lowercase
151
+ # @param [Nokogiri::XML::Document] xml_doc Xml document to convert
152
+ def convert_to_lower_case(xml_doc)
153
+ xml_doc.traverse do |node|
154
+ node.name = node.name.downcase if node.is_a?(Nokogiri::XML::Element)
155
+ end
156
+ end
157
+
158
+ # Returns the value at the provided xpath
159
+ # @param [Savon::Response] response
160
+ # @param [String] xpath
161
+ # @return [Enumerable] Elements found through Xpath
162
+ def xpath_elements_for(response: nil, xpath: nil, attribute: nil)
163
+ raise ArgumentError('response and xpath must be passed to method') unless response && xpath
164
+
165
+ xpath = "//*[@#{attribute}]" unless attribute.nil?
166
+ xpath = '//' + xpath if xpath[0] != '/'
167
+ temp_doc = response.doc.dup
168
+ convert_to_lower_case(temp_doc) if convert_to_lower?
169
+ if strip_namespaces? && !xpath.include?(':')
170
+ temp_doc.remove_namespaces!
171
+ temp_doc.xpath(xpath)
172
+ else
173
+ temp_doc.xpath(xpath, temp_doc.collect_namespaces)
174
+ end
175
+ end
176
+
177
+ # Based on a exchange, return the value at the provided xpath
178
+ # If the path does not begin with a '/', a '//' is added to it
179
+ # @param [Savon::Response] response
180
+ # @param [String] path Xpath
181
+ # @param [String] attribute Generic attribute to find. Will override path
182
+ # @return [String] Value at Xpath
183
+ def value_from_path(response, path, attribute: nil)
184
+ results = xpath_elements_for(response: response, xpath: path, attribute: attribute)
185
+ raise NoElementAtPath, "No value at Xpath '#{path}' in XML #{response.doc}" if results.empty?
186
+ return results.first.inner_text if attribute.nil?
187
+
188
+ results.first.attributes[attribute].inner_text
189
+ end
190
+
191
+ # @return [Enumerable] List of values returned from path
192
+ def values_from_path(response, path, attribute: nil)
193
+ xpath_elements_for(response: response, xpath: path, attribute: attribute).map(&:inner_text)
194
+ end
195
+
196
+ # alias elements xpath_elements_for
197
+
198
+ # @return [Boolean] Whether any of the keys of the Body Hash include value
199
+ def include_value?(response, expected_value)
200
+ response.body.include_value?(expected_value)
201
+ end
202
+
203
+ # Hash of response body
204
+ def to_hash(response)
205
+ response.body
206
+ end
207
+
208
+ # Convenience methods for once off usage of a SOAP request
209
+ class << self
210
+ # Implement undefined setter with []= for FactoryBot to use without needing to define params to set
211
+ # @param [Object] method_name Name of method not defined
212
+ # @param [Object] args Arguments passed to method
213
+ # @param [Object] block
214
+ def method_missing(method_name, *args, &block)
215
+ tmp_class = new(method_name)
216
+ operations = tmp_class.operations
217
+ if operations.include? method_name
218
+ tmp_class.operation = method_name
219
+ exchange = Exchange.new(method_name, *args)
220
+ exchange.exchange_handler = tmp_class
221
+ yield exchange if block_given?
222
+ exchange
223
+ else
224
+ super
225
+ end
226
+ end
227
+
228
+ def respond_to_missing?(method_name, *args)
229
+ tmp_class = new(args)
230
+ operations = tmp_class.operations
231
+ operations.include?(method_name) || super
232
+ end
233
+ end
234
+ end
235
+ end