webmock 1.8.8 → 1.8.9
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/.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
|