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
@@ -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
|
+
|