vcr 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +1 -1
  2. data/.gitmodules +3 -0
  3. data/CHANGELOG.md +58 -1
  4. data/Gemfile +2 -4
  5. data/Gemfile.lock +26 -49
  6. data/README.md +92 -23
  7. data/Rakefile +6 -3
  8. data/features/fixtures/vcr_cassettes/1.9.1/record_all.yml +62 -0
  9. data/features/fixtures/vcr_cassettes/not_1.9.1/record_all.yml +61 -0
  10. data/features/http_client.feature +2 -2
  11. data/features/net_http.feature +15 -4
  12. data/features/record_response.feature +36 -7
  13. data/features/replay_recorded_response.feature +8 -8
  14. data/features/rspec.feature +100 -0
  15. data/features/step_definitions/net_http_steps.rb +13 -1
  16. data/features/step_definitions/vcr_steps.rb +26 -16
  17. data/features/support/env.rb +13 -5
  18. data/features/webmock.feature +4 -4
  19. data/lib/vcr.rb +5 -5
  20. data/lib/vcr/cassette.rb +55 -19
  21. data/lib/vcr/extensions/net_http.rb +2 -4
  22. data/lib/vcr/http_stubbing_adapters/fakeweb.rb +0 -4
  23. data/lib/vcr/http_stubbing_adapters/webmock.rb +2 -9
  24. data/lib/vcr/internet_connection.rb +13 -0
  25. data/lib/vcr/ping.rb +26 -0
  26. data/lib/vcr/rspec.rb +39 -0
  27. data/lib/vcr/version.rb +1 -1
  28. data/spec/cassette_spec.rb +160 -69
  29. data/spec/config_spec.rb +1 -1
  30. data/spec/cucumber_tags_spec.rb +3 -2
  31. data/spec/deprecations_spec.rb +1 -1
  32. data/spec/extensions/net_http_response_spec.rb +2 -4
  33. data/spec/extensions/net_http_spec.rb +3 -1
  34. data/spec/fixtures/1.9.1/cassette_spec/with_localhost_requests.yml +23 -0
  35. data/spec/fixtures/not_1.9.1/cassette_spec/with_localhost_requests.yml +23 -0
  36. data/spec/http_stubbing_adapters/fakeweb_spec.rb +3 -9
  37. data/spec/http_stubbing_adapters/webmock_spec.rb +7 -18
  38. data/spec/internet_connection_spec.rb +19 -0
  39. data/spec/monkey_patches.rb +45 -6
  40. data/spec/request_matcher_spec.rb +1 -1
  41. data/spec/rspec_spec.rb +46 -0
  42. data/spec/spec_helper.rb +6 -11
  43. data/spec/structs_spec.rb +1 -1
  44. data/spec/support/fixnum_extension.rb +10 -0
  45. data/spec/support/http_library_adapters.rb +31 -2
  46. data/spec/support/ruby_interpreter.rb +7 -0
  47. data/spec/support/vcr_localhost_server.rb +86 -44
  48. data/spec/support/webmock_macros.rb +14 -0
  49. data/spec/vcr_spec.rb +1 -1
  50. data/spec/version_spec.rb +1 -1
  51. data/vcr.gemspec +10 -6
  52. metadata +155 -39
@@ -53,6 +53,29 @@
53
53
  - "277"
54
54
  body: 127.0.0.1 response
55
55
  http_version: "1.1"
56
+ - !ruby/struct:VCR::HTTPInteraction
57
+ request: !ruby/struct:VCR::Request
58
+ method: :get
59
+ uri: http://0.0.0.0:80/
60
+ body:
61
+ headers:
62
+ response: !ruby/struct:VCR::Response
63
+ status: !ruby/struct:VCR::ResponseStatus
64
+ code: 404
65
+ message: Not Found
66
+ headers:
67
+ content-type:
68
+ - text/html; charset=iso-8859-1
69
+ connection:
70
+ - close
71
+ server:
72
+ - Apache/2.2.3 (CentOS)
73
+ date:
74
+ - Thu, 25 Feb 2010 07:53:52 GMT
75
+ content-length:
76
+ - "277"
77
+ body: 127.0.0.1 response
78
+ http_version: "1.1"
56
79
  - !ruby/struct:VCR::HTTPInteraction
57
80
  request: !ruby/struct:VCR::Request
58
81
  method: :get
