soaspec 0.1.1 → 0.1.2

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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +15 -15
  3. data/.gitlab-ci.yml +31 -31
  4. data/.rspec +3 -3
  5. data/.rubocop.yml +2 -2
  6. data/CODE_OF_CONDUCT.md +74 -74
  7. data/ChangeLog +384 -384
  8. data/Gemfile +6 -6
  9. data/LICENSE.txt +21 -21
  10. data/README.md +85 -85
  11. data/Rakefile +24 -24
  12. data/Todo.md +6 -6
  13. data/exe/soaspec +119 -119
  14. data/exe/soaspec-virtual-server +103 -103
  15. data/exe/xml_to_yaml_file +60 -60
  16. data/lib/soaspec.rb +91 -91
  17. data/lib/soaspec/core_ext/hash.rb +83 -83
  18. data/lib/soaspec/exchange.rb +234 -234
  19. data/lib/soaspec/exchange_handlers/exchange_handler.rb +103 -103
  20. data/lib/soaspec/exchange_handlers/handler_accessors.rb +106 -106
  21. data/lib/soaspec/exchange_handlers/rest_accessors.rb +92 -92
  22. data/lib/soaspec/exchange_handlers/rest_handler.rb +311 -311
  23. data/lib/soaspec/exchange_handlers/rest_methods.rb +44 -44
  24. data/lib/soaspec/exchange_handlers/soap_handler.rb +236 -236
  25. data/lib/soaspec/exe_helpers.rb +56 -56
  26. data/lib/soaspec/generator/.rspec.erb +5 -5
  27. data/lib/soaspec/generator/.travis.yml.erb +5 -5
  28. data/lib/soaspec/generator/Gemfile.erb +8 -8
  29. data/lib/soaspec/generator/README.md.erb +29 -29
  30. data/lib/soaspec/generator/Rakefile.erb +19 -19
  31. data/lib/soaspec/generator/config/data/default.yml.erb +1 -1
  32. data/lib/soaspec/generator/lib/blz_service.rb.erb +26 -26
  33. data/lib/soaspec/generator/lib/dynamic_class_content.rb.erb +12 -12
  34. data/lib/soaspec/generator/lib/shared_example.rb.erb +8 -8
  35. data/lib/soaspec/generator/spec/dynamic_soap_spec.rb.erb +12 -12
  36. data/lib/soaspec/generator/spec/soap_spec.rb.erb +51 -51
  37. data/lib/soaspec/generator/spec/spec_helper.rb.erb +20 -20
  38. data/lib/soaspec/generator/template/soap_template.xml +6 -6
  39. data/lib/soaspec/interpreter.rb +40 -40
  40. data/lib/soaspec/matchers.rb +65 -65
  41. data/lib/soaspec/not_found_errors.rb +13 -13
  42. data/lib/soaspec/soaspec_shared_examples.rb +24 -24
  43. data/lib/soaspec/spec_logger.rb +27 -27
  44. data/lib/soaspec/test_server/bank.wsdl +90 -90
  45. data/lib/soaspec/test_server/get_bank.rb +160 -160
  46. data/lib/soaspec/test_server/invoices.rb +27 -27
  47. data/lib/soaspec/test_server/namespace.xml +14 -14
  48. data/lib/soaspec/test_server/note.xml +5 -5
  49. data/lib/soaspec/test_server/puppy_service.rb +20 -20
  50. data/lib/soaspec/test_server/test_attribute.rb +13 -13
  51. data/lib/soaspec/version.rb +2 -2
  52. data/lib/soaspec/wsdl_generator.rb +144 -144
  53. data/soaspec.gemspec +46 -45
  54. data/test.wsdl +116 -116
  55. data/test.xml +10 -10
  56. data/test_wsdl.rb +43 -43
  57. metadata +17 -3
