vcr 2.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.travis.yml +2 -1
  2. data/CHANGELOG.md +58 -1
  3. data/Gemfile +0 -6
  4. data/README.md +12 -3
  5. data/Rakefile +2 -2
  6. data/features/.nav +2 -0
  7. data/features/cassettes/allow_unused_http_interactions.feature +86 -0
  8. data/features/cassettes/naming.feature +3 -2
  9. data/features/cassettes/persistence.feature +63 -0
  10. data/features/configuration/debug_logging.feature +3 -3
  11. data/features/configuration/ignore_request.feature +1 -1
  12. data/features/hooks/after_http_request.feature +1 -1
  13. data/features/step_definitions/cli_steps.rb +33 -0
  14. data/features/support/env.rb +0 -1
  15. data/lib/vcr.rb +13 -0
  16. data/lib/vcr/cassette.rb +57 -44
  17. data/lib/vcr/cassette/{reader.rb → erb_renderer.rb} +9 -11
  18. data/lib/vcr/cassette/http_interaction_list.rb +18 -0
  19. data/lib/vcr/cassette/persisters.rb +42 -0
  20. data/lib/vcr/cassette/persisters/file_system.rb +60 -0
  21. data/lib/vcr/cassette/serializers.rb +1 -1
  22. data/lib/vcr/cassette/serializers/json.rb +1 -1
  23. data/lib/vcr/cassette/serializers/psych.rb +1 -1
  24. data/lib/vcr/cassette/serializers/syck.rb +1 -1
  25. data/lib/vcr/cassette/serializers/yaml.rb +1 -1
  26. data/lib/vcr/configuration.rb +49 -25
  27. data/lib/vcr/errors.rb +6 -0
  28. data/lib/vcr/library_hooks/excon.rb +1 -1
  29. data/lib/vcr/library_hooks/fakeweb.rb +16 -3
  30. data/lib/vcr/library_hooks/faraday.rb +13 -0
  31. data/lib/vcr/library_hooks/typhoeus.rb +5 -1
  32. data/lib/vcr/library_hooks/webmock.rb +90 -35
  33. data/lib/vcr/middleware/faraday.rb +12 -4
  34. data/lib/vcr/request_handler.rb +10 -2
  35. data/lib/vcr/structs.rb +31 -7
  36. data/lib/vcr/version.rb +1 -1
  37. data/script/ci.sh +14 -0
  38. data/spec/spec_helper.rb +8 -1
  39. data/spec/support/shared_example_groups/hook_into_http_library.rb +31 -1
  40. data/spec/vcr/cassette/{reader_spec.rb → erb_renderer_spec.rb} +15 -21
  41. data/spec/vcr/cassette/http_interaction_list_spec.rb +15 -0
  42. data/spec/vcr/cassette/persisters/file_system_spec.rb +64 -0
  43. data/spec/vcr/cassette/persisters_spec.rb +39 -0
  44. data/spec/vcr/cassette/serializers_spec.rb +4 -3
  45. data/spec/vcr/cassette_spec.rb +65 -41
  46. data/spec/vcr/configuration_spec.rb +33 -2
  47. data/spec/vcr/library_hooks/fakeweb_spec.rb +11 -0
  48. data/spec/vcr/library_hooks/faraday_spec.rb +24 -0
  49. data/spec/vcr/library_hooks/webmock_spec.rb +82 -2
  50. data/spec/vcr/middleware/faraday_spec.rb +32 -0
  51. data/spec/vcr/structs_spec.rb +60 -20
  52. data/spec/vcr/util/hooks_spec.rb +7 -0
  53. data/spec/vcr_spec.rb +7 -0
  54. data/vcr.gemspec +2 -2
  55. metadata +31 -26
  56. data/Guardfile +0 -9
  57. data/script/FullBuildRakeFile +0 -56
  58. data/script/full_build +0 -1
  59. data/script/spec +0 -1
@@ -17,7 +17,7 @@ module VCR
17
17
 
18
18
  # Constructs a new instance of the Faraday middleware.
19
19
  #
20
- # @param [#call] the faraday app
20
+ # @param [#call] app the faraday app
21
21
  def initialize(app)
22
22
  super
23
23
  @app = app
@@ -57,10 +57,18 @@ module VCR
57
57
  @vcr_request ||= VCR::Request.new \