@@ -1,15 +1,9 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe VCR::HttpStubbingAdapters::FakeWeb do
4
- it_should_behave_like 'an http stubbing adapter', ['net/http'], [:method, :uri, :host, :path]
5
-
6
- describe '#should_unwind_response?' do
7
- let(:response) { ::Net::HTTPOK.new('1.1', 200, 'OK') }
4
+ without_webmock_callbacks
8
5
 
9
- it 'returns true' do
10
- described_class.should_unwind_response?(response).should be_true
11
- end
12
- end
6
+ it_should_behave_like 'an http stubbing adapter', ['net/http'], [:method, :uri, :host, :path]
13
7
 
14
8
  describe '#check_version!' do
15
9
  disable_warnings
@@ -1,27 +1,16 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe VCR::HttpStubbingAdapters::WebMock do
4
+ without_monkey_patches :vcr
5
+
4
6
  it_should_behave_like 'an http stubbing adapter',
5
- %w[net/http patron httpclient em-http-request],
7
+ %w[net/http patron httpclient em-http-request curb],
6
8
  [:method, :uri, :host, :path, :body, :headers]
7
9
 
8
- describe '#should_unwind_response?' do
9
- let(:response) { ::Net::HTTPOK.new('1.1', 200, 'OK') }
10
-
11
- it 'returns true when the response has not been extended with WebMock::Net::HTTPResponse' do
12
- described_class.should_unwind_response?(response).should be_true
13
- end
14
-
15
- it 'returns false when the response has been extended with WebMock::Net::HTTPResponse' do
16
- response.extend WebMock::Net::HTTPResponse
17
- described_class.should_unwind_response?(response).should be_false
18
- end
19
- end
20
-
21
10
  describe '#check_version!' do
22
11
  before(:each) { WebMock.should respond_to(:version) }
23
12
 
24
- %w( 0.9.9 0.9.10 0.1.30 1.0.30 1.2.9 1.3.2 ).each do |version|
13
+ %w( 0.9.9 0.9.10 0.1.30 1.0.30 1.2.9 1.3.9 ).each do |version|
25
14
  it "raises an error when WebMock's version is #{version}" do
26
15
  WebMock.stub!(:version).and_return(version)
27
16
  described_class.should_not_receive(:warn)
@@ -29,7 +18,7 @@ describe VCR::HttpStubbingAdapters::WebMock do
29
18
  end
30
19
  end
31
20
 
32
- %w( 1.3.3 1.3.10 1.3.99 ).each do |version|
21
+ %w( 1.4.0 1.4.10 1.4.99 ).each do |version|
33
22
  it "does nothing when WebMock's version is #{version}" do
34
23
  WebMock.stub!(:version).and_return(version)
35
24
  described_class.should_not_receive(:warn)
@@ -37,7 +26,7 @@ describe VCR::HttpStubbingAdapters::WebMock do
37
26
  end
38
27
  end
39
28
 
40
- %w( 1.4.0 1.10.0 2.0.0 ).each do |version|
29
+ %w( 1.5.0 1.10.0 2.0.0 ).each do |version|
41
30
  it "does nothing when WebMock's version is #{version}" do
42
31
  WebMock.stub!(:version).and_return(version)
43
32
  described_class.should_receive(:warn).with(/VCR is known to work with WebMock ~> .*\./)
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe VCR::InternetConnection do
4
+ describe '.available?' do
5
+ before(:each) do
6
+ described_class.instance_variable_set(:@available, nil)
7
+ end
8
+
9
+ it 'returns true when pinging example.com succeeds' do
10
+ Ping.stub(:pingecho).with("example.com", anything, anything).and_return(true)
11
+ described_class.available?.should be_true
12
+ end
13
+
14
+ it 'returns false when pinging example.com fails' do
15
+ Ping.stub(:pingecho).with("example.com", anything, anything).and_return(false)
16
+ described_class.available?.should be_false
17
+ end
18
+ end
19
+ end
@@ -1,6 +1,13 @@
1
1
  module MonkeyPatches
2
2
  extend self
3
3
 
4
+ module RSpecMacros
5
+ def without_monkey_patches(scope)
6
+ before(:each) { MonkeyPatches.disable!(scope) }
7
+ after(:each) { MonkeyPatches.enable!(scope) }
8
+ end
9
+ end
10
+
4
11
  NET_HTTP_SINGLETON = class << Net::HTTP; self; end
