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.
- 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
|