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
@@ -17,7 +17,7 @@ module VCR
|
|
17
17
|
|
18
18
|
# Constructs a new instance of the Faraday middleware.
|
19
19
|
#
|
20
|
-
# @param [#call] the faraday app
|
20
|
+
# @param [#call] app the faraday app
|
21
21
|
def initialize(app)
|
22
22
|
super
|
23
23
|
@app = app
|
@@ -57,10 +57,18 @@ module VCR
|
|
57
57
|
@vcr_request ||= VCR::Request.new \
|
58
58
|
env[:method],
|
59
59
|
env[:url].to_s,
|
60
|
-
env[:body],
|
60
|
+
raw_body_from(env[:body]),
|
61
61
|
env[:request_headers]
|
62
62
|
end
|
63
63
|
|
64
|
+
def raw_body_from(body)
|
65
|
+
return body unless body.respond_to?(:read)
|
66
|
+
|
67
|
+
body.read.tap do |b|
|
68
|
+
body.rewind if body.respond_to?(:rewind)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
64
72
|
def response_for(env)
|
65
73
|
response = env[:response]
|
66
74
|
return nil unless response
|
@@ -68,7 +76,7 @@ module VCR
|
|
68
76
|
VCR::Response.new(
|
69
77
|
VCR::ResponseStatus.new(response.status, nil),
|
70
78
|
response.headers,
|
71
|
-
response.body,
|
79
|
+
raw_body_from(response.body),
|
72
80
|
nil
|
73
81
|
)
|
74
82
|
end
|
@@ -77,7 +85,7 @@ module VCR
|
|
77
85
|
app.call(env)
|
78
86
|
end
|
79
87
|
|
80
|
-
def
|
88
|
+
def on_stubbed_by_vcr_request
|
81
89
|
headers = env[:response_headers] ||= ::Faraday::Utils::Headers.new
|
82
90
|
headers.update stubbed_response.headers if stubbed_response.headers
|
83
91
|
env.update :status => stubbed_response.status.code, :body => stubbed_response.body
|
data/lib/vcr/request_handler.rb
CHANGED
@@ -32,8 +32,9 @@ module VCR
|
|
32
32
|
|
33
33
|
def request_type(consume_stub = false)
|
34
34
|
case
|
35
|
+
when externally_stubbed? then :externally_stubbed
|
35
36
|
when should_ignore? then :ignored
|
36
|
-
when has_response_stub?(consume_stub) then :
|
37
|
+
when has_response_stub?(consume_stub) then :stubbed_by_vcr
|
37
38
|
when VCR.real_http_connections_allowed? then :recordable
|
38
39
|
else :unhandled
|
39
40
|
end
|
@@ -50,6 +51,10 @@ module VCR
|
|
50
51
|
VCR.configuration.invoke_hook(:after_http_request, @after_hook_typed_request, vcr_response)
|
51
52
|
end
|
52
53
|
|
54
|
+
def externally_stubbed?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
53
58
|
def should_ignore?
|
54
59
|
disabled? || VCR.request_ignorer.ignore?(vcr_request)
|
55
60
|
end
|
@@ -76,10 +81,13 @@ module VCR
|
|
76
81
|
end
|
77
82
|
|
78
83
|
# Subclasses can implement these
|
84
|
+
def on_externally_stubbed_request
|
85
|
+
end
|
86
|
+
|
79
87
|
def on_ignored_request
|
80
88
|
end
|
81
89
|
|
82
|
-
def
|
90
|
+
def on_stubbed_by_vcr_request
|
83
91
|
end
|
84
92
|
|
85
93
|
def on_recordable_request
|
data/lib/vcr/structs.rb
CHANGED
@@ -60,6 +60,11 @@ module VCR
|
|
60
60
|
|
61
61
|
def initialize(*args)
|
62
62
|
super
|
63
|
+
|
64
|
+
if body && !body.is_a?(String)
|
65
|
+
raise ArgumentError, "#{self.class} initialized with an invalid body: #{body.inspect}."
|
66
|
+
end
|
67
|
+
|
63
68
|
# Ensure that the body is a raw string, in case the string instance
|
64
69
|
# has been subclassed or extended with additional instance variables
|
65
70
|
# or attributes, so that it is serialized to YAML as a raw string.
|
@@ -109,7 +114,7 @@ module VCR
|
|
109
114
|
else [v]
|
110
115
|
end
|
111
116
|
|
112
|
-
new_headers[k] = convert_to_raw_strings(val_array)
|
117
|
+
new_headers[String.new(k)] = convert_to_raw_strings(val_array)
|
113
118
|
@normalized_header_keys[k.downcase] = k
|
114
119
|
end if headers
|
115
120
|
|
@@ -249,9 +254,19 @@ module VCR
|
|
249
254
|
type == :ignored
|
250
255
|
end
|
251
256
|
|
252
|
-
# @return [Boolean] whether or not this request
|
253
|
-
|
254
|
-
|
257
|
+
# @return [Boolean] whether or not this request is being stubbed by VCR
|
258
|
+
# @see #externally_stubbed?
|
259
|
+
# @see #stubbed?
|
260
|
+
def stubbed_by_vcr?
|
261
|
+
type == :stubbed_by_vcr
|
262
|
+
end
|
263
|
+
|
264
|
+
# @return [Boolean] whether or not this request is being stubbed by an
|
265
|
+
# external library (such as WebMock or FakeWeb).
|
266
|
+
# @see #stubbed_by_vcr?
|
267
|
+
# @see #stubbed?
|
268
|
+
def externally_stubbed?
|
269
|
+
type == :externally_stubbed
|
255
270
|
end
|
256
271
|
|
257
272
|
# @return [Boolean] whether or not this request will be recorded.
|
@@ -270,6 +285,14 @@ module VCR
|
|
270
285
|
ignored? || recordable?
|
271
286
|
end
|
272
287
|
|
288
|
+
# @return [Boolean] whether or not this request will be stubbed.
|
289
|
+
# It may be stubbed by an external library or by VCR.
|
290
|
+
# @see #stubbed_by_vcr?
|
291
|
+
# @see #externally_stubbed?
|
292
|
+
def stubbed?
|
293
|
+
stubbed_by_vcr? || externally_stubbed?
|
294
|
+
end
|
295
|
+
|
273
296
|
undef method
|
274
297
|
end
|
275
298
|
|
@@ -497,10 +520,11 @@ module VCR
|
|
497
520
|
# Replaces a string in any part of the HTTP interaction (headers, request body,
|
498
521
|
# response body, etc) with the given replacement text.
|
499
522
|
#
|
500
|
-
# @param [
|
501
|
-
# @param [
|
523
|
+
# @param [#to_s] text the text to replace
|
524
|
+
# @param [#to_s] replacement_text the text to put in its place
|
502
525
|
def filter!(text, replacement_text)
|
503
|
-
|
526
|
+
text, replacement_text = text.to_s, replacement_text.to_s
|
527
|
+
return self if [text, replacement_text].any? { |t| t.empty? }
|
504
528
|
filter_object!(self, text, replacement_text)
|
505
529
|
end
|
506
530
|
|
data/lib/vcr/version.rb
CHANGED
data/script/ci.sh
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Kill the whole script on error
|
2
|
+
set -e
|
3
|
+
|
4
|
+
# Setup vendored rspec-1
|
5
|
+
git submodule init
|
6
|
+
git submodule update
|
7
|
+
|
8
|
+
bundle exec ruby -w -I./spec -r./spec/capture_warnings -rspec_helper -S rspec spec --format progress --backtrace
|
9
|
+
|
10
|
+
bundle exec cucumber
|
11
|
+
|
12
|
+
bundle exec rake yard_coverage
|
13
|
+
|
14
|
+
bundle exec rake check_code_coverage
|
data/spec/spec_helper.rb
CHANGED
@@ -28,7 +28,14 @@ end
|
|
28
28
|
|
29
29
|
require 'rspec'
|
30
30
|
|
31
|
-
|
31
|
+
require "support/fixnum_extension.rb"
|
32
|
+
require "support/http_library_adapters.rb"
|
33
|
+
require "support/ruby_interpreter.rb"
|
34
|
+
require "support/shared_example_groups/hook_into_http_library.rb"
|
35
|
+
require "support/shared_example_groups/request_hooks.rb"
|
36
|
+
require "support/sinatra_app.rb"
|
37
|
+
require "support/vcr_localhost_server.rb"
|
38
|
+
require "support/vcr_stub_helpers.rb"
|
32
39
|
|
33
40
|
require 'vcr'
|
34
41
|
require 'monkey_patches'
|
@@ -144,6 +144,28 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
|
|
144
144
|
directly_stub_request(:get, request_url, "stubbed response")
|
145
145
|
get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
|
146
146
|
end
|
147
|
+
|
148
|
+
it 'can directly stub the request when VCR is turned on and no cassette is in use' do
|
149
|
+
directly_stub_request(:get, request_url, "stubbed response")
|
150
|
+
get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'can directly stub the request when VCR is turned on and a cassette is in use' do
|
154
|
+
VCR.use_cassette("temp") do
|
155
|
+
directly_stub_request(:get, request_url, "stubbed response")
|
156
|
+
get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'does not record requests that are directly stubbed' do
|
161
|
+
VCR.should respond_to(:record_http_interaction)
|
162
|
+
VCR.should_not_receive(:record_http_interaction)
|
163
|
+
|
164
|
+
VCR.use_cassette("temp") do
|
165
|
+
directly_stub_request(:get, request_url, "stubbed response")
|
166
|
+
get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
|
167
|
+
end
|
168
|
+
end
|
147
169
|
end
|
148
170
|
end
|
149
171
|
|
@@ -306,6 +328,14 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
|
|
306
328
|
it_behaves_like "request hooks", library_hook_name, :ignored
|
307
329
|
end
|
308
330
|
|
331
|
+
context "when the request is directly stubbed" do
|
332
|
+
before(:each) do
|
333
|
+
directly_stub_request(:get, request_url, "FOO!")
|
334
|
+
end
|
335
|
+
|
336
|
+
it_behaves_like "request hooks", library_hook_name, :externally_stubbed
|
337
|
+
end if method_defined?(:directly_stub_request)
|
338
|
+
|
309
339
|
context 'when the request is recorded' do
|
310
340
|
let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') }
|
311
341
|
|
@@ -341,7 +371,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
|
|
341
371
|
stub_requests([http_interaction(request_url)], [:method, :uri])
|
342
372
|
end
|
343
373
|
|
344
|
-
it_behaves_like "request hooks", library_hook_name, :
|
374
|
+
it_behaves_like "request hooks", library_hook_name, :stubbed_by_vcr
|
345
375
|
end
|
346
376
|
|
347
377
|
context 'when the request is not allowed' do
|
@@ -1,54 +1,48 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe VCR::Cassette::
|
4
|
-
describe '#
|
5
|
-
def
|
6
|
-
described_class.new(*args).
|
3
|
+
describe VCR::Cassette::ERBRenderer do
|
4
|
+
describe '#render' do
|
5
|
+
def render(*args)
|
6
|
+
described_class.new(*args).render
|
7
7
|
end
|
8
8
|
|
9
9
|
let(:no_vars_content) { '<%= 3 + 4 %>. Some ERB' }
|
10
10
|
let(:vars_content) { '<%= var1 %>. ERB with Vars! <%= var2 %>' }
|
11
11
|
|
12
|
-
before(:each) do
|
13
|
-
File.stub(:read) do |name|
|
14
|
-
case name
|
15
|
-
when 'no_vars'; no_vars_content
|
16
|
-
when 'vars'; vars_content
|
17
|
-
else raise ArgumentError.new("Unexpected file name: #{name}")
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
12
|
context 'when ERB is disabled' do
|
23
|
-
it '
|
24
|
-
|
25
|
-
|
13
|
+
it 'returns the given template' do
|
14
|
+
render(no_vars_content, false).should eq(no_vars_content)
|
15
|
+
render(no_vars_content, nil).should eq(no_vars_content)
|
26
16
|
end
|
27
17
|
end
|
28
18
|
|
29
19
|
context 'when ERB is enabled but no variables are passed' do
|
30
20
|
it 'renders the file content as ERB' do
|
31
|
-
|
21
|
+
render(no_vars_content, true).should eq("7. Some ERB")
|
32
22
|
end
|
33
23
|
|
34
24
|
it 'raises an appropriate error when the ERB template needs variables' do
|
35
25
|
expect {
|
36
|
-
|
26
|
+
render(vars_content, true, "vars")
|
37
27
|
}.to raise_error(VCR::Errors::MissingERBVariableError,
|
38
28
|
%{The ERB in the vars cassette file references undefined variable var1. } +
|
39
29
|
%{Pass it to the cassette using :erb => #{ {:var1=>"some value"}.inspect }.}
|
40
30
|
)
|
41
31
|
end
|
32
|
+
|
33
|
+
it 'gracefully handles the template being nil' do
|
34
|
+
render(nil, true).should be_nil
|
35
|
+
end
|
42
36
|
end
|
43
37
|
|
44
38
|
context 'when ERB is enabled and variables are passed' do
|
45
39
|
it 'renders the file content as ERB with the passed variables' do
|
46
|
-
|
40
|
+
render(vars_content, :var1 => 'foo', :var2 => 'bar').should eq('foo. ERB with Vars! bar')
|
47
41
|
end
|
48
42
|
|
49
43
|
it 'raises an appropriate error when one or more of the needed variables are not passed' do
|
50
44
|
expect {
|
51
|
-
|
45
|
+
render(vars_content, { :var1 => 'foo' }, "vars")
|
52
46
|
}.to raise_error(VCR::Errors::MissingERBVariableError,
|
53
47
|
%{The ERB in the vars cassette file references undefined variable var2. } +
|
54
48
|
%{Pass it to the cassette using :erb => #{ {:var1 => "foo", :var2 => "some value"}.inspect }.}
|
@@ -86,6 +86,21 @@ module VCR
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
+
describe "#assert_no_unused_interactions?" do
|
90
|
+
it 'should raise a SkippedHTTPRequestError when there are unused interactions left' do
|
91
|
+
expect { list.assert_no_unused_interactions! }.to raise_error(Errors::UnusedHTTPInteractionError)
|
92
|
+
list.response_for(request_with(:method => :put))
|
93
|
+
expect { list.assert_no_unused_interactions! }.to raise_error(Errors::UnusedHTTPInteractionError)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should raise nothing when there are no unused interactions left' do
|
97
|
+
[:put, :post, :post].each do |method|
|
98
|
+
list.response_for(request_with(:method => method))
|
99
|
+
end
|
100
|
+
list.assert_no_unused_interactions! # should not raise an error.
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
89
104
|
describe "has_interaction_matching?" do
|
90
105
|
it 'returns false when the list is empty' do
|
91
106
|
HTTPInteractionList.new([], [:method]).should_not have_interaction_matching(stub)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'vcr/cassette/persisters/file_system'
|
3
|
+
|
4
|
+
module VCR
|
5
|
+
class Cassette
|
6
|
+
class Persisters
|
7
|
+
describe FileSystem do
|
8
|
+
before { FileSystem.storage_location = VCR.configuration.cassette_library_dir }
|
9
|
+
|
10
|
+
describe "#[]" do
|
11
|
+
it 'reads from the given file, relative to the configured storage location' do
|
12
|
+
File.open(FileSystem.storage_location + '/foo.txt', 'w') { |f| f.write('1234') }
|
13
|
+
FileSystem["foo.txt"].should eq("1234")
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'handles directories in the given file name' do
|
17
|
+
FileUtils.mkdir_p FileSystem.storage_location + '/a'
|
18
|
+
File.open(FileSystem.storage_location + '/a/b', 'w') { |f| f.write('1234') }
|
19
|
+
FileSystem["a/b"].should eq("1234")
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns nil if the file does not exist' do
|
23
|
+
FileSystem["non_existant_file"].should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#[]=" do
|
28
|
+
it 'writes the given file contents to the given file name' do
|
29
|
+
File.exist?(FileSystem.storage_location + '/foo.txt').should be_false
|
30
|
+
FileSystem["foo.txt"] = "bar"
|
31
|
+
File.read(FileSystem.storage_location + '/foo.txt').should eq("bar")
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'creates any needed intermediary directories' do
|
35
|
+
File.exist?(FileSystem.storage_location + '/a').should be_false
|
36
|
+
FileSystem["a/b"] = "bar"
|
37
|
+
File.read(FileSystem.storage_location + '/a/b').should eq("bar")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#absolute_path_to_file" do
|
42
|
+
it "returns the absolute path to the given relative file based on the storage location" do
|
43
|
+
expected = File.join(FileSystem.storage_location, "bar/bazz.json")
|
44
|
+
FileSystem.absolute_path_to_file("bar/bazz.json").should eq(expected)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns nil if the storage_location is not set" do
|
48
|
+
FileSystem.storage_location = nil
|
49
|
+
FileSystem.absolute_path_to_file("bar/bazz.json").should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it "sanitizes the file name" do
|
53
|
+
expected = File.join(FileSystem.storage_location, "_t_i-t_1_2_f_n.json")
|
54
|
+
FileSystem.absolute_path_to_file("\nt \t! i-t_1.2_f n.json").should eq(expected)
|
55
|
+
|
56
|
+
expected = File.join(FileSystem.storage_location, "a_1/b")
|
57
|
+
FileSystem.absolute_path_to_file("a 1/b").should eq(expected)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'vcr/cassette/persisters'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
class Cassette
|
5
|
+
describe Persisters do
|
6
|
+
describe "#[]=" do
|
7
|
+
context 'when there is already a persister registered for the given name' do
|
8
|
+
before(:each) do
|
9
|
+
subject[:foo] = :old_persister
|
10
|
+
subject.stub :warn
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'overrides the existing persister' do
|
14
|
+
subject[:foo] = :new_persister
|
15
|
+
subject[:foo].should be(:new_persister)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'warns that there is a name collision' do
|
19
|
+
subject.should_receive(:warn).with(
|
20
|
+
/WARNING: There is already a VCR cassette persister registered for :foo\. Overriding it/
|
21
|
+
)
|
22
|
+
subject[:foo] = :new_persister
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#[]" do
|
28
|
+
it 'raises an error when given an unrecognized persister name' do
|
29
|
+
expect { subject[:foo] }.to raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns the named persister' do
|
33
|
+
subject[:file_system].should be(VCR::Cassette::Persisters::FileSystem)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|