5
12
 
6
13
  MONKEY_PATCHES = [
@@ -10,15 +17,27 @@ module MonkeyPatches
10
17
  [NET_HTTP_SINGLETON, :socket_type]
11
18
  ]
12
19
 
13
- def enable!
14
- MONKEY_PATCHES.each do |mp|
15
- realias mp.first, mp.last, :with_monkeypatches
20
+ def enable!(scope)
21
+ case scope
22
+ when :all
23
+ MONKEY_PATCHES.each do |mp|
24
+ realias mp.first, mp.last, :with_monkeypatches
25
+ end
26
+ when :vcr
27
+ realias Net::HTTP, :request, :with_vcr
28
+ else raise ArgumentError.new("Unexpected scope: #{scope}")
16
29
  end
17
30
  end
18
31
 
19
- def disable!
20
- MONKEY_PATCHES.each do |mp|
21
- realias mp.first, mp.last, :without_monkeypatches
32
+ def disable!(scope)
33
+ case scope
34
+ when :all
35
+ MONKEY_PATCHES.each do |mp|
36
+ realias mp.first, mp.last, :without_monkeypatches
37
+ end
38
+ when :vcr
39
+ realias Net::HTTP, :request, :without_vcr
40
+ else raise ArgumentError.new("Unexpected scope: #{scope}")
22
41
  end
23
42
  end
24
43
 
@@ -68,3 +87,23 @@ module MonkeyPatches
68
87
  end
69
88
  end
70
89
 
90
+ # Require all the HTTP libraries--these must be required before WebMock
91
+ # for WebMock to work with them.
92
+ require 'httpclient'
93
+
94
+ if RUBY_INTERPRETER == :mri
95
+ require 'patron'
96
+ require 'em-http-request'
97
+ require 'curb'
98
+ end
99
+
100
+ # The FakeWeb adapter must be required after WebMock's so
101
+ # that VCR's Net::HTTP monkey patch is loaded last.
102
+ # This allows us to disable it (i.e. by realiasing to
103
+ # the version of Net::HTTP's methods before it was loaded)
104
+ require 'vcr/http_stubbing_adapters/webmock'
105
+ require 'vcr/http_stubbing_adapters/fakeweb'
106
+
107
+ # All Net::HTTP monkey patches have now been loaded, so capture the
108
+ # appropriate method definitions so we can disable them later.
109
+ MonkeyPatches.init
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe VCR::RequestMatcher do
4
4
  it 'raises an error when given invalid match attributes' do
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe VCR::RSpec::Macros do
4
+ extend described_class
5
+
6
+ describe '#use_vcr_cassette' do
7
+ def self.perform_test(context_name, expected_cassette_name, *args, &block)
8
+ context context_name do
9
+ after(:each) do
10
+ if @test_ejection
11
+ VCR.current_cassette.should be_nil
12
+ end
13
+ end
14
+
15
+ use_vcr_cassette *args
16
+
17
+ it 'ejects the cassette in an after hook' do
18
+ VCR.current_cassette.should be_a(VCR::Cassette)
19
+ @test_ejection = true
20
+ end
21
+
22
+ it "creates a cassette named '#{expected_cassette_name}" do
23
+ VCR.current_cassette.name.should == expected_cassette_name
24
+ end
25
+
26
+ module_eval(&block) if block
27
+ end
28
+ end
29
+
30
+ perform_test 'when called with an explicit name', 'explicit_name', 'explicit_name'
31
+
32
+ perform_test 'when called with an explicit name and some options', 'explicit_name', 'explicit_name', :match_requests_on => [:method, :host] do
33
+ it 'uses the provided cassette options' do
34
+ VCR.current_cassette.match_requests_on.should == [:method, :host]
35
+ end
36
+ end
37
+
38
+ perform_test 'when called with nothing', 'VCR::RSpec::Macros/#use_vcr_cassette/when called with nothing'
39
+
40
+ perform_test 'when called with some options', 'VCR::RSpec::Macros/#use_vcr_cassette/when called with some options', :match_requests_on => [:method, :host] do
41
+ it 'uses the provided cassette options' do
42
+ VCR.current_cassette.match_requests_on.should == [:method, :host]
43
+ end
44
+ end
45
+ end
46
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,14 +2,6 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler.setup
4
4
 
