soaspec 0.2.33 → 0.3.1

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