vcr 2.4.0 → 2.5.0

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