5
- require 'monkey_patches'
6
-
7
- require 'patron' unless RUBY_PLATFORM =~ /java/
8
- require 'httpclient'
9
- require 'em-http-request' unless RUBY_PLATFORM =~ /java/
10
- require 'vcr'
11
- require 'vcr/http_stubbing_adapters/fakeweb'
12
- require 'vcr/http_stubbing_adapters/webmock'
13
5
  require 'rspec'
14
6
 
15
7
  # Ruby 1.9.1 has a different yaml serialization format.
@@ -19,12 +11,17 @@ YAML_SERIALIZATION_VERSION = RUBY_VERSION == '1.9.1' ? '1.9.1' : 'not_1.9.1'
19
11
  # in ./support/ and its subdirectories.
20
12
  Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
21
13
 
14
+ require 'vcr'
15
+ require 'monkey_patches'
16
+
22
17
  RSpec.configure do |config|
23
18
  config.extend TempCassetteLibraryDir
24
19
  config.extend DisableWarnings
20
+ config.extend MonkeyPatches::RSpecMacros
21
+ config.extend WebMockMacros
25
22
 
26
23
  config.color_enabled = true
27
- config.debug = RUBY_PLATFORM != 'java'
24
+ config.debug = RUBY_INTERPRETER == :mri
28
25
 
29
26
  config.before(:each) do
30
27
  VCR::Config.default_cassette_options = { :record => :new_episodes }
@@ -41,5 +38,3 @@ RSpec.configure do |config|
41
38
  config.run_all_when_everything_filtered = true
42
39
  end
43
40
 
44
- MonkeyPatches.init
45
-
data/spec/structs_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  shared_examples_for "a header normalizer" do
4
4
  let(:instance) do
@@ -0,0 +1,10 @@
1
+ unless 7.respond_to?(:days)
2
+ class Fixnum
3
+ def days
4
+ self *
5
+ 24 * # hours
6
+ 60 * # minutes
7
+ 60 # seconds
8
+ end
9
+ end
10
+ end
@@ -67,12 +67,41 @@ HTTP_LIBRARY_ADAPTERS['em-http-request'] = Module.new do
67
67
  end
68
68
  end
69
69
 
70
+ HTTP_LIBRARY_ADAPTERS['curb'] = Module.new do
71
+ def self.http_library_name; "Curb"; end
72
+
73
+ def get_body_string(response)
74
+ response.body_str
75
+ end
76
+
77
+ def get_header(header_key, response)
78
+ headers = response.header_str.split("\r\n")[1..-1]
79
+ headers.each do |h|
80
+ if h =~ /^#{Regexp.escape(header_key)}: (.*)$/
81
+ return $1.split(', ')
82
+ end
83
+ end
84
+ end
85
+
86
+ def make_http_request(method, url, body = nil, headers = {})
87
+ Curl::Easy.new(url) do |c|
88
+ c.headers = headers
89
+
90
+ if [:post, :put].include?(method)
91
+ c.send("http_#{method}", body)
92
+ else
93
+ c.send("http_#{method}")
94
+ end
95
+ end
96
+ end
97
+ end
98
+
70
99
  NET_CONNECT_NOT_ALLOWED_ERROR = /You can use VCR to automatically record this request and replay it later/
71
100
 
72
101
  module HttpLibrarySpecs
73
102
  def test_http_library(library, supported_request_match_attributes)
74
- # patron and em-http-client cannot be installed on jruby
75
- return if %w[patron em-http-request].include?(library) && RUBY_PLATFORM == 'java'
103
+ # curb, patron and em-http-client cannot be installed on jruby
104
+ return if %w[curb patron em-http-request].include?(library) && RUBY_INTERPRETER != :mri
76
105
 
77
106
  unless adapter_module = HTTP_LIBRARY_ADAPTERS[library]
78
107
  raise ArgumentError.new("No http library adapter module could be found for #{library}")
