vcr 2.7.0 → 2.8.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 (47) hide show
  1. checksums.yaml +8 -8
  2. data/.rspec +1 -1
  3. data/.travis.yml +4 -0
  4. data/CHANGELOG.md +29 -0
  5. data/CONTRIBUTING.md +0 -13
  6. data/Gemfile +1 -3
  7. data/Gemfile.lock +13 -22
  8. data/README.md +7 -1
  9. data/Rakefile +10 -6
  10. data/cucumber.yml +2 -2
  11. data/features/request_matching/body_as_json.feature +90 -0
  12. data/features/support/env.rb +1 -1
  13. data/gemfiles/typhoeus_old.gemfile +2 -3
  14. data/gemfiles/typhoeus_old.gemfile.lock +32 -38
  15. data/lib/vcr.rb +1 -1
  16. data/lib/vcr/errors.rb +22 -2
  17. data/lib/vcr/library_hooks/excon.rb +16 -1
  18. data/lib/vcr/library_hooks/webmock.rb +1 -1
  19. data/lib/vcr/middleware/excon.rb +55 -58
  20. data/lib/vcr/middleware/excon/legacy_methods.rb +1 -1
  21. data/lib/vcr/request_matcher_registry.rb +18 -0
  22. data/lib/vcr/test_frameworks/rspec.rb +2 -2
  23. data/lib/vcr/version.rb +1 -1
  24. data/script/ci.sh +1 -2
  25. data/spec/monkey_patches.rb +3 -2
  26. data/spec/spec_helper.rb +1 -2
  27. data/spec/support/shared_example_groups/excon.rb +28 -9
  28. data/spec/support/shared_example_groups/request_hooks.rb +1 -1
  29. data/spec/vcr/cassette/persisters/file_system_spec.rb +2 -2
  30. data/spec/vcr/cassette_spec.rb +7 -7
  31. data/spec/vcr/configuration_spec.rb +13 -13
  32. data/spec/vcr/errors_spec.rb +23 -0
  33. data/spec/vcr/library_hooks/excon_spec.rb +15 -0
  34. data/spec/vcr/library_hooks/fakeweb_spec.rb +1 -1
  35. data/spec/vcr/library_hooks_spec.rb +8 -8
  36. data/spec/vcr/middleware/faraday_spec.rb +1 -1
  37. data/spec/vcr/middleware/rack_spec.rb +1 -1
  38. data/spec/vcr/request_ignorer_spec.rb +2 -2
  39. data/spec/vcr/request_matcher_registry_spec.rb +59 -33
  40. data/spec/vcr/structs_spec.rb +2 -2
  41. data/spec/vcr/test_frameworks/cucumber_spec.rb +1 -1
  42. data/spec/vcr/test_frameworks/rspec_spec.rb +1 -1
  43. data/spec/vcr/util/hooks_spec.rb +5 -5
  44. data/spec/vcr/version_spec.rb +20 -4
  45. data/spec/vcr_spec.rb +7 -7
  46. metadata +4 -3
  47. data/.limited_red +0 -1
data/lib/vcr.rb CHANGED
@@ -293,7 +293,7 @@ module VCR
293
293
  # @private
294
294
  def real_http_connections_allowed?
295
295
  return current_cassette.recording? if current_cassette
296
- configuration.allow_http_connections_when_no_cassette? || @turned_off
296
+ !!(configuration.allow_http_connections_when_no_cassette? || @turned_off)
297
297
  end
298
298
 
299
299
  # @return [RequestMatcherRegistry] the request matcher registry
@@ -77,14 +77,34 @@ module VCR
77
77
  def construct_message
78
78
  ["", "", "=" * 80,
79
79
  "An HTTP request has been made that VCR does not know how to handle:",
80
- " #{request_description}\n",
80
+ "#{request_description}\n",
81
81
  cassette_description,
82
82
  formatted_suggestions,
83
83
  "=" * 80, "", ""].join("\n")
84
84
  end
85
85
 
