vcr 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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?
|