vcr 2.7.0 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
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