webmock 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/CHANGELOG.md +17 -0
  2. data/README.md +13 -0
  3. data/VERSION +1 -1
  4. data/lib/webmock/errors.rb +5 -1
  5. data/lib/webmock/http_lib_adapters/httpclient.rb +10 -8
  6. data/lib/webmock/http_lib_adapters/net_http.rb +56 -64
  7. data/lib/webmock/http_lib_adapters/patron.rb +2 -3
  8. data/lib/webmock/util/headers.rb +18 -3
  9. data/lib/webmock/webmock.rb +12 -5
  10. data/spec/example_curl_output.txt +2 -0
  11. data/spec/httpclient_spec.rb +8 -0
  12. data/spec/httpclient_spec_helper.rb +9 -1
  13. data/spec/net_http_spec_helper.rb +10 -1
  14. data/spec/other_net_http_libs_spec.rb +0 -10
  15. data/spec/patron_spec_helper.rb +8 -2
  16. data/spec/response_spec.rb +4 -2
  17. data/spec/util/headers_spec.rb +17 -0
  18. data/spec/webmock_spec.rb +103 -15
  19. data/test/test_webmock.rb +1 -1
  20. data/webmock.gemspec +2 -23
  21. metadata +2 -23
  22. data/spec/vendor/samuel-0.2.1/.document +0 -5
  23. data/spec/vendor/samuel-0.2.1/.gitignore +0 -5
  24. data/spec/vendor/samuel-0.2.1/LICENSE +0 -20
  25. data/spec/vendor/samuel-0.2.1/README.rdoc +0 -70
  26. data/spec/vendor/samuel-0.2.1/Rakefile +0 -62
  27. data/spec/vendor/samuel-0.2.1/VERSION +0 -1
  28. data/spec/vendor/samuel-0.2.1/lib/samuel.rb +0 -52
  29. data/spec/vendor/samuel-0.2.1/lib/samuel/net_http.rb +0 -10
  30. data/spec/vendor/samuel-0.2.1/lib/samuel/request.rb +0 -96
  31. data/spec/vendor/samuel-0.2.1/samuel.gemspec +0 -69
  32. data/spec/vendor/samuel-0.2.1/test/request_test.rb +0 -193
  33. data/spec/vendor/samuel-0.2.1/test/samuel_test.rb +0 -42
  34. data/spec/vendor/samuel-0.2.1/test/test_helper.rb +0 -66
  35. data/spec/vendor/samuel-0.2.1/test/thread_test.rb +0 -32
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  #Changelog
2
2
 