86
86
  def request_description
87
- "#{request.method.to_s.upcase} #{request.uri}"
87
+ lines = []
88
+
89
+ lines << " #{request.method.to_s.upcase} #{request.uri}"
90
+
91
+ if match_request_on_body?
92
+ lines << " Body: #{request.body}"
93
+ end
94
+
95
+ lines.join("\n")
96
+ end
97
+
98
+ def match_request_on_body?
99
+ current_matchers.include?(:body)
100
+ end
101
+
102
+ def current_matchers
103
+ if VCR.current_cassette
104
+ VCR.current_cassette.match_requests_on
105
+ else
106
+ VCR.configuration.default_cassette_options[:match_requests_on]
107
+ end
88
108
  end
89
109
 
90
110
  def cassette_description
@@ -1,6 +1,21 @@
1
1
  require 'vcr/middleware/excon'
2
2
 
3
- Excon.defaults[:middlewares] << VCR::Middleware::Excon
3
+ module VCR
4
+ class LibraryHooks
5
+ module Excon
6
+ # @private
7
+ def self.configure_middleware
8
+ middlewares = ::Excon.defaults[:middlewares]
9
+
10
+ middlewares << VCR::Middleware::Excon::Request
11
+ response_parser_index = middlewares.index(::Excon::Middleware::ResponseParser)
12
+ middlewares.insert(response_parser_index + 1, VCR::Middleware::Excon::Response)
13
+ end
14
+
15
+ configure_middleware
16
+ end
17
+ end
18
+ end
4
19
 
5
20
  VCR.configuration.after_library_hooks_loaded do
6
21
  # ensure WebMock's Excon adapter does not conflict with us here
@@ -114,7 +114,7 @@ module VCR
114
114
 
115
115
  def on_stubbed_by_vcr_request
116
116
  {
117
- :body => stubbed_response.body,
117
+ :body => stubbed_response.body.dup, # Excon mutates the body, so we must dup it :-(
118
118
  :status => [stubbed_response.status.code.to_i, stubbed_response.status.message],
119
119
  :headers => stubbed_response.headers
120
120
  }
@@ -2,46 +2,50 @@ require 'excon'
2
2
  require 'vcr/request_handler'
3
3
  require 'vcr/util/version_checker'
4
4
 
5
- VCR::VersionChecker.new('Excon', Excon::VERSION, '0.22.0', '0.28').check_version!
5
+ VCR::VersionChecker.new('Excon', Excon::VERSION, '0.25.2', '0.29').check_version!
6
6
 
7
7
  module VCR
8
8
  # Contains middlewares for use with different libraries.
9
9
  module Middleware
10
- # Excon middleware that uses VCR to record and replay HTTP requests made
11
- # through Excon.
12
- #
13
- # @note You can either add this to the middleware stack of an Excon connection
14
- # yourself, or configure {VCR::Configuration#hook_into} to hook into `:excon`.
15
- # Setting the config option will add this middleware to Excon's default
16
- # middleware stack.
17
- class Excon < ::Excon::Middleware::Base
10
+ # Contains Excon middlewares.
11
+ module Excon
12
+ # One part of the Excon middleware that uses VCR to record
13
+ # and replay HTTP requests made through Excon.
14
+ #
18
15
  # @private
19
- def initialize(*args)
20
- # Excon appears to create a new instance of this middleware for each
21
- # request, which means it should be safe to store per-request state
22
- # like this request_handler object on the middleware instance.
23
- # I'm not 100% sure about this yet and should verify with @geemus.
24
- @request_handler = RequestHandler.new
25
- super
26
- end
16
+ class Request < ::Excon::Middleware::Base
17
+ # @private
18
+ def request_call(params)
19
+ params[:vcr_request_handler] = request_handler = RequestHandler.new
20
+ request_handler.before_request(params)
27
21
 
28
- # @private
29
- def request_call(params)
30
- @request_handler.before_request(params)
31
- super
22
+ super
23
+ end
32
24
  end
