webmock 1.8.8 → 1.8.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +5 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -1
- data/README.md +1 -0
- data/lib/webmock.rb +1 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +30 -64
- data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb +2 -6
- data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +4 -9
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +1 -1
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +13 -17
- data/lib/webmock/http_lib_adapters/net_http.rb +9 -15
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +2 -5
- data/lib/webmock/request_pattern.rb +7 -5
- data/lib/webmock/request_stub.rb +1 -1
- data/lib/webmock/util/query_mapper.rb +188 -0
- data/lib/webmock/util/uri.rb +9 -10
- data/lib/webmock/version.rb +1 -1
- data/spec/acceptance/httpclient/httpclient_spec.rb +27 -0
- data/spec/acceptance/httpclient/httpclient_spec_helper.rb +2 -2
- data/spec/unit/util/uri_spec.rb +3 -3
- data/webmock.gemspec +1 -1
- metadata +9 -8
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.8.9
|
4
|
+
|
5
|
+
* Fixed problem with caching nil responses when the same HTTPClient instance is used.
|
6
|
+
|
7
|
+
Thanks to [Myron Marston](https://github.com/myronmarston)
|
8
|
+
|
9
|
+
* Added support for Addressable >= 2.3.0. Addressable 2.3.0 removed support for multiple query value notations and broke backwards compatibility.
|
10
|
+
|
11
|
+
https://github.com/sporkmonger/addressable/commit/f51e290b5f68a98293327a7da84eb9e2d5f21c62
|
12
|
+
https://github.com/sporkmonger/addressable/issues/77
|
13
|
+
|
14
|
+
|
3
15
|
## 1.8.8
|
4
16
|
|
5
17
|
* Fixed Net::HTTP adapter so that it returns `nil` for an empty body response.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -712,6 +712,7 @@ People who submitted patches and new features or suggested improvements. Many th
|
|
712
712
|
* Julien Boyer
|
713
713
|
* Kevin Glowacz
|
714
714
|
* Hans Hasselberg
|
715
|
+
* Andrew France
|
715
716
|
|
716
717
|
For a full list of contributors you can visit the
|
717
718
|
[contributors](https://github.com/bblimke/webmock/contributors) page.
|
data/lib/webmock.rb
CHANGED
@@ -170,117 +170,83 @@ if defined?(Curl)
|
|
170
170
|
### Mocks of Curl::Easy methods below here.
|
171
171
|
###
|
172
172
|
|
173
|
-
def
|
173
|
+
def http(method)
|
174
174
|
@webmock_method = method
|
175
|
-
|
175
|
+
super
|
176
176
|
end
|
177
|
-
alias_method :http_without_webmock, :http
|
178
|
-
alias_method :http, :http_with_webmock
|
179
177
|
|
180
178
|
%w[ get head delete ].each do |verb|
|
181
|
-
define_method "http_#{verb}
|
179
|
+
define_method "http_#{verb}" do
|
182
180
|
@webmock_method = verb
|
183
|
-
|
181
|
+
super()
|
184
182
|
end
|
185
|
-
|
186
|
-
alias_method "http_#{verb}_without_webmock", "http_#{verb}"
|
187
|
-
alias_method "http_#{verb}", "http_#{verb}_with_webmock"
|
188
183
|
end
|
189
184
|
|
190
|
-
def
|
185
|
+
def http_put data = nil
|
191
186
|
@webmock_method = :put
|
192
187
|
@put_data = data if data
|
193
|
-
|
188
|
+
super
|
194
189
|
end
|
195
|
-
alias_method :http_put_without_webmock, :http_put
|
196
|
-
alias_method :http_put, :http_put_with_webmock
|
197
190
|
|
198
|
-
def
|
191
|
+
def http_post *data
|
199
192
|
@webmock_method = :post
|
200
193
|
@post_body = data.join('&') if data && !data.empty?
|
201
|
-
|
194
|
+
super
|
202
195
|
end
|
203
|
-
alias_method :http_post_without_webmock, :http_post
|
204
|
-
alias_method :http_post, :http_post_with_webmock
|
205
|
-
|
206
196
|
|
207
|
-
def
|
197
|
+
def perform
|
208
198
|
@webmock_method ||= :get
|
209
|
-
curb_or_webmock
|
210
|
-
perform_without_webmock
|
211
|
-
end
|
199
|
+
curb_or_webmock { super }
|
212
200
|
end
|
213
|
-
alias :perform_without_webmock :perform
|
214
|
-
alias :perform :perform_with_webmock
|
215
201
|
|
216
|
-
def
|
202
|
+
def put_data= data
|
217
203
|
@webmock_method = :put
|
218
204
|
@put_data = data
|
219
|
-
|
205
|
+
super
|
220
206
|
end
|
221
|
-
alias_method :put_data_without_webmock=, :put_data=
|
222
|
-
alias_method :put_data=, :put_data_with_webmock=
|
223
207
|
|
224
|
-
def
|
208
|
+
def post_body= data
|
225
209
|
@webmock_method = :post
|
226
|
-
|
210
|
+
super
|
227
211
|
end
|
228
|
-
alias_method :post_body_without_webmock=, :post_body=
|
229
|
-
alias_method :post_body=, :post_body_with_webmock=
|
230
212
|
|
231
|
-
def
|
213
|
+
def delete= value
|
232
214
|
@webmock_method = :delete if value
|
233
|
-
|
215
|
+
super
|
234
216
|
end
|
235
|
-
alias_method :delete_without_webmock=, :delete=
|
236
|
-
alias_method :delete=, :delete_with_webmock=
|
237
217
|
|
238
|
-
def
|
218
|
+
def head= value
|
239
219
|
@webmock_method = :head if value
|
240
|
-
|
220
|
+
super
|
241
221
|
end
|
242
|
-
alias_method :head_without_webmock=, :head=
|
243
|
-
alias_method :head=, :head_with_webmock=
|
244
222
|
|
245
|
-
def
|
246
|
-
@body_str ||
|
223
|
+
def body_str
|
224
|
+
@body_str || super
|
247
225
|
end
|
248
|
-
alias :body_str_without_webmock :body_str
|
249
|
-
alias :body_str :body_str_with_webmock
|
250
226
|
|
251
|
-
def
|
252
|
-
@response_code ||
|
227
|
+
def response_code
|
228
|
+
@response_code || super
|
253
229
|
end
|
254
|
-
alias :response_code_without_webmock :response_code
|
255
|
-
alias :response_code :response_code_with_webmock
|
256
230
|
|
257
|
-
def
|
258
|
-
@header_str ||
|
231
|
+
def header_str
|
232
|
+
@header_str || super
|
259
233
|
end
|
260
|
-
alias :header_str_without_webmock :header_str
|
261
|
-
alias :header_str :header_str_with_webmock
|
262
234
|
|
263
|
-
def
|
264
|
-
@last_effective_url ||
|
235
|
+
def last_effective_url
|
236
|
+
@last_effective_url || super
|
265
237
|
end
|
266
|
-
alias :last_effective_url_without_webmock :last_effective_url
|
267
|
-
alias :last_effective_url :last_effective_url_with_webmock
|
268
238
|
|
269
|
-
def
|
270
|
-
@content_type ||
|
239
|
+
def content_type
|
240
|
+
@content_type || super
|
271
241
|
end
|
272
|
-
alias :content_type_without_webmock :content_type
|
273
|
-
alias :content_type :content_type_with_webmock
|
274
242
|
|
275
243
|
%w[ success failure header body complete progress ].each do |callback|
|
276
244
|
class_eval <<-METHOD, __FILE__, __LINE__
|
277
|
-
def on_#{callback}
|
245
|
+
def on_#{callback} &block
|
278
246
|
@on_#{callback} = block
|
279
|
-
|
247
|
+
super
|
280
248
|
end
|
281
249
|
METHOD
|
282
|
-
alias_method "on_#{callback}_without_webmock", "on_#{callback}"
|
283
|
-
alias_method "on_#{callback}", "on_#{callback}_with_webmock"
|
284
250
|
end
|
285
251
|
end
|
286
252
|
end
|
@@ -47,7 +47,7 @@ if defined?(EventMachine::HttpRequest)
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
50
|
+
def send_request(&block)
|
51
51
|
request_signature = build_request_signature
|
52
52
|
|
53
53
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
@@ -61,7 +61,7 @@ if defined?(EventMachine::HttpRequest)
|
|
61
61
|
webmock_response.should_timeout ? "WebMock timeout error" : nil)
|
62
62
|
client
|
63
63
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
64
|
-
http =
|
64
|
+
http = super
|
65
65
|
http.callback {
|
66
66
|
if WebMock::CallbackRegistry.any_callbacks?
|
67
67
|
webmock_response = build_webmock_response(http)
|
@@ -76,10 +76,6 @@ if defined?(EventMachine::HttpRequest)
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
-
alias_method :send_request_without_webmock, :send_request
|
80
|
-
alias_method :send_request, :send_request_with_webmock
|
81
|
-
|
82
|
-
|
83
79
|
private
|
84
80
|
|
85
81
|
def build_webmock_response(http)
|
@@ -48,7 +48,7 @@ if defined?(EventMachine::HttpClient)
|
|
48
48
|
end
|
49
49
|
|
50
50
|
class WebMockHttpConnection < HttpConnection
|
51
|
-
def
|
51
|
+
def activate_connection(client)
|
52
52
|
request_signature = client.request_signature
|
53
53
|
|
54
54
|
if client.stubbed_webmock_response
|
@@ -65,13 +65,11 @@ if defined?(EventMachine::HttpClient)
|
|
65
65
|
finalize_request(client)
|
66
66
|
@conn.set_deferred_status :succeeded
|
67
67
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
68
|
-
|
68
|
+
super
|
69
69
|
else
|
70
70
|
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
71
71
|
end
|
72
72
|
end
|
73
|
-
alias_method :real_activate_connection, :activate_connection
|
74
|
-
alias_method :activate_connection, :webmock_activate_connection
|
75
73
|
end
|
76
74
|
|
77
75
|
class WebMockHttpClient < EventMachine::HttpClient
|
@@ -92,7 +90,7 @@ if defined?(EventMachine::HttpClient)
|
|
92
90
|
end
|
93
91
|
end
|
94
92
|
|
95
|
-
def
|
93
|
+
def send_request(head, body)
|
96
94
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
97
95
|
|
98
96
|
if stubbed_webmock_response
|
@@ -104,15 +102,12 @@ if defined?(EventMachine::HttpClient)
|
|
104
102
|
}
|
105
103
|
self
|
106
104
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
107
|
-
|
105
|
+
super
|
108
106
|
else
|
109
107
|
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
110
108
|
end
|
111
109
|
end
|
112
110
|
|
113
|
-
alias_method :send_request_without_webmock, :send_request
|
114
|
-
alias_method :send_request, :send_request_with_webmock
|
115
|
-
|
116
111
|
def set_deferred_status(status, *args)
|
117
112
|
if status == :succeeded && !stubbed_webmock_response && WebMock::CallbackRegistry.any_callbacks?
|
118
113
|
webmock_response = build_webmock_response
|
@@ -42,7 +42,7 @@ if defined?(Excon)
|
|
42
42
|
params = params.dup
|
43
43
|
method = (params.delete(:method) || :get).to_s.downcase.to_sym
|
44
44
|
params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash)
|
45
|
-
uri
|
45
|
+
uri = Addressable::URI.new(params).to_s
|
46
46
|
WebMock::RequestSignature.new method, uri, :body => params[:body], :headers => params[:headers]
|
47
47
|
end
|
48
48
|
|
@@ -28,16 +28,18 @@ if defined?(::HTTPClient)
|
|
28
28
|
|
29
29
|
|
30
30
|
class WebMockHTTPClient < HTTPClient
|
31
|
+
alias_method :do_get_block_without_webmock, :do_get_block
|
32
|
+
alias_method :do_get_stream_without_webmock, :do_get_stream
|
31
33
|
|
32
|
-
def
|
33
|
-
|
34
|
+
def do_get_block(req, proxy, conn, &block)
|
35
|
+
do_get(req, proxy, conn, false, &block)
|
34
36
|
end
|
35
37
|
|
36
|
-
def
|
37
|
-
|
38
|
+
def do_get_stream(req, proxy, conn, &block)
|
39
|
+
do_get(req, proxy, conn, true, &block)
|
38
40
|
end
|
39
41
|
|
40
|
-
def
|
42
|
+
def do_get(req, proxy, conn, stream = false, &block)
|
41
43
|
request_signature = build_request_signature(req, :reuse_existing)
|
42
44
|
|
43
45
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
@@ -53,6 +55,9 @@ if defined?(::HTTPClient)
|
|
53
55
|
{:lib => :httpclient}, request_signature, webmock_response)
|
54
56
|
res
|
55
57
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
58
|
+
# in case there is a nil entry in the hash...
|
59
|
+
webmock_responses.delete(request_signature)
|
60
|
+
|
56
61
|
res = if stream
|
57
62
|
do_get_stream_without_webmock(req, proxy, conn, &block)
|
58
63
|
else
|
@@ -72,27 +77,18 @@ if defined?(::HTTPClient)
|
|
72
77
|
end
|
73
78
|
end
|
74
79
|
|
75
|
-
def
|
80
|
+
def do_request_async(method, uri, query, body, extheader)
|
76
81
|
req = create_request(method, uri, query, body, extheader)
|
77
82
|
request_signature = build_request_signature(req)
|
78
83
|
webmock_request_signatures << request_signature
|
79
84
|
|
80
85
|
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
|
81
|
-
|
86
|
+
super
|
82
87
|
else
|
83
88
|
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
84
89
|
end
|
85
90
|
end
|
86
91
|
|
87
|
-
alias_method :do_get_block_without_webmock, :do_get_block
|
88
|
-
alias_method :do_get_block, :do_get_block_with_webmock
|
89
|
-
|
90
|
-
alias_method :do_get_stream_without_webmock, :do_get_stream
|
91
|
-
alias_method :do_get_stream, :do_get_stream_with_webmock
|
92
|
-
|
93
|
-
alias_method :do_request_async_without_webmock, :do_request_async
|
94
|
-
alias_method :do_request_async, :do_request_async_with_webmock
|
95
|
-
|
96
92
|
def build_httpclient_response(webmock_response, stream = false, &block)
|
97
93
|
body = stream ? StringIO.new(webmock_response.body) : webmock_response.body
|
98
94
|
response = HTTP::Message.new_response(body)
|
@@ -136,7 +132,7 @@ if defined?(::HTTPClient)
|
|
136
132
|
|
137
133
|
def build_request_signature(req, reuse_existing = false)
|
138
134
|
uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
|
139
|
-
uri.
|
135
|
+
uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query) if req.header.request_query
|
140
136
|
uri.port = req.header.request_uri.port
|
141
137
|
uri = uri.omit(:userinfo)
|
142
138
|
|
@@ -32,11 +32,9 @@ module WebMock
|
|
32
32
|
|
33
33
|
@webMockNetHTTP = Class.new(Net::HTTP) do
|
34
34
|
class << self
|
35
|
-
def
|
35
|
+
def socket_type
|
36
36
|
StubSocket
|
37
37
|
end
|
38
|
-
alias_method :socket_type_without_webmock, :socket_type
|
39
|
-
alias_method :socket_type, :socket_type_with_webmock
|
40
38
|
|
41
39
|
if Module.method(:const_defined?).arity == 1
|
42
40
|
def const_defined?(name)
|
@@ -63,7 +61,7 @@ module WebMock
|
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
66
|
-
def
|
64
|
+
def request(request, body = nil, &block)
|
67
65
|
request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)
|
68
66
|
|
69
67
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
@@ -88,19 +86,17 @@ module WebMock
|
|
88
86
|
response = if (started? && !WebMock::Config.instance.net_http_connect_on_start) || !started?
|
89
87
|
@started = false #otherwise start_with_connect wouldn't execute and connect
|
90
88
|
start_with_connect {
|
91
|
-
response =
|
89
|
+
response = super(request, nil, &nil)
|
92
90
|
after_request.call(response)
|
93
91
|
}
|
94
92
|
else
|
95
|
-
response =
|
93
|
+
response = super(request, nil, &nil)
|
96
94
|
after_request.call(response)
|
97
95
|
end
|
98
96
|
else
|
99
97
|
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
100
98
|
end
|
101
99
|
end
|
102
|
-
alias_method :request_without_webmock, :request
|
103
|
-
alias_method :request, :request_with_webmock
|
104
100
|
|
105
101
|
def start_without_connect
|
106
102
|
raise IOError, 'HTTP session already opened' if @started
|
@@ -116,15 +112,15 @@ module WebMock
|
|
116
112
|
self
|
117
113
|
end
|
118
114
|
|
119
|
-
|
115
|
+
alias_method :start_with_connect, :start
|
116
|
+
|
117
|
+
def start(&block)
|
120
118
|
if WebMock::Config.instance.net_http_connect_on_start
|
121
|
-
|
119
|
+
super(&block)
|
122
120
|
else
|
123
121
|
start_without_connect(&block)
|
124
122
|
end
|
125
123
|
end
|
126
|
-
alias_method :start_with_connect, :start
|
127
|
-
alias_method :start, :start_with_conditional_connect
|
128
124
|
|
129
125
|
def build_net_http_response(webmock_response, &block)
|
130
126
|
response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
|
@@ -204,7 +200,7 @@ end
|
|
204
200
|
module Net #:nodoc: all
|
205
201
|
|
206
202
|
class WebMockNetBufferedIO < BufferedIO
|
207
|
-
def
|
203
|
+
def initialize(io, debug_output = nil)
|
208
204
|
@read_timeout = 60
|
209
205
|
@rbuf = ''
|
210
206
|
@debug_output = debug_output
|
@@ -217,8 +213,6 @@ module Net #:nodoc: all
|
|
217
213
|
end
|
218
214
|
raise "Unable to create local socket" unless @io
|
219
215
|
end
|
220
|
-
alias_method :initialize_without_webmock, :initialize
|
221
|
-
alias_method :initialize, :initialize_with_webmock
|
222
216
|
end
|
223
217
|
|
224
218
|
end
|
@@ -13,7 +13,7 @@ if defined?(::Patron)
|
|
13
13
|
OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession)
|
14
14
|
|
15
15
|
class WebMockPatronSession < ::Patron::Session
|
16
|
-
def
|
16
|
+
def handle_request(req)
|
17
17
|
request_signature =
|
18
18
|
WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req)
|
19
19
|
|
@@ -28,7 +28,7 @@ if defined?(::Patron)
|
|
28
28
|
{:lib => :patron}, request_signature, webmock_response)
|
29
29
|
res
|
30
30
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
31
|
-
res =
|
31
|
+
res = super
|
32
32
|
if WebMock::CallbackRegistry.any_callbacks?
|
33
33
|
webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
|
34
34
|
build_webmock_response(res)
|
@@ -41,9 +41,6 @@ if defined?(::Patron)
|
|
41
41
|
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
42
42
|
end
|
43
43
|
end
|
44
|
-
|
45
|
-
alias_method :handle_request_without_webmock, :handle_request
|
46
|
-
alias_method :handle_request, :handle_request_with_webmock
|
47
44
|
end
|
48
45
|
|
49
46
|
def self.enable!
|
@@ -95,7 +95,7 @@ module WebMock
|
|
95
95
|
elsif rSpecHashIncludingMatcher?(query_params)
|
96
96
|
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
|
97
97
|
else
|
98
|
-
|
98
|
+
WebMock::Util::QueryMapper.query_to_values(query_params)
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
@@ -109,7 +109,7 @@ module WebMock
|
|
109
109
|
class URIRegexpPattern < URIPattern
|
110
110
|
def matches?(uri)
|
111
111
|
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
|
112
|
-
(@query_params.nil? || @query_params == uri.
|
112
|
+
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query))
|
113
113
|
end
|
114
114
|
|
115
115
|
def to_s
|
@@ -123,7 +123,8 @@ module WebMock
|
|
123
123
|
def matches?(uri)
|
124
124
|
if @pattern.is_a?(Addressable::URI)
|
125
125
|
if @query_params
|
126
|
-
uri.omit(:query) === @pattern &&
|
126
|
+
uri.omit(:query) === @pattern &&
|
127
|
+
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query))
|
127
128
|
else
|
128
129
|
uri === @pattern
|
129
130
|
end
|
@@ -135,7 +136,8 @@ module WebMock
|
|
135
136
|
def add_query_params(query_params)
|
136
137
|
super
|
137
138
|
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
|
138
|
-
|
139
|
+
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query) || {}).merge(@query_params)
|
140
|
+
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash)
|
139
141
|
@query_params = nil
|
140
142
|
end
|
141
143
|
end
|
@@ -199,7 +201,7 @@ module WebMock
|
|
199
201
|
when :xml then
|
200
202
|
Crack::XML.parse(body)
|
201
203
|
else
|
202
|
-
|
204
|
+
WebMock::Util::QueryMapper.query_to_values(body)
|
203
205
|
end
|
204
206
|
end
|
205
207
|
|
data/lib/webmock/request_stub.rb
CHANGED
@@ -0,0 +1,188 @@
|
|
1
|
+
module WebMock::Util
|
2
|
+
class QueryMapper
|
3
|
+
#This class is based on Addressable::URI pre 2.3.0
|
4
|
+
|
5
|
+
##
|
6
|
+
# Converts the query component to a Hash value.
|
7
|
+
#
|
8
|
+
# @option [Symbol] notation
|
9
|
+
# May be one of <code>:flat</code>, <code>:dot</code>, or
|
10
|
+
# <code>:subscript</code>. The <code>:dot</code> notation is not
|
11
|
+
# supported for assignment. Default value is <code>:subscript</code>.
|
12
|
+
#
|
13
|
+
# @return [Hash, Array] The query string parsed as a Hash or Array object.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# WebMock::Util::QueryMapper.query_to_values("?one=1&two=2&three=3")
|
17
|
+
# #=> {"one" => "1", "two" => "2", "three" => "3"}
|
18
|
+
# WebMock::Util::QueryMapper("?one[two][three]=four").query_values
|
19
|
+
# #=> {"one" => {"two" => {"three" => "four"}}}
|
20
|
+
# WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
|
21
|
+
# :notation => :dot
|
22
|
+
# )
|
23
|
+
# #=> {"one" => {"two" => {"three" => "four"}}}
|
24
|
+
# WebMock::Util::QueryMapper.query_to_values("?one[two][three]=four",
|
25
|
+
# :notation => :flat
|
26
|
+
# )
|
27
|
+
# #=> {"one[two][three]" => "four"}
|
28
|
+
# WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
|
29
|
+
# :notation => :flat
|
30
|
+
# )
|
31
|
+
# #=> {"one.two.three" => "four"}
|
32
|
+
# WebMock::Util::QueryMapper(
|
33
|
+
# "?one[two][three][]=four&one[two][three][]=five"
|
34
|
+
# )
|
35
|
+
# #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
|
36
|
+
# WebMock::Util::QueryMapper.query_to_values(
|
37
|
+
# "?one=two&one=three").query_values(:notation => :flat_array)
|
38
|
+
# #=> [['one', 'two'], ['one', 'three']]
|
39
|
+
def self.query_to_values(query, options={})
|
40
|
+
defaults = {:notation => :subscript}
|
41
|
+
options = defaults.merge(options)
|
42
|
+
if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation])
|
43
|
+
raise ArgumentError,
|
44
|
+
"Invalid notation. Must be one of: " +
|
45
|
+
"[:flat, :dot, :subscript, :flat_array]."
|
46
|
+
end
|
47
|
+
dehash = lambda do |hash|
|
48
|
+
hash.each do |(key, value)|
|
49
|
+
if value.kind_of?(Hash)
|
50
|
+
hash[key] = dehash.call(value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
|
54
|
+
hash.sort.inject([]) do |accu, (_, value)|
|
55
|
+
accu << value; accu
|
56
|
+
end
|
57
|
+
else
|
58
|
+
hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
return nil if query == nil
|
62
|
+
empty_accumulator = :flat_array == options[:notation] ? [] : {}
|
63
|
+
return ((query.split("&").map do |pair|
|
64
|
+
pair.split("=", 2) if pair && !pair.empty?
|
65
|
+
end).compact.inject(empty_accumulator.dup) do |accumulator, (key, value)|
|
66
|
+
value = true if value.nil?
|
67
|
+
key = Addressable::URI.unencode_component(key)
|
68
|
+
if value != true
|
69
|
+
value = Addressable::URI.unencode_component(value.gsub(/\+/, " "))
|
70
|
+
end
|
71
|
+
if options[:notation] == :flat
|
72
|
+
if accumulator[key]
|
73
|
+
raise ArgumentError, "Key was repeated: #{key.inspect}"
|
74
|
+
end
|
75
|
+
accumulator[key] = value
|
76
|
+
elsif options[:notation] == :flat_array
|
77
|
+
accumulator << [key, value]
|
78
|
+
else
|
79
|
+
if options[:notation] == :dot
|
80
|
+
array_value = false
|
81
|
+
subkeys = key.split(".")
|
82
|
+
elsif options[:notation] == :subscript
|
83
|
+
array_value = !!(key =~ /\[\]$/)
|
84
|
+
subkeys = key.split(/[\[\]]+/)
|
85
|
+
end
|
86
|
+
current_hash = accumulator
|
87
|
+
for i in 0...(subkeys.size - 1)
|
88
|
+
subkey = subkeys[i]
|
89
|
+
current_hash[subkey] = {} unless current_hash[subkey]
|
90
|
+
current_hash = current_hash[subkey]
|
91
|
+
end
|
92
|
+
if array_value
|
93
|
+
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
|
94
|
+
current_hash[subkeys.last] << value
|
95
|
+
else
|
96
|
+
current_hash[subkeys.last] = value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
accumulator
|
100
|
+
end).inject(empty_accumulator.dup) do |accumulator, (key, value)|
|
101
|
+
if options[:notation] == :flat_array
|
102
|
+
accumulator << [key, value]
|
103
|
+
else
|
104
|
+
accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
|
105
|
+
end
|
106
|
+
accumulator
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Sets the query component for this URI from a Hash object.
|
112
|
+
# This method produces a query string using the :subscript notation.
|
113
|
+
# An empty Hash will result in a nil query.
|
114
|
+
#
|
115
|
+
# @param [Hash, #to_hash, Array] new_query_values The new query values.
|
116
|
+
def self.values_to_query(new_query_values)
|
117
|
+
if new_query_values == nil
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
if !new_query_values.is_a?(Array)
|
122
|
+
if !new_query_values.respond_to?(:to_hash)
|
123
|
+
raise TypeError,
|
124
|
+
"Can't convert #{new_query_values.class} into Hash."
|
125
|
+
end
|
126
|
+
new_query_values = new_query_values.to_hash
|
127
|
+
new_query_values = new_query_values.map do |key, value|
|
128
|
+
key = key.to_s if key.kind_of?(Symbol)
|
129
|
+
[key, value]
|
130
|
+
end
|
131
|
+
# Useful default for OAuth and caching.
|
132
|
+
# Only to be used for non-Array inputs. Arrays should preserve order.
|
133
|
+
new_query_values.sort!
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Joins and converts parent and value into a properly encoded and
|
138
|
+
# ordered URL query.
|
139
|
+
#
|
140
|
+
# @private
|
141
|
+
# @param [String] parent an URI encoded component.
|
142
|
+
# @param [Array, Hash, Symbol, #to_str] value
|
143
|
+
#
|
144
|
+
# @return [String] a properly escaped and ordered URL query.
|
145
|
+
to_query = lambda do |parent, value|
|
146
|
+
if value.is_a?(Hash)
|
147
|
+
value = value.map do |key, val|
|
148
|
+
[
|
149
|
+
Addressable::URI.encode_component(key, Addressable::URI::CharacterClasses::UNRESERVED),
|
150
|
+
val
|
151
|
+
]
|
152
|
+
end
|
153
|
+
value.sort!
|
154
|
+
buffer = ""
|
155
|
+
value.each do |key, val|
|
156
|
+
new_parent = "#{parent}[#{key}]"
|
157
|
+
buffer << "#{to_query.call(new_parent, val)}&"
|
158
|
+
end
|
159
|
+
return buffer.chop
|
160
|
+
elsif value.is_a?(Array)
|
161
|
+
buffer = ""
|
162
|
+
value.each_with_index do |val, i|
|
163
|
+
new_parent = "#{parent}[#{i}]"
|
164
|
+
buffer << "#{to_query.call(new_parent, val)}&"
|
165
|
+
end
|
166
|
+
return buffer.chop
|
167
|
+
elsif value == true
|
168
|
+
return parent
|
169
|
+
else
|
170
|
+
encoded_value = Addressable::URI.encode_component(
|
171
|
+
value, Addressable::URI::CharacterClasses::UNRESERVED
|
172
|
+
)
|
173
|
+
return "#{parent}=#{encoded_value}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
178
|
+
buffer = ""
|
179
|
+
new_query_values.each do |parent, value|
|
180
|
+
encoded_parent = Addressable::URI.encode_component(
|
181
|
+
parent, Addressable::URI::CharacterClasses::UNRESERVED
|
182
|
+
)
|
183
|
+
buffer << "#{to_query.call(encoded_parent, value)}&"
|
184
|
+
end
|
185
|
+
return buffer.chop
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
data/lib/webmock/util/uri.rb
CHANGED
@@ -1,23 +1,22 @@
|
|
1
|
-
module Addressable
|
2
|
-
class URI
|
3
|
-
module CharacterClasses
|
4
|
-
USERINFO = UNRESERVED + SUB_DELIMS + "\\:"
|
5
|
-
end
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
1
|
module WebMock
|
10
2
|
|
11
3
|
module Util
|
12
4
|
|
13
5
|
class URI
|
6
|
+
module CharacterClasses
|
7
|
+
USERINFO = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS + "\\:"
|
8
|
+
end
|
9
|
+
|
14
10
|
ADDRESSABLE_URIS = Hash.new do |hash, key|
|
15
11
|
hash[key] = Addressable::URI.heuristic_parse(key)
|
16
12
|
end
|
17
13
|
|
18
14
|
NORMALIZED_URIS = Hash.new do |hash, uri|
|
19
15
|
normalized_uri = WebMock::Util::URI.heuristic_parse(uri)
|
20
|
-
|
16
|
+
if normalized_uri.query_values
|
17
|
+
sorted_query_values = sort_query_values(WebMock::Util::QueryMapper.query_to_values(normalized_uri.query) || {})
|
18
|
+
normalized_uri.query = WebMock::Util::QueryMapper.values_to_query(sorted_query_values)
|
19
|
+
end
|
21
20
|
normalized_uri = normalized_uri.normalize #normalize! is slower
|
22
21
|
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
|
23
22
|
hash[uri] = normalized_uri
|
@@ -63,7 +62,7 @@ module WebMock
|
|
63
62
|
end
|
64
63
|
|
65
64
|
def self.encode_unsafe_chars_in_userinfo(userinfo)
|
66
|
-
Addressable::URI.encode_component(userinfo,
|
65
|
+
Addressable::URI.encode_component(userinfo, WebMock::Util::URI::CharacterClasses::USERINFO)
|
67
66
|
end
|
68
67
|
|
69
68
|
def self.is_uri_localhost?(uri)
|
data/lib/webmock/version.rb
CHANGED
@@ -73,4 +73,31 @@ describe "HTTPClient" do
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
+
context 'when a client instance is re-used for another identical request' do
|
77
|
+
let(:client) { HTTPClient.new }
|
78
|
+
let(:webmock_server_url) {"http://#{WebMockServer.instance.host_with_port}/"}
|
79
|
+
|
80
|
+
before { WebMock.allow_net_connect! }
|
81
|
+
|
82
|
+
it 'invokes the global_stub_request hook for each request' do
|
83
|
+
request_signatures = []
|
84
|
+
WebMock.globally_stub_request do |request_sig|
|
85
|
+
request_signatures << request_sig
|
86
|
+
nil # to let the request be made for real
|
87
|
+
end
|
88
|
+
|
89
|
+
# To make two requests that have the same request signature, the headers must match.
|
90
|
+
# Since the webmock server has a Set-Cookie header, the 2nd request will automatically
|
91
|
+
# include a Cookie header (due to how httpclient works), so we have to set the header
|
92
|
+
# manually on the first request but not on the 2nd request.
|
93
|
+
http_request(:get, webmock_server_url, :client => client,
|
94
|
+
:headers => { "Cookie" => "bar=; foo=" })
|
95
|
+
http_request(:get, webmock_server_url, :client => client)
|
96
|
+
|
97
|
+
request_signatures.should have(2).signatures
|
98
|
+
# Verify the request signatures were identical as needed by this example
|
99
|
+
request_signatures.first.should eq(request_signatures.last)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
76
103
|
end
|
@@ -5,11 +5,11 @@ module HTTPClientSpecHelper
|
|
5
5
|
|
6
6
|
def http_request(method, uri, options = {}, &block)
|
7
7
|
uri = Addressable::URI.heuristic_parse(uri)
|
8
|
-
c = HTTPClient.new
|
8
|
+
c = options.fetch(:client) { HTTPClient.new }
|
9
9
|
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
10
10
|
c.set_basic_auth(nil, uri.user, uri.password) if uri.user
|
11
11
|
params = [method, "#{uri.omit(:userinfo, :query).normalize.to_s}",
|
12
|
-
uri.
|
12
|
+
WebMock::Util::QueryMapper.query_to_values(uri.query), options[:body], options[:headers] || {}]
|
13
13
|
if HTTPClientSpecHelper.async_mode
|
14
14
|
connection = c.request_async(*params)
|
15
15
|
connection.join
|
data/spec/unit/util/uri_spec.rb
CHANGED
@@ -170,19 +170,19 @@ describe WebMock::Util::URI do
|
|
170
170
|
it "should successfully handle array parameters" do
|
171
171
|
uri_string = 'http://www.example.com:80/path?a[]=b&a[]=c'
|
172
172
|
uri = WebMock::Util::URI.normalize_uri(uri_string)
|
173
|
-
uri.
|
173
|
+
WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"a"=>["b", "c"]}
|
174
174
|
end
|
175
175
|
|
176
176
|
it "should successfully handle hash parameters" do
|
177
177
|
uri_string = 'http://www.example.com:80/path?a[d]=b&a[e]=c&a[b][c]=1'
|
178
178
|
uri = WebMock::Util::URI.normalize_uri(uri_string)
|
179
|
-
uri.
|
179
|
+
WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"a"=>{"d"=>"b", "e"=>"c", "b"=>{"c"=>"1"}}}
|
180
180
|
end
|
181
181
|
|
182
182
|
it "should successfully handle nested hash parameters" do
|
183
183
|
uri_string = 'http://www.example.com:80/path?one[two][three][]=four&one[two][three][]=five'
|
184
184
|
uri = WebMock::Util::URI.normalize_uri(uri_string)
|
185
|
-
uri.
|
185
|
+
WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"one"=>{"two"=>{"three" => ["four", "five"]}}}
|
186
186
|
end
|
187
187
|
end
|
188
188
|
|
data/webmock.gemspec
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webmock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 37
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 8
|
9
|
-
-
|
10
|
-
version: 1.8.
|
9
|
+
- 9
|
10
|
+
version: 1.8.9
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Bartosz Blimke
|
@@ -15,21 +15,21 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-08-15 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: addressable
|
22
22
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
23
23
|
none: false
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
hash:
|
27
|
+
hash: 9
|
28
28
|
segments:
|
29
29
|
- 2
|
30
30
|
- 2
|
31
|
-
-
|
32
|
-
version: 2.2.
|
31
|
+
- 7
|
32
|
+
version: 2.2.7
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
35
|
requirement: *id001
|
@@ -254,6 +254,7 @@ files:
|
|
254
254
|
- lib/webmock/util/hash_keys_stringifier.rb
|
255
255
|
- lib/webmock/util/headers.rb
|
256
256
|
- lib/webmock/util/json.rb
|
257
|
+
- lib/webmock/util/query_mapper.rb
|
257
258
|
- lib/webmock/util/uri.rb
|
258
259
|
- lib/webmock/util/version_checker.rb
|
259
260
|
- lib/webmock/version.rb
|