vcr 2.0.0.rc1 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/.gitignore +2 -0
  2. data/.limited_red +1 -0
  3. data/.travis.yml +10 -1
  4. data/.yardopts +9 -0
  5. data/CHANGELOG.md +51 -1
  6. data/Gemfile +5 -1
  7. data/LICENSE +1 -1
  8. data/README.md +23 -28
  9. data/Rakefile +63 -18
  10. data/Upgrade.md +200 -0
  11. data/features/.nav +2 -0
  12. data/features/cassettes/automatic_re_recording.feature +19 -15
  13. data/features/cassettes/dynamic_erb.feature +12 -4
  14. data/features/cassettes/exclusive.feature +31 -23
  15. data/features/cassettes/format.feature +54 -30
  16. data/features/cassettes/naming.feature +1 -1
  17. data/features/cassettes/update_content_length_header.feature +16 -12
  18. data/features/configuration/allow_http_connections_when_no_cassette.feature +1 -1
  19. data/features/configuration/debug_logging.feature +52 -0
  20. data/features/configuration/filter_sensitive_data.feature +4 -4
  21. data/features/configuration/hook_into.feature +5 -2
  22. data/features/configuration/ignore_request.feature +5 -3
  23. data/features/configuration/preserve_exact_body_bytes.feature +103 -0
  24. data/features/hooks/after_http_request.feature +17 -4
  25. data/features/hooks/around_http_request.feature +2 -1
  26. data/features/hooks/before_http_request.feature +25 -8
  27. data/features/hooks/before_playback.feature +16 -12
  28. data/features/hooks/before_record.feature +2 -2
  29. data/features/http_libraries/em_http_request.feature +82 -58
  30. data/features/http_libraries/net_http.feature +6 -6
  31. data/features/middleware/faraday.feature +2 -1
  32. data/features/middleware/rack.feature +2 -2
  33. data/features/record_modes/all.feature +19 -15
  34. data/features/record_modes/new_episodes.feature +17 -13
  35. data/features/record_modes/none.feature +15 -11
  36. data/features/record_modes/once.feature +16 -12
  37. data/features/request_matching/body.feature +28 -20
  38. data/features/request_matching/custom_matcher.feature +28 -20
  39. data/features/request_matching/headers.feature +34 -26
  40. data/features/request_matching/host.feature +28 -20
  41. data/features/request_matching/identical_request_sequence.feature +28 -20
  42. data/features/request_matching/method.feature +28 -20
  43. data/features/request_matching/path.feature +28 -20
  44. data/features/request_matching/playback_repeats.feature +28 -20
  45. data/features/request_matching/uri.feature +28 -20
  46. data/features/request_matching/uri_without_param.feature +28 -20
  47. data/features/support/env.rb +7 -6
  48. data/features/support/vcr_cucumber_helpers.rb +1 -0
  49. data/features/test_frameworks/cucumber.feature +8 -8
  50. data/features/test_frameworks/rspec_macro.feature +4 -4
  51. data/features/test_frameworks/rspec_metadata.feature +6 -6
  52. data/features/test_frameworks/shoulda.feature +1 -1
  53. data/features/test_frameworks/test_unit.feature +1 -1
  54. data/lib/vcr.rb +156 -5
  55. data/lib/vcr/cassette.rb +80 -30
  56. data/lib/vcr/cassette/http_interaction_list.rb +33 -4
  57. data/lib/vcr/cassette/migrator.rb +2 -3
  58. data/lib/vcr/cassette/reader.rb +1 -0
  59. data/lib/vcr/cassette/serializers.rb +22 -0
  60. data/lib/vcr/cassette/serializers/json.rb +27 -2
  61. data/lib/vcr/cassette/serializers/psych.rb +26 -2
  62. data/lib/vcr/cassette/serializers/syck.rb +28 -2
  63. data/lib/vcr/cassette/serializers/yaml.rb +28 -2
  64. data/lib/vcr/configuration.rb +348 -10
  65. data/lib/vcr/deprecations.rb +8 -0
  66. data/lib/vcr/errors.rb +40 -0
  67. data/lib/vcr/extensions/net_http_response.rb +12 -11
  68. data/lib/vcr/library_hooks.rb +1 -0
  69. data/lib/vcr/library_hooks/excon.rb +24 -3
  70. data/lib/vcr/library_hooks/fakeweb.rb +32 -16
  71. data/lib/vcr/library_hooks/faraday.rb +3 -0
  72. data/lib/vcr/library_hooks/typhoeus.rb +40 -37
  73. data/lib/vcr/library_hooks/webmock.rb +54 -34
  74. data/lib/vcr/middleware/faraday.rb +13 -0
  75. data/lib/vcr/middleware/rack.rb +35 -0
  76. data/lib/vcr/request_handler.rb +60 -8
  77. data/lib/vcr/request_ignorer.rb +1 -0
  78. data/lib/vcr/request_matcher_registry.rb +28 -0
  79. data/lib/vcr/structs.rb +245 -38
  80. data/lib/vcr/test_frameworks/cucumber.rb +10 -0
  81. data/lib/vcr/test_frameworks/rspec.rb +26 -1
  82. data/lib/vcr/util/hooks.rb +29 -27
  83. data/lib/vcr/util/internet_connection.rb +2 -0
  84. data/lib/vcr/util/logger.rb +25 -0
  85. data/lib/vcr/util/variable_args_block_caller.rb +1 -0
  86. data/lib/vcr/util/version_checker.rb +1 -0
  87. data/lib/vcr/version.rb +8 -1
  88. data/spec/capture_warnings.rb +3 -3
  89. data/spec/monkey_patches.rb +28 -13
  90. data/spec/spec_helper.rb +17 -0
  91. data/spec/support/http_library_adapters.rb +7 -4
  92. data/spec/support/shared_example_groups/hook_into_http_library.rb +96 -32
  93. data/spec/support/shared_example_groups/request_hooks.rb +9 -8
  94. data/spec/support/sinatra_app.rb +3 -1
  95. data/spec/support/vcr_localhost_server.rb +1 -0
  96. data/spec/vcr/cassette/http_interaction_list_spec.rb +119 -54
  97. data/spec/vcr/cassette/migrator_spec.rb +19 -6
  98. data/spec/vcr/cassette/serializers_spec.rb +51 -6
  99. data/spec/vcr/cassette_spec.rb +44 -19
  100. data/spec/vcr/configuration_spec.rb +91 -6
  101. data/spec/vcr/library_hooks/excon_spec.rb +54 -16
  102. data/spec/vcr/library_hooks/fakeweb_spec.rb +12 -21
  103. data/spec/vcr/library_hooks/typhoeus_spec.rb +2 -29
  104. data/spec/vcr/library_hooks/webmock_spec.rb +4 -18
  105. data/spec/vcr/middleware/faraday_spec.rb +1 -16
  106. data/spec/vcr/structs_spec.rb +194 -61
  107. data/spec/vcr/test_frameworks/rspec_spec.rb +10 -0
  108. data/spec/vcr/util/hooks_spec.rb +104 -56
  109. data/spec/vcr/util/version_checker_spec.rb +45 -0
  110. data/spec/vcr_spec.rb +11 -0
  111. data/vcr.gemspec +30 -34
  112. metadata +149 -95
  113. data/spec/support/shared_example_groups/version_checking.rb +0 -34
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe VCR::Configuration do
4
4
  describe '#cassette_library_dir=' do
