vcr 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.travis.yml +2 -1
  2. data/CHANGELOG.md +58 -1
  3. data/Gemfile +0 -6
  4. data/README.md +12 -3
  5. data/Rakefile +2 -2
  6. data/features/.nav +2 -0
  7. data/features/cassettes/allow_unused_http_interactions.feature +86 -0
  8. data/features/cassettes/naming.feature +3 -2
  9. data/features/cassettes/persistence.feature +63 -0
  10. data/features/configuration/debug_logging.feature +3 -3
  11. data/features/configuration/ignore_request.feature +1 -1
  12. data/features/hooks/after_http_request.feature +1 -1
  13. data/features/step_definitions/cli_steps.rb +33 -0
  14. data/features/support/env.rb +0 -1
  15. data/lib/vcr.rb +13 -0
  16. data/lib/vcr/cassette.rb +57 -44
  17. data/lib/vcr/cassette/{reader.rb → erb_renderer.rb} +9 -11
  18. data/lib/vcr/cassette/http_interaction_list.rb +18 -0
  19. data/lib/vcr/cassette/persisters.rb +42 -0
  20. data/lib/vcr/cassette/persisters/file_system.rb +60 -0
  21. data/lib/vcr/cassette/serializers.rb +1 -1
  22. data/lib/vcr/cassette/serializers/json.rb +1 -1
  23. data/lib/vcr/cassette/serializers/psych.rb +1 -1
  24. data/lib/vcr/cassette/serializers/syck.rb +1 -1
  25. data/lib/vcr/cassette/serializers/yaml.rb +1 -1
  26. data/lib/vcr/configuration.rb +49 -25
  27. data/lib/vcr/errors.rb +6 -0
  28. data/lib/vcr/library_hooks/excon.rb +1 -1
  29. data/lib/vcr/library_hooks/fakeweb.rb +16 -3
  30. data/lib/vcr/library_hooks/faraday.rb +13 -0
  31. data/lib/vcr/library_hooks/typhoeus.rb +5 -1
  32. data/lib/vcr/library_hooks/webmock.rb +90 -35
  33. data/lib/vcr/middleware/faraday.rb +12 -4
  34. data/lib/vcr/request_handler.rb +10 -2
  35. data/lib/vcr/structs.rb +31 -7
  36. data/lib/vcr/version.rb +1 -1
  37. data/script/ci.sh +14 -0
  38. data/spec/spec_helper.rb +8 -1
  39. data/spec/support/shared_example_groups/hook_into_http_library.rb +31 -1
  40. data/spec/vcr/cassette/{reader_spec.rb → erb_renderer_spec.rb} +15 -21
  41. data/spec/vcr/cassette/http_interaction_list_spec.rb +15 -0
  42. data/spec/vcr/cassette/persisters/file_system_spec.rb +64 -0
  43. data/spec/vcr/cassette/persisters_spec.rb +39 -0
  44. data/spec/vcr/cassette/serializers_spec.rb +4 -3
  45. data/spec/vcr/cassette_spec.rb +65 -41
  46. data/spec/vcr/configuration_spec.rb +33 -2
  47. data/spec/vcr/library_hooks/fakeweb_spec.rb +11 -0
  48. data/spec/vcr/library_hooks/faraday_spec.rb +24 -0
  49. data/spec/vcr/library_hooks/webmock_spec.rb +82 -2
  50. data/spec/vcr/middleware/faraday_spec.rb +32 -0
  51. data/spec/vcr/structs_spec.rb +60 -20
  52. data/spec/vcr/util/hooks_spec.rb +7 -0
  53. data/spec/vcr_spec.rb +7 -0
  54. data/vcr.gemspec +2 -2
  55. metadata +31 -26
  56. data/Guardfile +0 -9
  57. data/script/FullBuildRakeFile +0 -56
  58. data/script/full_build +0 -1
  59. data/script/spec +0 -1
@@ -1,4 +1,3 @@
1
- require 'fileutils'
2
1
  require 'vcr/util/hooks'
3
2
 
4
3
  module VCR
@@ -8,23 +7,26 @@ module VCR
8
7
  include VariableArgsBlockCaller
9
8
  include Logger
10
9
 
