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,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
- # Load this file for demoing Soaspec (e.g in IRB). Has common settings applicable for demoing
4
-
5
- require_relative '../soaspec'
6
- Soaspec::SpecLogger.output_to_terminal = true
1
+ # frozen_string_literal: true
2
+
3
+ # Load this file for demoing Soaspec (e.g in IRB). Has common settings applicable for demoing
4
+
5
+ require_relative '../soaspec'
6
+ Soaspec::SpecLogger.output_to_terminal = true
@@ -1,24 +1,24 @@
1
- # frozen_string_literal: true
2
-
3
- module Soaspec
4
- # Standard Error related to Soaspec
5
- class Error < StandardError; end
6
- # Error related to a response
7
- class ResponseError < Error; end
8
- # Error related to not having a recorded baseline yet
9
- class BaselineError < Error; end
10
- end
11
-
12
- # Raised to represent when there's no element at an Xpath
13
- class NoElementAtPath < Soaspec::Error
14
- def initialize(msg = 'No element at path found')
15
- super
16
- end
17
- end
18
-
19
- # Did not find any element by provided key in the Hash
20
- class NoElementInHash < Soaspec::Error
21
- def initialize(msg = 'No element in Hash found')
22
- super
23
- end
24
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Soaspec
4
+ # Standard Error related to Soaspec
5
+ class Error < StandardError; end
6
+ # Error related to a response
7
+ class ResponseError < Error; end
8
+ # Error related to not having a recorded baseline yet
9
+ class BaselineError < Error; end
10
+ end
11
+
12
+ # Raised to represent when there's no element at an Xpath
13
+ class NoElementAtPath < Soaspec::Error
14
+ def initialize(msg = 'No element at path found')
15
+ super
16
+ end
17
+ end
18
+
19
+ # Did not find any element by provided key in the Hash
20
+ class NoElementInHash < Soaspec::Error
21
+ def initialize(msg = 'No element in Hash found')
22
+ super
23
+ end
24
+ end
@@ -1,129 +1,131 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../soaspec'
4
- require_relative 'exchange_properties'
5
- require_relative 'exchange_extractor'
6
- require_relative 'request_builder'
7
- require_relative 'exchange_repeater'
8
- require_relative 'variable_storer'
9
-
10
- # This represents a request / response pair
11
- # Essentially, params in the exchange that are set are related to the request
12
- # What is returned is related to the response
13
- #
14
- # It is tied to an ExchangeHandler that needs to be defined either globally before it's created or in 'default_handler_used'
15
- class Exchange
16
- extend Soaspec::ExchangeProperties
17
- include Soaspec::ExchangeExtractor
18
- include Soaspec::RequestBuilder
19
- include Soaspec::ExchangeRepeater
20
- include Soaspec::VariableStorer
21
-
22
- # Instance of ExchangeHandler for which this exchange is made
23
- attr_accessor :exchange_handler
24
- # @return [Integer] How many times to retry for a success
25
- attr_accessor :retry_count
26
- # @return [Integer] Times request was retried before being returned
27
- attr_accessor :times_retried
28
- # @@return [String] Name used for displaying class
29
- attr_accessor :test_name
30
- # Expect Factory to fail upon trying to create
31
- attr_writer :fail_factory
32
- # Parameters to override for default params
33
- attr_accessor :override_parameters
34
-
35
- # Set retry for success variable to true so that request will be retried
36
- # for retry_count until it's true
37
- def retry_for_success
38
- @retry_for_success = true
39
- self
40
- end
41
-
42
- # This is set on an individual Exchange marking it as one that should be retried
43
- # @return [Bool] Whether to keep making request until success code reached
44
- def retry_for_success?
45
- @retry_for_success
46
- end
47
-
48
- # Defined as general rule from ExchangeHandler
49
- # @return [Boolean] Whether exception is an exception that must be retried
50
- def invalid_exception?
51
- !exchange_handler.retry_on_exceptions.find { |e| e == exchange_handler.exception.class }.nil?
52
- end
53
-
54
- # Override this in subclass to tie that subclass to an ExchangeHandler
55
- # @return [Soaspec::ExchangeHandler] Soaspec::ExchangeHandler used by this exchange
56
- def default_handler_used; end
57
-
58
- # Create new Exchange according to parameters set. A response will be made if called
59
- # explicitly with 'response' method or through other methods that use it like 'status_code'
60
- # @param [Symbol, String] name Name shown in RSpec run
61
- # @param [Hash] override_parameters Parameters to override for default params
62
- def initialize(name = self.class.to_s, override_parameters = {})
63
- self.test_name ||= name.to_s
64
- # As a last resort this uses the global parameter. The handler should be set straight before an exchange is made to use this
65
- @exchange_handler ||= default_handler_used || Soaspec.api_handler
66
- raise '@exchange_handler not set. Set either with `Soaspec.api_handler = Handler.new` or within the exchange' unless @exchange_handler
67
-
68
- @fail_factory = nil
69
- @override_parameters = override_parameters
70
- @retry_for_success = false
71
- self.retry_count = exchange_handler.retry_exception_limit
72
- exchange_handler.elements.each { |element| methods_for_element(element) }
73
- end
74
-
75
- # @return [Hash] Hash representing what will be sent
76
- def request_parameters
77
- exchange_handler.request_parameters(@override_parameters)
78
- end
79
-
80
- # Make request to handler with parameters defined
81
- # Will retry until success code reached if retry_for_success? is set
82
- # @return [Response] Response from Api handler
83
- def make_request
84
- Soaspec::SpecLogger.info 'Example ' + test_name
85
- request_params = @override_parameters
86
- (0..retry_count).each do |count|
87
- response = exchange_handler.make_request(request_params)
88
- return response if !retry_for_success? && !invalid_exception?
89
- return response if (200..299).cover? exchange_handler.status_code_for(response)
90
-
91
- sleep exchange_handler.retry_pause_time # Time before retrying
92
- self.times_retried = count
93
- break response if count == retry_count
94
- end
95
- end
96
-
97
- # Name describing this class when used with `RSpec.describe`
98
- # This will make the request and store the response
99
- # @return [String] Name given when initializing
100
- def to_s
101
- test_name
102
- end
103
-
104
- # @return [RestClient::Response,Savon::Response] Returns response object from Api.
105
- # Will make the request if not made and then cache it for later on
106
- # @example For SOAP it will be a Savon response
107
- # response.body (body of response as Hash)
108
- # response.header (head of response as Hash)
109
- # @example For REST it will be a RestClient::Response
110
- def response
111
- require 'forwardable'
112
- Soaspec.last_exchange = self
113
- @response ||= make_request
114
- @response.define_singleton_method(:exchange) { Soaspec.last_exchange } unless @response.respond_to?(:exchange)
115
- @response.extend Forwardable
116
- @response.delegate %i[value_from_path values_from_path] => :exchange
117
- @response
118
- end
119
-
120
- # @return [ResponseObject] Currently returning response object. This will change (in 0.3) to be itself to
121
- # allow easy method chaining
122
- def call
123
- if Soaspec.log_warnings
124
- warn 'This "call" method will be changed to return "Exchange" object in 0.3. ' \
125
- 'Use "response" method if you want the "response" object'
126
- end
127
- response
128
- end
129
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../soaspec'
4
+ require_relative 'exchange_properties'
5
+ require_relative 'exchange_extractor'
6
+ require_relative 'request_builder'
7
+ require_relative 'exchange_repeater'
8
+ require_relative 'variable_storer'
9
+
10
+ # This represents a request / response pair
11
+ # Essentially, params in the exchange that are set are related to the request
12
+ # What is returned is related to the response
13
+ #
14
+ # It is tied to an ExchangeHandler that needs to be defined either globally before it's created or in 'default_handler_used'
15
+ class Exchange
16
+ extend Soaspec::ExchangeProperties
17
+ include Soaspec::ExchangeExtractor
18
+ include Soaspec::RequestBuilder
19
+ include Soaspec::ExchangeRepeater
20
+ include Soaspec::VariableStorer
21
+
22
+ # @return [ExchangeHandler] Instance of ExchangeHandler for which this exchange is made
23
+ attr_accessor :exchange_handler
24
+ # @return [Integer] How many times to retry for a success
25
+ attr_accessor :retry_count
26
+ # @return [Integer] Times request was retried before being returned
27
+ attr_accessor :times_retried
28
+ # @return [String] Name used for displaying class
29
+ attr_accessor :test_name
30
+ # @return [Boolean] Expect Factory to fail upon trying to create
31
+ attr_writer :fail_factory
32
+ # @return [Hash] Parameters to override for default params defined in ExchangeHandler
33
+ # These are the parameters specific to the Exchange and will override, append to
34
+ # what's defined in the ExchangeHandler
35
+ attr_accessor :override_parameters
36
+
37
+ # Set retry for success variable to true so that request will be retried
38
+ # for retry_count until it's true
39
+ def retry_for_success
40
+ @retry_for_success = true
41
+ self
42
+ end
43
+
44
+ # This is set on an individual Exchange marking it as one that should be retried
45
+ # @return [Bool] Whether to keep making request until success code reached
46
+ def retry_for_success?
47
+ @retry_for_success
48
+ end
49
+
50
+ # Defined as general rule from ExchangeHandler
51
+ # @return [Boolean] Whether exception is an exception that must be retried
52
+ def invalid_exception?
53
+ !exchange_handler.retry_on_exceptions.find { |e| e == exchange_handler.exception.class }.nil?
54
+ end
55
+
56
+ # Override this in subclass to tie that subclass to an ExchangeHandler
57
+ # @return [Soaspec::ExchangeHandler] Soaspec::ExchangeHandler used by this exchange
58
+ def default_handler_used; end
59
+
60
+ # Create new Exchange according to parameters set. A response will be made if called
61
+ # explicitly with 'response' method or through other methods that use it like 'status_code'
62
+ # @param [Symbol, String] name Name shown in RSpec run
63
+ # @param [Hash] override_parameters Parameters to override for default params (set through ExchangeHandler or Exchange class)
64
+ # These are the parameters that would be specific for a test
65
+ def initialize(name = self.class.to_s, override_parameters = {})
66
+ if name.is_a? Hash # Name not provided
67
+ override_parameters = name
68
+ name = nil
69
+ end
70
+ self.test_name ||= name.to_s
71
+ @override_parameters = override_parameters
72
+ # As a last resort this uses the global parameter. The handler should be set straight before an exchange is made to use this
73
+ @exchange_handler ||= default_handler_used || Soaspec.api_handler
74
+ @fail_factory = nil
75
+ @retry_for_success = false
76
+ self.retry_count = exchange_handler.retry_exception_limit
77
+ exchange_handler.elements.each { |element| methods_for_element(element) }
78
+ end
79
+
80
+ # @return [Hash] Hash representing what will be sent
81
+ def request_parameters
82
+ exchange_handler.request_parameters(@override_parameters)
83
+ end
84
+
85
+ # Make request to handler with parameters defined
86
+ # Will retry until success code reached if retry_for_success? is set
87
+ # @return [Response] Response from Api handler
88
+ def make_request
89
+ Soaspec::SpecLogger.info 'Example ' + test_name
90
+ request_params = @override_parameters
91
+ (0..retry_count).each do |count|
92
+ response = exchange_handler.make_request(request_params)
93
+ return response if !retry_for_success? && !invalid_exception?
94
+ return response if (200..299).cover? exchange_handler.status_code_for(response)
95
+
96
+ sleep exchange_handler.retry_pause_time # Time before retrying
97
+ self.times_retried = count
98
+ break response if count == retry_count
99
+ end
100
+ end
101
+
102
+ # Name describing this class when used with `RSpec.describe`
103
+ # This will make the request and store the response
104
+ # @return [String] Name given when initializing
105
+ def to_s
106
+ test_name
107
+ end
108
+
109
+ # @return [RestClient::Response,Savon::Response] Returns response object from Api.
110
+ # Will make the request if not made and then cache it for later on
111
+ # @example For SOAP it will be a Savon response
112
+ # response.body (body of response as Hash)
113
+ # response.header (head of response as Hash)
114
+ # @example For REST it will be a RestClient::Response
115
+ def response
116
+ require 'forwardable'
117
+ Soaspec.last_exchange = self
118
+ @response ||= make_request
119
+ @response.define_singleton_method(:exchange) { Soaspec.last_exchange } unless @response.respond_to?(:exchange)
120
+ @response.extend Forwardable
121
+ @response.delegate %i[value_from_path values_from_path] => :exchange
122
+ @response
123
+ end
124
+
125
+ # @return [ResponseObject] Currently returning response object. This will change (in 0.3) to be itself to
126
+ # allow easy method chaining
127
+ def call
128
+ response
129
+ self
130
+ end
131
+ end
@@ -1,90 +1,105 @@
1
- # frozen_string_literal: true
2
-
3
- module Soaspec
4
- # Methods for extracting aspects of the traffic for a request / response
5
- # in an exchange from the ExchangeHandler that it's tied to
6
- module ExchangeExtractor
7
- # Request of API call. Either intended request or actual request
8
- # @return [Object] Object representing request of API
9
- def request
10
- exchange_handler.request(@response)
11
- end
12
-
13
- # Get status code from api class. This is http response code for Web Api
14
- # @return [Integer] Status code from api class
15
- def status_code
16
- exchange_handler.status_code_for(response)
17
- end
18
-
19
- # @return [Boolean] Whether Api success code is successful
20
- def successful_status_code?
21
- (200..299).cover? status_code
22
- end
23
-
24
- # Extract value from path api class
25
- # @example Extract unique value
26
- # @exchange['unique_value_name']
27
- # @example Extract value via JSON path
28
- # @exchange['$..path.to.element']
29
- # @example Extract value via XPath
30
- # @exchange['//path/to/element']
31
- # @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
32
- # @return [String] Value at path
33
- def [](path)
34
- exchange_handler.value_from_path(response, path.to_s)
35
- end
36
-
37
- alias value_from_path []
38
-
39
- # Using same path syntax as []. Returns true of false depending on whether an element is found
40
- # @return [Boolean] Whether an element exists at the path
41
- def element?(path)
42
- self[path]
43
- true
44
- rescue NoElementAtPath
45
- false
46
- end
47
-
48
- # @example Counting items in a JSON list
49
- # # Say there is JSON response {"notes":[{"title":"note1","note":"A note"},{"title":"note2"}]}
50
- # titles = @exchange.values_at_path('$..title')
51
- # expect(titles.count).to eq 2
52
- # expect(titles.first).to eq 'note1'
53
- # @param [String] path XPath, JSONPath to extract value
54
- # @param [String] attribute Attribute to obtain from XML element
55
- # @return [Array] List of values found at path
56
- def values_from_path(path, attribute: nil)
57
- exchange_handler.values_from_path(response, path, attribute: attribute)
58
- end
59
-
60
- # Return the response equivalent of the response. XML, JSON will be converted to a Hash
61
- # @example Counting items in a JSON list
62
- # # Say there is JSON response {"notes":[{"title":"note1","note":"A note"},{"title":"note2"}]}
63
- # hash = @exchange.to_hash
64
- # expect(hash['notes'].count).to eq 2
65
- # expect(hash['notes'].first['title']).to eq 'note1'
66
- # @return [Hash] Hash representing the response of the API
67
- def to_hash
68
- exchange_handler.to_hash(response)
69
- end
70
-
71
- private
72
-
73
- # Used to define methods on an exchange based on what's defined by the ExchangeHandler's methods
74
- # @param [String] element Element to define methods for
75
- def methods_for_element(element)
76
- element_name = element.to_s.split('__custom_path_').last
77
- define_singleton_method(element_name) do
78
- exchange_handler.__send__(element, response) # Forward the call onto handler to retrieve the element for the response
79
- end
80
- define_singleton_method("#{element_name}?") do
81
- begin
82
- __send__ element_name
83
- true
84
- rescue NoElementAtPath
85
- false
86
- end
87
- end
88
- end
89
- end
90
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Soaspec
4
+ # Methods for extracting aspects of the traffic for a request / response
5
+ # in an exchange from the ExchangeHandler that it's tied to
6
+ module ExchangeExtractor
7
+ # Request of API call. Either intended request or actual request
8
+ # @return [Object] Object representing request of API
9
+ def request
10
+ exchange_handler.request(@response)
11
+ end
12
+
13
+ # Get status code from api class. This is http response code for Web Api
14
+ # @return [Integer] Status code from api class
15
+ def status_code
16
+ exchange_handler.status_code_for(response)
17
+ end
18
+
19
+ # @return [Boolean] Whether Api success code is successful
20
+ def successful_status_code?
21
+ (200..299).cover? status_code
22
+ end
23
+
24
+ # Extract value from path api class
25
+ # @example Extract unique value
26
+ # @exchange['unique_value_name']
27
+ # @example Extract value via JSON path
28
+ # @exchange['$..path.to.element']
29
+ # @example Extract value via XPath
30
+ # @exchange['//path/to/element']
31
+ # @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
32
+ # @return [String] Value at path
33
+ def [](path)
34
+ exchange_handler.value_from_path(response, path.to_s)
35
+ end
36
+
37
+ alias value_from_path []
38
+
39
+ # @return [Symbol] Type of response. XML, JSON, etc
40
+ def format
41
+ Interpreter.response_type_for(response)
42
+ end
43
+
44
+ # Using same path syntax as []. Returns true of false depending on whether an element is found
45
+ # @return [Boolean] Whether an element exists at the path
46
+ def element?(path)
47
+ self[path]
48
+ true
49
+ rescue NoElementAtPath
50
+ false
51
+ end
52
+
53
+ # @example Counting items in a JSON list
54
+ # # Say there is JSON response {"notes":[{"title":"note1","note":"A note"},{"title":"note2"}]}
55
+ # titles = @exchange.values_at_path('$..title')
56
+ # expect(titles.count).to eq 2
57
+ # expect(titles.first).to eq 'note1'
58
+ # @param [String] path XPath, JSONPath to extract value
59
+ # @param [String] attribute Attribute to obtain from XML element
60
+ # @return [Array] List of values found at path
61
+ def values_from_path(path, attribute: nil)
62
+ exchange_handler.values_from_path(response, path, attribute: attribute)
63
+ end
64
+
65
+ # Return the response equivalent of the response. XML, JSON will be converted to a Hash
66
+ # @example Counting items in a JSON list
67
+ # # Say there is JSON response {"notes":[{"title":"note1","note":"A note"},{"title":"note2"}]}
68
+ # hash = @exchange.to_hash
69
+ # expect(hash['notes'].count).to eq 2
70
+ # expect(hash['notes'].first['title']).to eq 'note1'
71
+ # @return [Hash] Hash representing the response of the API
72
+ def to_hash
73
+ exchange_handler.to_hash(response)
74
+ end
75
+
76
+ # @return [String] Get multiline pretty version of response
77
+ def pretty_response_body
78
+ case format
79
+ when :json then JSON.pretty_generate to_hash
80
+ when :xml then response.body # TODO: Single line XML make multiline
81
+ else
82
+ response.body
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # Used to define methods on an exchange based on what's defined by the ExchangeHandler's methods
89
+ # @param [String] element Element to define methods for
90
+ def methods_for_element(element)
91
+ element_name = element.to_s.split('__custom_path_').last
92
+ define_singleton_method(element_name) do
93
+ exchange_handler.__send__(element, response) # Forward the call onto handler to retrieve the element for the response
94
+ end
95
+ define_singleton_method("#{element_name}?") do
96
+ begin
97
+ __send__ element_name
98
+ true
99
+ rescue NoElementAtPath
100
+ false
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end