soaspec 0.2.24 → 0.2.25

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