5
5
  let(:tmp_dir) { VCR::SPEC_ROOT + '/../tmp/cassette_library_dir/new_dir' }
6
- after(:each) { FileUtils.rm_rf tmp_dir }
6
+ after(:each) { FileUtils.rm_rf tmp_dir }
7
7
 
8
8
  it 'creates the directory if it does not exist' do
9
9
  expect { subject.cassette_library_dir = tmp_dir }.to change { File.exist?(tmp_dir) }.from(false).to(true)
@@ -12,6 +12,13 @@ describe VCR::Configuration do
12
12
  it 'does not raise an error if given nil' do
13
13
  expect { subject.cassette_library_dir = nil }.to_not raise_error
14
14
  end
15
+
16
+ it 'resolves the given directory to an absolute path, so VCR continues to work even if the current directory changes' do
17
+ relative_dir = 'tmp/cassette_library_dir/new_dir'
18
+ subject.cassette_library_dir = relative_dir
19
+ absolute_dir = File.join(VCR::SPEC_ROOT.sub(/\/spec\z/, ''), relative_dir)
20
+ subject.cassette_library_dir.should eq(absolute_dir)
21
+ end
15
22
  end
16
23
 
17
24
  describe '#default_cassette_options' do
@@ -110,21 +117,51 @@ describe VCR::Configuration do
110
117
  end
111
118
  end
112
119
 
120
+ describe "#before_record hook", :with_monkey_patches => :fakeweb do
121
+ specify 'the request on the yielded interaction is not typed even though the request given to before_http_request is' do
122
+ before_record_req = before_request_req = nil
123
+ VCR.configure do |c|
124
+ c.before_http_request { |r| before_request_req = r }
125
+ c.before_record { |i| before_record_req = i.request }
126
+ end
127
+
128
+ VCR.use_cassette("example") do
129
+ ::Net::HTTP.get_response(URI("http://localhost:#{VCR::SinatraApp.port}/foo"))
130
+ end
131
+
132
+ before_record_req.should_not respond_to(:type)
133
+ before_request_req.should respond_to(:type)
134
+ end unless RUBY_VERSION =~ /^1\.8/
135
+ end
136
+
137
+ [:before_record, :before_playback].each do |hook_type|
138
+ describe "##{hook_type}" do
139
+ it 'sets up a tag filter' do
140
+ called = false
141
+ VCR.configuration.send(hook_type, :my_tag) { called = true }
142
+ VCR.configuration.invoke_hook(hook_type, stub, stub(:tags => []))
143
+ called.should be_false
144
+ VCR.configuration.invoke_hook(hook_type, stub, stub(:tags => [:my_tag]))
145
+ called.should be_true
146
+ end
147
+ end
148
+ end
149
+
113
150
  %w[ filter_sensitive_data define_cassette_placeholder ].each do |method|
114
151
  describe "##{method}" do
