vcr 2.1.1 → 2.2.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 (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?