58
58
  env[:method],
59
59
  env[:url].to_s,
60
- env[:body],
60
+ raw_body_from(env[:body]),
61
61
  env[:request_headers]
62
62
  end
63
63
 
64
+ def raw_body_from(body)
65
+ return body unless body.respond_to?(:read)
66
+
67
+ body.read.tap do |b|
68
+ body.rewind if body.respond_to?(:rewind)
69
+ end
70
+ end
71
+
64
72
  def response_for(env)
65
73
  response = env[:response]
66
74
  return nil unless response
@@ -68,7 +76,7 @@ module VCR
68
76
  VCR::Response.new(
69
77
  VCR::ResponseStatus.new(response.status, nil),
70
78
  response.headers,
71
- response.body,
79
+ raw_body_from(response.body),
72
80
  nil
73
81
  )
74
82
  end
@@ -77,7 +85,7 @@ module VCR
77
85
  app.call(env)
78
86
  end
79
87
 
80
- def on_stubbed_request
88
+ def on_stubbed_by_vcr_request
81
89
  headers = env[:response_headers] ||= ::Faraday::Utils::Headers.new
82
90
  headers.update stubbed_response.headers if stubbed_response.headers
83
91
  env.update :status => stubbed_response.status.code, :body => stubbed_response.body
@@ -32,8 +32,9 @@ module VCR
32
32
 
33
33
  def request_type(consume_stub = false)
34
34
  case
35
+ when externally_stubbed? then :externally_stubbed
35
36
  when should_ignore? then :ignored
36
- when has_response_stub?(consume_stub) then :stubbed
37
+ when has_response_stub?(consume_stub) then :stubbed_by_vcr
37
38
  when VCR.real_http_connections_allowed? then :recordable
38
39
  else :unhandled
39
40
  end
@@ -50,6 +51,10 @@ module VCR
50
51
  VCR.configuration.invoke_hook(:after_http_request, @after_hook_typed_request, vcr_response)
51
52
  end
52
53
 
54
+ def externally_stubbed?
55
+ false
56
+ end
57
+
53
58
  def should_ignore?
54
59
  disabled? || VCR.request_ignorer.ignore?(vcr_request)
55
60
  end
@@ -76,10 +81,13 @@ module VCR
76
81
  end
77
82
 
78
83
  # Subclasses can implement these
84
+ def on_externally_stubbed_request
85
+ end
86
+
79
87
  def on_ignored_request
80
88
  end
81
89
 
82
- def on_stubbed_request
90
+ def on_stubbed_by_vcr_request
83
91
  end
84
92
 
85
93
  def on_recordable_request
data/lib/vcr/structs.rb CHANGED
@@ -60,6 +60,11 @@ module VCR
60
60
 
61
61
  def initialize(*args)
62
62
  super
63
+
64
+ if body && !body.is_a?(String)
65
+ raise ArgumentError, "#{self.class} initialized with an invalid body: #{body.inspect}."
66
+ end
67
+
63
68
  # Ensure that the body is a raw string, in case the string instance
64
69
  # has been subclassed or extended with additional instance variables
65
70
  # or attributes, so that it is serialized to YAML as a raw string.
@@ -109,7 +114,7 @@ module VCR
109
114
  else [v]
110
115
  end
111
116
 
112
- new_headers[k] = convert_to_raw_strings(val_array)
117
+ new_headers[String.new(k)] = convert_to_raw_strings(val_array)
113
118
  @normalized_header_keys[k.downcase] = k
114
119
  end if headers
115
120
 
@@ -249,9 +254,19 @@ module VCR
249
254
  type == :ignored
250
255
  end
251
256
 
252
- # @return [Boolean] whether or not this request will be stubbed
253
- def stubbed?
254
- type == :stubbed
257
+ # @return [Boolean] whether or not this request is being stubbed by VCR
258
+ # @see #externally_stubbed?
259
+ # @see #stubbed?
260
+ def stubbed_by_vcr?
261
+ type == :stubbed_by_vcr
262
+ end
263
+
264
+ # @return [Boolean] whether or not this request is being stubbed by an
265
+ # external library (such as WebMock or FakeWeb).
266
+ # @see #stubbed_by_vcr?
267
+ # @see #stubbed?
268
+ def externally_stubbed?
269
+ type == :externally_stubbed
255
270
  end
