vcr 2.5.0 → 2.6.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.
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