soaspec 0.2.23 → 0.2.24

Sign up to get free protection for your applications and to get access to all the features.
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