33
25
 
26
+ # One part of the Excon middleware that uses VCR to record
27
+ # and replay HTTP requests made through Excon.
28
+ #
34
29
  # @private
35
- def response_call(params)
36
- @request_handler.after_request(params)
37
- super
38
- end
30
+ class Response < ::Excon::Middleware::Base
31
+ # @private
32
+ def response_call(params)
33
+ complete_request(params)
34
+ super
35
+ end
39
36
 
40
- # @private
41
- def error_call(params)
42
- @request_handler.ensure_response_body_can_be_read_for_error_case
43
- @request_handler.after_request(params)
44
- super
37
+ def error_call(params)
38
+ complete_request(params)
39
+ super
40
+ end
41
+
42
+ private
43
+
44
+ def complete_request(params)
45
+ if handler = params.delete(:vcr_request_handler)
46
+ handler.after_request(params[:response])
47
+ end
48
+ end
45
49
  end
46
50
 
47
51
  # Handles a single Excon request.
@@ -50,7 +54,6 @@ module VCR
50
54
  class RequestHandler < ::VCR::RequestHandler
51
55
  def initialize
52
56
  @request_params = nil
53
- @response_params = nil
54
57
  @response_body_reader = nil
55
58
  @should_record = false
56
59
  end
@@ -65,18 +68,11 @@ module VCR
65
68
  handle
66
69
  end
67
70
 
68
- # Performs after_request processing based on the provided
69
- # response_params.
71
+ # Performs after_request processing based on the provided response.
70
72
  #
71
73
  # @private
72
- def after_request(response_params)
73
- # If @response_params is already set, it indicates we've already run the
74
- # after_request logic. This can happen when if the response triggers an error,
75
- # whch would then trigger the error_call middleware callback, leading to this
76
- # being called a second time.
77
- return if @response_params
78
-
79
- @response_params = response_params
74
+ def after_request(response)
75
+ vcr_response = vcr_response_for(response)
80
76
 
81
77
  if should_record?
82
78
  VCR.record_http_interaction(VCR::HTTPInteraction.new(vcr_request, vcr_response))
@@ -92,7 +88,7 @@ module VCR
92
88
  @response_body_reader = NonStreamingResponseBodyReader
93
89
  end
94
90
 
95
- attr_reader :request_params, :response_params, :response_body_reader
91
+ attr_reader :request_params, :response_body_reader
96
92
 
97
93
  private
98
94
 
@@ -106,7 +102,7 @@ module VCR
106
102
 
107
103
  def on_stubbed_by_vcr_request
108
104
  request_params[:response] = {
109
- :body => stubbed_response.body,
105
+ :body => stubbed_response.body.dup, # Excon mutates the body, so we must dup it :-(
110
106
  :headers => normalized_headers(stubbed_response.headers || {}),
111
107
  :status => stubbed_response.status.code
112
108
  }
@@ -138,21 +134,15 @@ module VCR
138
134
  end
139
135
  end
140
136
 
141
- def vcr_response
142
- return @vcr_response if defined?(@vcr_response)
137
+ def vcr_response_for(response)
138
+ return nil if response.nil?
143
139
 
144
- if should_record? || response_params.has_key?(:response)
145
- response = response_params.fetch(:response)
146
-
147
- @vcr_response = VCR::Response.new(
148
- VCR::ResponseStatus.new(response.fetch(:status), nil),
149
- response.fetch(:headers),
150
- response_body_reader.read_body_from(response),
151
- nil
152
- )
153
- else
154
- @vcr_response = nil
155
- end
140
+ VCR::Response.new(
141
+ VCR::ResponseStatus.new(response.fetch(:status), nil),
142
+ response.fetch(:headers),
143
+ response_body_reader.read_body_from(response),
144
+ nil
145
+ )
156
146
  end
157
147
 
158
148
  def normalized_headers(headers)
@@ -201,7 +191,14 @@ module VCR
201
191
  #
202
192
  # @private
203
193
  def read_body_from(response_params)
