vcr 2.4.0 → 2.5.0

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 (85) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +2 -0
  4. data/CHANGELOG.md +31 -0
  5. data/Gemfile +1 -2
  6. data/Gemfile.lock +63 -46
  7. data/README.md +5 -2
  8. data/features/cassettes/allow_unused_http_interactions.feature +1 -1
  9. data/features/cassettes/automatic_re_recording.feature +1 -1
  10. data/features/cassettes/decompress.feature +3 -3
  11. data/features/cassettes/dynamic_erb.feature +2 -2
  12. data/features/cassettes/exclusive.feature +1 -1
  13. data/features/cassettes/naming.feature +1 -1
  14. data/features/cassettes/no_cassette.feature +6 -3
  15. data/features/cassettes/persistence.feature +1 -1
  16. data/features/cassettes/update_content_length_header.feature +1 -1
  17. data/features/configuration/allow_http_connections_when_no_cassette.feature +1 -1
  18. data/features/configuration/cassette_library_dir.feature +1 -1
  19. data/features/configuration/debug_logging.feature +5 -5
  20. data/features/configuration/filter_sensitive_data.feature +2 -2
  21. data/features/configuration/hook_into.feature +4 -7
  22. data/features/getting_started.md +2 -2
  23. data/features/hooks/before_playback.feature +5 -5
  24. data/features/hooks/before_record.feature +5 -5
  25. data/features/middleware/rack.feature +2 -2
  26. data/features/record_modes/all.feature +1 -1
  27. data/features/record_modes/new_episodes.feature +1 -1
  28. data/features/record_modes/none.feature +1 -1
  29. data/features/record_modes/once.feature +1 -1
  30. data/features/request_matching/custom_matcher.feature +1 -1
  31. data/features/request_matching/headers.feature +0 -2
  32. data/features/request_matching/playback_repeats.feature +1 -1
  33. data/features/request_matching/uri_without_param.feature +1 -1
  34. data/features/support/env.rb +1 -0
  35. data/features/test_frameworks/cucumber.feature +8 -8
  36. data/features/test_frameworks/rspec_macro.feature +2 -2
  37. data/features/test_frameworks/rspec_metadata.feature +1 -1
  38. data/gemfiles/typhoeus_old.gemfile +1 -1
  39. data/gemfiles/typhoeus_old.gemfile.lock +31 -57
  40. data/lib/vcr/cassette/migrator.rb +8 -1
  41. data/lib/vcr/configuration.rb +9 -2
  42. data/lib/vcr/library_hooks/excon.rb +2 -184
  43. data/lib/vcr/library_hooks/typhoeus.rb +1 -1
  44. data/lib/vcr/library_hooks/typhoeus_0.4.rb +4 -0
  45. data/lib/vcr/library_hooks/webmock.rb +1 -1
  46. data/lib/vcr/middleware/excon.rb +226 -0
  47. data/lib/vcr/version.rb +1 -1
  48. data/spec/acceptance/threading_spec.rb +28 -0
  49. data/spec/monkey_patches.rb +3 -7
  50. data/spec/quality_spec.rb +1 -1
  51. data/spec/spec_helper.rb +7 -4
  52. data/spec/support/http_library_adapters.rb +4 -3
  53. data/spec/support/shared_example_groups/excon.rb +22 -0
  54. data/spec/support/shared_example_groups/hook_into_http_library.rb +46 -46
  55. data/spec/support/shared_example_groups/request_hooks.rb +8 -8
  56. data/spec/vcr/cassette/erb_renderer_spec.rb +5 -5
  57. data/spec/vcr/cassette/http_interaction_list_spec.rb +52 -40
  58. data/spec/vcr/cassette/migrator_spec.rb +11 -11
  59. data/spec/vcr/cassette/persisters/file_system_spec.rb +11 -11
  60. data/spec/vcr/cassette/persisters_spec.rb +2 -2
  61. data/spec/vcr/cassette/serializers_spec.rb +13 -12
  62. data/spec/vcr/cassette_spec.rb +58 -58
  63. data/spec/vcr/configuration_spec.rb +43 -31
  64. data/spec/vcr/deprecations_spec.rb +3 -3
  65. data/spec/vcr/errors_spec.rb +25 -25
  66. data/spec/vcr/extensions/net_http_response_spec.rb +7 -7
  67. data/spec/vcr/library_hooks/excon_spec.rb +7 -85
  68. data/spec/vcr/library_hooks/fakeweb_spec.rb +15 -13
  69. data/spec/vcr/library_hooks/faraday_spec.rb +4 -4
  70. data/spec/vcr/library_hooks/typhoeus_0.4_spec.rb +5 -0
  71. data/spec/vcr/library_hooks/typhoeus_spec.rb +3 -3
  72. data/spec/vcr/library_hooks/webmock_spec.rb +13 -5
  73. data/spec/vcr/library_hooks_spec.rb +9 -9
  74. data/spec/vcr/middleware/faraday_spec.rb +10 -10
  75. data/spec/vcr/middleware/rack_spec.rb +20 -15
  76. data/spec/vcr/request_ignorer_spec.rb +3 -3
  77. data/spec/vcr/request_matcher_registry_spec.rb +88 -61
  78. data/spec/vcr/structs_spec.rb +85 -85
  79. data/spec/vcr/test_frameworks/cucumber_spec.rb +7 -7
  80. data/spec/vcr/test_frameworks/rspec_spec.rb +10 -10
  81. data/spec/vcr/util/hooks_spec.rb +20 -20
  82. data/spec/vcr/util/internet_connection_spec.rb +2 -2
  83. data/spec/vcr_spec.rb +50 -48
  84. data/vcr.gemspec +4 -4
  85. metadata +308 -372
