Skip to content

Commit

Permalink
Added some failing scenarios for the new :match_requests_on feature I…
Browse files Browse the repository at this point in the history
…'m working on.
  • Loading branch information
myronmarston committed Aug 12, 2010
1 parent 3ced987 commit 9a98992
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -40,7 +40,7 @@ begin
Cucumber::Rake::Task.new(
{ sanitized_http_lib => "#{features_subtasks.last}_prep" },
"Run the features using #{http_stubbing_adapter} and #{http_lib}") do |t|
t.cucumber_opts = ['--format', 'progress', '--tags', "@all_http_libs,@#{sanitized_http_lib}"]
t.cucumber_opts = ['--format', 'progress', '--tags', "@#{http_stubbing_adapter},@all_http_libs,@#{sanitized_http_lib}"]

# disable scenarios on heroku that can't pass due to heroku's restrictions
t.cucumber_opts += ['--tags', '~@spawns_localhost_server'] if ENV.keys.include?('HEROKU_SLUG')
Expand Down
35 changes: 35 additions & 0 deletions features/fixtures/vcr_cassettes/1.9.1/match_requests_on.yml
@@ -0,0 +1,35 @@
---
- !ruby/struct:VCR::HTTPInteraction
:request: !ruby/struct:VCR::Request
:method: :post
:uri: http://example.com:80/
:body: foo=bar
:headers:
x-http-user:
- bob
:response: !ruby/struct:VCR::Response
:status: !ruby/struct:VCR::ResponseStatus
:code: 200
:message: OK
:headers:
etag:
- "\"24ec5-1b6-4059a80bfd280\""
:body: foo=bar response | bob response
:http_version: "1.1"
- !ruby/struct:VCR::HTTPInteraction
:request: !ruby/struct:VCR::Request
:method: :post
:uri: http://example.com:80/
:body: bar=bazz
:headers:
x-http-user:
- joe
:response: !ruby/struct:VCR::Response
:status: !ruby/struct:VCR::ResponseStatus
:code: 200
:message: OK
:headers:
etag:
- "\"24ec5-1b6-4059a80bfd280\""
:body: bar=bazz response | joe response
:http_version: "1.1"
35 changes: 35 additions & 0 deletions features/fixtures/vcr_cassettes/not_1.9.1/match_requests_on.yml
@@ -0,0 +1,35 @@
---
- !ruby/struct:VCR::HTTPInteraction
request: !ruby/struct:VCR::Request
method: :post
uri: http://example.com:80/
body: foo=bar
headers:
x-http-user:
- bob
response: !ruby/struct:VCR::Response
status: !ruby/struct:VCR::ResponseStatus
code: 200
message: OK
headers:
etag:
- "\"24ec5-1b6-4059a80bfd280\""
body: foo=bar response | bob response
http_version: "1.1"
- !ruby/struct:VCR::HTTPInteraction
request: !ruby/struct:VCR::Request
method: :post
uri: http://example.com:80/
body: bar=bazz
headers:
x-http-user:
- joe
response: !ruby/struct:VCR::Response
status: !ruby/struct:VCR::ResponseStatus
code: 200
message: OK
headers:
etag:
- "\"24ec5-1b6-4059a80bfd280\""
body: bar=bazz response | joe response
http_version: "1.1"
63 changes: 56 additions & 7 deletions features/step_definitions/vcr_steps.rb
@@ -1,23 +1,38 @@
require 'tmpdir'

RSpec::Matchers.define :have_expected_response do |url, regex_str|
def responses_for_url(responses, url)
RSpec::Matchers.define :have_expected_response do |regex_str, request_attributes|
def matched_responses(responses, request_attributes)
url, request_body, request_headers = request_attributes[:url], request_attributes[:request_body], request_attributes[:request_headers]

selector = case url
when String then lambda { |r| URI.parse(r.uri) == URI.parse(url) }
when Regexp then lambda { |r| r.uri == url }
else raise ArgumentError.new("Unexpected url: #{url.class.to_s}: #{url.inspect}")
end

responses.select(&selector)
matched = responses.select(&selector)

if request_body
matched = matched.select { |r| r.request.body == request_body }
end

(request_headers || {}).each do |r_key, r_val|
matched = matched.select do |r|
r.request.headers.any? { |k, v| k.downcase == r_key.downcase && v == [r_val] }
end
end