256
271
 
257
272
  # @return [Boolean] whether or not this request will be recorded.
@@ -270,6 +285,14 @@ module VCR
270
285
  ignored? || recordable?
271
286
  end
272
287
 
288
+ # @return [Boolean] whether or not this request will be stubbed.
289
+ # It may be stubbed by an external library or by VCR.
290
+ # @see #stubbed_by_vcr?
291
+ # @see #externally_stubbed?
292
+ def stubbed?
293
+ stubbed_by_vcr? || externally_stubbed?
294
+ end
295
+
273
296
  undef method
274
297
  end
275
298
 
@@ -497,10 +520,11 @@ module VCR
497
520
  # Replaces a string in any part of the HTTP interaction (headers, request body,
498
521
  # response body, etc) with the given replacement text.
499
522
  #
500
- # @param [String] text the text to replace
501
- # @param [String] replacement_text the text to put in its place
523
+ # @param [#to_s] text the text to replace
524
+ # @param [#to_s] replacement_text the text to put in its place
502
525
  def filter!(text, replacement_text)
503
- return self if [text, replacement_text].any? { |t| t.to_s.empty? }
526
+ text, replacement_text = text.to_s, replacement_text.to_s
527
+ return self if [text, replacement_text].any? { |t| t.empty? }
504
528
  filter_object!(self, text, replacement_text)
505
529
  end
506
530
 
data/lib/vcr/version.rb CHANGED
@@ -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.1.1'
13
+ string = '2.2.0'
14
14
 
15
15
  def string.parts
16
16
  split('.').map { |p| p.to_i }
data/script/ci.sh ADDED
@@ -0,0 +1,14 @@
1
+ # Kill the whole script on error
2
+ set -e
3
+
4
+ # Setup vendored rspec-1
5
+ git submodule init
6
+ git submodule update
7
+
8
+ bundle exec ruby -w -I./spec -r./spec/capture_warnings -rspec_helper -S rspec spec --format progress --backtrace
9
+
10
+ bundle exec cucumber
11
+
12
+ bundle exec rake yard_coverage
13
+
14
+ bundle exec rake check_code_coverage
data/spec/spec_helper.rb CHANGED
@@ -28,7 +28,14 @@ end
28
28
 
29
29
  require 'rspec'
30
30
 
31
- Dir['./spec/support/**/*.rb'].each { |f| require f }
31
+ require "support/fixnum_extension.rb"
32
+ require "support/http_library_adapters.rb"
33
+ require "support/ruby_interpreter.rb"
34
+ require "support/shared_example_groups/hook_into_http_library.rb"
35
+ require "support/shared_example_groups/request_hooks.rb"
36
+ require "support/sinatra_app.rb"
37
+ require "support/vcr_localhost_server.rb"
38
+ require "support/vcr_stub_helpers.rb"
32
39
 
33
40
  require 'vcr'
34
41
  require 'monkey_patches'
@@ -144,6 +144,28 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
144
144
  directly_stub_request(:get, request_url, "stubbed response")
145
145
  get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
146
146
  end
147
+
148
+ it 'can directly stub the request when VCR is turned on and no cassette is in use' do
149
+ directly_stub_request(:get, request_url, "stubbed response")
150
+ get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
151
+ end
152
+
153
+ it 'can directly stub the request when VCR is turned on and a cassette is in use' do
154
+ VCR.use_cassette("temp") do
155
+ directly_stub_request(:get, request_url, "stubbed response")
156
+ get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
157
+ end
158
+ end
159
+
160
+ it 'does not record requests that are directly stubbed' do
161
+ VCR.should respond_to(:record_http_interaction)
162
+ VCR.should_not_receive(:record_http_interaction)
163
+
164
+ VCR.use_cassette("temp") do
165
+ directly_stub_request(:get, request_url, "stubbed response")
166
+ get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
167
+ end
168
+ end
147
169
  end
148
170
  end
149
171
 
@@ -306,6 +328,14 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
306
328
  it_behaves_like "request hooks", library_hook_name, :ignored
307
329
  end
308
330
 
331
+ context "when the request is directly stubbed" do
332
+ before(:each) do
333
+ directly_stub_request(:get, request_url, "FOO!")
334
+ end
335
+
336
+ it_behaves_like "request hooks", library_hook_name, :externally_stubbed
337
+ end if method_defined?(:directly_stub_request)
338
+
309
339
  context 'when the request is recorded' do