204
- @chunks.join('')
194
+ if @chunks.none?
195
+ # Not sure why, but sometimes the body comes through the params
196
+ # instead of via the streaming block even when the block was
197
+ # configured.
198
+ response_params[:body]
199
+ else
200
+ @chunks.join('')
201
+ end
205
202
  end
206
203
  end
207
204
 
@@ -1,6 +1,6 @@
1
1
  module VCR
2
2
  module Middleware
3
- class Excon
3
+ module Excon
4
4
  # Contains legacy methods only needed when integrating with older versions
5
5
  # of Excon.
6
6
  # @api private
@@ -125,6 +125,24 @@ module VCR
125
125
  VCR.configuration.query_parser.call(r1.parsed_uri.query.to_s) ==
126
126
  VCR.configuration.query_parser.call(r2.parsed_uri.query.to_s)
127
127
  end
128
+
129
+ try_to_register_body_as_json
130
+ end
131
+
132
+ def try_to_register_body_as_json
133
+ begin
134
+ require 'json'
135
+ rescue LoadError
136
+ return
137
+ end
138
+
139
+ register(:body_as_json) do |r1, r2|
140
+ begin
141
+ JSON.parse(r1.body) == JSON.parse(r2.body)
142
+ rescue JSON::ParserError
143
+ false
144
+ end
145
+ end
128
146
  end
129
147
  end
130
148
  end
@@ -20,7 +20,7 @@ module VCR
20
20
  when_tagged_with_vcr = { :vcr => lambda { |v| !!v } }
21
21
 
22
22
  config.before(:each, when_tagged_with_vcr) do |ex|
23
- example = respond_to?(:example) ? self.example : ex
23
+ example = ex.respond_to?(:metadata) ? ex : ex.example
24
24
 
25
25
  options = example.metadata[:vcr]
26
26
  options = options.is_a?(Hash) ? options.dup : {} # in case it's just :vcr => true
@@ -31,7 +31,7 @@ module VCR
31
31
  end
32
32
 
33
33
  config.after(:each, when_tagged_with_vcr) do |ex|
34
- example = respond_to?(:example) ? self.example : ex
34
+ example = ex.respond_to?(:metadata) ? ex : ex.example
35
35
  VCR.eject_cassette(:skip_no_unused_interactions_assertion => !!example.exception)
36
36
  end
37
37
  end
@@ -10,7 +10,7 @@ module VCR
10
10
  # * `parts` [Array<Integer>] List of the version parts.
11
11
  def version
12
12
  @version ||= begin
13
- string = '2.7.0'
13
+ string = '2.8.0'
14
14
 
15
15
  def string.parts
16
16
  split('.').map { |p| p.to_i }
@@ -13,8 +13,7 @@ bundle install --gemfile=gemfiles/typhoeus_old.gemfile --without extras
13
13
  BUNDLE_GEMFILE=gemfiles/typhoeus_old.gemfile bundle exec rspec spec/vcr/library_hooks/typhoeus_0.4_spec.rb --format progress --backtrace
14
14
 
15
15
  # Setup vendored rspec-1
16
- git submodule init
17
- git submodule update
16
+ bundle exec rake submodules
18
17
 
19
18
  echo "-------- Running Specs ---------"
20
19
  bundle exec ruby -I./spec -r./spec/capture_warnings -rspec_helper -S rspec spec --format progress --backtrace
@@ -42,7 +42,7 @@ module MonkeyPatches
42
42
  ::Typhoeus::Hydra.stub_finders << finder
43
43
  end
44
44
  when :excon
45
- ::Excon.defaults[:middlewares] << VCR::Middleware::Excon
45
+ VCR::LibraryHooks::Excon.configure_middleware
46
46
  when :vcr
47
47
  realias Net::HTTP, :request, :with_vcr
48
48
  else raise ArgumentError.new("Unexpected scope: #{scope}")
@@ -69,7 +69,8 @@ module MonkeyPatches
69
69
  end