matched
end

match do |responses|
regex = /#{regex_str}/i
responses_for_url(responses, url).detect { |r| r.response.body =~ regex }
matched_responses(responses, request_attributes).detect { |r| r.response.body =~ regex }
end

failure_message_for_should do |responses|
responses = responses_for_url(responses, url)
url = request_attributes[:url]
responses = matched_responses(responses, request_attributes)
response_bodies = responses.map { |r| r.response.body }
"expected a response for #{url.inspect} to match /#{regex_str}/. Responses for #{url.inspect}:\n\n #{response_bodies.join("\n\n")}"
end
Expand Down Expand Up @@ -98,7 +113,15 @@ def capture_response(url)
end

Given /^the "([^\"]*)" library file has a response for \/(\S+)\/ that matches \/(.+)\/$/ do |cassette_name, url_regex, body_regex|
recorded_interactions_for(cassette_name).should have_expected_response(/#{url_regex}/, body_regex)
recorded_interactions_for(cassette_name).should have_expected_response(body_regex, :url => /#{url_regex}/)
end

Given /^the "([^"]*)" library file has a response for "([^"]*)" with the request body "([^"]*)" that matches \/(.+)\/$/ do |cassette_name, url, request_body, response_regex|
recorded_interactions_for(cassette_name).should have_expected_response(response_regex, :url => url, :request_body => request_body)
end

Given /^the "([^"]*)" library file has a response for "([^"]*)" with the request header "([^"]*)=([^"]*)" that matches \/(.+)\/$/ do |cassette_name, url, header_key, header_value, response_regex|
recorded_interactions_for(cassette_name).should have_expected_response(response_regex, :url => url, :request_headers => { header_key => header_value })
end

Given /^this scenario is tagged with the vcr cassette tag: "([^\"]+)"$/ do |tag|
Expand All @@ -119,6 +142,18 @@ def capture_response(url)
end
end

When /^I make an HTTP post request to "([^"]*)" with request body "([^"]*)"$/ do |url, request_body|
capture_response(url) do |uri, path|
make_http_request(:post, url, request_body)
end
end

When /^I make an HTTP post request to "([^"]*)" with request header "([^"]*)=([^"]*)"$/ do |url, header_key, header_val|
capture_response(url) do |uri, path|
make_http_request(:post, url, '', { header_key => header_val })
end
end

When /^I make (.*) requests? to "([^\"]*)"(?: and "([^\"]*)")? within the "([^\"]*)" cassette(?: using cassette options: (.*))?$/ do |http_request_type, url1, url2, cassette_name, options|
options = options.to_s == '' ? { :record => :new_episodes } : eval(options)
urls = [url1, url2].select { |u| u.to_s.size > 0 }
Expand All @@ -129,9 +164,23 @@ def capture_response(url)
end
end

When /^I make an HTTP post request to "([^"]*)" with request body "([^"]*)" within the "([^"]*)" cassette(?: using cassette options: (.*))?$/ do |url, request_body, cassette_name, options|
options = options.to_s == '' ? { :record => :new_episodes } : eval(options)
VCR.use_cassette(cassette_name, options) do
When %{I make an HTTP post request to "#{url}" with request body "#{request_body}"}
end
end