@@ -101,9 +101,16 @@ module VCR
101
101
  join('-')
102
102
  end
103
103
 
104
+ EMPTY_STRING = if String.method_defined?(:force_encoding)
105
+ ''.force_encoding("US-ASCII")
106
+ else
107
+ ''
108
+ end
109
+
104
110
  def normalize_body(object)
105
- object.body = '' if object.body.nil?
111
+ object.body = EMPTY_STRING if object.body.nil?
106
112
  end
113
+
107
114
  end
108
115
  end
109
116
  end
@@ -392,13 +392,16 @@ module VCR
392
392
  "VCR::Configuration#around_http_request requires fibers, " +
393
393
  "which are not available on your ruby intepreter."
394
394
  else
395
- fiber, hook_allowed, hook_decaration = nil, false, caller.first
395
+ fibers = {}
396
+ hook_allowed, hook_decaration = false, caller.first
396
397
  before_http_request(*filters) do |request|
397
398
  hook_allowed = true
398
399
  fiber = start_new_fiber_for(request, block)
400
+ fibers[Thread.current] = fiber
399
401
  end
400
402
 
401
403
  after_http_request(lambda { hook_allowed }) do |request, response|
404
+ fiber = fibers.delete(Thread.current)
402
405
  resume_fiber(fiber, response, hook_decaration)
403
406
  end
404
407
  end
@@ -406,7 +409,10 @@ module VCR
406
409
  # Configures RSpec to use a VCR cassette for any example
407
410
  # tagged with `:vcr`.
408
411
  def configure_rspec_metadata!
409
- VCR::RSpec::Metadata.configure!
412
+ unless @rspec_metadata_configured
413
+ VCR::RSpec::Metadata.configure!
414
+ @rspec_metadata_configured = true
415
+ end
410
416
  end
411
417
 
412
418
  # An object to log debug output to.
@@ -462,6 +468,7 @@ module VCR
462
468
 
463
469
  def initialize
464
470
  @allow_http_connections_when_no_cassette = nil