310
340
  let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') }
311
341
 
@@ -341,7 +371,7 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
341
371
  stub_requests([http_interaction(request_url)], [:method, :uri])
342
372
  end
343
373
 
344
- it_behaves_like "request hooks", library_hook_name, :stubbed
374
+ it_behaves_like "request hooks", library_hook_name, :stubbed_by_vcr
345
375
  end
346
376
 
347
377
  context 'when the request is not allowed' do
@@ -1,54 +1,48 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe VCR::Cassette::Reader do
4
- describe '#read' do
5
- def read(*args)
6
- described_class.new(*args).read
3
+ describe VCR::Cassette::ERBRenderer do
4
+ describe '#render' do
5
+ def render(*args)
6
+ described_class.new(*args).render
7
7
  end
8
8
 
9
9
  let(:no_vars_content) { '<%= 3 + 4 %>. Some ERB' }
10
10
  let(:vars_content) { '<%= var1 %>. ERB with Vars! <%= var2 %>' }
11
11
 
12
- before(:each) do
13
- File.stub(:read) do |name|
14
- case name
15
- when 'no_vars'; no_vars_content
16
- when 'vars'; vars_content
17
- else raise ArgumentError.new("Unexpected file name: #{name}")
18
- end
19
- end
20
- end
21
-
22
12
  context 'when ERB is disabled' do
23
- it 'reads the raw file content' do
24
- read('no_vars', false).should eq(no_vars_content)
25
- read('no_vars', nil).should eq(no_vars_content)
13
+ it 'returns the given template' do
14
+ render(no_vars_content, false).should eq(no_vars_content)
15
+ render(no_vars_content, nil).should eq(no_vars_content)
26
16
  end
27
17
  end
28
18
 
29
19
  context 'when ERB is enabled but no variables are passed' do
30
20
  it 'renders the file content as ERB' do
31
- read('no_vars', true).should eq("7. Some ERB")
21
+ render(no_vars_content, true).should eq("7. Some ERB")
32
22
  end
33
23
 
34
24
  it 'raises an appropriate error when the ERB template needs variables' do