When /^I make an HTTP post request to "([^"]*)" with request header "([^"]*)=([^"]*)" within the "([^"]*)" cassette(?: using cassette options: (.*))?$/ do |url, header_key, header_value, cassette_name, options|
options = options.to_s == '' ? { :record => :new_episodes } : eval(options)
VCR.use_cassette(cassette_name, options) do
When %{I make an HTTP post request to "#{url}" with request header "#{header_key}=#{header_value}"}
end
end

Then /^the "([^\"]*)" library file should have a response for "([^\"]*)" that matches \/(.+)\/$/ do |cassette_name, url, regex_str|
interactions = recorded_interactions_for(cassette_name)
interactions.should have_expected_response(url, regex_str)
interactions.should have_expected_response(regex_str, :url => url)
end

Then /^the "([^\"]*)" library file should have exactly (\d+) response$/ do |cassette_name, response_count|
Expand Down
26 changes: 26 additions & 0 deletions features/webmock.feature
@@ -0,0 +1,26 @@
@webmock
Feature: Replay recorded response
In order to have deterministic, fast tests that do not depend on an internet connection
As a TDD/BDD developer who uses WebMock
I want to replay responses for requests I have previously recorded

Scenario: Use the :match_requests_on option to differentiate requests by request body (for "foo=bar")
Given the "match_requests_on" library file has a response for "http://example.com/" with the request body "foo=bar" that matches /foo=bar response/
When I make an HTTP post request to "http://example.com/" with request body "foo=bar" within the "match_requests_on" cassette using cassette options: { :match_requests_on => [:uri, :body], :record => :none }
Then the response for "http://example.com/" should match /foo=bar response/

Scenario: Use the :match_requests_on option to differentiate requests by request body (for "bar=bazz")
Given the "match_requests_on" library file has a response for "http://example.com/" with the request body "bar=bazz" that matches /bar=bazz response/
When I make an HTTP post request to "http://example.com/" with request body "bar=bazz" within the "match_requests_on" cassette using cassette options: { :match_requests_on => [:uri, :body], :record => :none }
Then the response for "http://example.com/" should match /bar=bazz response/

Scenario: Use the :match_requests_on option to differentiate requests by request header (for "X-HTTP-USER=joe")
Given the "match_requests_on" library file has a response for "http://example.com/" with the request header "X-HTTP-USER=joe" that matches /joe response/
When I make an HTTP post request to "http://example.com/" with request header "X-HTTP-USER=joe" within the "match_requests_on" cassette using cassette options: { :match_requests_on => [:uri, :headers], :record => :none }
Then the response for "http://example.com/" should match /joe response/

Scenario: Use the :match_requests_on option to differentiate requests by request header (for "X-HTTP-USER=bob")
Given the "match_requests_on" library file has a response for "http://example.com/" with the request header "X-HTTP-USER=bob" that matches /bob response/
When I make an HTTP post request to "http://example.com/" with request header "X-HTTP-USER=bob" within the "match_requests_on" cassette using cassette options: { :match_requests_on => [:uri, :headers], :record => :none }
Then the response for "http://example.com/" should match /bob response/

2 changes: 1 addition & 1 deletion lib/vcr/cassette.rb
Expand Up @@ -12,7 +12,7 @@ class MissingERBVariableError < NameError; end

def initialize(name, options = {})
options = VCR::Config.default_cassette_options.merge(options)
invalid_options = options.keys - [:record, :erb, :allow_real_http]
invalid_options = options.keys - [:record, :erb, :allow_real_http, :match_requests_on]

if invalid_options.size > 0
raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.")
Expand Down
19 changes: 10 additions & 9 deletions spec/support/http_library_adapters.rb
Expand Up @@ -5,12 +5,13 @@ def get_header(header_key, response)
response.get_fields(header_key)
end

def make_http_request(method, url, body = {})
def make_http_request(method, url, body = {}, headers = {})
uri = URI.parse(url)
case method
when :get
Net::HTTP.get_response(URI.parse(url))
Net::HTTP.get_response(uri)
when :post
Net::HTTP.post_form(URI.parse(url), body)
Net::HTTP.new(uri.host, uri.port).post(uri.path, body, headers)
end
end
end
Expand All @@ -22,7 +23,7 @@ def get_header(header_key, response)
response.headers[header_key]
end

def make_http_request(method, url, body = {})
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}"
Expand All @@ -31,7 +32,7 @@ def make_http_request(method, url, body = {})
when :get
sess.get(uri.path)
when :post
sess.post(uri.path, body)
sess.post(uri.path, body, headers)
end
end
end
Expand All @@ -46,12 +47,12 @@ def get_header(header_key, response)
response.header[header_key]
end

def make_http_request(method, url, body = {})
def make_http_request(method, url, body = {}, headers = {})
case method
when :get
HTTPClient.new.get(url)
when :post
HTTPClient.new.post(url, body)
HTTPClient.new.post(url, body, headers)
end
end
end
Expand All @@ -65,12 +66,12 @@ def get_header(header_key, response)
response.response_header[header_key.upcase.gsub('-', '_')].split(', ')
end

def make_http_request(method, url, body = {})
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
when :post then EventMachine::HttpRequest.new(url).post :body => body, :head => headers
end

http.callback { EventMachine.stop }
Expand Down

0 comments on commit 9a98992

Please sign in to comment.