diff --git a/Rakefile b/Rakefile index 67296364..9723c982 100644 --- a/Rakefile +++ b/Rakefile @@ -2,7 +2,9 @@ require 'rubygems' require 'rake' require "rspec/core/rake_task" -RSpec::Core::RakeTask.new(:spec) +RSpec::Core::RakeTask.new(:spec) do |t| + t.spec_opts = ['--format', 'documentation'] +end desc "Run all examples using rcov" RSpec::Core::RakeTask.new :rcov => :cleanup_rcov_files do |t| diff --git a/lib/vcr/cassette.rb b/lib/vcr/cassette.rb index 6aafd8f9..b9a80178 100644 --- a/lib/vcr/cassette.rb +++ b/lib/vcr/cassette.rb @@ -99,7 +99,7 @@ def load_recorded_interactions recorded_interactions.replace(@original_recorded_interactions) end - VCR.http_stubbing_adapter.stub_requests(recorded_interactions) + VCR.http_stubbing_adapter.stub_requests(recorded_interactions, match_requests_on) end @@struct_cache = Hash.new do |hash, attributes| diff --git a/lib/vcr/http_stubbing_adapters/fakeweb.rb b/lib/vcr/http_stubbing_adapters/fakeweb.rb index 2d9e4c44..15649efa 100644 --- a/lib/vcr/http_stubbing_adapters/fakeweb.rb +++ b/lib/vcr/http_stubbing_adapters/fakeweb.rb @@ -19,15 +19,19 @@ def http_connections_allowed=(value) ::FakeWeb.allow_net_connect = value end - def stub_requests(http_interactions) + def stub_requests(http_interactions, match_attributes = RequestMatcher::DEFAULT_MATCH_ATTRIBUTES) requests = Hash.new([]) http_interactions.each do |i| - requests[[i.request.method, i.request.uri]] += [i.response] + requests[i.request.matcher(match_attributes)] += [i.response] end - requests.each do |request, responses| - ::FakeWeb.register_uri(request.first, request.last, responses.map{ |r| response_hash(r) }) + requests.each do |request_matcher, responses| + ::FakeWeb.register_uri( + request_matcher.method || :any, + request_matcher.uri, + responses.map{ |r| response_hash(r) } + ) end end diff --git a/lib/vcr/http_stubbing_adapters/webmock.rb b/lib/vcr/http_stubbing_adapters/webmock.rb index 119ef75f..fd56d647 100644 --- a/lib/vcr/http_stubbing_adapters/webmock.rb +++ b/lib/vcr/http_stubbing_adapters/webmock.rb @@ -21,17 +21,23 @@ def http_connections_allowed=(value) ::WebMock::Config.instance.allow_net_connect = value end - def stub_requests(recorded_responses) + def stub_requests(http_interactions, match_attributes = RequestMatcher::DEFAULT_MATCH_ATTRIBUTES) requests = Hash.new([]) - # TODO: use the entire request signature, but make it configurable. - recorded_responses.each do |rr| - requests[[rr.method, rr.uri]] += [rr.response] + http_interactions.each do |i| + requests[i.request.matcher(match_attributes)] += [i.response] end - requests.each do |request, responses| - ::WebMock.stub_request(request.first, request.last). - to_return(responses.map{ |r| response_hash(r) }) + requests.each do |request_matcher, responses| + stub = ::WebMock.stub_request(request_matcher.method || :any, request_matcher.uri) + + with_hash = {} + with_hash[:body] = request_matcher.body if request_matcher.match_requests_on?(:body) + with_hash[:headers] = request_matcher.headers if request_matcher.match_requests_on?(:headers) + + stub = stub.with(with_hash) if with_hash.size > 0 + + stub.to_return(responses.map{ |r| response_hash(r) }) end end diff --git a/spec/cassette_spec.rb b/spec/cassette_spec.rb index b1562809..11d638d7 100644 --- a/spec/cassette_spec.rb +++ b/spec/cassette_spec.rb @@ -144,11 +144,24 @@ def cassette_body(name, options = {}) VCR::Cassette.new(:name, :record => record_mode) end - it "#{load_interactions ? 'loads' : 'does not load'} the recorded interactions from the library yml file" do - VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") - cassette = VCR::Cassette.new('example', :record => record_mode) + if load_interactions + + [true, false].each do |ignore_localhost| + expected_uri_hosts = %w(example.com) + expected_uri_hosts += VCR::LOCALHOST_ALIASES unless ignore_localhost + + it "#{ ignore_localhost ? 'does not load' : 'loads' } localhost interactions from the cassette file when http_stubbing_adapter.ignore_localhost is set to #{ignore_localhost}" do + VCR.http_stubbing_adapter.stub!(:ignore_localhost?).and_return(ignore_localhost) + VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + cassette = VCR::Cassette.new('with_localhost_requests', :record => record_mode) + cassette.recorded_interactions.map { |i| URI.parse(i.uri).host }.should =~ expected_uri_hosts + end + end + + it "loads the recorded interactions from the library yml file" do + VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + cassette = VCR::Cassette.new('example', :record => record_mode) - if load_interactions cassette.should have(3).recorded_interactions i1, i2, i3 = *cassette.recorded_interactions @@ -164,35 +177,34 @@ def cassette_body(name, options = {}) i3.request.method.should == :get i3.request.uri.should == 'http://example.com:80/' i3.response.body.should =~ /Another example\.com response/ - else - cassette.should have(0).recorded_interactions end - end - it "#{load_interactions ? 'stubs' : 'does not stub'} the recorded requests with the http stubbing adapter" do - VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + it "stubs the recorded requests with the http stubbing adapter" do + VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + VCR.http_stubbing_adapter.should_receive(:stub_requests).with([an_instance_of(VCR::HTTPInteraction)]*3, anything) + cassette = VCR::Cassette.new('example', :record => record_mode) + end - if load_interactions - VCR.http_stubbing_adapter.should_receive(:stub_requests).with([an_instance_of(VCR::HTTPInteraction)]*3) - else - VCR.http_stubbing_adapter.should_receive(:stub_requests).never + it "passes the :match_request_on option to #stub_requests" do + VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + VCR.http_stubbing_adapter.should_receive(:stub_requests).with(anything, [:body, :headers]) + cassette = VCR::Cassette.new('example', :record => record_mode, :match_requests_on => [:body, :headers]) end - cassette = VCR::Cassette.new('example', :record => record_mode) - end + else - if load_interactions - [true, false].each do |ignore_localhost| - expected_uri_hosts = %w(example.com) - expected_uri_hosts += VCR::LOCALHOST_ALIASES unless ignore_localhost + it "does not stub the recorded requests with the http stubbing adapter" do + VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + VCR.http_stubbing_adapter.should_not_receive(:stub_requests) + cassette = VCR::Cassette.new('example', :record => record_mode) + end - it "#{ ignore_localhost ? 'does not load' : 'loads' } localhost interactions from the cassette file when http_stubbing_adapter.ignore_localhost is set to #{ignore_localhost}" do - VCR.http_stubbing_adapter.stub!(:ignore_localhost?).and_return(ignore_localhost) - VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") - cassette = VCR::Cassette.new('with_localhost_requests', :record => record_mode) - cassette.recorded_interactions.map { |i| URI.parse(i.uri).host }.should =~ expected_uri_hosts - end + it "does not load the recorded interactions from the library yml file" do + VCR::Config.cassette_library_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec") + cassette = VCR::Cassette.new('example', :record => record_mode) + cassette.should have(0).recorded_interactions end + end end end diff --git a/spec/fixtures/not_1.9.1/match_requests_on.yml b/spec/fixtures/not_1.9.1/match_requests_on.yml new file mode 100644 index 00000000..fd6bad3a --- /dev/null +++ b/spec/fixtures/not_1.9.1/match_requests_on.yml @@ -0,0 +1,155 @@ +--- +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/method + body: + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: post method response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :get + uri: http://example.com:80/method + body: + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: get method response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example1.com:80/host + body: + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: example1.com host response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example2.com:80/host + body: + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: example2.com host response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/uri1 + body: + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: uri1 response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/uri2 + body: + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: uri2 response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/ + body: param=val1 + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: val1 body response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/ + body: param=val2 + headers: + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: val2 body response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/ + body: + headers: + x-http-header1: + - val1 + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: val1 header response + http_version: "1.1" +- !ruby/struct:VCR::HTTPInteraction + request: !ruby/struct:VCR::Request + method: :post + uri: http://example.com:80/ + body: + headers: + x-http-header1: + - val2 + response: !ruby/struct:VCR::Response + status: !ruby/struct:VCR::ResponseStatus + code: 200 + message: OK + headers: + etag: + - "\"24ec5-1b6-4059a80bfd280\"" + body: val2 header response + http_version: "1.1" diff --git a/spec/http_stubbing_adapters/fakeweb_spec.rb b/spec/http_stubbing_adapters/fakeweb_spec.rb index ed07618a..198fa492 100644 --- a/spec/http_stubbing_adapters/fakeweb_spec.rb +++ b/spec/http_stubbing_adapters/fakeweb_spec.rb @@ -2,7 +2,7 @@ describe VCR::HttpStubbingAdapters::FakeWeb do it_should_behave_like 'an http stubbing adapter' - it_should_behave_like 'an http stubbing adapter that supports Net::HTTP' + it_should_behave_like 'an http stubbing adapter that supports Net::HTTP', :method, :uri, :host describe '#check_version!' do disable_warnings diff --git a/spec/http_stubbing_adapters/webmock_spec.rb b/spec/http_stubbing_adapters/webmock_spec.rb index f982f99c..2c24b5d8 100644 --- a/spec/http_stubbing_adapters/webmock_spec.rb +++ b/spec/http_stubbing_adapters/webmock_spec.rb @@ -1,23 +1,25 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe VCR::HttpStubbingAdapters::WebMock do + supported_match_attributes = [:method, :uri, :host, :body, :headers] + it_should_behave_like 'an http stubbing adapter' - it_should_behave_like 'an http stubbing adapter that supports Net::HTTP' + it_should_behave_like 'an http stubbing adapter that supports Net::HTTP', *supported_match_attributes context "using patron" do - it_should_behave_like 'an http stubbing adapter that supports some HTTP library' do + it_should_behave_like 'an http stubbing adapter that supports some HTTP library', *supported_match_attributes do include PatronAdapter end end unless RUBY_PLATFORM =~ /java/ context "using httpclient" do - it_should_behave_like 'an http stubbing adapter that supports some HTTP library' do + it_should_behave_like 'an http stubbing adapter that supports some HTTP library', *supported_match_attributes do include HTTPClientAdapter end end context "using em-http-request" do - it_should_behave_like 'an http stubbing adapter that supports some HTTP library' do + it_should_behave_like 'an http stubbing adapter that supports some HTTP library', *supported_match_attributes do include EmHTTPRequestAdapter end end unless RUBY_PLATFORM =~ /java/ diff --git a/spec/support/http_library_adapters.rb b/spec/support/http_library_adapters.rb index 9afc0024..5ace9fed 100644 --- a/spec/support/http_library_adapters.rb +++ b/spec/support/http_library_adapters.rb @@ -7,12 +7,7 @@ def get_header(header_key, response) def make_http_request(method, url, body = {}, headers = {}) uri = URI.parse(url) - case method - when :get - Net::HTTP.get_response(uri) - when :post - Net::HTTP.new(uri.host, uri.port).post(uri.path, body, headers) - end + Net::HTTP.new(uri.host, uri.port).send_request(method.to_s.upcase, uri.path, body, headers) end end @@ -24,16 +19,7 @@ def get_header(header_key, response) end def make_http_request(method, url, body = {}, headers = {}) - uri = URI.parse(url) - sess = Patron::Session.new - sess.base_url = "#{uri.scheme}://#{uri.host}:#{uri.port}" - - case method - when :get - sess.get(uri.path) - when :post - sess.post(uri.path, body, headers) - end + Patron::Session.new.request(method, url, headers, :data => body) end end @@ -48,12 +34,7 @@ def get_header(header_key, response) end def make_http_request(method, url, body = {}, headers = {}) - case method - when :get - HTTPClient.new.get(url) - when :post - HTTPClient.new.post(url, body, headers) - end + HTTPClient.new.request(method, url, nil, body, headers) end end @@ -69,11 +50,7 @@ def get_header(header_key, response) def make_http_request(method, url, body = {}, headers = {}) http = nil EventMachine.run do - http = case method - when :get then EventMachine::HttpRequest.new(url).get - when :post then EventMachine::HttpRequest.new(url).post :body => body, :head => headers - end - + http = EventMachine::HttpRequest.new(url).send(method, :body => body, :head => headers) http.callback { EventMachine.stop } end http diff --git a/spec/support/http_stubbing_adapter.rb b/spec/support/http_stubbing_adapter.rb index 4af26388..e21d6119 100644 --- a/spec/support/http_stubbing_adapter.rb +++ b/spec/support/http_stubbing_adapter.rb @@ -52,20 +52,106 @@ end end -shared_examples_for "an http stubbing adapter that supports Net::HTTP" do +shared_examples_for "an http stubbing adapter that supports Net::HTTP" do |*args| context "using Net::HTTP" do - it_should_behave_like 'an http stubbing adapter that supports some HTTP library' do + it_should_behave_like 'an http stubbing adapter that supports some HTTP library', *args do include NetHTTPAdapter end end end -shared_examples_for "an http stubbing adapter that supports some HTTP library" do +shared_examples_for "an http stubbing adapter that supports some HTTP library" do |*supported_request_match_attributes| include HttpStubbingAdapterStubbed subject { described_class } NET_CONNECT_NOT_ALLOWED_ERROR = [StandardError, /You can use VCR to automatically record this request and replay it later/] unless defined?(NET_CONNECT_NOT_ALLOWED_ERROR) + describe 'stubbing using specific match_attributes', :focus => true do + before(:each) { subject.http_connections_allowed = false } + let(:interactions) { YAML.load(File.read(File.join(File.dirname(__FILE__), '..', 'fixtures', YAML_SERIALIZATION_VERSION, 'match_requests_on.yml'))) } + + def self.matching_on(attribute, &block) + describe attribute do + let(:perform_stubbing) { subject.stub_requests(interactions, [attribute]) } + module_eval(&block) + end + end + + matching_on :method do + before(:each) { perform_stubbing } + + [:get, :post].each do |http_method| + it "returns the expected response for a :#{http_method}" do + get_body_string(make_http_request(http_method, 'http://some-wrong-domain.com/')).should == "#{http_method} method response" + end + end + + it 'raises an error for another method' do + expect { make_http_request(:put, 'http://some-wrong-domain.com/') }.to raise_error(*NET_CONNECT_NOT_ALLOWED_ERROR) + end + end + + matching_on :host do + before(:each) { perform_stubbing } + + 1.upto(2) do |i| + it "returns the expected response from example#{i}.com" do + get_body_string(make_http_request(:get, "http://example#{i}.com/some-wrong-path")).should == "example#{i}.com host response" + end + end + + it 'raises an error for another host' do + expect { make_http_request(:get, 'http://example3.com/some-wrong-path') }.to raise_error(*NET_CONNECT_NOT_ALLOWED_ERROR) + end + end + + matching_on :uri do + before(:each) { perform_stubbing } + + 1.upto(2) do |i| + it "returns the expected response from example.com/uri#{i}" do + get_body_string(make_http_request(:get, "http://example.com/uri#{i}")).should == "uri#{i} response" + end + end + + it 'raises an error for another uri' do + expect { make_http_request(:get, 'http://example.com/uri3') }.to raise_error(*NET_CONNECT_NOT_ALLOWED_ERROR) + end + end + + matching_on :body do + before(:each) { perform_stubbing } + + 1.upto(2) do |i| + it "returns the expected response for request body 'param=val#{i}'" do + get_body_string(make_http_request(:get, "http://wrong-domain.com/wrong/path", "param=val#{i}")).should == "val#{i} body response" + end + end + + it 'raises an error for another request body' do + expect { + res = make_http_request(:get, "http://wrong-domain.com/wrong/path", "param=val3") + }.to raise_error(*NET_CONNECT_NOT_ALLOWED_ERROR) + end + end + + matching_on :headers do + before(:each) { perform_stubbing } + + 1.upto(2) do |i| + it "returns the expected response for request header 'X-HTTP-HEADER1 = val#{i}'" do + get_body_string(make_http_request(:get, "http://wrong-domain.com/wrong/path", {}, 'X-HTTP-HEADER1' => "val#{i}")).should == "val#{i} header response" + end + end + + it 'raises an error for another request header' do + expect { + make_http_request(:get, "http://wrong-domain.com/wrong/path", {}, 'X-HTTP-HEADER1' => "val3") + }.to raise_error(*NET_CONNECT_NOT_ALLOWED_ERROR) + end + end + end + def self.test_real_http_request(http_allowed) if http_allowed