35
25
  expect {
36
- read('vars', true)
26
+ render(vars_content, true, "vars")
37
27
  }.to raise_error(VCR::Errors::MissingERBVariableError,
38
28
  %{The ERB in the vars cassette file references undefined variable var1. } +
39
29
  %{Pass it to the cassette using :erb => #{ {:var1=>"some value"}.inspect }.}
40
30
  )
41
31
  end
32
+
33
+ it 'gracefully handles the template being nil' do
34
+ render(nil, true).should be_nil
35
+ end
42
36
  end
43
37
 
44
38
  context 'when ERB is enabled and variables are passed' do
45
39
  it 'renders the file content as ERB with the passed variables' do
46
- read('vars', :var1 => 'foo', :var2 => 'bar').should eq('foo. ERB with Vars! bar')
40
+ render(vars_content, :var1 => 'foo', :var2 => 'bar').should eq('foo. ERB with Vars! bar')
47
41
  end
48
42
 
49
43
  it 'raises an appropriate error when one or more of the needed variables are not passed' do
50
44
  expect {
51
- read('vars', :var1 => 'foo')
45
+ render(vars_content, { :var1 => 'foo' }, "vars")
52
46
  }.to raise_error(VCR::Errors::MissingERBVariableError,
53
47
  %{The ERB in the vars cassette file references undefined variable var2. } +
54
48
  %{Pass it to the cassette using :erb => #{ {:var1 => "foo", :var2 => "some value"}.inspect }.}
@@ -86,6 +86,21 @@ module VCR
86
86
  end
87
87
  end
88
88
 
89
+ describe "#assert_no_unused_interactions?" do
90
+ it 'should raise a SkippedHTTPRequestError when there are unused interactions left' do
91
+ expect { list.assert_no_unused_interactions! }.to raise_error(Errors::UnusedHTTPInteractionError)
92
+ list.response_for(request_with(:method => :put))
93
+ expect { list.assert_no_unused_interactions! }.to raise_error(Errors::UnusedHTTPInteractionError)
94
+ end
95
+
96
+ it 'should raise nothing when there are no unused interactions left' do
97
+ [:put, :post, :post].each do |method|
98
+ list.response_for(request_with(:method => method))
99
+ end
100
+ list.assert_no_unused_interactions! # should not raise an error.
101
+ end
102
+ end
103
+
89
104
  describe "has_interaction_matching?" do
90
105
  it 'returns false when the list is empty' do
91
106
  HTTPInteractionList.new([], [:method]).should_not have_interaction_matching(stub)
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+ require 'vcr/cassette/persisters/file_system'
3
+
4
+ module VCR
5
+ class Cassette
6
+ class Persisters
7
+ describe FileSystem do
8
+ before { FileSystem.storage_location = VCR.configuration.cassette_library_dir }
9
+
10
+ describe "#[]" do
11
+ it 'reads from the given file, relative to the configured storage location' do
12
+ File.open(FileSystem.storage_location + '/foo.txt', 'w') { |f| f.write('1234') }
13
+ FileSystem["foo.txt"].should eq("1234")
14
+ end
15
+
16
+ it 'handles directories in the given file name' do
17
+ FileUtils.mkdir_p FileSystem.storage_location + '/a'
18
+ File.open(FileSystem.storage_location + '/a/b', 'w') { |f| f.write('1234') }
19
+ FileSystem["a/b"].should eq("1234")
20
+ end
21
+
22
+ it 'returns nil if the file does not exist' do
23
+ FileSystem["non_existant_file"].should be_nil
24
+ end
25
+ end
26
+
27
+ describe "#[]=" do
28
+ it 'writes the given file contents to the given file name' do
29
+ File.exist?(FileSystem.storage_location + '/foo.txt').should be_false
30
+ FileSystem["foo.txt"] = "bar"
31
+ File.read(FileSystem.storage_location + '/foo.txt').should eq("bar")
32
+ end
33
+
34
+ it 'creates any needed intermediary directories' do
35
+ File.exist?(FileSystem.storage_location + '/a').should be_false
36
+ FileSystem["a/b"] = "bar"
37
+ File.read(FileSystem.storage_location + '/a/b').should eq("bar")
38
+ end
39
+ end
40
+
41
+ describe "#absolute_path_to_file" do
42
+ it "returns the absolute path to the given relative file based on the storage location" do
43
+ expected = File.join(FileSystem.storage_location, "bar/bazz.json")
44
+ FileSystem.absolute_path_to_file("bar/bazz.json").should eq(expected)
45
+ end
46
+
47
+ it "returns nil if the storage_location is not set" do
48
+ FileSystem.storage_location = nil
49
+ FileSystem.absolute_path_to_file("bar/bazz.json").should be_nil
50
+ end
51
+
52
+ it "sanitizes the file name" do
53
+ expected = File.join(FileSystem.storage_location, "_t_i-t_1_2_f_n.json")
54
+ FileSystem.absolute_path_to_file("\nt \t! i-t_1.2_f n.json").should eq(expected)
55
+
56
+ expected = File.join(FileSystem.storage_location, "a_1/b")
57
+ FileSystem.absolute_path_to_file("a 1/b").should eq(expected)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,39 @@
1
+ require 'vcr/cassette/persisters'
2
+
3
+ module VCR
4
+ class Cassette
5
+ describe Persisters do
6
+ describe "#[]=" do
7
+ context 'when there is already a persister registered for the given name' do
8
+ before(:each) do
9
+ subject[:foo] = :old_persister
10
+ subject.stub :warn
11
+ end
12
+
13
+ it 'overrides the existing persister' do
14
+ subject[:foo] = :new_persister
15
+ subject[:foo].should be(:new_persister)
16
+ end
17
+
18
+ it 'warns that there is a name collision' do
19
+ subject.should_receive(:warn).with(
20
+ /WARNING: There is already a VCR cassette persister registered for :foo\. Overriding it/
21
+ )
22
+ subject[:foo] = :new_persister
23
+ end
24
+ end
25
+ end
26
+
27
+ describe "#[]" do
28
+ it 'raises an error when given an unrecognized persister name' do
29
+ expect { subject[:foo] }.to raise_error(ArgumentError)
30
+ end
31
+
32
+ it 'returns the named persister' do
33
+ subject[:file_system].should be(VCR::Cassette::Persisters::FileSystem)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+