webmock 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +17 -0
- data/README.md +13 -0
- data/VERSION +1 -1
- data/lib/webmock/errors.rb +5 -1
- data/lib/webmock/http_lib_adapters/httpclient.rb +10 -8
- data/lib/webmock/http_lib_adapters/net_http.rb +56 -64
- data/lib/webmock/http_lib_adapters/patron.rb +2 -3
- data/lib/webmock/util/headers.rb +18 -3
- data/lib/webmock/webmock.rb +12 -5
- data/spec/example_curl_output.txt +2 -0
- data/spec/httpclient_spec.rb +8 -0
- data/spec/httpclient_spec_helper.rb +9 -1
- data/spec/net_http_spec_helper.rb +10 -1
- data/spec/other_net_http_libs_spec.rb +0 -10
- data/spec/patron_spec_helper.rb +8 -2
- data/spec/response_spec.rb +4 -2
- data/spec/util/headers_spec.rb +17 -0
- data/spec/webmock_spec.rb +103 -15
- data/test/test_webmock.rb +1 -1
- data/webmock.gemspec +2 -23
- metadata +2 -23
- data/spec/vendor/samuel-0.2.1/.document +0 -5
- data/spec/vendor/samuel-0.2.1/.gitignore +0 -5
- data/spec/vendor/samuel-0.2.1/LICENSE +0 -20
- data/spec/vendor/samuel-0.2.1/README.rdoc +0 -70
- data/spec/vendor/samuel-0.2.1/Rakefile +0 -62
- data/spec/vendor/samuel-0.2.1/VERSION +0 -1
- data/spec/vendor/samuel-0.2.1/lib/samuel.rb +0 -52
- data/spec/vendor/samuel-0.2.1/lib/samuel/net_http.rb +0 -10
- data/spec/vendor/samuel-0.2.1/lib/samuel/request.rb +0 -96
- data/spec/vendor/samuel-0.2.1/samuel.gemspec +0 -69
- data/spec/vendor/samuel-0.2.1/test/request_test.rb +0 -193
- data/spec/vendor/samuel-0.2.1/test/samuel_test.rb +0 -42
- data/spec/vendor/samuel-0.2.1/test/test_helper.rb +0 -66
- 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.
|
1
|
+
1.1.0
|
data/lib/webmock/errors.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
module WebMock
|
2
2
|
|
3
|
-
class NetConnectNotAllowedError < StandardError
|
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?(
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
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?(
|
15
|
+
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
16
16
|
handle_request_without_webmock(req)
|
17
17
|
else
|
18
|
-
|
19
|
-
WebMock.assertion_failure(message)
|
18
|
+
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
20
19
|
end
|
21
20
|
end
|
22
21
|
|
data/lib/webmock/util/headers.rb
CHANGED
@@ -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("-"),
|
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.
|
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
|
-
|
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)
|
data/lib/webmock/webmock.rb
CHANGED
@@ -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.
|
38
|
-
uri = URI
|
43
|
+
if uri.is_a?(String)
|
44
|
+
uri = WebMock::Util::URI.normalize_uri(uri)
|
39
45
|
end
|
40
|
-
Config.instance.allow_net_connect ||
|
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
|
data/spec/httpclient_spec.rb
CHANGED
@@ -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 =>
|
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}",
|
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
|