11
- # The directory to read cassettes from and write cassettes to.
10
+ # Gets the directory to read cassettes from and write cassettes to.
11
+ #
12
+ # @return [String] the directory to read cassettes from and write cassettes to
13
+ def cassette_library_dir
14
+ VCR.cassette_persisters[:file_system].storage_location
15
+ end
16
+
17
+ # Sets the directory to read cassettes from and writes cassettes to.
12
18
  #
13
- # @overload cassette_library_dir
14
- # @return [String] the directory to read cassettes from and write cassettes to
15
- # @overload cassette_library_dir=(dir)
16
- # @param dir [String] the directory to read cassettes from and write cassettes to
17
- # @return [void]
18
19
  # @example
19
20
  # VCR.configure do |c|
20
21
  # c.cassette_library_dir = 'spec/cassettes'
21
22
  # end
22
- attr_reader :cassette_library_dir
23
-
24
- # Sets the directory to read cassettes from and write cassettes to.
25
- def cassette_library_dir=(cassette_library_dir)
26
- FileUtils.mkdir_p(cassette_library_dir) if cassette_library_dir
27
- @cassette_library_dir = cassette_library_dir ? absolute_path_for(cassette_library_dir) : nil
23
+ #
24
+ # @param dir [String] the directory to read cassettes from and write cassettes to
25
+ # @return [void]
26
+ # @note This is only necessary if you use the `:file_system`
27
+ # cassette persister (the default).
28
+ def cassette_library_dir=(dir)
29
+ VCR.cassette_persisters[:file_system].storage_location = dir
28
30
  end
29
31
 
30
32
  # Default options to apply to every cassette.
@@ -162,7 +164,7 @@ module VCR
162
164
  # end
163
165
  #
164
166
  # @param placeholder [String] The placeholder string.
165
- # @param tag [Symbol] Set this apply this to only to cassettes
167
+ # @param tag [Symbol] Set this to apply this only to cassettes
166
168
  # with a matching tag; otherwise it will apply to every cassette.
167
169
  # @yield block that determines what string to replace
168
170
  # @yieldparam interaction [(optional) VCR::HTTPInteraction::HookAware] the HTTP interaction
@@ -199,6 +201,23 @@ module VCR
199
201
  VCR.cassette_serializers
200
202
  end
201
203
 
204
+ # Gets the registry of cassette persisters. Use it to register a custom persister.
205
+ #
206
+ # @example
207
+ # VCR.configure do |c|
208
+ # c.cassette_persisters[:my_custom_persister] = my_custom_persister
209
+ # end
210
+ #
211
+ # @return [VCR::Cassette::Persisters] the cassette persister registry object.
212
+ # @note Custom persisters must implement the following interface:
213
+ #
214
+ # * `persister[storage_key]` # returns previously persisted content
215
+ # * `persister[storage_key] = content` # persists given content
216
+ def cassette_persisters
217
+ VCR.cassette_persisters
218
+ end
219
+
220
+ define_hook :before_record
202
221
  # Adds a callback that will be called before the recorded HTTP interactions
203
222
  # are serialized and written to disk.
204
223
  #
@@ -222,11 +241,11 @@ module VCR
222
241
  # serialized and written to disk.
223
242
  # @yieldparam cassette [(optional) VCR::Cassette] The current cassette.
224
243
  # @see #before_playback
225
- define_hook :before_record
226
244
  def before_record(tag = nil, &block)
227
- super(filter_from(tag), &block)
245
+ super(tag_filter_from(tag), &block)
228
246
  end
229
247
 
248
+ define_hook :before_playback
230
249
  # Adds a callback that will be called before a previously recorded
231
250
  # HTTP interaction is loaded for playback.
232
251
  #
@@ -250,9 +269,8 @@ module VCR
250
269
  # loaded.
251
270
  # @yieldparam cassette [(optional) VCR::Cassette] The current cassette.
252
271
  # @see #before_record
253
- define_hook :before_playback
254
272
  def before_playback(tag = nil, &block)
255
- super(filter_from(tag), &block)
273
+ super(tag_filter_from(tag), &block)
256
274
  end
257
275
 
258
276
  # Adds a callback that will be called with each HTTP request before it is made.
@@ -273,6 +291,7 @@ module VCR
273
291
  # @see #around_http_request
274
292
  define_hook :before_http_request
275
293
 
294
+ define_hook :after_http_request, :prepend
276
295
  # Adds a callback that will be called with each HTTP request after it is complete.
277
296
  #
278
297
  # @example