@@ -0,0 +1,7 @@
1
+ RUBY_INTERPRETER = if RUBY_PLATFORM == 'java'
2
+ :jruby
3
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
4
+ :rubinius
5
+ else
6
+ :mri
7
+ end
@@ -1,53 +1,95 @@
1
- require 'capybara'
2
- require 'capybara/wait_until'
3
-
4
- # This uses a separate process rather than a separate thread to run the server.
5
- # Patron always times out when this is running in a thread in the same process.
6
- #
7
- # However, jruby doesn't support forking, and patron doesn't work on jruby...
8
- # so we just leave Capybara::Server's definition of #boot.
9
- class VCR::LocalhostServer < Capybara::Server
10
-
11
- def boot
12
- return self unless @app
13
- find_available_port
14
- Capybara.log "application has already booted" and return self if responsive?
15
- Capybara.log "booting Rack applicartion on port #{port}"
16
-
17
- pid = Process.fork do
18
- trap(:INT) { Rack::Handler::WEBrick.shutdown }
19
- Rack::Handler::WEBrick.run(Identify.new(@app), :Port => port, :AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new))
20
- exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet.
21
- end
22
- Capybara.log "checking if application has booted"
23
-
24
- Capybara::WaitUntil.timeout(10) do
25
- if responsive?
26
- Capybara.log("application has booted")
27
- true
28
- else
29
- sleep 0.5
30
- false
1
+ require 'rack'
2
+ require 'rack/handler/webrick'
3
+
4
+ # The code for this is inspired by Capybara's server:
5
+ # http://github.com/jnicklas/capybara/blob/0.3.9/lib/capybara/server.rb
6
+ module VCR
7
+ class LocalhostServer
8
+ class Identify
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ if env["PATH_INFO"] == "/__identify__"
15
+ [200, {}, @app.object_id.to_s]
16
+ else
17
+ @app.call(env)
18
+ end
31
19
  end
32
20
  end
33
21
 
34
- at_exit do
35
- Process.kill('INT', pid)
36
- begin
37
- Process.wait(pid)
38
- rescue Errno::ECHILD
39
- # ignore this error...I think it means the child process has already exited.
22
+ attr_reader :port
23
+
24
+ def initialize(rack_app)
25
+ @port = find_available_port
26
+ @rack_app = rack_app
27
+ concurrently { boot }
28
+ wait_until(10, "Boot failed.") { booted? }
29
+ end
30
+
31
+ private
32
+
33
+ def find_available_port
34
+ server = TCPServer.new('127.0.0.1', 0)
35
+ server.addr[1]
36
+ ensure
37
+ server.close if server
38
+ end
39
+
40
+ def boot
41
+ # Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters.
42
+ Rack::Handler::WEBrick.run(Identify.new(@rack_app), :Port => port, :AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new))
43
+ end
44
+
45
+ def booted?
46
+ res = ::Net::HTTP.get_response("localhost", '/__identify__', port)
47
+
48
+ if res.is_a?(::Net::HTTPSuccess) or res.is_a?(::Net::HTTPRedirection)
49
+ return res.body == @rack_app.object_id.to_s
50
+ end
51
+ rescue Errno::ECONNREFUSED, Errno::EBADF
52
+ return false
53
+ end
54
+
55
+ def concurrently
56
+ if RUBY_INTERPRETER == :mri
57
+ # Patron times out when the server is running in a separate thread in the same process,
58
+ # so use a separate process.
59
+ pid = Process.fork do
60
+ yield
61
+ exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet.
62
+ end
63
+
64
+ at_exit do
65
+ Process.kill('INT', pid)
66
+ begin
67
+ Process.wait(pid)
68
+ rescue Errno::ECHILD
69
+ # ignore this error...I think it means the child process has already exited.
70
+ end
71
+ end
72
+ else
73
+ # JRuby doesn't support forking.
74
+ # Rubinius does, but there's a weird issue with the booted? check not working,
75
+ # so we're just using a thread for now.
76
+ Thread.new { yield }
40
77
  end
41
78
  end
42
79
 
43
- self
44
- rescue Timeout::Error
45
- Capybara.log "Rack application timed out during boot"
46
- exit
47
- end unless RUBY_PLATFORM =~ /java/
80
+ def wait_until(timeout, error_message, &block)
81
+ start_time = Time.now
48
82
 
49
- STATIC_SERVERS = Hash.new do |h, k|
50
- h[k] = server = new(lambda { |env| [200, {}, StringIO.new(k)] })
51
- server.boot
83
+ while true
84
+ return if yield
85
+ raise TimeoutError.new(error_message) if (Time.now - start_time) > timeout
86
+ sleep(0.05)
87
+ end
88
+ end
89
+
90
+ STATIC_SERVERS = Hash.new do |h, k|
91
+ h[k] = new(lambda { |env| [200, {}, StringIO.new(k)] })
92
+ end
52
93
  end
53
94
  end
95
+