vcr 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +0 -3
  3. data/CHANGELOG.md +32 -3
  4. data/Gemfile +33 -0
  5. data/Gemfile.lock +99 -119
  6. data/README.md +19 -7
  7. data/Rakefile +5 -9
  8. data/benchmarks/null_logging.rb +62 -0
  9. data/features/.nav +0 -1
  10. data/features/about_these_examples.md +1 -2
  11. data/features/cassettes/allow_unused_http_interactions.feature +15 -1
  12. data/features/cassettes/decompress.feature +6 -2
  13. data/features/cassettes/format.feature +20 -12
  14. data/features/cassettes/freezing_time.feature +68 -0
  15. data/features/configuration/cassette_library_dir.feature +5 -0
  16. data/features/configuration/preserve_exact_body_bytes.feature +5 -0
  17. data/features/configuration/uri_parser.feature +2 -4
  18. data/features/http_libraries/net_http.feature +1 -1
  19. data/features/request_matching/headers.feature +0 -1
  20. data/features/step_definitions/cli_steps.rb +1 -4
  21. data/features/test_frameworks/cucumber.feature +59 -0
  22. data/features/test_frameworks/rspec_metadata.feature +59 -1
  23. data/gemfiles/typhoeus_old.gemfile +19 -0
  24. data/gemfiles/typhoeus_old.gemfile.lock +84 -86
  25. data/lib/vcr.rb +12 -3
  26. data/lib/vcr/cassette.rb +32 -11
  27. data/lib/vcr/cassette/http_interaction_list.rb +3 -2
  28. data/lib/vcr/cassette/migrator.rb +1 -0
  29. data/lib/vcr/cassette/serializers/json.rb +1 -1
  30. data/lib/vcr/configuration.rb +17 -9
  31. data/lib/vcr/library_hooks/typhoeus.rb +3 -2
  32. data/lib/vcr/library_hooks/webmock.rb +1 -1
  33. data/lib/vcr/middleware/excon.rb +13 -1
  34. data/lib/vcr/middleware/faraday.rb +1 -0
  35. data/lib/vcr/request_handler.rb +1 -1
  36. data/lib/vcr/structs.rb +19 -4
  37. data/lib/vcr/test_frameworks/cucumber.rb +2 -2
  38. data/lib/vcr/test_frameworks/rspec.rb +10 -2
  39. data/lib/vcr/util/logger.rb +41 -7
  40. data/lib/vcr/version.rb +1 -1
  41. data/script/ci.sh +8 -1
  42. data/spec/acceptance/threading_spec.rb +6 -0
  43. data/spec/capture_warnings.rb +9 -1
  44. data/spec/spec_helper.rb +6 -2
  45. data/spec/support/configuration_stubbing.rb +8 -0
  46. data/spec/support/http_library_adapters.rb +1 -1
  47. data/spec/support/limited_uri.rb +1 -0
  48. data/spec/support/shared_example_groups/excon.rb +23 -1
  49. data/spec/support/shared_example_groups/hook_into_http_library.rb +12 -12
  50. data/spec/support/shared_example_groups/request_hooks.rb +1 -1
  51. data/spec/support/sinatra_app.rb +9 -0
  52. data/spec/support/vcr_localhost_server.rb +4 -25
  53. data/spec/support/vcr_stub_helpers.rb +1 -1
  54. data/spec/vcr/cassette/http_interaction_list_spec.rb +41 -14
  55. data/spec/vcr/cassette/migrator_spec.rb +1 -1
  56. data/spec/vcr/cassette/persisters_spec.rb +2 -2
  57. data/spec/vcr/cassette/serializers_spec.rb +13 -4
  58. data/spec/vcr/cassette_spec.rb +107 -58
  59. data/spec/vcr/configuration_spec.rb +23 -23
  60. data/spec/vcr/deprecations_spec.rb +9 -9
  61. data/spec/vcr/errors_spec.rb +6 -6
  62. data/spec/vcr/library_hooks/excon_spec.rb +15 -10
  63. data/spec/vcr/library_hooks/fakeweb_spec.rb +8 -8
  64. data/spec/vcr/library_hooks/faraday_spec.rb +1 -1
  65. data/spec/vcr/library_hooks/typhoeus_0.4_spec.rb +2 -2
  66. data/spec/vcr/library_hooks/typhoeus_spec.rb +68 -9
  67. data/spec/vcr/library_hooks/webmock_spec.rb +6 -10
  68. data/spec/vcr/middleware/faraday_spec.rb +33 -5
  69. data/spec/vcr/middleware/rack_spec.rb +2 -2
  70. data/spec/vcr/request_matcher_registry_spec.rb +11 -6
  71. data/spec/vcr/structs_spec.rb +114 -47
  72. data/spec/vcr/test_frameworks/cucumber_spec.rb +4 -4
  73. data/spec/vcr/util/hooks_spec.rb +2 -2
  74. data/spec/vcr/util/internet_connection_spec.rb +3 -3
  75. data/spec/vcr/util/version_checker_spec.rb +4 -4
  76. data/spec/vcr_spec.rb +22 -16
  77. data/vcr.gemspec +2 -31
  78. metadata +9 -328
  79. data/features/test_frameworks/shoulda.feature +0 -64
@@ -223,7 +223,7 @@ HTTP_LIBRARY_ADAPTERS['excon'] = Module.new do
223
223
  end
224
224
 
225
225
  def normalize_request_headers(headers)
226
- headers
226
+ headers.merge('User-Agent' => [Excon::USER_AGENT])
227
227
  end
228
228
  end
229
229
 
@@ -1,4 +1,5 @@
1
1
  require 'forwardable'