@@ -1,83 +1,83 @@
1
-
2
-
3
-
4
- # Override Hash class with convience methods
5
- class Hash
6
-
7
- # Transform each key in Hash to a symbol. Privately used by non-self method
8
- def self.transform_keys_to_symbols(value)
9
- return value if not value.is_a?(Hash)
10
- hash = value.inject({}){|memo,(k,v)| memo[k.to_sym] = Hash.transform_keys_to_symbols(v); memo}
11
- return hash
12
- end
13
-
14
- # Take keys of hash and transform those to a symbols
15
- def transform_keys_to_symbols
16
- inject({}){|memo, (k, v)| memo[k.to_sym] = Hash.transform_keys_to_symbols(v); memo}
17
- end
18
-
19
- # Returns all keys that have a particular value within a nested Hash
20
- def find_all_values_for(key)
21
- result = []
22
- result << self[key]
23
- self.values.each do |hash_value|
24
- # next if hash_value.is_a? Array
25
- #
26
- if hash_value.is_a?(Array)
27
- hash_value.each do |array_element|
28
- result += array_element.find_all_values_for(key) if array_element.is_a? Hash
29
- end
30
- end
31
-
32
- values = [hash_value]
33
- values.each do |value|
34
- result += value.find_all_values_for(key) if value.is_a? Hash
35
- end
36
- end
37
- result.compact
38
- end
39
-
40
- # Value present in nested Hash.
41
- def include_value?(value)
42
- each_value do |v|
43
- return true if v == value
44
- next unless v.is_a? Hash
45
- v.each_value do |v|
46
- return true if v == value
47
- next unless v.is_a? Hash
48
- v.each_value do |v|
49
- return true if v == value
50
- end
51
- end
52
- end
53
- false
54
- end
55
-
56
- # # Whether key is present at least once
57
- # def include_key?(key)
58
- # result = find_all_values_for key
59
- # result != []
60
- # end
61
-
62
- # Loop through each item within a key within a Hash if the key exists
63
- # @param [Key] key Key within hash to iterate through
64
- def each_if_not_null(key)
65
- case key.class.to_s
66
- when 'String'
67
- if self[key]
68
- self[key].each do |list_item|
69
- yield(list_item)
70
- end
71
- end
72
- when 'Array', 'Hash'
73
- if self[key[0]]
74
- if self[key[0]][key[1]]
75
- self[key[0]][key[1]].each do |list_item|
76
- yield(list_item)
77
- end
78
- end
79
- end
80
- end
81
- end
82
-
83
- end
1
+
2
+
3
+
4
+ # Override Hash class with convience methods
5
+ class Hash
6
+
7
+ # Transform each key in Hash to a symbol. Privately used by non-self method
8
+ def self.transform_keys_to_symbols(value)
9
+ return value if not value.is_a?(Hash)
10
+ hash = value.inject({}){|memo,(k,v)| memo[k.to_sym] = Hash.transform_keys_to_symbols(v); memo}
11
+ return hash
12
+ end
13
+
14
+ # Take keys of hash and transform those to a symbols
15
+ def transform_keys_to_symbols
16
+ inject({}){|memo, (k, v)| memo[k.to_sym] = Hash.transform_keys_to_symbols(v); memo}
17
+ end
18
+
19
+ # Returns all keys that have a particular value within a nested Hash
20
+ def find_all_values_for(key)
21
+ result = []
22
+ result << self[key]
23
+ self.values.each do |hash_value|
24
+ # next if hash_value.is_a? Array
25
+ #
26
+ if hash_value.is_a?(Array)
27
+ hash_value.each do |array_element|
28
+ result += array_element.find_all_values_for(key) if array_element.is_a? Hash
29
+ end
30
+ end
31
+
32
+ values = [hash_value]
33
+ values.each do |value|
34
+ result += value.find_all_values_for(key) if value.is_a? Hash
35
+ end
36
+ end
37
+ result.compact
38
+ end
39
+
40
+ # Value present in nested Hash.
41
+ def include_value?(value)
42
+ each_value do |v|
43
+ return true if v == value
44
+ next unless v.is_a? Hash
45
+ v.each_value do |v|
46
+ return true if v == value
47
+ next unless v.is_a? Hash
48
+ v.each_value do |v|
49
+ return true if v == value
50
+ end
51
+ end
52
+ end
53
+ false
54
+ end
55
+
56
+ # # Whether key is present at least once
57
+ # def include_key?(key)
58
+ # result = find_all_values_for key
59
+ # result != []
60
+ # end
61
+
62
+ # Loop through each item within a key within a Hash if the key exists
63
+ # @param [Key] key Key within hash to iterate through
64
+ def each_if_not_null(key)
65
+ case key.class.to_s
66
+ when 'String'
67
+ if self[key]
68
+ self[key].each do |list_item|
69
+ yield(list_item)
70
+ end
71
+ end
72
+ when 'Array', 'Hash'
73
+ if self[key[0]]
74
+ if self[key[0]][key[1]]
75
+ self[key[0]][key[1]].each do |list_item|
76
+ yield(list_item)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ end
@@ -1,235 +1,235 @@
1
- require_relative '../soaspec'
2
-
3
- # Convenience methods to set Exchange specific properties
4
- module ExchangeAccessors
5
-
6
- # Set default exchange handler for this exchange
7
- # This is helpful for when you need a new exchange handler created for each exchange
8
- def default_handler(handler_class, name = handler_class.to_s, params = '')
9
- define_method('default_handler_used') do
10
- params_used = Hash[params.map do |k, param|
11
- [k, param.is_a?(String) ? ERB.new(param).result(binding) : param]
12
- end]
13
- handler_class.new name, params_used
14
- end
15
- end
16
-
17
- # Set retry_for_success to true, retrying response until a successful status code is returned
18
- def expect_positive_status(retry_count: 3)
19
- define_method('retry_count') do
20
- retry_count
21
- end
22
- define_method('retry_for_success?') do
23
- true
24
- end
25
- end
26
-
27
- end
28
-
29
- # This represents a request / response pair
30
- # Essentially, params in the exchange that are set are related to the request
31
- # What is returned is related to the response
32
- class Exchange
33
- extend ExchangeAccessors
34
-
35
- # Instance of ExchangeHandler for which this exchange is made
36
- attr_accessor :exchange_handler
37
- # How many times to retry for a success
38
- attr_accessor :retry_count
39
- # Name used for displaying class
40
- attr_accessor :test_name
41
- # Expect Factory to fail upon trying to create
42
- attr_writer :fail_factory
43
-
44
-
45
- def values_from_path(path, attribute: nil)
46
- exchange_handler.values_from_path(response, path, attribute: attribute)
47
- end
48
-
49
- # Set retry for success variable to true so that request will be retried
50
- # for retry_count until it's true
51
- def retry_for_success
52
- @retry_for_success = true
53
- self
54
- end
55
-
56
- # @return [Bool] Whether to keep making request until success code reached
57
- def retry_for_success?
58
- @retry_for_success
59
- end
60
-
61
- # @return [Boolean] Soaspec::ExchangeHandler used by this exchange
62
- def default_handler_used
63
- nil
64
- end
65
-
66
- # @param [String] element Element to define methods for
67
- def methods_for_element(element)
68
- element_name = element.to_s.split('__custom_path_').last
69
- define_singleton_method(element_name) do
70
- exchange_handler.__send__(element, response) # Forward the call onto handler to retrieve the element for the response
71
- end
72
- define_singleton_method("#{element_name}?") do
73
- begin
74
- __send__ element_name
75
- true
76
- rescue NoElementAtPath
77
- false
78
- end
79
- end
80
- end
81
-
82
- # @param [Symbol, String] name Name shown in RSpec run
83
- # @param [Hash] override_parameters Parameters to override for default params
84
- def initialize(name = self.class.to_s, override_parameters = {})
85
- self.test_name ||= name.to_s
86
- # As a last resort this uses the global parameter. The handler should be set straight before an exchange is made to use this
87
- @exchange_handler ||= default_handler_used || Soaspec.api_handler
88
- raise '@exchange_handler not set. Set either with `Soaspec.api_handler = Handler.new` or within the exchange' unless @exchange_handler
89
- @fail_factory = nil
90
- @override_parameters = override_parameters
91
- @retry_for_success = false
92
- self.retry_count = 3
93
- @exchange_handler.elements.each { |element| methods_for_element(element) }
94
- end
95
-
96
- # Specify a url to add onto the base_url of the ExchangeHandler used
97
- # @param [String] url Url to add onto the base_url of the ExchangeHandler used
98
- def suburl=(url)
99
- @override_parameters[:suburl] = url
100
- end
101
-
102
- # Specify HTTP method to use. Default is :post
103
- # @param [Symbol] method HTTP method. E.g, :get, :patch
104
- def method=(method)
105
- @override_parameters[:method] = method
106
- end
107
-
108
- # Make request to handler with parameters defined
109
- # Will retry until success code reached if retry_for_success? is set
110
- # @return [Response] Response from Api handler
111
- def make_request
112
- Soaspec::SpecLogger.info 'Example ' + test_name
113
- request_params = @override_parameters
114
- (1..retry_count).each do |count|
115
- response = exchange_handler.make_request(request_params)
116
- return response unless retry_for_success?
117
- return response if (200..299).cover? @exchange_handler.status_code_for(response)
118
- sleep 0.5
119
- break response if count == retry_count
120
- end
121
- end
122
-
123
- # Stores a value in the api handler that can be accessed by the provided name
124
- # @param [Symbol] name Name of method to use to access this value within handler
125
- # @param [String] value Path to value to store
126
- def store(name, value)
127
- exchange_handler.store(name, self[value])
128
- end
129
-
130
- # Retrieve the stored value from the Api Handler
131
- # @param [String, Symbol] name Name of value to retrieve
132
- # @return [Object] value from the Api Handler stored previously
133
- def retrieve(name)
134
- method = '__stored_val__' + name.to_s
135
- raise ArgumentError('Value not stored at ') unless exchange_handler.respond_to? method
136
- exchange_handler.send(method)
137
- end
138
-
139
- # Name describing this class when used with `RSpec.describe`
140
- # This will make the request and store the response
141
- # @return [String] Name given when initializing
142
- def to_s
143
- test_name
144
- end
145
-
146
- # Returns response object from Api. Will make the request if not made and then cache it for later on
147
- # For example for SOAP it will be a Savon response
148
- # response.body (body of response as Hash)
149
- # response.header (head of response as Hash)
150
- def response
151
- @response ||= make_request
152
- end
153
-
154
- alias call response
155
-
156
- # Get status code from api class. This is http response for Web Api
157
- # @return [Integer] Status code from api class
158
- def status_code
159
- exchange_handler.status_code_for(response)
160
- end
161
-
162
- # Dummy request used to make a request without verifying it and ignoring WSDL errors
163
- # @return [Boolean] Always returns true. Unless of course an unexpected exception occurs
164
- def dummy_request
165
- make_request
166
- true
167
- rescue Savon::HTTPError
168
- puts 'Resolver error'
169
- # This seems to occur first time IP address asks for WSDL
170
- true
171
- end
172
-
173
- # @return [Boolean] Whether an element exists at the path
174
- def element?(path)
175
- [path]
176
- true
177
- rescue NoElementAtPath
178
- false
179
- end
180
-
181
- # Extract value from path api class
182
- # @param [Object] path Path to return element for api class E.g - for SOAP this is XPath string. For JSON, this is Hash dig Array
183
- # @return [String] Value at path
184
- def [](path)
185
- exchange_handler.value_from_path(response, path.to_s)
186
- end
187
-
188
- # Set a parameter request in the request body.
189
- # Can be used to build a request over several steps (e.g Cucumber)
190
- # Will be used with FactoryBot
191
- def []=(key, value)
192
- @override_parameters[:body] ||= {}
193
- @override_parameters[:body][key] = value
194
- end
195
-
196
- # Implement undefined setter with []= for FactoryBot to use without needing to define params to set
197
- # @param [Object] method_name Name of method not defined
198
- # @param [Object] args Arguments passed to method
199
- # @param [Object] block
200
- def method_missing(method_name, *args, &block)
201
- set_value = args.first
202
- if method_name[-1] == '=' # A setter method
203
- getter_name = method_name[0..-2]
204
- if set_value.class < Exchange # This would be prerequisite exchange
205
- define_singleton_method(getter_name) do
206
- set_value
207
- end
208
- self[getter_name] = set_value.id if set_value.respond_to?(:id)
209
- else
210
- self[getter_name] = set_value
211
- end
212
- else
213
- super
214
- end
215
- end
216
-
217
- # Used for setters that are not defined
218
- def respond_to_missing?(method_name, *args)
219
- method_name[-1] == '=' || super
220
- end
221
-
222
- # Makes request, caching the response and returning self
223
- # Used by FactoryBot
224
- # @return [Self]
225
- def save!
226
- @retry_for_success = @fail_factory ? false : true
227
- call
228
- self
229
- end
230
-
231
- def to_hash
232
- exchange_handler.to_hash(response)
233
- end
234
-
1
+ require_relative '../soaspec'
2
+
3
+ # Convenience methods to set Exchange specific properties
4
+ module ExchangeAccessors
5
+
6
+ # Set default exchange handler for this exchange
7
+ # This is helpful for when you need a new exchange handler created for each exchange
8
+ def default_handler(handler_class, name = handler_class.to_s, params = '')
9
+ define_method('default_handler_used') do
10
+ params_used = Hash[params.map do |k, param|
11
+ [k, param.is_a?(String) ? ERB.new(param).result(binding) : param]
12
+ end]
13
+ handler_class.new name, params_used
14
+ end
15
+ end
16
+
17
+ # Set retry_for_success to true, retrying response until a successful status code is returned
18
+ def expect_positive_status(retry_count: 3)
19
+ define_method('retry_count') do
20
+ retry_count
21
+ end
22
+ define_method('retry_for_success?') do
23
+ true
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ # This represents a request / response pair
30
+ # Essentially, params in the exchange that are set are related to the request
31
+ # What is returned is related to the response
32
+ class Exchange
33
+ extend ExchangeAccessors
34
+
35
+ # Instance of ExchangeHandler for which this exchange is made
36
+ attr_accessor :exchange_handler
37
+ # How many times to retry for a success
38
+ attr_accessor :retry_count
39
+ # Name used for displaying class
40
+ attr_accessor :test_name
41
+ # Expect Factory to fail upon trying to create
42
+ attr_writer :fail_factory
43
+
44
+
45
+ def values_from_path(path, attribute: nil)
46
+ exchange_handler.values_from_path(response, path, attribute: attribute)
47
+ end
48
+
49
+ # Set retry for success variable to true so that request will be retried
50
+ # for retry_count until it's true
51
+ def retry_for_success
52
+ @retry_for_success = true
53
+ self
54
+ end
55
+
56
+ # @return [Bool] Whether to keep making request until success code reached
57
+ def retry_for_success?
58
+ @retry_for_success
59
+ end
60
+
61
+ # @return [Boolean] Soaspec::ExchangeHandler used by this exchange
62
+ def default_handler_used
63
+ nil
64
+ end
65
+
66
+ # @param [String] element Element to define methods for
67
+ def methods_for_element(element)
68
+ element_name = element.to_s.split('__custom_path_').last
69
+ define_singleton_method(element_name) do
70
+ exchange_handler.__send__(element, response) # Forward the call onto handler to retrieve the element for the response
71
+ end
72
+ define_singleton_method("#{element_name}?") do
73
+ begin
74
+ __send__ element_name
75
+ true
76
+ rescue NoElementAtPath
77
+ false
78
+ end
79
+ end
80
+ end
81
+
82
+ # @param [Symbol, String] name Name shown in RSpec run
83
+ # @param [Hash] override_parameters Parameters to override for default params
84
+ def initialize(name = self.class.to_s, override_parameters = {})
85
+ self.test_name ||= name.to_s
86
+ # As a last resort this uses the global parameter. The handler should be set straight before an exchange is made to use this
87
+ @exchange_handler ||= default_handler_used || Soaspec.api_handler
88
+ raise '@exchange_handler not set. Set either with `Soaspec.api_handler = Handler.new` or within the exchange' unless @exchange_handler
89
+ @fail_factory = nil
90
+ @override_parameters = override_parameters
91
+ @retry_for_success = false
92
+ self.retry_count = 3
93
+ @exchange_handler.elements.each { |element| methods_for_element(element) }
94
+ end
95
+
96
+ # Specify a url to add onto the base_url of the ExchangeHandler used
97
+ # @param [String] url Url to add onto the base_url of the ExchangeHandler used
98
+ def suburl=(url)
99
+ @override_parameters[:suburl] = url
100
+ end
101
+
102
+ # Specify HTTP method to use. Default is :post
103
+ # @param [Symbol] method HTTP method. E.g, :get, :patch
104
+ def method=(method)
105
+ @override_parameters[:method] = method
106
+ end
107
+
108
+ # Make request to handler with parameters defined
109
+ # Will retry until success code reached if retry_for_success? is set
110
+ # @return [Response] Response from Api handler
111
+ def make_request
112
+ Soaspec::SpecLogger.info 'Example ' + test_name
113
+ request_params = @override_parameters
114
+ (1..retry_count).each do |count|
115
+ response = exchange_handler.make_request(request_params)
116
+ return response unless retry_for_success?
117
+ return response if (200..299).cover? @exchange_handler.status_code_for(response)
118
+ sleep 0.5
119
+ break response if count == retry_count
120
+ end
121
+ end
122
+
123
+ # Stores a value in the api handler that can be accessed by the provided name
124
+ # @param [Symbol] name Name of method to use to access this value within handler
125
+ # @param [String] value Path to value to store
126
+ def store(name, value)
127
+ exchange_handler.store(name, self[value])
128
+ end
129
+
130
+ # Retrieve the stored value from the Api Handler
131
+ # @param [String, Symbol] name Name of value to retrieve
132
+ # @return [Object] value from the Api Handler stored previously
133
+ def retrieve(name)
134
+ method = '__stored_val__' + name.to_s
135
+ raise ArgumentError('Value not stored at ') unless exchange_handler.respond_to? method
136
+ exchange_handler.send(method)
137
+ end
138
+
139
+ # Name describing this class when used with `RSpec.describe`
140
+ # This will make the request and store the response
141
+ # @return [String] Name given when initializing
142
+ def to_s
143
+ test_name
144
+ end
145
+
146
+ # Returns response object from Api. Will make the request if not made and then cache it for later on
147
+ # For example for SOAP it will be a Savon response
148
+ # response.body (body of response as Hash)
149
+ # response.header (head of response as Hash)
150
+ def response
151
+ @response ||= make_request
152
+ end
153
+
154
+ alias call response
155
+
156
+ # Get status code from api class. This is http response for Web Api
157
+ # @return [Integer] Status code from api class
158
+ def status_code
159
+ exchange_handler.status_code_for(response)
160
+ end
161
+
162
+ # Dummy request used to make a request without verifying it and ignoring WSDL errors
163
+ # @return [Boolean] Always returns true. Unless of course an unexpected exception occurs
164
+ def dummy_request
165
+ make_request
166
+ true
167
+ rescue Savon::HTTPError
168
+ puts 'Resolver error'
169
+ # This seems to occur first time IP address asks for WSDL
170
+ true
171
+ end
172
+
173
+ # @return [Boolean] Whether an element exists at the path
174
+ def element?(path)
175
+ [path]
176
+ true
177
+ rescue NoElementAtPath
178
+ false
179
+ end
180
+
181
+ # Extract value from path api class
182
+ # @param [Object] path Path to return element for api class E.g - for SOAP this is XPath string. For JSON, this is Hash dig Array
183
+ # @return [String] Value at path
184
+ def [](path)
185
+ exchange_handler.value_from_path(response, path.to_s)
186
+ end
187
+
188
+ # Set a parameter request in the request body.
189
+ # Can be used to build a request over several steps (e.g Cucumber)
190
+ # Will be used with FactoryBot
191
+ def []=(key, value)
192
+ @override_parameters[:body] ||= {}
193
+ @override_parameters[:body][key] = value
194
+ end
195
+
196
+ # Implement undefined setter with []= for FactoryBot to use without needing to define params to set
197
+ # @param [Object] method_name Name of method not defined
198
+ # @param [Object] args Arguments passed to method
199
+ # @param [Object] block
200
+ def method_missing(method_name, *args, &block)
201
+ set_value = args.first
202
+ if method_name[-1] == '=' # A setter method
203
+ getter_name = method_name[0..-2]
204
+ if set_value.class < Exchange # This would be prerequisite exchange
205
+ define_singleton_method(getter_name) do
206
+ set_value
207
+ end
208
+ self[getter_name] = set_value.id if set_value.respond_to?(:id)
209
+ else
210
+ self[getter_name] = set_value
211
+ end
212
+ else
213
+ super
214
+ end
215
+ end
216
+
217
+ # Used for setters that are not defined
218
+ def respond_to_missing?(method_name, *args)
219
+ method_name[-1] == '=' || super
220
+ end
221
+
222
+ # Makes request, caching the response and returning self
223
+ # Used by FactoryBot
224
+ # @return [Self]
225
+ def save!
226
+ @retry_for_success = @fail_factory ? false : true
227
+ call
228
+ self
229
+ end
230
+
231
+ def to_hash
232
+ exchange_handler.to_hash(response)
233
+ end
234
+
235
235
  end