soaspec 0.2.24 → 0.2.25

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