2
+ require 'uri'
2
3
 
3
4
  class LimitedURI
4
5
  extend Forwardable
@@ -1,7 +1,7 @@
1
1
  shared_examples "Excon streaming" do
2
2
  context "when Excon's streaming API is used" do
3
3
  it 'properly records and plays back the response' do
4
- VCR.stub(:real_http_connections_allowed? => true)
4
+ allow(VCR).to receive(:real_http_connections_allowed?).and_return(true)
5
5
  recorded, played_back = [1, 2].map do
6
6
  chunks = []
7
7
 
@@ -17,6 +17,28 @@ shared_examples "Excon streaming" do
17
17
  expect(recorded).to eq(played_back)
18
18
  expect(recorded).to eq("FOO!")
19
19
  end
20
+
21
+ it 'properly records and plays back the response for unexpected status' do
22
+ allow(VCR).to receive(:real_http_connections_allowed?).and_return(true)
23
+ recorded, played_back = [1, 2].map do
24
+ chunks = []
25
+
26
+ VCR.use_cassette('excon_streaming_error', :record => :once) do
27
+ begin
28
+ Excon.get "http://localhost:#{VCR::SinatraApp.port}/404_not_200", :expects => 200, :response_block => lambda { |chunk, remaining_bytes, total_bytes|
29
+ chunks << chunk
30
+ }
31
+ rescue Excon::Errors::Error => e
32
+ chunks << e.response.body
33
+ end
34
+ end
35
+
36
+ chunks.join
37
+ end
38
+
39
+ expect(recorded).to eq(played_back)
40
+ expect(recorded).to eq('404 not 200')
41
+ end
20
42
  end
21
43
  end
22
44
 
@@ -109,7 +109,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
109
109
  call_count = 0
110
110
  [:has_interaction_matching?, :response_for].each do |method_name|
111
111
  orig_meth = VCR.http_interactions.method(method_name)
112
- VCR.http_interactions.stub(method_name) do |*args|
112
+ allow(VCR.http_interactions).to receive(method_name) do |*args|
113
113
  call_count += 1
114
114
  orig_meth.call(*args)
115
115
  end
@@ -135,7 +135,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
135
135
  VCR.insert_cassette('example')
136
136
  disable_real_connections
137
137
 
138
- VCR.should_receive(:record_http_interaction) do |interaction|
138
+ expect(VCR).to receive(:record_http_interaction) do |interaction|
139
139
  expect(interaction.request.uri).to eq(request_url)
140
140
  end
141
141
 
@@ -173,7 +173,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
173
173
 
174
174
  it 'does not record requests that are directly stubbed' do
175
175
  expect(VCR).to respond_to(:record_http_interaction)
176
- VCR.should_not_receive(:record_http_interaction)
176
+ expect(VCR).not_to receive(:record_http_interaction)
177
177
 
178
178
  VCR.use_cassette("temp") do
179
179
  directly_stub_request(:get, request_url, "stubbed response")
@@ -220,7 +220,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
220
220
  VCR.use_cassette('new_cassette', &request)
221
221
  end
222
222
 
223
- VCR.should_receive(:record_http_interaction) do
223
+ expect(VCR).to receive(:record_http_interaction) do
224
224
  expect(VCR.current_cassette.name).to eq('new_cassette')
225
225
  end
226
226
 
@@ -370,7 +370,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
370
370
  specify 'the after_http_request hook can be used to eject a cassette after the request is recorded' do
371
371
  VCR.configuration.after_http_request { |request| VCR.eject_cassette }
372
372
 
373
- VCR.should_receive(:record_http_interaction) do |interaction|
373
+ expect(VCR).to receive(:record_http_interaction) do |interaction|
374
374
  expect(VCR.current_cassette).to be(inserted_cassette)
375
375
  end
376
376
 
@@ -408,7 +408,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
408
408
  end
409
409
 
410
410
  describe '.stub_requests using specific match_attributes' do
411
- before(:each) { VCR.stub(:real_http_connections_allowed? => false) }
411
+ before(:each) { allow(VCR).to receive(:real_http_connections_allowed?).and_return(false) }
412
412
  let(:interactions) { interactions_from('match_requests_on.yml') }
413
413
 
414
414
  let(:normalized_interactions) do
@@ -486,17 +486,17 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
486
486
  describe 'recording new http requests' do
487
487
  let(:recorded_interaction) do
488
488
  interaction = nil
489
- VCR.should_receive(:record_http_interaction) { |i| interaction = i }
489
+ expect(VCR).to receive(:record_http_interaction) { |i| interaction = i }
490
490
  make_http_request(:post, url, "the body", { 'X-Http-Foo' => 'bar' })
491
491
  interaction
492
492
  end
493
493
 
494
494
  it 'does not record the request if the hook is disabled' do
495
495
  VCR.library_hooks.exclusively_enabled :something_else do
496
- VCR.should_not_receive(:record_http_interaction)
496
+ expect(VCR).not_to receive(:record_http_interaction)
497
497
  make_http_request(:get, url)
498
498
  end
499
- end unless other.include?(:not_disableable)
499
+ end
500
500
 
501
501
  it 'records the request uri' do
502
502
  expect(recorded_interaction.request.uri).to eq(url)
@@ -534,7 +534,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
534
534
  end
535
535
  else
536
536
  it 'does not allow real HTTP requests or record them' do
537
- VCR.should_receive(:record_http_interaction).never
537
+ expect(VCR).to receive(:record_http_interaction).never
538
538
  expect { make_http_request(:get, url) }.to raise_error(NET_CONNECT_NOT_ALLOWED_ERROR)
539
539
  end
540
540
  end