115
- let(:interaction) { mock('interaction') }
152
+ let(:interaction) { mock('interaction').as_null_object }
116
153
  before(:each) { interaction.stub(:filter!) }
117
154
 
118
155
  it 'adds a before_record hook that replaces the string returned by the block with the given string' do
119
156
  subject.send(method, 'foo', &lambda { 'bar' })
120
157
  interaction.should_receive(:filter!).with('bar', 'foo')
121
- subject.invoke_hook(:before_record, interaction)
158
+ subject.invoke_hook(:before_record, interaction, stub.as_null_object)
122
159
  end
123
160
 
124
161
  it 'adds a before_playback hook that replaces the given string with the string returned by the block' do
125
162
  subject.send(method, 'foo', &lambda { 'bar' })
126
163
  interaction.should_receive(:filter!).with('foo', 'bar')
127
- subject.invoke_hook(:before_playback, interaction)
164
+ subject.invoke_hook(:before_playback, interaction, stub.as_null_object)
128
165
  end
129
166
 
130
167
  it 'tags the before_record hook when given a tag' do
@@ -140,14 +177,14 @@ describe VCR::Configuration do
140
177
  it 'yields the interaction to the block for the before_record hook' do
141
178
  yielded_interaction = nil
142
179
  subject.send(method, 'foo', &lambda { |i| yielded_interaction = i; 'bar' })
143
- subject.invoke_hook(:before_record, interaction)
180
+ subject.invoke_hook(:before_record, interaction, stub.as_null_object)
144
181
  yielded_interaction.should equal(interaction)
145
182
  end
146
183
 
147
184
  it 'yields the interaction to the block for the before_playback hook' do
148
185
  yielded_interaction = nil
149
186
  subject.send(method, 'foo', &lambda { |i| yielded_interaction = i; 'bar' })
150
- subject.invoke_hook(:before_playback, interaction)
187
+ subject.invoke_hook(:before_playback, interaction, stub.as_null_object)
151
188
  yielded_interaction.should equal(interaction)
152
189
  end
153
190
  end
@@ -169,4 +206,52 @@ describe VCR::Configuration do
169
206
  subject.cassette_serializers[:custom].should be(custom_serializer)
170
207
  end
171
208
  end
209
+
210
+ describe "#preserve_exact_body_bytes_for?" do
211
+ def message_for(body)
212
+ stub(:body => body)
213
+ end
214
+
215
+ context "default hook" do
216
+ it "returns false when there is no current cassette" do
217
+ subject.preserve_exact_body_bytes_for?(message_for "string").should be_false
218
+ end
219
+
220
+ it "returns false when the current cassette has been created without the :preserve_exact_body_bytes option" do
221
+ VCR.insert_cassette('foo')
222
+ subject.preserve_exact_body_bytes_for?(message_for "string").should be_false
223
+ end
224
+
225
+ it 'returns true when the current cassette has been created with the :preserve_exact_body_bytes option' do
226
+ VCR.insert_cassette('foo', :preserve_exact_body_bytes => true)
227
+ subject.preserve_exact_body_bytes_for?(message_for "string").should be_true
228
+ end
229
+ end
230
+
231
+ it "returns true when the configured block returns true" do
232
+ subject.preserve_exact_body_bytes { |msg| msg.body == "a" }
233
+ subject.preserve_exact_body_bytes_for?(message_for "a").should be_true
234
+ subject.preserve_exact_body_bytes_for?(message_for "b").should be_false
235
+ end
236
+
237
+ it "returns true when any of the registered blocks returns true" do
238
+ called_hooks = []
239
+ subject.preserve_exact_body_bytes { called_hooks << :hook_1; false }
240
+ subject.preserve_exact_body_bytes { called_hooks << :hook_2; true }
241
+ subject.preserve_exact_body_bytes_for?(message_for "a").should be_true
242
+ called_hooks.should eq([:hook_1, :hook_2])
243
+ end
244
+
245
+ it "invokes the configured hook with the http message and the current cassette" do
246
+ VCR.use_cassette('example') do |cassette|
247
+ cassette.should be_a(VCR::Cassette)
248
+ message = stub(:message)
249
+
250
+ yielded_objects = nil
251
+ subject.preserve_exact_body_bytes { |a, b| yielded_objects = [a, b] }
252
+ subject.preserve_exact_body_bytes_for?(message)
253
+ yielded_objects.should eq([message, cassette])
254
+ end
255
+ end
256
+ end
172
257
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "Excon hook" do
3
+ describe "Excon hook", :with_monkey_patches => :excon do
4
4
  # TODO: figure out a way to get disabling VCR to work with Excon
5
5
  # and allow dirct excon stubs to work.
6
6
  # def directly_stub_request(method, url, response_body)
@@ -9,20 +9,6 @@ describe "Excon hook" do
9
9
 
10
10
  it_behaves_like 'a hook into an HTTP library', :excon, 'excon', :status_message_not_exposed
11
11
 
