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.
- data/.travis.yml +2 -1
- data/CHANGELOG.md +58 -1
- data/Gemfile +0 -6
- data/README.md +12 -3
- data/Rakefile +2 -2
- data/features/.nav +2 -0
- data/features/cassettes/allow_unused_http_interactions.feature +86 -0
- data/features/cassettes/naming.feature +3 -2
- data/features/cassettes/persistence.feature +63 -0
- data/features/configuration/debug_logging.feature +3 -3
- data/features/configuration/ignore_request.feature +1 -1
- data/features/hooks/after_http_request.feature +1 -1
- data/features/step_definitions/cli_steps.rb +33 -0
- data/features/support/env.rb +0 -1
- data/lib/vcr.rb +13 -0
- data/lib/vcr/cassette.rb +57 -44
- data/lib/vcr/cassette/{reader.rb → erb_renderer.rb} +9 -11
- data/lib/vcr/cassette/http_interaction_list.rb +18 -0
- data/lib/vcr/cassette/persisters.rb +42 -0
- data/lib/vcr/cassette/persisters/file_system.rb +60 -0
- data/lib/vcr/cassette/serializers.rb +1 -1
- data/lib/vcr/cassette/serializers/json.rb +1 -1
- data/lib/vcr/cassette/serializers/psych.rb +1 -1
- data/lib/vcr/cassette/serializers/syck.rb +1 -1
- data/lib/vcr/cassette/serializers/yaml.rb +1 -1
- data/lib/vcr/configuration.rb +49 -25
- data/lib/vcr/errors.rb +6 -0
- data/lib/vcr/library_hooks/excon.rb +1 -1
- data/lib/vcr/library_hooks/fakeweb.rb +16 -3
- data/lib/vcr/library_hooks/faraday.rb +13 -0
- data/lib/vcr/library_hooks/typhoeus.rb +5 -1
- data/lib/vcr/library_hooks/webmock.rb +90 -35
- data/lib/vcr/middleware/faraday.rb +12 -4
- data/lib/vcr/request_handler.rb +10 -2
- data/lib/vcr/structs.rb +31 -7
- data/lib/vcr/version.rb +1 -1
- data/script/ci.sh +14 -0
- data/spec/spec_helper.rb +8 -1
- data/spec/support/shared_example_groups/hook_into_http_library.rb +31 -1
- data/spec/vcr/cassette/{reader_spec.rb → erb_renderer_spec.rb} +15 -21
- data/spec/vcr/cassette/http_interaction_list_spec.rb +15 -0
- data/spec/vcr/cassette/persisters/file_system_spec.rb +64 -0
- data/spec/vcr/cassette/persisters_spec.rb +39 -0
- data/spec/vcr/cassette/serializers_spec.rb +4 -3
- data/spec/vcr/cassette_spec.rb +65 -41
- data/spec/vcr/configuration_spec.rb +33 -2
- data/spec/vcr/library_hooks/fakeweb_spec.rb +11 -0
- data/spec/vcr/library_hooks/faraday_spec.rb +24 -0
- data/spec/vcr/library_hooks/webmock_spec.rb +82 -2
- data/spec/vcr/middleware/faraday_spec.rb +32 -0
- data/spec/vcr/structs_spec.rb +60 -20
- data/spec/vcr/util/hooks_spec.rb +7 -0
- data/spec/vcr_spec.rb +7 -0
- data/vcr.gemspec +2 -2
- metadata +31 -26
- data/Guardfile +0 -9
- data/script/FullBuildRakeFile +0 -56
- data/script/full_build +0 -1
- data/script/spec +0 -1
data/lib/vcr/configuration.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
23
|
-
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
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(
|
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(
|
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
|
-
|
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
|
-
:
|
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
|
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
|
@@ -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
|
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
|
-
|
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
|
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
|
-
|
11
|
+
extend self
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
@request = request
|
16
|
-
end
|
13
|
+
attr_accessor :global_hook_disabled
|
14
|
+
alias global_hook_disabled? global_hook_disabled
|
17
15
|
|
18
|
-
|
16
|
+
def with_global_hook_disabled
|
17
|
+
self.global_hook_disabled = true
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
begin
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
self.global_hook_disabled = false
|
23
23
|
end
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
36
|
-
return nil unless
|
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
|
-
|
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
|
47
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
89
|
-
|
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?
|