webmock 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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