12
- it_performs('version checking', 'Excon',
13
- :valid => %w[ 0.6.5 0.7.9 ],
14
- :too_low => %w[ 0.5.99 0.6.4 ],
15
- :too_high => %w[ 0.8.0 1.0.0 ]
16
- ) do
17
- before(:each) { @orig_version = Excon::VERSION }
18
- after(:each) { Excon::VERSION = @orig_version }
19
-
20
- # Cannot be regular method def as that raises a "dynamic constant assignment" error
21
- define_method :stub_version do |version|
22
- Excon::VERSION = version
23
- end
24
- end
25
-
26
12
  context "when the query is specified as a hash option" do
27
13
  let(:excon) { ::Excon.new("http://localhost:#{VCR::SinatraApp.port}/search") }
28
14
 
@@ -74,7 +60,51 @@ describe "Excon hook" do
74
60
  }.to raise_error(Excon::Errors::Error)
75
61
  end
76
62
 
77
- it_behaves_like "request hooks", :excon do
63
+ it 'performs the right number of retries' do
64
+ connection = Excon.new("http://localhost:#{VCR::SinatraApp.port}/not_found")
65
+
66
+ # Excon define's .stub so we can't use RSpec's here...
67
+ Excon.should_receive(:new).at_least(:once).and_return(connection)
68
+
69
+ connection.extend Module.new {
70
+ def request_kernel_call_counts
71
+ @request_kernel_call_counts ||= Hash.new(0)
72
+ end
73
+
74
+ def request_kernel(params, &block)
75
+ request_kernel_call_counts[params[:mock]] += 1
76
+ super
77
+ end
78
+ }
79
+
80
+ expect {
81
+ connection.get(:expects => 200, :idempotent => true, :retry_limit => 3)
82
+ }.to raise_error(Excon::Errors::Error)
83
+
84
+ connection.request_kernel_call_counts.should eq(true => 3, false => 3)
85
+ end
86
+
87
+ def error_raised_by
88
+ yield
89
+ rescue => e
90
+ return e
91
+ else
92
+ raise "No error was raised"
93
+ end
94
+
95
+ it 'raises the same error class as excon itself raises' do
96
+ real_error, stubbed_error = 2.times.map do
97
+ error_raised_by do
98
+ VCR.use_cassette('excon_error', :record => :once) do
99
+ Excon.get("http://localhost:#{VCR::SinatraApp.port}/not_found", :expects => 200)
100
+ end
101
+ end
102
+ end
103
+
104
+ stubbed_error.class.should be(real_error.class)
105
+ end
106
+
107
+ it_behaves_like "request hooks", :excon, :recordable do
78
108
  undef make_request
79
109
  def make_request(disabled = false)