@@ -291,7 +310,9 @@ module VCR
291
310
  # @yieldparam response [VCR::Response] the response from the request
292
311
  # @see #before_http_request
293
312
  # @see #around_http_request
294
- define_hook :after_http_request, :prepend
313
+ def after_http_request(*filters)
314
+ super(*filters.map { |f| request_filter_from(f) })
315
+ end
295
316
 
296
317
  # Adds a callback that will be executed around each HTTP request.
297
318
  #
@@ -398,7 +419,9 @@ module VCR
398
419
  @default_cassette_options = {
399
420
  :record => :once,
400
421
  :match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
401
- :serialize_with => :yaml
422
+ :allow_unused_http_interactions => true,
423
+ :serialize_with => :yaml,
424
+ :persist_with => :file_system
402
425
  }
403
426
 
404
427
  self.debug_logger = NullDebugLogger
@@ -428,15 +451,16 @@ module VCR
428
451
  end
429
452
  end
430
453
 
431
- def absolute_path_for(path)
432
- Dir.chdir(path) { Dir.pwd }
433
- end
434
-
435
- def filter_from(tag)
454
+ def tag_filter_from(tag)
436
455
  return lambda { true } unless tag
437
456
  lambda { |_, cassette| cassette.tags.include?(tag) }
438
457
  end
439
458
 
459
+ def request_filter_from(object)
460
+ return object unless object.is_a?(Symbol)
461
+ lambda { |arg| arg.send(object) }
462
+ end
463
+
440
464
  def register_built_in_hooks
441
465
  before_playback(:update_content_length_header) do |interaction|
442
466
  interaction.response.update_content_length_header
data/lib/vcr/errors.rb CHANGED
@@ -45,6 +45,12 @@ module VCR
45
45
  # @see VCR::Response#decompress
46
46
  class UnknownContentEncodingError < Error; end
47
47
 
48
+ # Error raised when you eject a cassette before all previously
49
+ # recorded HTTP interactions are played back.
50
+ # @note Only applicable when :allow_episode_skipping is false.
51
+ # @see VCR::HTTPInteractionList#assert_no_unused_interactions!
52
+ class UnusedHTTPInteractionError < Error; end
53
+
48
54
  # Error raised when an HTTP request is made that VCR is unable to handle.
49
55
  # @note VCR will raise this to force you to do something about the
50
56
  # HTTP request. The idea is that you want to handle _every_ HTTP
@@ -23,7 +23,7 @@ module VCR
23
23
 
24
24
  private
25
25
 
26
- def on_stubbed_request
26
+ def on_stubbed_by_vcr_request
27
27
  @vcr_response = stubbed_response