3
+ ## 1.1.0
4
+
5
+ * [VCR](http://github.com/myronmarston/vcr/) compatibility. Many thanks to Myron Masteron for all suggestions.
6
+
7
+ * Support for stubbing requests and returning responses with multiple headers with the same name. i.e multiple Accept headers.
8
+
9
+ stub_http_request(:get, 'www.example.com').
10
+ with(:headers => {'Accept' => ['image/png', 'image/jpeg']}).
11
+ to_return(:body => 'abc')
12
+ RestClient.get('www.example.com',
13
+ {"Accept" => ['image/png', 'image/jpeg']}) # ===> "abc\n"
14
+
15
+ * When real net connections are disabled and unstubbed request is made, WebMock throws WebMock::NetConnectNotAllowedError instead of assertion error or StandardError.
16
+
17
+ * Added WebMock.version()
18
+
19
+
3
20
  ## 1.0.0
4
21
 
5
22
  * Added support for [Patron](http://toland.github.com/patron/)
data/README.md CHANGED
@@ -87,6 +87,15 @@ You can also use WebMock without RSpec or Test::Unit support:
87
87
  http.request(req, 'abc')
88
88
  } # ===> Success
89
89
 
90
+ ### Matching multiple headers with the same name
91
+
92
+ stub_http_request(:get, 'www.example.com').with(:headers => {'Accept' => ['image/jpeg', 'image/png'] })
93
+
94
+ req = Net::HTTP::Get.new("/")
95
+ req['Accept'] = ['image/png']
96
+ req.add_field('Accept', 'image/jpeg')
97
+ Net::HTTP.start("www.example.com") {|http| http.request(req) } # ===> Success
98
+
90
99
  ### Matching requests against provided block
91
100
 
92
101
  stub_request(:post, "www.example.com").with { |request| request.body == "abc" }
@@ -384,6 +393,9 @@ i.e the following two sets of headers are equal:
384
393
 
385
394
  `{ :header1 => "value1", "Content-Length" => 123, "x-cuSTOM-HeAder" => "value" }`
386
395
 
396
+ ## Recording real requests and responses and replaying them later
397
+
398
+ To record your application's real HTTP interactions and replay them later in tests you can use [VCR](http://github.com/myronmarston/vcr) with WebMock.
387
399
 
388
400
  ## Bugs and Issues
389
401
 
@@ -417,6 +429,7 @@ People who submitted patches and new features or suggested improvements. Many th
417
429
  * Tekin Suleyman
418
430
  * Tom Ward
419
431
  * Nadim Bitar
432
+ * Myron Masteron
420
433
 
421
434
  ## Background
422
435
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -1,5 +1,9 @@
1
1
  module WebMock
2
2
 
3
- class NetConnectNotAllowedError < StandardError; end;
3
+ class NetConnectNotAllowedError < StandardError
4
+ def initialize(request_signature)
5
+ super("Real HTTP connections are disabled. Unregistered request: #{request_signature}")
6
+ end
7
+ end
4
8
 
5
9
  end
@@ -19,15 +19,14 @@ if defined?(HTTPClient)
19
19
  webmock_response = WebMock.response_for_request(request_signature)
20
20
  response = build_httpclient_response(webmock_response, stream, &block)
21
21
  conn.push(response)
22
- elsif WebMock.net_connect_allowed?(req.header.request_uri)
22
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
23
23
  if stream
24
24
  do_get_stream_without_webmock(req, proxy, conn, &block)
25
25
  else
26
26
  do_get_block_without_webmock(req, proxy, conn, &block)
27
27
  end
28
28
  else
29
- message = "Real HTTP connections are disabled. Unregistered request: #{request_signature}"
30
- WebMock.assertion_failure(message)
29
+ raise NetConnectNotAllowedError.new(request_signature)
31
30
  end
32
31
  end
33
32
 
@@ -35,11 +34,10 @@ if defined?(HTTPClient)
35
34
  req = create_request(method, uri, query, body, extheader)
36
35
  request_signature = build_request_signature(req)
37
36
 
38
- if WebMock.registered_request?(request_signature) || WebMock.net_connect_allowed?(uri)
37
+ if WebMock.registered_request?(request_signature) || WebMock.net_connect_allowed?(request_signature.uri)
39
38
  do_request_async_without_webmock(method, uri, query, body, extheader)
40
39
  else
41
- message = "Real HTTP connections are disabled. Unregistered request: #{request_signature}"
42
- WebMock.assertion_failure(message)
40
+ raise NetConnectNotAllowedError.new(request_signature)
43
41
  end
44
42
  end
45
43
 
@@ -76,8 +74,12 @@ if defined?(HTTPClient)
76
74
 
77
75
  auth = www_auth.basic_auth
78
76
  auth.challenge(req.header.request_uri, nil)
79
-
80
- headers = Hash[*req.header.all.flatten]
77
+
78
+ headers = req.header.all.inject({}) do |headers, header|
79
+ headers[header[0]] ||= [];
80
+ headers[header[0]] << header[1]
81
+ headers
82
+ end
81
83
 
82
84
  if (auth_cred = auth.get(req)) && auth.scheme == 'Basic'
83
85
  userinfo = WebMock::Util::Headers.decode_userinfo_from_header(auth_cred)
@@ -53,37 +53,7 @@ module Net #:nodoc: all
53
53
  end
54
54
 
55
55
  def request_with_webmock(request, body = nil, &block)
56
- protocol = use_ssl? ? "https" : "http"
57
-
58
- path = request.path
59
- path = Addressable::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/
60
-
61
- if request["authorization"] =~ /^Basic /
62
- userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])
63
- userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) + "@"
64
- else
65
- userinfo = ""
66
- end
67
-
68
- uri = "#{protocol}://#{userinfo}#{self.address}:#{self.port}#{path}"
69
- method = request.method.downcase.to_sym
70
-
71
- headers = Hash[*request.to_hash.map {|k,v| [k, v.flatten]}.flatten]
72
-
73
- headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v =~ /^Basic / } #we added it to url userinfo
74
-
75
- if request.body_stream
76
- body = request.body_stream.read
77
- request.body_stream = nil
78
- end
79
-
80
- if body != nil && body.respond_to?(:read)
81
- request.set_body_internal body.read
82
- else
83
- request.set_body_internal body
84
- end
85
-
86
- request_signature = WebMock::RequestSignature.new(method, uri, :body => request.body, :headers => headers)
56
+ request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)
87
57
 