80
110
  expect {
@@ -83,5 +113,13 @@ describe "Excon hook" do
83
113
  end
84
114
  end
85
115
  end
116
+
117
+ describe "VCR.configuration.after_library_hooks_loaded hook" do
118
+ it 'disables the webmock excon adapter so it does not conflict with our typhoeus hook' do
119
+ ::WebMock::HttpLibAdapters::ExconAdapter.should respond_to(:disable!)
120
+ ::WebMock::HttpLibAdapters::ExconAdapter.should_receive(:disable!)
121
+ $excon_after_loaded_hook.conditionally_invoke
122
+ end
123
+ end
86
124
  end
87
125
 
@@ -29,25 +29,20 @@ describe "FakeWeb hook", :with_monkey_patches => :fakeweb do
29
29
  end
30
30
  end
31
31
 
32
- it_performs('version checking', 'FakeWeb',
33
- :valid => %w[ 1.3.0 1.3.1 1.3.99 ],
34
- :too_low => %w[ 1.2.8 1.1.30 0.30.30 ],
35
- :too_high => %w[ 1.4.0 1.10.0 2.0.0 ]
36
- ) do
37
- before(:each) { @orig_version = FakeWeb::VERSION }
38
- after(:each) { FakeWeb::VERSION = @orig_version }
39
-
40
- # Cannot be regular method def as that raises a "dynamic constant assignment" error
41
- define_method :stub_version do |version|
42
- FakeWeb::VERSION = version
43
- end
44
- end
45
-
46
32
  describe "some specific Net::HTTP edge cases" do
47
33
  before(:each) do
48
34
  VCR.stub(:real_http_connections_allowed? => true)
49
35
  end
50
36
 
37
+ it 'records the request body when using #post_form' do
38
+ VCR.should_receive(:record_http_interaction) do |interaction|
39
+ interaction.request.body.should eq("q=ruby")
40
+ end
41
+
42
+ uri = URI("http://localhost:#{VCR::SinatraApp.port}/foo")
43
+ Net::HTTP.post_form(uri, 'q' => 'ruby')
44
+ end
45
+
51
46
  it "does not record headers for which Net::HTTP sets defaults near the end of the real request" do
52
47
  VCR.should_receive(:record_http_interaction) do |interaction|
53
48
  interaction.request.headers.should_not have_key('content-type')
@@ -102,12 +97,8 @@ describe "FakeWeb hook", :with_monkey_patches => :fakeweb do
102
97
  end
103
98
  end
104
99
 
105
- describe "VCR.configuration.after_library_hooks_loaded hook", :disable_warnings do
106
- before(:each) do
107
- load "vcr/library_hooks/fakeweb.rb" # to re-add the hook since it's cleared by each test
108
- end
109
-
110
- let(:run_hook) { VCR.configuration.invoke_hook(:after_library_hooks_loaded) }
100
+ describe "VCR.configuration.after_library_hooks_loaded hook" do
101
+ let(:run_hook) { $fakeweb_after_loaded_hook.conditionally_invoke }
111
102
 
112
103
  context 'when WebMock has been loaded' do
113
104
  before(:each) { defined?(WebMock).should be_true }
@@ -133,7 +124,7 @@ describe "FakeWeb hook", :with_monkey_patches => :fakeweb do
133
124
  VCR.configuration.ignore_request { |r| true }
134
125
  end
135
126
 
136
- it_behaves_like "request hooks", :fakeweb do
127
+ it_behaves_like "request hooks", :fakeweb, :ignored do
137
128
  undef assert_expected_response
138
129
  def assert_expected_response(response)
139
130
  response.should be_nil
@@ -21,37 +21,10 @@ describe "Typhoeus hook", :with_monkey_patches => :typhoeus do
21
21
 
22
22
  it_behaves_like 'a hook into an HTTP library', :typhoeus, 'typhoeus'
23
23
 
24
- def stub_callback_registration
25
- # stub the callback registration methods so we don't get a second
26
- # callback registered when we load the typhoeus file below.
27
- # Note that we have to use `stub!`, not `stub` because
28
- # Typhoeus::Hydra defines its own stub method...so to use RSpec's,
29
- # we use stub!
30
- ::Typhoeus::Hydra.stub!(:after_request_before_on_complete)
31
- ::Typhoeus::Hydra.stub!(:register_stub_finder)
32
- end
33
-
34
- it_performs('version checking', 'Typhoeus',
35
- :valid => %w[ 0.3.2 0.3.10 ],
36
- :too_low => %w[ 0.2.0 0.2.31 0.3.1 ],
37
- :too_high => %w[ 0.4.0 1.0.0 ]
38
- ) do
39
- before(:each) { @orig_version = Typhoeus::VERSION }
40
- after(:each) { Typhoeus::VERSION = @orig_version }
41
-
42
- # Cannot be regular method def as that raises a "dynamic constant assignment" error
43
- define_method :stub_version do |version|
44
- Typhoeus::VERSION = version
45
- end
46
- end
47
-
48
- describe "VCR.configuration.after_library_hooks_loaded hook", :disable_warnings do
49
- before(:each) { stub_callback_registration }
50
-
24
+ describe "VCR.configuration.after_library_hooks_loaded hook" do
51
25
  it 'disables the webmock typhoeus adapter so it does not conflict with our typhoeus hook' do
52
- load "vcr/library_hooks/typhoeus.rb" # to re-add the hook since it's cleared by each test
53
26
  ::WebMock::HttpLibAdapters::TyphoeusAdapter.should_receive(:disable!)
54
- VCR.configuration.invoke_hook(:after_library_hooks_loaded)
27
+ $typhoeus_after_loaded_hook.conditionally_invoke
55
28
  end
56
29
  end
57
30
  end unless RUBY_PLATFORM == 'java'
@@ -18,8 +18,10 @@ describe "WebMock hook", :with_monkey_patches => :webmock do
18
18
  ::WebMock.stub_request(method, url).to_return(:body => response_body)
19
19
  end
20
20
 
21
- %w[net/http patron httpclient em-http-request curb typhoeus].each do |lib|
22
- it_behaves_like 'a hook into an HTTP library', :webmock, lib do
21
+ %w[net/http patron httpclient em-http-request curb typhoeus excon].each do |lib|
22
+ other = []
23
+ other << :status_message_not_exposed if lib == 'excon'
24
+ it_behaves_like 'a hook into an HTTP library', :webmock, lib, *other do
23
25
  if lib == 'net/http'
24
26
  def normalize_request_headers(headers)
25
27
  headers.merge(DEFAULT_REQUEST_HEADERS)
@@ -27,20 +29,4 @@ describe "WebMock hook", :with_monkey_patches => :webmock do
27
29
  end
28
30
  end
29
31
  end
30
-
31
- it_performs('version checking', 'WebMock',
32
- :valid => %w[ 1.7.8 1.7.10 1.7.99 ],
33
- :too_low => %w[ 0.9.9 0.9.10 0.1.30 1.0.30 1.7.7 ],
34
- :too_high => %w[ 1.8.0 1.10.0 2.0.0 ]
35
- ) do
36
-
37
- def stub_callback_registration
38
- ::WebMock.stub(:after_request)
39
- ::WebMock.stub(:globally_stub_request)
40
- end
41
-
42
- def stub_version(version)
43
- WebMock.stub(:version).and_return(version)
44
- end
45
- end
46
32
  end
@@ -9,21 +9,6 @@ describe VCR::Middleware::Faraday do
9
9
  :not_disableable
10
10
  end
11
11
 
12
- it_performs('version checking', 'Faraday',
13
- :valid => %w[ 0.7.0 0.7.10 ],
14
- :too_low => %w[ 0.6.9 0.5.99 ],
15
- :too_high => %w[ 0.8.0 1.0.0 ],
16
- :file => 'vcr/middleware/faraday.rb'
17
- ) do
18
- before(:each) { @orig_version = Faraday::VERSION }
19
- after(:each) { Faraday::VERSION = @orig_version }
20
-
21
- # Cannot be regular method def as that raises a "dynamic constant assignment" error
22
- define_method :stub_version do |version|
23
- ::Faraday::VERSION = version
24
- end
25
- end
26
-
27
12
  context 'when making parallel requests' do
28
13
  include VCRStubHelpers
29
14
  let(:parallel_manager) { ::Faraday::Adapter::Typhoeus.setup_parallel_manager }
@@ -81,7 +66,7 @@ describe VCR::Middleware::Faraday do
81
66
  end
82
67
  end
83
68
 
84
- it_behaves_like "request hooks", :faraday do
69
+ it_behaves_like "request hooks", :faraday, :recordable do
85
70
  let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') }
86
71
 
87
72
  undef make_request
@@ -49,23 +49,13 @@ end
49
49
 
50
50
  module VCR
51
51
  describe HTTPInteraction do
52
- %w( uri method ).each do |attr|
53
- it "delegates :#{attr} to the request" do
54
- sig = mock('request')
55
- sig.should_receive(attr).and_return(:the_value)
56
- instance = described_class.new(sig, nil)
57
- instance.send(attr).should eq(:the_value)
52
+ if ''.respond_to?(:encoding)
53
+ def body_hash(key, value)
54
+ { key => value, 'encoding' => 'UTF-8' }
58
55
  end
59
- end
60
-
61
- describe '#ignored?' do
62
- it 'returns false by default' do
63
- should_not be_ignored
64
- end
65
-
66
- it 'returns true when #ignore! has been called' do
67
- subject.ignore!
68
- should be_ignored
56
+ else
57
+ def body_hash(key, value)
58
+ { key => value }
69
59
  end
70
60
  end
71
61
 
@@ -90,7 +80,7 @@ module VCR
90
80
  'request' => {
91
81
  'method' => 'get',
92
82
  'uri' => 'http://foo.com/',
93
- 'body' => 'req body',
83
+ 'body' => body_hash('string', 'req body'),
94
84
  'headers' => { "bar" => ["foo"] }
95
85
  },
96
86
  'response' => {
@@ -99,7 +89,7 @@ module VCR
99
89
  'message' => 'OK'
100
90
  },
101
91
  'headers' => { "foo" => ["bar"] },
102
- 'body' => 'res body',
92
+ 'body' => body_hash('string', 'res body'),
103
93
  'http_version' => '1.1'
104
94
  },