70
70
 
71
71
  if defined?(::Excon)
72
- ::Excon.defaults[:middlewares].delete(VCR::Middleware::Excon)
72
+ ::Excon.defaults[:middlewares].delete(VCR::Middleware::Excon::Request)
73
+ ::Excon.defaults[:middlewares].delete(VCR::Middleware::Excon::Response)
73
74
  end
74
75
  end
75
76
 
@@ -54,7 +54,6 @@ end
54
54
  RSpec.configure do |config|
55
55
  config.order = :rand
56
56
  config.color_enabled = true
57
- config.treat_symbols_as_metadata_keys_with_true_values = true
58
57
 
59
58
  config.expect_with :rspec do |expectations|
60
59
  expectations.syntax = :expect
@@ -65,7 +64,7 @@ RSpec.configure do |config|
65
64
  end
66
65
 
67
66
  tmp_dir = File.expand_path('../../tmp/cassette_library_dir', __FILE__)
68
- config.before(:each) do
67
+ config.before(:each) do |example|
69
68
  unless example.metadata[:skip_vcr_reset]
70
69
  VCR.reset!
71
70
  VCR.configuration.cassette_library_dir = tmp_dir
@@ -1,17 +1,19 @@
1
1
  shared_examples "Excon streaming" do
2
2
  context "when Excon's streaming API is used" do
3
+ def make_request_to(path)
4
+ chunks = []
5
+
6
+ Excon.get "http://localhost:#{VCR::SinatraApp.port}#{path}", :response_block => lambda { |chunk, remaining_bytes, total_bytes|
7
+ chunks << chunk
8
+ }
9
+
10
+ chunks.join
11
+ end
12
+
3
13
  it 'properly records and plays back the response' do
4
14
  allow(VCR).to receive(:real_http_connections_allowed?).and_return(true)
5
15
  recorded, played_back = [1, 2].map do
6
- chunks = []
7
-
8
- VCR.use_cassette('excon_streaming', :record => :once) do
9
- Excon.get "http://localhost:#{VCR::SinatraApp.port}/foo", :response_block => lambda { |chunk, remaining_bytes, total_bytes|
10
- chunks << chunk
11
- }
12
- end
13
-
14
- chunks.join
16
+ make_request_to('/foo')
15
17
  end
16
18
 
17
19
  expect(recorded).to eq(played_back)
@@ -39,6 +41,23 @@ shared_examples "Excon streaming" do
39
41
  expect(recorded).to eq(played_back)
40
42
  expect(recorded).to eq('404 not 200')
41
43
  end
44
+
45
+ context "when a cassette is played back and appended to" do
46
+ it 'does not allow Excon to mutate the response body in the cassette' do
47
+ VCR.use_cassette('excon_streaming', :record => :new_episodes) do
48
+ expect(make_request_to('/')).to eq('GET to root')
49
+ end
50
+
51
+ VCR.use_cassette('excon_streaming', :record => :new_episodes) do
52
+ expect(make_request_to('/')).to eq('GET to root')
53
+ expect(make_request_to('/foo')).to eq('FOO!') # so it will save to disk again
54
+ end
55
+
56
+ VCR.use_cassette('excon_streaming', :record => :new_episodes) do
57
+ expect(make_request_to('/')).to eq('GET to root')
58
+ end
59
+ end
60
+ end
42
61
  end
43
62
  end
44
63
 
@@ -36,7 +36,7 @@ shared_examples_for "request hooks" do |library_hook_name, request_type|
36
36
  VCR.configuration.send(hook) { |r| hook_called = true }
37
37
 
38
38
  make_request(:disabled)
39
- expect(hook_called).to be_false
39
+ expect(hook_called).to be false
40
40
  end
41
41
 
42
42
  specify "the #type of the yielded request given to the #{hook} hook is #{request_type}" do
@@ -26,13 +26,13 @@ module VCR
26
26
 
27
27
  describe "#[]=" do
28
28
  it 'writes the given file contents to the given file name' do