88
58
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
89
59
 
@@ -91,12 +61,11 @@ module Net #:nodoc: all
91
61
  @socket = Net::HTTP.socket_type.new
92
62
  webmock_response = WebMock.response_for_request(request_signature)
93
63
  build_net_http_response(webmock_response, &block)
94
- elsif WebMock.net_connect_allowed?(uri)
64
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
95
65
  connect_without_webmock
96
66
  request_without_webmock(request, nil, &block)
97
67
  else
98
- message = "Real HTTP connections are disabled. Unregistered request: #{request_signature}"
99
- WebMock.assertion_failure(message)
68
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
100
69
  end
101
70
  end
102
71
  alias_method :request_without_webmock, :request
@@ -130,47 +99,70 @@ module Net #:nodoc: all
130
99
 
131
100
  response
132
101
  end
133
-
102
+
134
103
  end
135
104
 
136
105
  end
137
106
 
138
107
  module WebMock
139
108
  module NetHTTPUtility
140
- def self.puts_warning_for_net_http_around_advice_libs_if_needed
141
- libs = {"Samuel" => defined?(Samuel)}
142
- warnings = libs.select { |_, loaded| loaded }.map do |name, _|
143
- <<-TEXT.gsub(/ {10}/, '')
144
- \e[1mWarning: WebMock was loaded after #{name}\e[0m
145
- * #{name}'s code is being ignored when a request is handled by WebMock,
146
- because both libraries work by patching Net::HTTP.
147
- * To fix this, just reorder your requires so that WebMock is before #{name}.
148
- TEXT
149
- end
150
- $stderr.puts "\n" + warnings.join("\n") + "\n" if warnings.any?
109
+
110
+ def self.request_signature_from_request(net_http, request, body = nil)
111
+ protocol = net_http.use_ssl? ? "https" : "http"
112
+
113
+ path = request.path
114
+ path = Addressable::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/
115
+
116
+ if request["authorization"] =~ /^Basic /
117
+ userinfo = WebMock::Util::Headers.decode_userinfo_from_header(request["authorization"])
118
+ userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) + "@"
119
+ else
120
+ userinfo = ""
121
+ end
122
+
123
+ uri = "#{protocol}://#{userinfo}#{net_http.address}:#{net_http.port}#{path}"
124
+ method = request.method.downcase.to_sym
125
+
126
+ headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
127
+
128
+ headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v.first =~ /^Basic / } #we added it to url userinfo
129
+
130
+
131
+ if request.body_stream
132
+ body = request.body_stream.read
133
+ request.body_stream = nil
151
134
  end
152
135
 
153
- def self.record_loaded_net_http_replacement_libs
154
- libs = {"RightHttpConnection" => defined?(RightHttpConnection)}
155
- @loaded_net_http_replacement_libs = libs.map { |name, loaded| name if loaded }.compact
136
+ if body != nil && body.respond_to?(:read)
137
+ request.set_body_internal body.read
138
+ else
139
+ request.set_body_internal body
156
140
  end
157
141
 