105
95
  'recorded_at' => "Wed, 04 May 2011 12:30:00 GMT"
@@ -125,9 +115,86 @@ module VCR
125
115
  i = HTTPInteraction.from_hash(hash)
126
116
  i.response.should eq(Response.new(ResponseStatus.new))
127
117
  end
118
+
119
+ it 'decodes the base64 body string' do
120
+ hash['request']['body'] = body_hash('base64_string', Base64.encode64('req body'))
121
+ hash['response']['body'] = body_hash('base64_string', Base64.encode64('res body'))
122
+
123
+ i = HTTPInteraction.from_hash(hash)
124
+ i.request.body.should eq('req body')
125
+ i.response.body.should eq('res body')
126
+ end
127
+
128
+ if ''.respond_to?(:encoding)
129
+ it 'force encodes the decoded base64 string as the original encoding' do
130
+ string = "café"
131
+ string.force_encoding("US-ASCII")
132
+ string.should_not be_valid_encoding
133
+
134
+ hash['request']['body'] = { 'base64_string' => Base64.encode64(string.dup), 'encoding' => 'US-ASCII' }
135
+ hash['response']['body'] = { 'base64_string' => Base64.encode64(string.dup), 'encoding' => 'US-ASCII' }
136
+
137
+ i = HTTPInteraction.from_hash(hash)
138
+ i.request.body.encoding.name.should eq("US-ASCII")
139
+ i.response.body.encoding.name.should eq("US-ASCII")
140
+ i.request.body.bytes.to_a.should eq(string.bytes.to_a)
141
+ i.response.body.bytes.to_a.should eq(string.bytes.to_a)
142
+ i.request.body.should_not be_valid_encoding
143
+ i.response.body.should_not be_valid_encoding
144
+ end
145
+
146
+ it 'does not attempt to force encode the decoded base64 string when there is no encoding given (i.e. if the cassette was recorded on ruby 1.8)' do
147
+ hash['request']['body'] = { 'base64_string' => Base64.encode64('foo') }
148
+
149
+ i = HTTPInteraction.from_hash(hash)
150
+ i.request.body.should eq('foo')
151
+ i.request.body.encoding.name.should eq("ASCII-8BIT")
152
+ end
153
+
154
+ it 'tries to encode strings to the original encoding' do
155
+ hash['request']['body'] = { 'string' => "abc", 'encoding' => 'ISO-8859-1' }
156
+ hash['response']['body'] = { 'string' => "abc", 'encoding' => 'ISO-8859-1' }
157
+
158
+ i = HTTPInteraction.from_hash(hash)
159
+ i.request.body.should eq("abc")
160
+ i.response.body.should eq("abc")
161
+ i.request.body.encoding.name.should eq("ISO-8859-1")
162
+ i.response.body.encoding.name.should eq("ISO-8859-1")
163
+ end
164
+
165
+ context 'when the string cannot be encoded as the original encoding' do
166
+ before do
167
+ Request.stub(:warn)
168
+ Response.stub(:warn)
169
+
170
+ hash['request']['body'] = { 'string' => "\xFAbc", 'encoding' => 'ISO-8859-1' }
171
+ hash['response']['body'] = { 'string' => "\xFAbc", 'encoding' => 'ISO-8859-1' }
172
+ expect { "\xFAbc".encode("ISO-8859-1") }.to raise_error(EncodingError)
173
+ end
174
+
175
+ it 'does not force the encoding' do
176
+ i = HTTPInteraction.from_hash(hash)
177
+ i.request.body.should eq("\xFAbc")
178
+ i.response.body.should eq("\xFAbc")
179
+ i.request.body.encoding.name.should_not eq("ISO-8859-1")
180
+ i.response.body.encoding.name.should_not eq("ISO-8859-1")
181
+ end
182
+
183
+ it 'prints a warning and informs users of the :preserve_exact_body_bytes option' do
184
+ Request.should_receive(:warn).with(/ISO-8859-1.*preserve_exact_body_bytes/)
185
+ Response.should_receive(:warn).with(/ISO-8859-1.*preserve_exact_body_bytes/)
186
+
187
+ HTTPInteraction.from_hash(hash)
188
+ end
189
+ end
190
+ end
128
191
  end