28
28
  {
29
29
  :body => stubbed_response.body,
@@ -40,6 +40,10 @@ module VCR
40
40
 
41
41
  private
42
42
 
43
+ def externally_stubbed?
44
+ ::FakeWeb.registered_uri?(request_method, uri)
45
+ end
46
+
43
47
  def request_type(*args)
44
48
  @request_type || super
45
49
  end
@@ -64,11 +68,16 @@ module VCR
64
68
  super
65
69
  end
66
70
 
71
+ def on_externally_stubbed_request
72
+ # just perform the request--FakeWeb will handle it
73
+ perform_request(:started)
74
+ end
75
+
67
76
  def on_recordable_request
68
77
  perform_request(net_http.started?, :record_interaction)
69
78
  end
70
79
 
71
- def on_stubbed_request
80
+ def on_stubbed_by_vcr_request
72
81
  with_exclusive_fakeweb_stub(stubbed_response) do
73
82
  # force it to be considered started since it doesn't
74
83
  # recurse in this case like the others.
@@ -124,9 +133,13 @@ module VCR
124
133
  end
125
134
  end
126
135
 
136
+ def request_method
137
+ request.method.downcase.to_sym
138
+ end
139
+
127
140
  def vcr_request
128
141
  @vcr_request ||= VCR::Request.new \
129
- request.method.downcase.to_sym,
142
+ request_method,
130
143
  uri,
131
144
  (request_body || request.body),
132
145
  request.to_hash
@@ -171,7 +184,7 @@ module FakeWeb
171
184
  # ensure HTTP requests are always allowed; VCR takes care of disallowing
172
185
  # them at the appropriate times in its hook
173
186
  def allow_net_connect_with_vcr?(*args)
174
- VCR.turned_on? ? true : allow_net_connect_without_vcr?
187
+ VCR.turned_on? ? true : allow_net_connect_without_vcr?(*args)
175
188
  end
176
189
 
177
190
  alias allow_net_connect_without_vcr? allow_net_connect?
@@ -25,8 +25,21 @@ module VCR
25
25
  def insert_vcr_middleware
26
26
  return if handlers.any? { |h| h.klass == VCR::Middleware::Faraday }
27
27
  adapter_index = handlers.index { |h| h.klass < ::Faraday::Adapter }
28
+ adapter_index ||= handlers.size
29
+ warn_about_after_adapter_middleware(adapter_index)
28
30
  insert_before(adapter_index, VCR::Middleware::Faraday)
29
31
  end
32
+
33
+ def warn_about_after_adapter_middleware(adapter_index)
34
+ after_adapter_middleware_count = (handlers.size - adapter_index - 1)
35
+ return if after_adapter_middleware_count < 1
36
+
37
+ after_adapter_middlewares = handlers.last(after_adapter_middleware_count)
38
+ warn "WARNING: The Faraday connection stack contains middleware after " +
39
+ "the HTTP adapter (#{after_adapter_middlewares.map(&:inspect).join(', ')}). " +
40
+ "This is a non-standard configuration and VCR may not be able to " +
41
+ "record the HTTP requests made through this Faraday connection."
42
+ end
30
43
  end
31
44
  end
32
45
  end
@@ -25,6 +25,10 @@ module VCR
25
25
 
26
26
  private
27
27
 
28
+ def externally_stubbed?
29
+ ::Typhoeus::Hydra.stubs.detect { |stub| stub.matches?(request) }
30
+ end
31
+
28
32
  def set_typed_request_for_after_hook(*args)
29
33
  super
30
34
  request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
@@ -35,7 +39,7 @@ module VCR
35
39
  super
36
40
  end
37
41
 
38
- def on_stubbed_request
42
+ def on_stubbed_by_vcr_request
39
43
  ::Typhoeus::Response.new \
40
44
  :http_version => stubbed_response.http_version,
41
45
  :code => stubbed_response.status.code,
@@ -8,44 +8,103 @@ module VCR
8
8
  class LibraryHooks
9
9
  # @private
10
10
  module WebMock
11
- class RequestHandler < ::VCR::RequestHandler
11
+ extend self
12
12
 
13
- attr_reader :request
14
- def initialize(request)
15
- @request = request
16
- end
13
+ attr_accessor :global_hook_disabled
14
+ alias global_hook_disabled? global_hook_disabled
17
15
 
18
- private
16
+ def with_global_hook_disabled
17
+ self.global_hook_disabled = true
19
18
 
20
- def set_typed_request_for_after_hook(*args)
21
- super
22
- request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
19
+ begin
20
+ yield
21
+ ensure
22
+ self.global_hook_disabled = false
23
23
  end
24
+ end
24
25
 
25
- def vcr_request
26
- @vcr_request ||= VCR::Request.new \
27
- request.method,
28
- request.uri.to_s,
29
- request.body,
30
- request_headers
26
+ # @private
27
+ module Helpers
28
+ def vcr_request_for(webmock_request)
29
+ VCR::Request.new \
30
+ webmock_request.method,
31
+ webmock_request.uri.to_s,
32
+ webmock_request.body,
33
+ request_headers_for(webmock_request)
34
+ end
35
+
36
+ # @private
37
+ def vcr_response_for(webmock_response)
38
+ VCR::Response.new \
39
+ VCR::ResponseStatus.new(*webmock_response.status),
40
+ webmock_response.headers,
41
+ webmock_response.body,
42
+ nil
31
43
  end
32
44
 
33
45
  if defined?(::Excon)
34
46
  # @private
35
- def request_headers
36
- return nil unless request.headers
47
+ def request_headers_for(webmock_request)
48
+ return nil unless webmock_request.headers
37
49
 
38
50
  # WebMock hooks deeply into a Excon at a place where it manually adds a "Host"
39
51
  # header, but this isn't a header we actually care to store...
40
- request.headers.dup.tap do |headers|
52
+ webmock_request.headers.dup.tap do |headers|
41
53
  headers.delete("Host")
42
54
  end
43
55
  end
44
56
  else
45
57
  # @private
46
- def request_headers
47
- request.headers
58
+ def request_headers_for(webmock_request)
59
+ webmock_request.headers
60
+ end
61
+ end
62
+
63
+ def typed_request_for(webmock_request, remove = false)
64
+ if webmock_request.instance_variables.find { |v| v.to_sym == :@__typed_vcr_request }
65
+ meth = remove ? :remove_instance_variable : :instance_variable_get
66
+ return webmock_request.send(meth, :@__typed_vcr_request)
48
67
  end
68
+
69
+ warn <<-EOS.gsub(/^\s+\|/, '')
70
+ |WARNING: There appears to be a bug in WebMock's after_request hook
71
+ | and VCR is attempting to work around it. Some VCR features
72
+ | may not work properly.
73
+ EOS
74
+
75
+ Request::Typed.new(vcr_request_for(webmock_request), :unknown)
76
+ end
77
+ end
78
+
79
+ class RequestHandler < ::VCR::RequestHandler
80
+ include Helpers
81
+
82
+ attr_reader :request
83
+ def initialize(request)
84
+ @request = request
85
+ end
86
+
87
+ private
88
+
89
+ def externally_stubbed?
90
+ # prevent infinite recursion...
91
+ VCR::LibraryHooks::WebMock.with_global_hook_disabled do
92
+ ::WebMock.registered_request?(request)
93
+ end
94
+ end
95
+
96
+ def set_typed_request_for_after_hook(*args)
97
+ super
98
+ request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
99
+ end
100
+
101
+ def vcr_request
102
+ @vcr_request ||= vcr_request_for(request)
103
+ end
104
+
105
+ def on_externally_stubbed_request
106
+ # nil allows WebMock to handle the request
107
+ nil
49
108
  end
50
109
 
51
110
  def on_unhandled_request
@@ -53,7 +112,7 @@ module VCR
53
112
  super
54
113
  end
55
114
 
56
- def on_stubbed_request
115
+ def on_stubbed_by_vcr_request
57
116
  {
58
117
  :body => stubbed_response.body,
59
118
  :status => [stubbed_response.status.code.to_i, stubbed_response.status.message],
@@ -62,22 +121,16 @@ module VCR
62
121
  end
63
122
  end
64
123
 
65
- # @private
66
- def self.vcr_response_from(response)
67
- VCR::Response.new \
68
- VCR::ResponseStatus.new(response.status.first, response.status.last),
69
- response.headers,
70
- response.body,
71
- nil
72
- end
124
+ extend Helpers
73
125
 
74
- ::WebMock.globally_stub_request { |req| RequestHandler.new(req).handle }
126
+ ::WebMock.globally_stub_request do |req|
127
+ global_hook_disabled? ? nil : RequestHandler.new(req).handle
128
+ end
75
129
 
76
130
  ::WebMock.after_request(:real_requests_only => true) do |request, response|
77
131
  unless VCR.library_hooks.disabled?(:webmock)
78
132
  http_interaction = VCR::HTTPInteraction.new \
79
- request.send(:instance_variable_get, :@__typed_vcr_request),
80
- vcr_response_from(response)
133
+ typed_request_for(request), vcr_response_for(response)
81
134
 
82
135
  VCR.record_http_interaction(http_interaction)
83
136
  end
@@ -85,8 +138,10 @@ module VCR
85
138
 
86
139
  ::WebMock.after_request do |request, response|
87
140
  unless VCR.library_hooks.disabled?(:webmock)
88
- typed_vcr_request = request.send(:remove_instance_variable, :@__typed_vcr_request)
89
- VCR.configuration.invoke_hook(:after_http_request, typed_vcr_request, vcr_response_from(response))
141
+ VCR.configuration.invoke_hook \
142
+ :after_http_request,
143
+ typed_request_for(request, :remove),
144
+ vcr_response_for(response)
90
145
  end
91
146
  end
92
147
  end
@@ -99,7 +154,7 @@ module WebMock
99
154
  # ensure HTTP requests are always allowed; VCR takes care of disallowing
100
155
  # them at the appropriate times in its hook
101
156
  def net_connect_allowed_with_vcr?(*args)
102
- VCR.turned_on? ? true : net_connect_allowed_without_vcr?
157
+ VCR.turned_on? ? true : net_connect_allowed_without_vcr?(*args)
103
158
  end
104
159
 
105
160
  alias net_connect_allowed_without_vcr? net_connect_allowed?