158
- def self.puts_warning_for_net_http_replacement_libs_if_needed
159
- libs = {"RightHttpConnection" => defined?(RightHttpConnection)}
160
- warnings = libs.select { |_, loaded| loaded }.
161
- reject { |name, _| @loaded_net_http_replacement_libs.include?(name) }.
162
- map do |name, _|
163
- <<-TEXT.gsub(/ {10}/, '')
164
- \e[1mWarning: #{name} was loaded after WebMock\e[0m
165
- * WebMock's code is being ignored, because #{name} replaces parts of
166
- Net::HTTP without deferring to other libraries. This will break Net::HTTP requests.
167
- * To fix this, just reorder your requires so that #{name} is before WebMock.
168
- TEXT
169
- end
170
- $stderr.puts "\n" + warnings.join("\n") + "\n" if warnings.any?
142
+ WebMock::RequestSignature.new(method, uri, :body => request.body, :headers => headers)
143
+ end
144
+
145
+
146
+ def self.record_loaded_net_http_replacement_libs
147
+ libs = {"RightHttpConnection" => defined?(RightHttpConnection)}
148
+ @loaded_net_http_replacement_libs = libs.map { |name, loaded| name if loaded }.compact
149
+ end
150
+
151
+ def self.puts_warning_for_net_http_replacement_libs_if_needed
152
+ libs = {"RightHttpConnection" => defined?(RightHttpConnection)}
153
+ warnings = libs.select { |_, loaded| loaded }.
154
+ reject { |name, _| @loaded_net_http_replacement_libs.include?(name) }.
155
+ map do |name, _|
156
+ <<-TEXT.gsub(/ {10}/, '')
157
+ \e[1mWarning: #{name} was loaded after WebMock\e[0m
158
+ * WebMock's code is being ignored, because #{name} replaces parts of
159
+ Net::HTTP without deferring to other libraries. This will break Net::HTTP requests.
160
+ * To fix this, just reorder your requires so that #{name} is before WebMock.
161
+ TEXT
171
162
  end
163
+ $stderr.puts "\n" + warnings.join("\n") + "\n" if warnings.any?
172
164
  end
173
165
  end
166
+ end
174
167
 
175
- WebMock::NetHTTPUtility.record_loaded_net_http_replacement_libs
176
- WebMock::NetHTTPUtility.puts_warning_for_net_http_around_advice_libs_if_needed
168
+ WebMock::NetHTTPUtility.record_loaded_net_http_replacement_libs
@@ -12,11 +12,10 @@ if defined?(Patron)
12
12
  webmock_response = WebMock.response_for_request(request_signature)
13
13
  handle_file_name(req, webmock_response)
14
14
  build_patron_response(webmock_response)
15
- elsif WebMock.net_connect_allowed?(req.url)
15
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
16
16
  handle_request_without_webmock(req)
17
17
  else
18
- message = "Real HTTP connections are disabled. Unregistered request: #{request_signature}"
19
- WebMock.assertion_failure(message)
18
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
20
19
  end
21
20
  end
22
21
 
@@ -7,14 +7,29 @@ module WebMock
7
7
  def self.normalize_headers(headers)
8
8
  return nil unless headers
9
9
  array = headers.map { |name, value|
10
- [name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"), value.is_a?(Regexp) ? value : value.to_s]
10
+ [name.to_s.split(/_|-/).map { |segment| segment.capitalize }.join("-"),
11
+ case value
12
+ when Regexp then value
13
+ when Array then (value.size == 1) ? value.first : value.map {|v| v.to_s}.sort
14
+ else value.to_s
15
+ end
16
+ ]
11
17
  }
12
- Hash[*array.flatten]
18
+ Hash[*array.inject([]) {|r,x| r + x}]
13
19
  end
14
20
 
15
21
  def self.sorted_headers_string(headers)
16
22
  headers = WebMock::Util::Headers.normalize_headers(headers)
17
- '{' + headers.inspect[1..-2].split(', ').sort.join(', ').gsub("\"","'").gsub("\\","") + '}'
23
+ str = '{'
24
+ str << headers.map do |k,v|
25
+ v = case v
26
+ when Regexp then v.inspect
27
+ when Array then "["+v.map{|v| "'#{v.to_s}'"}.join(", ")+"]"
28
+ else "'#{v.to_s}'"
29
+ end
30
+ "'#{k}'=>#{v}"
31
+ end.sort.join(", ")
32
+ str << '}'
18
33
  end
19
34
 
20
35
  def self.decode_userinfo_from_header(header)
@@ -1,10 +1,16 @@
1
1
  module WebMock
2
2
  extend self
3
3
 
4
+ def self.version
5
+ open(File.join(File.dirname(__FILE__), '../../VERSION')) { |f|
6
+ f.read.strip
7
+ }
8
+ end
9
+
4
10
  def stub_request(method, uri)
5
11
  RequestRegistry.instance.register_request_stub(RequestStub.new(method, uri))
6
12
  end
7
-
13
+
8
14
  alias_method :stub_http_request, :stub_request
9
15
 
10
16
  def request(method, uri)
@@ -34,10 +40,11 @@ module WebMock
34
40
  end
35
41
 
36
42
  def net_connect_allowed?(uri = nil)
37
- if uri.class == String
38
- uri = URI::parse(uri)
43
+ if uri.is_a?(String)
44
+ uri = WebMock::Util::URI.normalize_uri(uri)
39
45
  end
40
- Config.instance.allow_net_connect || ( Config.instance.allow_localhost && uri.is_a?(URI) && uri.host == 'localhost' )
46
+ Config.instance.allow_net_connect ||
47
+ (Config.instance.allow_localhost && uri.is_a?(Addressable::URI) && uri.host == 'localhost')
41
48
  end
42
49
 
43
50
  def registered_request?(request_signature)
@@ -51,7 +58,7 @@ module WebMock
51
58
  def reset_webmock
52
59
  WebMock::RequestRegistry.instance.reset_webmock
53
60
  end
54
-
61
+
55
62
  def assertion_failure(message)
56
63
  raise message
57
64
  end
@@ -3,6 +3,8 @@ Content-Type: text/html; charset=UTF-8
3
3
  Connection: Keep-Alive
4
4
  Date: Sat, 23 Jan 2010 01:01:05 GMT
5
5
  Content-Length: 438
6
+ Accept: image/jpeg
7
+ Accept: image/png
6
8
 
7
9
  <HTML>
8
10
  <HEAD>
@@ -22,6 +22,14 @@ describe "Webmock with HTTPClient" do
22
22
  response_body.should == "abc"
23
23
  end
24
24
 
25
+ it "should match requests if headers are the same but in different order" do
26
+ stub_http_request(:get, "www.example.com").with(:headers => {"a" => ["b", "c"]} )
27
+ http_request(
28
+ :get, "http://www.example.com/",
29
+ :headers => {"a" => ["c", "b"]}).status.should == "200"
30
+ end
31
+
32
+
25
33
  describe "async requests" do
26
34
 
27
35
  before(:each) do
@@ -17,9 +17,17 @@ module HTTPClientSpecHelper
17
17
  else
18
18
  response = c.request(*params, &block)
19
19
  end
20
+ headers = response.header.all.inject({}) do |headers, header|
21
+ if !headers.has_key?(header[0])
22
+ headers[header[0]] = header[1]
23
+ else
24
+ headers[header[0]] = [headers[header[0]], header[1]].join(', ')
25
+ end
26
+ headers
27
+ end
20
28
  OpenStruct.new({
21
29
  :body => HTTPClientSpecHelper.async_mode ? response.content.read : response.content,
22
- :headers => Hash[*response.header.all.flatten],
30
+ :headers => headers,
23
31
  :status => response.code.to_s,
24
32
  :message => response.reason
25
33
  })
@@ -7,7 +7,16 @@ module NetHTTPSpecHelper
7
7
  end
8
8
  response = nil
9
9
  clazz = Net::HTTP.const_get("#{method.to_s.capitalize}")
10
- req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", options[:headers])
10
+ req = clazz.new("#{uri.path}#{uri.query ? '?' : ''}#{uri.query}", nil)
11
+ options[:headers].each do |k,v|
12
+ if v.is_a?(Array)
13
+ v.each_with_index do |v,i|
14
+ i == 0 ? (req[k] = v) : req.add_field(k, v)
15
+ end
16
+ else
17
+ req[k] = v
18
+ end
19
+ end if options[:headers]
11
20
 
12
21
  req.basic_auth uri.user, uri.password if uri.user
13
22
  http = Net::HTTP.new(uri.host, uri.port)
@@ -14,16 +14,6 @@ describe "loading other Net::HTTP based libraries" do
14
14
  `ruby #{load_path_opts} -e "#{requires}; #{additional_code}" 2>&1 | cat`
15
15
  end
16
16
 
17
- it "should requiring samuel before webmock prints warning" do
18
- output = capture_output_from_requiring %w(samuel webmock)
19
- output.should match(%r(Warning: WebMock was loaded after Samuel))
20
- end
21
-
22
- it "should requiring samuel after webmock does not print warning" do
23
- output = capture_output_from_requiring %w(webmock samuel)
24
- output.should be_empty
25
- end
26
-
27
17
  it "should requiring right http connection before webmock and then connecting does not print warning" do
28
18
  additional_code = "Net::HTTP.start('example.com')"
29
19
  output = capture_output_from_requiring %w(right_http_connection webmock), additional_code