129
192
 
130
193
  describe "#to_hash" do
194
+ before(:each) do
195
+ VCR.stub_chain(:configuration, :preserve_exact_body_bytes_for?).and_return(false)
196
+ end
197
+
131
198
  let(:hash) { interaction.to_hash }
132
199
 
133
200
  it 'returns a nested hash containing all of the pertinent details' do
@@ -138,7 +205,7 @@ module VCR
138
205
  hash['request'].should eq({
139
206
  'method' => 'get',
140
207
  'uri' => 'http://foo.com/',
141
- 'body' => 'req body',
208
+ 'body' => body_hash('string', 'req body'),
142
209
  'headers' => { "bar" => ["foo"] }
143
210
  })
144
211
 
@@ -148,11 +215,25 @@ module VCR
148
215
  'message' => 'OK'
149
216
  },
150
217
  'headers' => { "foo" => ["bar"] },
151
- 'body' => 'res body',
218
+ 'body' => body_hash('string', 'res body'),
152
219
  'http_version' => '1.1'
153
220
  })
154
221
  end
155
222
 
223
+ it 'encodes the body as base64 when the configuration is so set' do
224
+ VCR.stub_chain(:configuration, :preserve_exact_body_bytes_for?).and_return(true)
225
+ hash['request']['body'].should eq(body_hash('base64_string', Base64.encode64('req body')))
226
+ hash['response']['body'].should eq(body_hash('base64_string', Base64.encode64('res body')))
227
+ end
228
+
229
+ it "sets the string's original encoding", :if => ''.respond_to?(:encoding) do
230
+ interaction.request.body.force_encoding('ISO-8859-10')
231
+ interaction.response.body.force_encoding('ASCII-8BIT')
232
+
233
+ hash['request']['body']['encoding'].should eq('ISO-8859-10')
234
+ hash['response']['body']['encoding'].should eq('ASCII-8BIT')
235
+ end
236
+
156
237
  def assert_yielded_keys(hash, *keys)
157
238
  yielded_keys = []
158
239
  hash.each { |k, v| yielded_keys << k }
@@ -166,65 +247,120 @@ module VCR
166
247
  assert_yielded_keys hash['response']['status'], 'code', 'message'
167
248
  end
168
249
  end
250
+ end
169
251
 
170
- describe '#filter!' do
171
- let(:response_status) { VCR::ResponseStatus.new(200, "OK foo") }
172
- let(:body) { "The body foo this is (foo-Foo)" }
173
- let(:headers) do {
174
- 'x-http-foo' => ['bar23', '23foo'],
175
- 'x-http-bar' => ['foo23', '18']
176
- } end
252
+ describe HTTPInteraction::HookAware do
253
+ let(:response_status) { VCR::ResponseStatus.new(200, "OK foo") }
254
+ let(:body) { "The body foo this is (foo-Foo)" }
255
+ let(:headers) do {
256
+ 'x-http-foo' => ['bar23', '23foo'],
257
+ 'x-http-bar' => ['foo23', '18']
258
+ } end
259
+
260
+ let(:response) do
261
+ VCR::Response.new(
262
+ response_status,
263
+ headers.dup,
264
+ body.dup,
265
+ '1.1'
266
+ )
267
+ end
177
268
 
178
- let(:response) do
179
- VCR::Response.new(
180
- response_status,
181
- headers.dup,
182
- body.dup,
183
- '1.1'
184
- )
185
- end
269
+ let(:request) do
270
+ VCR::Request.new(
271
+ :get,
272
+ 'http://example-foo.com:80/foo/',
273
+ body.dup,
274
+ headers.dup
275
+ )
276
+ end
277
+
278
+ let(:interaction) { VCR::HTTPInteraction.new(request, response) }
279
+ subject { HTTPInteraction::HookAware.new(interaction) }
186
280
 
