soaspec 0.2.33 → 0.3.1

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