@@ -542,7 +542,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
542
542
 
543
543
  [true, false].each do |http_allowed|
544
544
  context "when VCR.real_http_connections_allowed? is returning #{http_allowed}" do
545
- before(:each) { VCR.stub(:real_http_connections_allowed? => http_allowed) }
545
+ before(:each) { allow(VCR).to receive(:real_http_connections_allowed?).and_return(http_allowed) }
546
546
 
547
547
  test_real_http_request(http_allowed, *other)
548
548
 
@@ -573,7 +573,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
573
573
  end
574
574
 
575
575
  it 'gets the stubbed responses when requests are made to http://example.com/foo, and does not record them' do
576
- VCR.should_receive(:record_http_interaction).never
576
+ expect(VCR).to receive(:record_http_interaction).never
577
577
  expect(get_body_string(make_http_request(:get, 'http://example.com/foo'))).to match(/example\.com get response \d with path=foo/)
578
578
  end
579
579
 
@@ -30,7 +30,7 @@ shared_examples_for "request hooks" do |library_hook_name, request_type|
30
30
 
31
31
  specify "the #{hook} hook is not called if the library hook is disabled" do
32
32
  expect(VCR.library_hooks).to respond_to(:disabled?)
33
- VCR.library_hooks.stub(:disabled? => true)
33
+ allow(VCR.library_hooks).to receive(:disabled?).and_return(true)
34
34
 
35
35
  hook_called = false
36
36
  VCR.configuration.send(hook) { |r| hook_called = true }
@@ -20,6 +20,10 @@ module VCR
20
20
  "FOO!"
21
21
  end
22
22
 
23
+ get '/redirect-to-root' do
24
+ redirect to('/')
25
+ end
26
+
23
27
  post '/foo' do
24
28
  "FOO!"
25
29
  end
@@ -38,6 +42,11 @@ module VCR
38
42
  status 204
39
43
  end
40
44
 
45
+ get '/404_not_200' do
46
+ status 404
47
+ '404 not 200'
48
+ end
49
+
41
50
  # we use a global counter so that every response is different;
42
51
  # this ensures that the test demonstrates that the response
43
52
  # is being played back (and not running a 2nd real request)
@@ -57,31 +57,10 @@ module VCR
57
57
  end
58
58
 
59
59
  def concurrently
60
- if should_use_subprocess?
61
- pid = Process.fork do
62
- trap(:INT) { ::Rack::Handler::WEBrick.shutdown }
63
- yield
64
- exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet.
65
- end
66
-
67
- at_exit do
68
- Process.kill('INT', pid)
69
- begin
70
- Process.wait(pid)
71
- rescue Errno::ECHILD
72
- # ignore this error...I think it means the child process has already exited.
73
- end
74
- end
75
- else
76
- # JRuby doesn't support forking.
77
- # Rubinius does, but there's a weird issue with the booted? check not working,
78
- # so we're just using a thread for now.
79
- Thread.new { yield }
80
- end
81
- end
82
-
83
- def should_use_subprocess?
84
- false
60
+ # JRuby doesn't support forking.
61
+ # Rubinius does, but there's a weird issue with the booted? check not working,
62
+ # so we're just using a thread for now.
63
+ Thread.new { yield }
85
64
  end
86
65
 
87
66
  def wait_until(timeout, error_message, &block)
@@ -5,7 +5,7 @@ module VCRStubHelpers
5
5
  end
6
6
 
7
7
  def stub_requests(*args)
8
- VCR.stub(:http_interactions => VCR::Cassette::HTTPInteractionList.new(*args))
8
+ allow(VCR).to receive(:http_interactions).and_return(VCR::Cassette::HTTPInteractionList.new(*args))
9
9
  end
10
10
 
11
11
  def http_interaction(url, response_body = "FOO!", status_code = 200)
@@ -2,17 +2,20 @@ require 'vcr/util/logger'
2
2
  require 'vcr/cassette/http_interaction_list'
3
3
  require 'vcr/request_matcher_registry'
4
4
  require 'vcr/structs'
5
+ require 'support/configuration_stubbing'
5
6
 
6
7
  module VCR
7
8
  class Cassette
8
9
  describe HTTPInteractionList do
10
+ include_context "configuration stubbing"
11
+
9
12
  ::RSpec::Matchers.define :respond_with do |expected|
10
13
  match { |a| expected.nil? ? a.nil? : a.body == expected }
11
14
  end
12
15
 
13
16
  before(:each) do
14
- VCR.stub(:request_matchers => VCR::RequestMatcherRegistry.new)
15
- VCR.stub_chain(:configuration, :debug_logger).and_return(stub.as_null_object)
17
+ allow(VCR).to receive(:request_matchers).and_return(VCR::RequestMatcherRegistry.new)
18
+ allow(config).to receive(:logger).and_return(double.as_null_object)
16
19
  end
17
20
 
18
21
  def request_with(values)
@@ -87,22 +90,46 @@ module VCR
87
90
 
88
91
  describe "#assert_no_unused_interactions?" do
89
92
  it 'should raise a SkippedHTTPRequestError when there are unused interactions left' do
90
- expect { list.assert_no_unused_interactions! }.to raise_error(Errors::UnusedHTTPInteractionError)
91
- list.response_for(request_with(:method => :put))
92
- expect { list.assert_no_unused_interactions! }.to raise_error(Errors::UnusedHTTPInteractionError)
93
+ expect {
94
+ list.assert_no_unused_interactions!
95
+ }.to raise_error(Errors::UnusedHTTPInteractionError)
96
+
97
+ list.response_for(request_with(:method => :put))
98
+ expect {
99
+ list.assert_no_unused_interactions!
100
+ }.to raise_error(Errors::UnusedHTTPInteractionError)
93
101
  end