471
+ @rspec_metadata_configured = false
465
472
  @default_cassette_options = {
466
473
  :record => :once,
467
474
  :match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
@@ -1,188 +1,6 @@
1
- require 'vcr/util/version_checker'
2
- require 'vcr/request_handler'
3
- require 'excon'
1
+ require 'vcr/middleware/excon'
4
2
 
5
- VCR::VersionChecker.new('Excon', Excon::VERSION, '0.9.6', '0.16').check_version!
6
-
7
- module VCR
8
- class LibraryHooks
9
- # @private
10
- module Excon
11
- class RequestHandler < ::VCR::RequestHandler
12
- attr_reader :params
13
- def initialize(params)
14
- @vcr_response = nil
15
- @params = params
16
- end
17
-
18
- def handle
19
- super
20
- ensure
21
- invoke_after_request_hook(@vcr_response)
22
- end
23
-
24
- private
25
-
26
- def on_stubbed_by_vcr_request
27
- @vcr_response = stubbed_response
28
- {
29
- :body => stubbed_response.body,
30
- :headers => normalized_headers(stubbed_response.headers || {}),
31
- :status => stubbed_response.status.code
32
- }
33
- end
34
-
35
- def on_ignored_request
36
- perform_real_request
37
- end
38
-
39
- def response_from_excon_error(error)
40
- if error.respond_to?(:response)
41
- error.response
42
- elsif error.respond_to?(:socket_error)
43
- response_from_excon_error(error.socket_error)
44
- else
45
- warn "WARNING: VCR could not extract a response from Excon error (#{error.inspect})"
46
- end
47
- end
48
-
49
- PARAMS_TO_DELETE = [:expects, :idempotent,
50
- :instrumentor_name, :instrumentor,
51
- :response_block, :request_block]
52
-
53
- def real_request_params
54
- # Excon supports a variety of options that affect how it handles failure
55
- # and retry; we don't want to use any options here--we just want to get
56
- # a raw response, and then the main request (with :mock => true) can
57
- # handle failure/retry on its own with its set options.
58
- scrub_params_from params.merge(:mock => false, :retry_limit => 0)
59
- end
60
-
61
- def new_connection
62
- # Ensure the connection is constructed with the exact same args
63
- # that the orginal connection was constructed with.
64
- args, options = params.fetch(:__construction_args)
65
- options = scrub_params_from(options) if options.is_a?(Hash)
66
- ::Excon::Connection.new(*[args, options].compact)
67
- end
68
-
69
- def scrub_params_from(hash)
70
- hash = hash.dup
71
- PARAMS_TO_DELETE.each { |key| hash.delete(key) }
72
- hash
73
- end
74
-
75
- def perform_real_request
76
- begin
77
- response = new_connection.request(real_request_params)
78
- rescue ::Excon::Errors::Error => excon_error
79
- response = response_from_excon_error(excon_error)
80
- end
81
-
82
- @vcr_response = vcr_response_from(response)
83
- yield response if block_given?
84
- raise excon_error if excon_error
85
-
86
- response.attributes
87
- end
88
-
89
- def on_recordable_request
90
- perform_real_request do |response|
91
- http_interaction = http_interaction_for(response)
92
- VCR.record_http_interaction(http_interaction)
93
- end
94
- end
95
-
96
- def uri
97
- @uri ||= "#{params[:scheme]}://#{params[:host]}:#{params[:port]}#{params[:path]}#{query}"
98
- end
99
-
100
- # based on:
101
- # https://github.com/geemus/excon/blob/v0.7.8/lib/excon/connection.rb#L117-132
102
- def query
103
- @query ||= case params[:query]
104
- when String
105
- "?#{params[:query]}"
106
- when Hash
107
- qry = '?'
108
- for key, values in params[:query]
109
- if values.nil?
110
- qry << key.to_s << '&'
111
- else
112
- for value in [*values]
113
- qry << key.to_s << '=' << CGI.escape(value.to_s) << '&'
114
- end
115
- end
116
- end
117
- qry.chop! # remove trailing '&'
118
- else
119
- ''
120
- end
121
- end
122
-
123
- def http_interaction_for(response)
124
- VCR::HTTPInteraction.new \
125
- vcr_request,
126
- vcr_response_from(response)
127
- end
128
-
129
- def vcr_request
130
- @vcr_request ||= begin
131
- headers = params[:headers].dup
132
- headers.delete("Host")
133
-
134
- VCR::Request.new \
135
- params[:method],
136
- uri,
137
- params[:body],
138
- headers
139
- end
140
- end
141
-
142
- def vcr_response_from(response)
143
- VCR::Response.new \
144
- VCR::ResponseStatus.new(response.status, nil),
145
- response.headers,
146
- response.body,
147
- nil
148
- end
149
-
150
- def normalized_headers(headers)
151
- normalized = {}
152
- headers.each do |k, v|
153
- v = v.join(', ') if v.respond_to?(:join)
154
- normalized[k] = v
155
- end
156
- normalized
157
- end
158
-
159
- ::Excon.stub({}) do |params|
160
- self.new(params).handle
161
- end
162
- end
163
-
164
- end
165
- end
166
- end
167
-
168
- ::Excon.defaults[:mock] = true
169
-
170
- # We want to get at the Excon::Connection class but WebMock does
171
- # some constant-replacing stuff to it, so we need to take that into
172
- # account.
173
- excon_connection = if defined?(::WebMock::HttpLibAdapters::ExconConnection)
174
- ::WebMock::HttpLibAdapters::ExconConnection.superclass
175
- else
176
- ::Excon::Connection
177
- end
178
-
179
- excon_connection.class_eval do
180
- def self.new(*args)
181
- super.tap do |instance|
182
- instance.connection[:__construction_args] = args
183
- end
184
- end
185
- end
3
+ Excon.defaults[:middlewares] << VCR::Middleware::Excon
186
4
 
187
5
  VCR.configuration.after_library_hooks_loaded do
188
6
  # ensure WebMock's Excon adapter does not conflict with us here
@@ -5,7 +5,7 @@ require 'typhoeus'
5
5
  if Float(Typhoeus::VERSION[/^\d+\.\d+/]) < 0.5
6
6
  require 'vcr/library_hooks/typhoeus_0.4'
7
7
  else
8
- VCR::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.5.0', '0.5').check_version!
8
+ VCR::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.5.0', '0.6').check_version!
9
9
 
10
10
  module VCR
11
11
  class LibraryHooks
@@ -97,3 +97,7 @@ module Typhoeus
97
97
  end unless Hydra.respond_to?(:allow_net_connect_with_vcr?)
98
98
  end
99
99
 
100
+ VCR.configuration.after_library_hooks_loaded do
101
+ ::Kernel.warn "WARNING: VCR's Typhoeus 0.4 integration is deprecated and will be removed in VCR 3.0."
102
+ end
103
+
@@ -2,7 +2,7 @@ require 'vcr/util/version_checker'
2
2
  require 'vcr/request_handler'
3
3
  require 'webmock'
4
4
 
5
- VCR::VersionChecker.new('WebMock', WebMock.version, '1.8.0', '1.9').check_version!
5
+ VCR::VersionChecker.new('WebMock', WebMock.version, '1.8.0', '1.11').check_version!
6
6
 
7
7
  module VCR
8
8
  class LibraryHooks
@@ -0,0 +1,226 @@
1
+ require 'excon'
2
+ require 'vcr/request_handler'
3
+ require 'vcr/util/version_checker'
4
+
5
+ VCR::VersionChecker.new('Excon', Excon::VERSION, '0.22.0', '0.22').check_version!
6
+
7
+ module VCR
8
+ # Contains middlewares for use with different libraries.
9
+ module Middleware
10
+ # Excon middleware that uses VCR to record and replay HTTP requests made
11
+ # through Excon.
12
+ #
13
+ # @note You can either add this to the middleware stack of an Excon connection
14
+ # yourself, or configure {VCR::Configuration#hook_into} to hook into `:excon`.
15
+ # Setting the config option will add this middleware to Excon's default
16
+ # middleware stack.
17
+ class Excon < ::Excon::Middleware::Base
18
+ # @private
19
+ def initialize(*args)
20
+ # Excon appears to create a new instance of this middleware for each
21
+ # request, which means it should be safe to store per-request state
22
+ # like this request_handler object on the middleware instance.
23
+ # I'm not 100% sure about this yet and should verify with @geemus.
24
+ @request_handler = RequestHandler.new
25
+ super
26
+ end
27
+
28
+ # @private
29
+ def request_call(params)
30
+ @request_handler.before_request(params)
31
+ super
32
+ end
33
+
34
+ # @private
35
+ def response_call(params)
36
+ @request_handler.after_request(params)
37
+ super
38
+ end
39
+
40
+ # @private
41
+ def error_call(params)
42
+ @request_handler.after_request(params)
43
+ super
44
+ end
45
+
46
+ # Handles a single Excon request.
47
+ #
48
+ # @private
49
+ class RequestHandler < ::VCR::RequestHandler
50
+ def initialize
51
+ @request_params = nil
52
+ @response_params = nil
53
+ @response_body_reader = nil
54
+ @should_record = false
55
+ end
56
+
57
+ # Performs before_request processing based on the provided
58
+ # request_params.
59
+ #
60
+ # @private
61
+ def before_request(request_params)
62
+ @request_params = request_params
63
+ @response_body_reader = create_response_body_reader
64
+ handle
65
+ end
66
+
67
+ # Performs after_request processing based on the provided
68
+ # response_params.
69
+ #
70
+ # @private
71
+ def after_request(response_params)
72
+ # If @response_params is already set, it indicates we've already run the
73
+ # after_request logic. This can happen when if the response triggers an error,
74
+ # whch would then trigger the error_call middleware callback, leading to this
75
+ # being called a second time.
76
+ return if @response_params
77
+
78
+ @response_params = response_params
79
+
80
+ if should_record?
81
+ VCR.record_http_interaction(VCR::HTTPInteraction.new(vcr_request, vcr_response))
82
+ end
83
+
84
+ invoke_after_request_hook(vcr_response)
85
+ end
86
+
87
+ attr_reader :request_params, :response_params, :response_body_reader
88
+
89
+ private
90
+
91
+ def should_record?
92
+ @should_record
93
+ end
94
+
95
+ def on_stubbed_by_vcr_request
96
+ request_params[:response] = {
97
+ :body => stubbed_response.body,
98
+ :headers => normalized_headers(stubbed_response.headers || {}),
99
+ :status => stubbed_response.status.code
100
+ }
101
+ end
102
+
103
+ def on_recordable_request
104
+ @should_record = true
105
+ end
106
+
107
+ def create_response_body_reader
108
+ block = request_params[:response_block]
109
+ return NonStreamingResponseBodyReader unless block
110
+
111
+ StreamingResponseBodyReader.new(block).tap do |response_block_wrapper|
112
+ request_params[:response_block] = response_block_wrapper
113
+ end
114
+ end
115
+
116
+ def vcr_request
117
+ @vcr_request ||= begin
118
+ headers = request_params[:headers].dup
119
+ headers.delete("Host")
120
+
121
+ VCR::Request.new \
122
+ request_params[:method],
123
+ uri,
124
+ request_params[:body],
125
+ headers
126
+ end
127
+ end
128
+
129
+ def vcr_response
130
+ return @vcr_response if defined?(@vcr_response)
131
+
132
+ if should_record? || response_params.has_key?(:response)
133
+ response = response_params.fetch(:response)
134
+
135
+ @vcr_response = VCR::Response.new(
136
+ VCR::ResponseStatus.new(response.fetch(:status), nil),
137
+ response.fetch(:headers),
138
+ response_body_reader.read_body_from(response),
139
+ nil
140
+ )
141
+ else
142
+ @vcr_response = nil
143
+ end
144
+ end
145
+
146
+ def normalized_headers(headers)
147
+ normalized = {}
148
+ headers.each do |k, v|
149
+ v = v.join(', ') if v.respond_to?(:join)
150
+ normalized[k] = v
151
+ end
152
+ normalized
153
+ end
154
+
155
+ def uri
156
+ @uri ||= "#{request_params[:scheme]}://#{request_params[:host]}:#{request_params[:port]}#{request_params[:path]}#{query}"
157
+ end
158
+
159
+ # based on:
160
+ # https://github.com/geemus/excon/blob/v0.7.8/lib/excon/connection.rb#L117-132
161
+ def query
162
+ @query ||= case request_params[:query]
163
+ when String
164
+ "?#{request_params[:query]}"
165
+ when Hash
166
+ qry = '?'
167
+ for key, values in request_params[:query]
168
+ if values.nil?
169
+ qry << key.to_s << '&'
170
+ else
171
+ for value in [*values]
172
+ qry << key.to_s << '=' << CGI.escape(value.to_s) << '&'
173
+ end
174
+ end
175
+ end
176
+ qry.chop! # remove trailing '&'
177
+ else
178
+ ''
179
+ end
180
+ end
181
+ end
182
+
183
+ # Wraps an Excon streaming `:response_block`, so that we can
184
+ # accumulate the response as it streams back from the real HTTP
185
+ # server in order to record it.
186
+ #
187
+ # @private
188
+ class StreamingResponseBodyReader
189
+ def initialize(response_block)
190
+ @response_block = response_block
191
+ @chunks = []
192
+ end
193
+
194
+ # @private
195
+ def call(chunk, remaining_bytes, total_bytes)
196
+ @chunks << chunk
197
+ @response_block.call(chunk, remaining_bytes, total_bytes)
198
+ end
199
+
200
+ # Provides a duck-typed interface that matches that of
201
+ # `NonStreamingResponseBodyReader`. The request handler
202
+ # will use this to get the response body.
203
+ #
204
+ # @private
205
+ def read_body_from(response_params)
206
+ @chunks.join('')
207
+ end
208
+ end
209
+
210
+ # Reads the body when no streaming is done.
211
+ #
212
+ # @private
213
+ class NonStreamingResponseBodyReader
214
+ # Provides a duck-typed interface that matches that of
215
+ # `StreamingResponseBodyReader`. The request handler
216
+ # will use this to get the response body.
217
+ #
218
+ # @private
219
+ def self.read_body_from(response_params)
220
+ response_params.fetch(:body)
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+