vcr 2.0.0.rc1 → 2.0.0.rc2

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 (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