94
102
 
95
103
  it 'should raise nothing when there are no unused interactions left' do
96
104
  [:put, :post, :post].each do |method|
97
105
  list.response_for(request_with(:method => method))
98
106
  end
99
- list.assert_no_unused_interactions! # should not raise an error.
107
+
108
+ expect {
109
+ list.assert_no_unused_interactions!
110
+ }.not_to raise_error
111
+ end
112
+
113
+ context 'when the null logger is in use' do
114
+ before { allow(config).to receive(:logger).and_return(Logger::Null) }
115
+
116
+ it 'includes formatted request details in the error message' do
117
+ expect {
118
+ list.assert_no_unused_interactions!
119
+ }.to raise_error(/\[put/)
120
+ end
121
+
122
+ it 'includes formatted response details in the error message' do
123
+ expect {
124
+ list.assert_no_unused_interactions!
125
+ }.to raise_error(/\[200 "put response"\]/)
126
+ end
100
127
  end
101
128
  end
102
129
 
103
130
  describe "has_interaction_matching?" do
104
131
  it 'returns false when the list is empty' do
105
- expect(HTTPInteractionList.new([], [:method])).not_to have_interaction_matching(stub)
132
+ expect(HTTPInteractionList.new([], [:method])).not_to have_interaction_matching(double)
106
133
  end
107
134
 
108
135
  it 'returns false when there is no matching interaction' do
@@ -136,10 +163,10 @@ module VCR
136
163
  end
137
164
 
138
165
  it "delegates to the parent list when it can't find a matching interaction" do
139
- parent_list = mock(:has_interaction_matching? => true)
140
- expect(HTTPInteractionList.new( [], [:method], false, parent_list)).to have_interaction_matching(stub)
141
- parent_list = mock(:has_interaction_matching? => false)
142
- expect(HTTPInteractionList.new( [], [:method], false, parent_list)).not_to have_interaction_matching(stub)
166
+ parent_list = double(:has_interaction_matching? => true)
167
+ expect(HTTPInteractionList.new( [], [:method], false, parent_list)).to have_interaction_matching(double)
168
+ parent_list = double(:has_interaction_matching? => false)
169
+ expect(HTTPInteractionList.new( [], [:method], false, parent_list)).not_to have_interaction_matching(double)
143
170
  end
144
171
 
145
172
  context 'when allow_playback_repeats is set to true' do
@@ -173,7 +200,7 @@ module VCR
173
200
 
174
201
  describe "#response_for" do
175
202
  it 'returns nil when the list is empty' do
176
- expect(HTTPInteractionList.new([], [:method]).response_for(stub)).to respond_with(nil)
203
+ expect(HTTPInteractionList.new([], [:method]).response_for(double)).to respond_with(nil)
177
204
  end
178
205
 
179
206
  it 'returns nil when there is no matching interaction' do
@@ -213,10 +240,10 @@ module VCR
213
240
  end
214
241
 
215
242
  it "delegates to the parent list when it can't find a matching interaction" do
216
- parent_list = mock(:response_for => response('parent'))
243
+ parent_list = double(:response_for => response('parent'))
217
244
  result = HTTPInteractionList.new(
218
245
  [], [:method], false, parent_list
219
- ).response_for(stub)
246
+ ).response_for(double)
220
247
 
221
248
  expect(result).to respond_with('parent')
222
249
  end
@@ -130,7 +130,7 @@ EOF
130
130
  subject { described_class.new(dir, out_io) }
131
131
 
132
132
  before(:each) do
133
- File.stub(:mtime).with(file_name).and_return(filemtime)
133
+ allow(File).to receive(:mtime).with(file_name).and_return(filemtime)
134
134
  end
135
135
 
136
136
  it 'migrates a cassette from the 1.x to 2.x format' do
@@ -7,7 +7,7 @@ module VCR
7
7
  context 'when there is already a persister registered for the given name' do
8
8
  before(:each) do
9
9
  subject[:foo] = :old_persister
10
- subject.stub :warn
10
+ allow(subject).to receive :warn
11
11
  end
12
12
 
13
13
  it 'overrides the existing persister' do
@@ -16,7 +16,7 @@ module VCR
16
16
  end
17
17
 
18
18
  it 'warns that there is a name collision' do
