Permalink
Browse files

Normalize structs so they are serialized the same regardless of which…

… HTTP lib was used to make the request.

- Remove extra padding on status message ("OK " => "OK")
- Set body to nil if it is the empty string
- Set headers to nil if it is an empty hash
- Remove extraneous headers added by the HTTP lib (i.e. Typhoeus user agent)
  • Loading branch information...
1 parent cb59842 commit 002587118892874d6bb19bb03c84e18079db5b53 @myronmarston committed Nov 13, 2010
Showing with 123 additions and 30 deletions.
  1. +11 −0 CHANGELOG.md
  2. +64 −13 lib/vcr/structs.rb
  3. +1 −1 spec/extensions/net_http_spec.rb
  4. +47 −16 spec/structs_spec.rb
View
11 CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## In git
+
+[Full Changelog](http://github.com/myronmarston/vcr/compare/v1.3.1...master)
+
+* Fix serialized structs so that they are normalized andthey will be the same
+ regardless of which HTTP library made the request.
+ * Status "OK " => "OK"
+ * Body '' => nil
+ * Headers {} => nil
+ * Remove extraneous headers added by the HTTP lib (i.e. Typhoeus user agent)
+
## 1.3.1 (November 11, 2010)
[Full Changelog](http://github.com/myronmarston/vcr/compare/v1.3.0...v1.3.1)
View
77 lib/vcr/structs.rb
@@ -1,26 +1,70 @@
require 'forwardable'
module VCR
+ module BodyNormalizer
+ def initialize(*args)
+ super
+ normalize_body
+ end
+
+ private
+
+ def normalize_body
+ # Ensure that the body is a raw string, in case the string instance
+ # has been subclassed or extended with additional instance variables
+ # or attributes, so that it is serialized to YAML as a raw string.
+ # This is needed for rest-client. See this ticket for more info:
+ # http://github.com/myronmarston/vcr/issues/4
+ self.body = case body
+ when nil, ''; nil
+ else String.new(body)
+ end
+ end
+ end
+
module HeaderNormalizer
+ # These headers get added by the various HTTP clients automatically,
+ # and we don't care about them. We store the headers for the purposes
+ # of request matching, and we only care to match on headers users
+ # explicitly set.
+ HEADERS_TO_SKIP = {
+ 'connection' => %w[ close Keep-Alive ],
+ 'accept' => %w[ */* ],
+ 'expect' => [''],
+ 'user-agent' => ["Typhoeus - http://github.com/pauldix/typhoeus/tree/master", 'Ruby']
+ }
+
def initialize(*args)
super
normalize_headers
end
private
+ def important_header_values(k, values)
+ skip_values = HEADERS_TO_SKIP[k] || []
+ values - skip_values
+ end
+
def normalize_headers
new_headers = {}
headers.each do |k, v|
- new_headers[k.downcase] = case v
+ k = k.downcase
+
+ val_array = case v
when Array then v
when nil then []
else [v]
end
+
+ important_vals = important_header_values(k, val_array)
+ next unless important_vals.size > 0
+
+ new_headers[k] = important_vals
end if headers
- self.headers = new_headers
+ self.headers = new_headers.empty? ? nil : new_headers
end
end
@@ -55,9 +99,24 @@ def u.default_port; nil; end
end
end
+ module StatusMessageNormalizer
+ def initialize(*args)
+ super
+ normalize_status_message
+ end
+
+ private
+
+ def normalize_status_message
+ self.message = message.strip if message
+ self.message = nil if message == ''
+ end
+ end
+
class Request < Struct.new(:method, :uri, :body, :headers)
include HeaderNormalizer
include URINormalizer
+ include BodyNormalizer
def self.from_net_http_request(net_http, request)
new(
@@ -74,24 +133,16 @@ def matcher(match_attributes)
end
class ResponseStatus < Struct.new(:code, :message)
+ include StatusMessageNormalizer
+
def self.from_net_http_response(response)
new(response.code.to_i, response.message)
end
end
class Response < Struct.new(:status, :headers, :body, :http_version)
include HeaderNormalizer
-
- def initialize(*args)
- super
-
- # Ensure that the body is a raw string, in case the string instance
- # has been subclassed or extended with additional instance variables
- # or attributes, so that it is serialized to YAML as a raw string.
- # This is needed for rest-client. See this ticket for more info:
- # http://github.com/myronmarston/vcr/issues/4
- self.body = String.new(body) if body
- end
+ include BodyNormalizer
def self.from_net_http_response(response)
new(
View
2 spec/extensions/net_http_spec.rb
@@ -43,7 +43,7 @@ def perform_get_with_returning_block
interaction.request.headers.should_not have_key('content-type')
interaction.request.headers.should_not have_key('host')
end
- Net::HTTP.new('example.com', 80).send_request('POST', '/', '', {})
+ Net::HTTP.new('example.com', 80).send_request('POST', '/', '', { 'x-http-user' => 'me' })
end
it "records headers for which Net::HTTP usually sets defaults when the user manually sets their values" do
View
63 spec/structs_spec.rb
@@ -2,20 +2,38 @@
shared_examples_for "a header normalizer" do
let(:instance) do
- with_headers('Some_Header' => 'value1', 'aNother' => ['a', 'b'], 'third' => [], 'FOURTH' => nil)
+ with_headers('Some_Header' => 'value1', 'aNother' => ['a', 'b'], 'third' => [], 'fourth' => nil)
end
it 'normalizes the hash to lower case keys and arrays of values' do
- instance.headers.should == {
- 'some_header' => ['value1'],
- 'another' => ['a', 'b'],
- 'third' => [],
- 'fourth' => []
- }
+ instance.headers['some_header'].should == ['value1']
+ instance.headers['another'].should == ['a', 'b']
end
- it 'set nil header to an empty hash' do
- with_headers(nil).headers.should == {}
+ it 'removes empty headers' do
+ instance.headers.should_not have_key('third')
+ instance.headers.should_not have_key('fourth')
+ end
+
+ it 'filters out unimportant default values set by the HTTP library' do
+ instance = with_headers('accept' => ['*/*'], 'connection' => 'close', 'http-user' => ['foo'], 'expect' => ['', 'bar'])
+ instance.headers.should == { 'http-user' => ['foo'], 'expect' => ['bar'] }
+ end
+
+ it 'sets empty hash header to nil' do
+ with_headers({}).headers.should be_nil
+ end
+end
+
+shared_examples_for "a body normalizer" do
+ it 'sets empty string to nil' do
+ instance('').body.should be_nil
+ end
+
+ it "ensures the body is serialized to yaml as a raw string" do
+ body = "My String"
+ body.instance_variable_set(:@foo, 7)
+ instance(body).body.to_yaml.should == "My String".to_yaml
end
end
@@ -67,7 +85,7 @@ def request(uri)
it { should be_instance_of(VCR::Request) }
its(:method) { should == :post }
its(:body) { should == 'id=7' }
- its(:headers) { should == { "accept" => ["*/*"], "content-type" => ["application/x-www-form-urlencoded"] } }
+ its(:headers) { should == { "content-type" => ["application/x-www-form-urlencoded"] } }
it 'sets the uri using the http_stubbing_adapter.request_uri' do
VCR.http_stubbing_adapter.should_receive(:request_uri).with(net_http, request).and_return('foo/bar')
@@ -80,6 +98,12 @@ def with_headers(headers)
described_class.new(:get, 'http://example.com/', nil, headers)
end
end
+
+ it_behaves_like 'a body normalizer' do
+ def instance(body)
+ described_class.new(:get, 'http://example.com/', body, {})
+ end
+ end
end
describe VCR::ResponseStatus do
@@ -91,6 +115,15 @@ def with_headers(headers)
its(:code) { should == 200 }
its(:message) { should == 'OK' }
end
+
+ it 'chomps leading and trailing spaces on the status message' do
+ described_class.new(200, ' OK ').message.should == 'OK'
+ end
+
+ it 'sets status message to nil when it is the empty string' do
+ described_class.new(200, '').message.should be_nil
+ described_class.new(200, ' ').message.should be_nil
+ end
end
describe VCR::Response do
@@ -103,7 +136,6 @@ def with_headers(headers)
its(:http_version) { should == '1.1' }
its(:headers) { should == {
"last-modified" => ['Tue, 15 Nov 2005 13:24:10 GMT'],
- "connection" => ['close'],
"etag" => ["\"24ec5-1b6-4059a80bfd280\""],
"content-type" => ["text/html; charset=UTF-8"],
"date" => ['Wed, 31 Mar 2010 02:43:26 GMT'],
@@ -125,11 +157,10 @@ def with_headers(headers)
end
end
- it "ensures the body is serialized to yaml as a raw string" do
- body = "My String"
- body.instance_variable_set(:@foo, 7)
- instance = described_class.new(:status, {}, body, :version)
- instance.body.to_yaml.should == "My String".to_yaml
+ it_behaves_like 'a body normalizer' do
+ def instance(body)
+ described_class.new(:status, {}, body, '1.1')
+ end
end
end

0 comments on commit 0025871

Please sign in to comment.