29
- expect(File.exist?(FileSystem.storage_location + '/foo.txt')).to be_false
29
+ expect(File.exist?(FileSystem.storage_location + '/foo.txt')).to be false
30
30
  FileSystem["foo.txt"] = "bar"
31
31
  expect(File.read(FileSystem.storage_location + '/foo.txt')).to eq("bar")
32
32
  end
33
33
 
34
34
  it 'creates any needed intermediary directories' do
35
- expect(File.exist?(FileSystem.storage_location + '/a')).to be_false
35
+ expect(File.exist?(FileSystem.storage_location + '/a')).to be false
36
36
  FileSystem["a/b"] = "bar"
37
37
  expect(File.read(FileSystem.storage_location + '/a/b')).to eq("bar")
38
38
  end
@@ -104,14 +104,14 @@ describe VCR::Cassette do
104
104
  it 'returns false when there is an existing cassette file with content' do
105
105
  cassette = VCR::Cassette.new("example", :record => :once)
106
106
  expect(File).to exist(cassette.file)
107
- expect(File.size?(cassette.file)).to be_true
107
+ expect(File.size?(cassette.file)).to be_truthy
108
108
  expect(cassette).not_to be_recording
109
109
  end
110
110
 
111
111
  it 'returns true when there is an empty existing cassette file' do
112
112
  cassette = VCR::Cassette.new("empty", :record => :once)
113
113
  expect(File).to exist(cassette.file)
114
- expect(File.size?(cassette.file)).to be_false
114
+ expect(File.size?(cassette.file)).to be_falsey
115
115
  expect(cassette).to be_recording
116
116
  end
117
117
 
@@ -313,7 +313,7 @@ describe VCR::Cassette do
313
313
  VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
314
314
  cassette = VCR::Cassette.new('example', :record => record_mode)
315
315
 
316
- expect(cassette).to have(3).previously_recorded_interactions
316
+ expect(cassette.send(:previously_recorded_interactions).size).to eq(3)
317
317
 
318
318
  i1, i2, i3 = *cassette.send(:previously_recorded_interactions)
319
319
 
@@ -363,7 +363,7 @@ describe VCR::Cassette do
363
363
  ).exactly(3).times
364
364
 
365
365
  cassette = VCR::Cassette.new('example', :record => record_mode)
366
- expect(cassette).to have(3).previously_recorded_interactions
366
+ expect(cassette.send(:previously_recorded_interactions).size).to eq(3)
367
367
  end
368
368
 
369
369
  it 'does not playback any interactions that are ignored in a before_playback hook' do
@@ -373,20 +373,20 @@ describe VCR::Cassette do
373
373
 
374
374
  VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
375
375
  cassette = VCR::Cassette.new('example', :record => record_mode)
376
- expect(cassette).to have(2).previously_recorded_interactions
376
+ expect(cassette.send(:previously_recorded_interactions).size).to eq(2)
377
377
  end
378
378
 
379
379
  it 'instantiates the http_interactions with the loaded interactions and the request matchers' do
380
380
  VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
381
381
  cassette = VCR::Cassette.new('example', :record => record_mode, :match_requests_on => [:body, :headers])
382
- expect(cassette.http_interactions.interactions).to have(3).interactions
382
+ expect(cassette.http_interactions.interactions.size).to eq(3)
383
383
  expect(cassette.http_interactions.request_matchers).to eq([:body, :headers])
384
384
  end
385
385
  else
386
386
  it 'instantiates the http_interactions with the no interactions and the request matchers' do
387
387
  VCR.configuration.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/cassette_spec"
388
388
  cassette = VCR::Cassette.new('example', :record => record_mode, :match_requests_on => [:body, :headers])
389
- expect(cassette.http_interactions.interactions).to have(0).interactions
389
+ expect(cassette.http_interactions.interactions.size).to eq(0)
390
390
  expect(cassette.http_interactions.request_matchers).to eq([:body, :headers])
391
391
  end
392
392
  end