19
- subject.should_receive(:warn).with(
19
+ expect(subject).to receive(:warn).with(
20
20
  /WARNING: There is already a VCR cassette persister registered for :foo\. Overriding it/
21
21
  )
22
22
  subject[:foo] = :new_persister
@@ -69,11 +69,20 @@ module VCR
69
69
  it_behaves_like "a serializer", :json, "json", :lazily_loaded do
70
70
  engines = {}
71
71
 
72
- engines[:yajl] = ::MultiJson::DecodeError unless RUBY_INTERPRETER == :jruby
72
+ if RUBY_INTERPRETER == :jruby
73
+ # don't test yajl on jruby
74
+ elsif RUBY_VERSION.to_f < 1.9
75
+ engines[:yajl] = MultiJson::LoadError
76
+ else
77
+ engines[:yajl] = ArgumentError
78
+ end
73
79
 
74
80
  if RUBY_VERSION =~ /1.9/
75
81
  engines[:json_gem] = EncodingError
76
- engines[:json_pure] = EncodingError
82
+
83
+ # Disable json_pure for now due to this bug:
84
+ # https://github.com/flori/json/issues/186
85
+ # engines[:json_pure] = EncodingError
77
86
  end
78
87
 
79
88
  engines.each do |engine, error|
@@ -114,7 +123,7 @@ module VCR
114
123
  context 'when there is already a serializer registered for the given name' do
115
124
  before(:each) do
116
125
  subject[:foo] = :old_serializer
117
- subject.stub :warn
126
+ allow(subject).to receive :warn
118
127
  end
119
128
 
120
129
  it 'overrides the existing serializer' do
@@ -123,7 +132,7 @@ module VCR
123
132
  end
124
133
 
125
134
  it 'warns that there is a name collision' do
126
- subject.should_receive(:warn).with(
135
+ expect(subject).to receive(:warn).with(
127
136
  /WARNING: There is already a VCR cassette serializer registered for :foo\. Overriding it/
128
137
  )
129
138
  subject[:foo] = :new_serializer
@@ -8,16 +8,26 @@ describe VCR::Cassette do
8
8
  VCR::HTTPInteraction.new(request, response).tap { |i| yield i if block_given? }
9
9
  end
10
10
 
11
+ def stub_old_interactions(interactions)
12
+ VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
13
+
14
+ hashes = interactions.map(&:to_hash)
15
+ allow(VCR.cassette_serializers[:yaml]).to receive(:deserialize).and_return({ 'http_interactions' => hashes })
16
+ allow(VCR::HTTPInteraction).to receive(:from_hash) do |hash|
17
+ interactions[hashes.index(hash)]
18
+ end
19
+ end
20
+
11
21
  describe '#file' do
12
22
  it 'delegates the file resolution to the FileSystem persister' do
13
23
  fs = VCR::Cassette::Persisters::FileSystem
14
24
  expect(fs).to respond_to(:absolute_path_to_file).with(1).argument
15
- fs.should_receive(:absolute_path_to_file).with("cassette name.yml") { "f.yml" }
25
+ expect(fs).to receive(:absolute_path_to_file).with("cassette name.yml") { "f.yml" }
16
26
  expect(VCR::Cassette.new("cassette name").file).to eq("f.yml")
17
27
  end
18
28
 
19
29
  it 'raises a NotImplementedError when a different persister is used' do
20
- VCR.cassette_persisters[:a] = stub
30
+ VCR.cassette_persisters[:a] = double
21
31
  cassette = VCR::Cassette.new("f", :persist_with => :a)
22
32
  expect { cassette.file }.to raise_error(NotImplementedError)
23
33
  end
@@ -38,7 +48,7 @@ describe VCR::Cassette do
38
48
  end
39
49
 
40
50
  describe '#record_http_interaction' do
41
- let(:the_interaction) { stub(:request => stub(:method => :get).as_null_object).as_null_object }
51
+ let(:the_interaction) { double(:request => double(:method => :get).as_null_object).as_null_object }
42
52
 
43
53
  it 'adds the interaction to #new_recorded_interactions' do
44
54
  cassette = VCR::Cassette.new(:test_cassette)
@@ -63,8 +73,8 @@ describe VCR::Cassette do
63
73
  let(:metadata) { subject.serializable_hash.reject { |k,v| k == "http_interactions" } }
64
74
 
65
75
  it 'includes the hash form of all recorded interactions' do
66
- interaction_1.stub(:to_hash => { "i" => 1, 'body' => '' })
67
- interaction_2.stub(:to_hash => { "i" => 2, 'body' => '' })
76
+ allow(interaction_1).to receive(:to_hash).and_return({ "i" => 1, 'body' => '' })
77
+ allow(interaction_2).to receive(:to_hash).and_return({ "i" => 2, 'body' => '' })
68
78
  expect(subject.serializable_hash).to include('http_interactions' => [{ "i" => 1, 'body' => '' }, { "i" => 2, 'body' => '' }])
69
79
  end
70
80
 
@@ -131,11 +141,11 @@ describe VCR::Cassette do
131
141
  let(:empty_cassette_yaml) { YAML.dump("http_interactions" => []) }
132
142
 
133
143
  it 'optionally renders the file as ERB using the ERBRenderer' do
134
- VCR::Cassette::Persisters::FileSystem.stub(:[] => empty_cassette_yaml)
144
+ allow(VCR::Cassette::Persisters::FileSystem).to receive(:[]).and_return(empty_cassette_yaml)
135
145
 
136
- VCR::Cassette::ERBRenderer.should_receive(:new).with(
146
+ expect(VCR::Cassette::ERBRenderer).to receive(:new).with(
137
147
  empty_cassette_yaml, anything, "foo"
138
- ).and_return(mock('renderer', :render => empty_cassette_yaml))
148
+ ).and_return(double('renderer', :render => empty_cassette_yaml))
139
149
 
140
150
  VCR::Cassette.new('foo', :record => :new_episodes).http_interactions
141
151
  end
@@ -145,9 +155,9 @@ describe VCR::Cassette do
145
155
  # test that it overrides the default
146
156
  VCR.configuration.default_cassette_options = { :erb => true }
147
157
 
148
- VCR::Cassette::ERBRenderer.should_receive(:new).with(
158
+ expect(VCR::Cassette::ERBRenderer).to receive(:new).with(
149
159
  anything, erb, anything
150
- ).and_return(mock('renderer', :render => empty_cassette_yaml))
160
+ ).and_return(double('renderer', :render => empty_cassette_yaml))
151
161
 
152
162
  VCR::Cassette.new('foo', :record => :new_episodes, :erb => erb).http_interactions
153
163
  end
@@ -155,9 +165,9 @@ describe VCR::Cassette do
155
165
  it "passes #{erb.inspect} to the VCR::Cassette::ERBRenderer when it is the default :erb option and none is given" do
156
166
  VCR.configuration.default_cassette_options = { :erb => erb }
157
167
 
158
- VCR::Cassette::ERBRenderer.should_receive(:new).with(
168
+ expect(VCR::Cassette::ERBRenderer).to receive(:new).with(
159
169
  anything, erb, anything
160
- ).and_return(mock('renderer', :render => empty_cassette_yaml))
170
+ ).and_return(double('renderer', :render => empty_cassette_yaml))
161
171
 
162
172
  VCR::Cassette.new('foo', :record => :new_episodes).http_interactions
163
173
  end
@@ -187,12 +197,12 @@ describe VCR::Cassette do
187
197
  expect(VCR::Cassette.new('empty', :record => :none).send(:previously_recorded_interactions)).to eq([])
188
198
  end
189
199
 
190
- let(:custom_persister) { stub("custom persister") }
200
+ let(:custom_persister) { double("custom persister") }
191
201
 
192
202
  it 'reads from the configured persister' do
193
203
  VCR.configuration.cassette_library_dir = nil
194
204
  VCR.cassette_persisters[:foo] = custom_persister
195
- custom_persister.should_receive(:[]).with("abc.yml") { "" }
205
+ expect(custom_persister).to receive(:[]).with("abc.yml") { "" }
196
206
  VCR::Cassette.new("abc", :persist_with => :foo).http_interactions
197
207
  end
198
208
 
@@ -209,24 +219,15 @@ describe VCR::Cassette do
209
219
  end
210
220
 
211
221
  context "when :#{record_mode} is passed as the record option" do
212
- def stub_old_interactions(interactions)
213
- hashes = interactions.map(&:to_hash)
214
- VCR.cassette_serializers[:yaml].stub(:deserialize => { 'http_interactions' => hashes })
215
- VCR::HTTPInteraction.stub(:from_hash) do |hash|
216
- interactions[hashes.index(hash)]
217
- end
218
- end
219
-
220
222
  unless record_mode == :all
221
223
  let(:interaction_1) { http_interaction { |i| i.request.uri = 'http://example.com/foo' } }
222
224
  let(:interaction_2) { http_interaction { |i| i.request.uri = 'http://example.com/bar' } }
223
225
  let(:interactions) { [interaction_1, interaction_2] }
224
- before(:each) { VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec" }
225
226
 
226
227
  it 'updates the content_length headers when given :update_content_length_header => true' do
227
228
  stub_old_interactions(interactions)
228
- interaction_1.response.should_receive(:update_content_length_header)
229
- interaction_2.response.should_receive(:update_content_length_header)
229
+ expect(interaction_1.response).to receive(:update_content_length_header)
230
+ expect(interaction_2.response).to receive(:update_content_length_header)
230
231
 
231
232
  VCR::Cassette.new('example', :record => record_mode, :update_content_length_header => true).http_interactions
232
233
  end
@@ -234,8 +235,8 @@ describe VCR::Cassette do
234
235
  [nil, false].each do |val|
235
236
  it "does not update the content_lenth headers when given :update_content_length_header => #{val.inspect}" do
236
237
  stub_old_interactions(interactions)
237
- interaction_1.response.should_not_receive(:update_content_length_header)
238
- interaction_2.response.should_not_receive(:update_content_length_header)
238
+ expect(interaction_1.response).not_to receive(:update_content_length_header)
239
+ expect(interaction_2.response).not_to receive(:update_content_length_header)
239
240
 
240
241
  VCR::Cassette.new('example', :record => record_mode, :update_content_length_header => val).http_interactions
241
242
  end
@@ -246,7 +247,7 @@ describe VCR::Cassette do
246
247
  subject { VCR::Cassette.new(File.basename(file_name).gsub('.yml', ''), :record => record_mode, :re_record_interval => 7.days) }
247
248
 
248
249
  context 'when the cassette file does not exist' do
249
- before(:each) { File.stub(:exist?).with(file_name).and_return(false) }
250
+ before(:each) { allow(File).to receive(:exist?).with(file_name).and_return(false) }
250
251
 
251
252
  it "has :#{record_mode} for the record mode" do
252
253
  expect(subject.record_mode).to eq(record_mode)
@@ -260,9 +261,9 @@ describe VCR::Cassette do
260
261
  end
261
262
  yaml = YAML.dump("http_interactions" => interactions)
262
263
 
263
- File.stub(:exist?).with(file_name).and_return(true)
264
- File.stub(:size?).with(file_name).and_return(true)
265
- File.stub(:read).with(file_name).and_return(yaml)
264
+ allow(File).to receive(:exist?).with(file_name).and_return(true)
265
+ allow(File).to receive(:size?).with(file_name).and_return(true)
266
+ allow(File).to receive(:read).with(file_name).and_return(yaml)
266
267
  end
267
268
 
268
269
  context 'and the earliest recorded interaction was recorded less than 7 days ago' do
@@ -285,12 +286,12 @@ describe VCR::Cassette do
285
286
  ] end
286
287
 
287
288
  it "has :all for the record mode when there is an internet connection available" do
288
- VCR::InternetConnection.stub(:available? => true)
289
+ allow(VCR::InternetConnection).to receive(:available?).and_return(true)
289
290
  expect(subject.record_mode).to eq(:all)
290
291
  end
291
292
 
292
293
  it "has :#{record_mode} for the record mode when there is no internet connection available" do
293
- VCR::InternetConnection.stub(:available? => false)
294
+ allow(VCR::InternetConnection).to receive(:available?).and_return(false)
294
295
  expect(subject.record_mode).to eq(record_mode)
295
296
  end
296
297
  end
@@ -299,7 +300,7 @@ describe VCR::Cassette do
299
300
  end
300
301
 
301
302
  it 'does not load ignored interactions' do
302
- VCR.request_ignorer.stub(:ignore?) do |request|
303
+ allow(VCR.request_ignorer).to receive(:ignore?) do |request|
303
304
  request.uri !~ /example\.com/
304
305
  end
305
306
 
@@ -338,14 +339,14 @@ describe VCR::Cassette do
338
339
  end
339
340
 
340
341
  it "instantiates the http_interactions with parent_list set to a null list if given :exclusive => true" do
341
- VCR.stub(:http_interactions => stub)
342
+ allow(VCR).to receive(:http_interactions).and_return(double)
342
343
  VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
343
344
  cassette = VCR::Cassette.new('example', :record => record_mode, :exclusive => true)
344
345
  expect(cassette.http_interactions.parent_list).to be(VCR::Cassette::HTTPInteractionList::NullList)
345
346
  end
346
347
 
347
348
  it "instantiates the http_interactions with parent_list set to VCR.http_interactions if given :exclusive => false" do
348
- VCR.stub(:http_interactions => stub)
349
+ allow(VCR).to receive(:http_interactions).and_return(double)
349
350
  VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
350
351
  cassette = VCR::Cassette.new('example', :record => record_mode, :exclusive => false)
351
352
  expect(cassette.http_interactions.parent_list).to be(VCR.http_interactions)
@@ -353,7 +354,9 @@ describe VCR::Cassette do
353
354
 
354
355
  if stub_requests
355
356
  it 'invokes the before_playback hooks' do
356
- VCR.configuration.should_receive(:invoke_hook).with(
357
+ VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
358
+
359
+ expect(VCR.configuration).to receive(:invoke_hook).with(
357
360
  :before_playback,
358
361
  an_instance_of(VCR::HTTPInteraction::HookAware),
359
362
  an_instance_of(VCR::Cassette)
@@ -391,17 +394,63 @@ describe VCR::Cassette do
391
394
  end
392
395
  end
393
396
 
397
+ describe ".originally_recorded_at" do
398
+ it 'returns the earliest `recorded_at` timestamp' do
399
+ i1 = http_interaction { |i| i.recorded_at = Time.now - 1000 }
400
+ i2 = http_interaction { |i| i.recorded_at = Time.now - 10000 }
401
+ i3 = http_interaction { |i| i.recorded_at = Time.now - 100 }
402
+
403
+ stub_old_interactions([i1, i2, i3])
404
+
405
+ cassette = VCR::Cassette.new("example")
406
+ expect(cassette.originally_recorded_at).to eq(i2.recorded_at)
407
+ end
408
+
409
+ it 'records nil for a cassette that has no prior recorded interactions' do
410
+ stub_old_interactions([])
411
+ cassette = VCR::Cassette.new("example")
412
+ expect(cassette.originally_recorded_at).to be_nil
413
+ end
414
+ end
415
+
394
416
  describe '#eject' do
395
- let(:custom_persister) { stub("custom persister", :[] => nil) }
417
+ let(:custom_persister) { double("custom persister", :[] => nil) }
396
418
 
397
- it 'asserts that there are no unused interactions if allow_unused_http_interactions is set to false' do
398
- cassette = VCR.insert_cassette("foo", :allow_unused_http_interactions => false)
419
+ context "when :allow_unused_http_interactions is set to false" do
420
+ it 'asserts that there are no unused interactions' do
421
+ cassette = VCR.insert_cassette("foo", :allow_unused_http_interactions => false)
399
422
 
400
- interaction_list = cassette.http_interactions
401
- expect(interaction_list).to respond_to(:assert_no_unused_interactions!).with(0).arguments
402
- interaction_list.should_receive(:assert_no_unused_interactions!)
423
+ interaction_list = cassette.http_interactions
424
+ expect(interaction_list).to respond_to(:assert_no_unused_interactions!).with(0).arguments
425
+ expect(interaction_list).to receive(:assert_no_unused_interactions!)
403
426
 
404
- cassette.eject
427
+ cassette.eject
428
+ end
429
+
430
+ it 'does not assert no unused interactions if there is an existing error' do
431
+ cassette = VCR.insert_cassette("foo", :allow_unused_http_interactions => false)
432
+ interaction_list = cassette.http_interactions
433
+ allow(interaction_list).to receive(:assert_no_unused_interactions!)
434
+
435
+ expect {
436
+ begin
437
+ raise "boom"
438
+ ensure
439
+ cassette.eject
440
+ end
441
+ }.to raise_error(/boom/)
442
+
443
+ expect(interaction_list).not_to have_received(:assert_no_unused_interactions!)
444
+ end
445
+
446
+ it 'does not assert no unused interactions if :skip_no_unused_interactions_assertion is passed' do
447
+ cassette = VCR.insert_cassette("foo", :allow_unused_http_interactions => false)
448
+
449
+ interaction_list = cassette.http_interactions
450
+ expect(interaction_list).not_to receive(:assert_no_unused_interactions!)
451
+
452
+ cassette.eject(:skip_no_unused_interactions_assertion => true)
453
+ end
405
454
  end
406
455
 
407
456
  it 'does not assert that there are no unused interactions if allow_unused_http_interactions is set to true' do
@@ -409,7 +458,7 @@ describe VCR::Cassette do
409
458
 
410
459
  interaction_list = cassette.http_interactions
411
460
  expect(interaction_list).to respond_to(:assert_no_unused_interactions!)
412
- interaction_list.should_not_receive(:assert_no_unused_interactions!)
461
+ expect(interaction_list).not_to receive(:assert_no_unused_interactions!)
413
462
 
414
463
  cassette.eject
415
464
  end
@@ -420,7 +469,7 @@ describe VCR::Cassette do
420
469
  cassette = VCR.insert_cassette("foo", :persist_with => :foo)
421
470
  cassette.record_http_interaction http_interaction
422
471
 
423
- custom_persister.should_receive(:[]=).with("foo.yml", /http_interactions/)
472
+ expect(custom_persister).to receive(:[]=).with("foo.yml", /http_interactions/)
424
473
 
425
474
  cassette.eject
426
475
  end
@@ -429,7 +478,7 @@ describe VCR::Cassette do
429
478
  cassette = VCR::Cassette.new(:eject_test)
430
479
  cassette.record_http_interaction http_interaction # so it has one
431
480
  expect(cassette).to respond_to(:serializable_hash)
432
- cassette.stub(:serializable_hash => { "http_interactions" => [1, 3, 5] })
481
+ allow(cassette).to receive(:serializable_hash).and_return({ "http_interactions" => [1, 3, 5] })
433
482
 
434
483
  expect { cassette.eject }.to change { File.exist?(cassette.file) }.from(false).to(true)
435
484
  saved_stuff = YAML.load_file(cassette.file)
@@ -443,12 +492,12 @@ describe VCR::Cassette do
443
492
  ]
444
493
 
445
494
  cassette = VCR::Cassette.new('example', :tag => :foo)
446
- cassette.stub!(:new_recorded_interactions).and_return(interactions)
495
+ allow(cassette).to receive(:new_recorded_interactions).and_return(interactions)
447
496
 
448
- VCR.configuration.stub(:invoke_hook).and_return([false])
497
+ allow(VCR.configuration).to receive(:invoke_hook).and_return([false])
449
498
 
450
499
  interactions.each do |i|
451
- VCR.configuration.should_receive(:invoke_hook).with(
500
+ expect(VCR.configuration).to receive(:invoke_hook).with(
452
501
  :before_record,
453
502
  an_instance_of(VCR::HTTPInteraction::HookAware),
454
503
  cassette
@@ -463,11 +512,11 @@ describe VCR::Cassette do
463
512
  interaction_2 = http_interaction { |i| i.request.uri = 'http://bar.com/'; i.response.body = 'res 2' }
464
513
 
465
514
  hook_aware_interaction_1 = interaction_1.hook_aware
466
- interaction_1.stub(:hook_aware => hook_aware_interaction_1)
515
+ allow(interaction_1).to receive(:hook_aware).and_return(hook_aware_interaction_1)
467
516
  hook_aware_interaction_1.ignore!
468
517
 
469
518
  cassette = VCR::Cassette.new('test_cassette')
470
- cassette.stub!(:new_recorded_interactions).and_return([interaction_1, interaction_2])
519
+ allow(cassette).to receive(:new_recorded_interactions).and_return([interaction_1, interaction_2])
471
520
  cassette.eject
472
521
 
473
522
  saved_recorded_interactions = ::YAML.load_file(cassette.file)
@@ -478,11 +527,11 @@ describe VCR::Cassette do
478
527
  interaction_1 = http_interaction { |i| i.request.uri = 'http://foo.com/'; i.response.body = 'res 1' }
479
528
 
480
529
  hook_aware_interaction_1 = interaction_1.hook_aware
481
- interaction_1.stub(:hook_aware => hook_aware_interaction_1)
530
+ allow(interaction_1).to receive(:hook_aware).and_return(hook_aware_interaction_1)
482
531
  hook_aware_interaction_1.ignore!
483
532
 
484
533
  cassette = VCR::Cassette.new('test_cassette')
485
- cassette.stub!(:new_recorded_interactions).and_return([interaction_1])
534
+ allow(cassette).to receive(:new_recorded_interactions).and_return([interaction_1])
486
535
  cassette.eject
487
536
 
488
537
  expect(File).not_to exist(cassette.file)
@@ -491,7 +540,7 @@ describe VCR::Cassette do
491
540
  it "writes the recorded interactions to a subdirectory if the cassette name includes a directory" do
492
541
  recorded_interactions = [http_interaction { |i| i.response.body = "subdirectory response" }]
493
542
  cassette = VCR::Cassette.new('subdirectory/test_cassette')
494
- cassette.stub(:new_recorded_interactions => recorded_interactions)
543
+ allow(cassette).to receive(:new_recorded_interactions).and_return(recorded_interactions)
495
544
 
496
545
  expect { cassette.eject }.to change { File.exist?(cassette.file) }.from(false).to(true)
497
546
  saved_recorded_interactions = YAML.load_file(cassette.file)
@@ -509,7 +558,7 @@ describe VCR::Cassette do
509
558
 
510
559
  it "does not re-write to disk the previously recorded interactions if there are no new ones" do
511
560
  yaml_file = subject.file
512
- File.should_not_receive(:open).with(subject.file, 'w')
561
+ expect(File).not_to receive(:open).with(subject.file, 'w')
513
562
  expect { subject.eject }.to_not change { File.mtime(yaml_file) }
514
563
  end
515
564
 
@@ -531,8 +580,8 @@ describe VCR::Cassette do
531
580
  let(:now) { Time.utc(2011, 6, 11, 12, 30) }
532
581
 
533
582
  before(:each) do
534
- Time.stub(:now => now)
535
- subject.stub(:previously_recorded_interactions => [interaction_foo_1])
583
+ allow(Time).to receive(:now).and_return(now)
584
+ allow(subject).to receive(:previously_recorded_interactions).and_return([interaction_foo_1])
536
585
  subject.record_http_interaction(interaction_foo_2)
537
586
  subject.record_http_interaction(interaction_bar)
538
587
  subject.eject