187
- let(:request) do
188
- VCR::Request.new(
189
- :get,
190
- 'http://example-foo.com:80/foo/',
191
- body.dup,
192
- headers.dup
193
- )
281
+ describe '#ignored?' do
282
+ it 'returns false by default' do
283
+ should_not be_ignored
194
284
  end
195
285
 
196
- let(:interaction) { VCR::HTTPInteraction.new(request, response) }
286
+ it 'returns true when #ignore! has been called' do
287
+ subject.ignore!
288
+ should be_ignored
289
+ end
290
+ end
197
291
 
198
- subject { interaction.filter!('foo', 'AAA') }
292
+ describe '#filter!' do
293
+ let(:filtered) { subject.filter!('foo', 'AAA') }
199
294
 
200
295
  it 'does nothing when given a blank argument' do
201
296
  expect {
202
- interaction.filter!(nil, 'AAA')
203
- interaction.filter!('foo', nil)
204
- interaction.filter!("", 'AAA')
205
- interaction.filter!('foo', "")
297
+ subject.filter!(nil, 'AAA')
298
+ subject.filter!('foo', nil)
299
+ subject.filter!("", 'AAA')
300
+ subject.filter!('foo', "")
206
301
  }.not_to change { interaction }
207
302
  end
208
303
 
209
304
  [:request, :response].each do |part|
210
305
  it "replaces the sensitive text in the #{part} header keys and values" do
211
- subject.send(part).headers.should eq({
306
+ filtered.send(part).headers.should eq({
212
307
  'x-http-AAA' => ['bar23', '23AAA'],
213
308
  'x-http-bar' => ['AAA23', '18']
214
309
  })
215
310
  end
216
311
 
217
312
  it "replaces the sensitive text in the #{part} body" do
218
- subject.send(part).body.should eq("The body AAA this is (AAA-Foo)")
313
+ filtered.send(part).body.should eq("The body AAA this is (AAA-Foo)")
219
314
  end
220
315
  end
221
316
 
222
317
  it 'replaces the sensitive text in the response status' do
223
- subject.response.status.message.should eq('OK AAA')
318
+ filtered.response.status.message.should eq('OK AAA')
224
319
  end
225
320
 
226
321
  it 'replaces sensitive text in the request URI' do
227
- subject.request.uri.should eq('http://example-AAA.com/AAA/')
322
+ filtered.request.uri.should eq('http://example-AAA.com/AAA/')
323
+ end
324
+ end
325
+ end
326
+
327
+ describe Request::Typed do
328
+ [:uri, :method, :headers, :body].each do |method|
329
+ it "delegates ##{method} to the request" do
330
+ request = stub(method => "delegated value")
331
+ Request::Typed.new(request, :type).send(method).should eq("delegated value")
332
+ end
333
+ end
334
+
335
+ describe "#type" do
336
+ it 'returns the initialized type' do
337
+ Request::Typed.new(stub, :ignored).type.should be(:ignored)
338
+ end
339
+ end
340
+
341
+ [:ignored, :stubbed, :recordable, :unhandled].each do |type|
342
+ describe "##{type}?" do
343
+ it "returns true if the type is set to :#{type}" do
344
+ Request::Typed.new(stub, type).send("#{type}?").should be_true
345
+ end
346
+
347
+ it "returns false if the type is set to :other" do
348
+ Request::Typed.new(stub, :other).send("#{type}?").should be_false
349
+ end
350
+ end
351
+ end
352
+
353
+ describe "#real?" do
354
+ [:ignored, :recordable].each do |type|
355
+ it "returns true if the type is set to :#{type}" do
356
+ Request::Typed.new(stub, type).should be_real
357
+ end
358
+ end
359
+
360
+ [:stubbed, :unhandled].each do |type|
361
+ it "returns false if the type is set to :#{type}" do
362
+ Request::Typed.new(stub, type).should_not be_real
363
+ end
228
364
  end
229
365
  end
230
366
  end
@@ -277,25 +413,22 @@ module VCR
277
413
  end
278
414
  end
279
415
 
280
- describe "#fiber_aware" do
416
+ describe Request::FiberAware do
417
+ subject { Request::FiberAware.new(Request.new) }
418
+
281
419
  it 'adds a #proceed method that yields in a fiber' do
282
420
  fiber = Fiber.new do |request|
283
421
  request.proceed
284
422
  :done
285
423
  end
286
424
 
287
- fiber.resume(subject.fiber_aware).should be_nil
425
+ fiber.resume(subject).should be_nil
288
426
  fiber.resume.should eq(:done)
289
427
  end
290
428
 
291
429
  it 'can be cast to a proc' do
292
- request = subject.fiber_aware
293
- request.should_receive(:proceed)
294
- lambda(&request).call
295
- end
296
-
297
- it 'returns the request' do
298
- subject.fiber_aware.should be(subject)
430
+ Fiber.should_receive(:yield)
431
+ lambda(&subject).call
299
432
  end
300
433
  end if RUBY_VERSION > '1.9'
301
434