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 CHANGED
@@ -7,5 +7,10 @@ rvm:
7
7
  - jruby-18mode
8
8
  - rbx-18mode
9
9
  - rbx-19mode
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: rbx-18mode
13
+ - rvm: rbx-19mode
14
+ - rvm: jruby-19mode
10
15
 
11
16
  script: "bundle exec rake && rake em_http_request_0_x_spec"
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
@@ -17,5 +17,5 @@ group :test do
17
17
  end
18
18
 
19
19
  platforms :jruby do
20
- gem 'jruby-openssl', '~> 0.7.4.0'
20
+ gem 'jruby-openssl', '~> 0.7.7'
21
21
  end
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
@@ -8,6 +8,7 @@ require 'webmock/version'
8
8
 
9
9
  require 'webmock/errors'
10
10
 
11
+ require 'webmock/util/query_mapper'
11
12
  require 'webmock/util/uri'
12
13
  require 'webmock/util/headers'
13
14
  require 'webmock/util/hash_counter'
@@ -170,117 +170,83 @@ if defined?(Curl)
170
170
  ### Mocks of Curl::Easy methods below here.
171
171
  ###
172
172
 
173
- def http_with_webmock(method)
173
+ def http(method)
174
174
  @webmock_method = method
175
- http_without_webmock(method)
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}_with_webmock" do
179
+ define_method "http_#{verb}" do
182
180
  @webmock_method = verb
183
- send( "http_#{verb}_without_webmock" )
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 http_put_with_webmock data = nil
185
+ def http_put data = nil
191
186
  @webmock_method = :put
192
187
  @put_data = data if data
193
- http_put_without_webmock(data)
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 http_post_with_webmock *data
191
+ def http_post *data
199
192
  @webmock_method = :post
200
193
  @post_body = data.join('&') if data && !data.empty?
201
- http_post_without_webmock(*data)
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 perform_with_webmock
197
+ def perform
208
198
  @webmock_method ||= :get
209
- curb_or_webmock do
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 put_data_with_webmock= data
202
+ def put_data= data
217
203
  @webmock_method = :put
218
204
  @put_data = data
219
- self.put_data_without_webmock = data
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 post_body_with_webmock= data
208
+ def post_body= data
225
209
  @webmock_method = :post
226
- self.post_body_without_webmock = data
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 delete_with_webmock= value
213
+ def delete= value
232
214
  @webmock_method = :delete if value
233
- self.delete_without_webmock = value
215
+ super
234
216
  end
235
- alias_method :delete_without_webmock=, :delete=
236
- alias_method :delete=, :delete_with_webmock=
237
217
 
238
- def head_with_webmock= value
218
+ def head= value
239
219
  @webmock_method = :head if value
240
- self.head_without_webmock = value
220
+ super
241
221
  end
242
- alias_method :head_without_webmock=, :head=
243
- alias_method :head=, :head_with_webmock=
244
222
 
245
- def body_str_with_webmock
246
- @body_str || body_str_without_webmock
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 response_code_with_webmock
252
- @response_code || response_code_without_webmock
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 header_str_with_webmock
258
- @header_str || header_str_without_webmock
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 last_effective_url_with_webmock
264
- @last_effective_url || last_effective_url_without_webmock
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 content_type_with_webmock
270
- @content_type || content_type_without_webmock
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}_with_webmock &block
245
+ def on_#{callback} &block
278
246
  @on_#{callback} = block
279
- on_#{callback}_without_webmock &block
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 send_request_with_webmock(&block)
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 = send_request_without_webmock(&block)
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 webmock_activate_connection(client)
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
- real_activate_connection(client)
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 send_request_with_webmock(head, body)
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
- send_request_without_webmock(head, body)
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 = Addressable::URI.new(params).to_s
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 do_get_block_with_webmock(req, proxy, conn, &block)
33
- do_get_with_webmock(req, proxy, conn, false, &block)
34
+ def do_get_block(req, proxy, conn, &block)
35
+ do_get(req, proxy, conn, false, &block)
34
36
  end
35
37
 
36
- def do_get_stream_with_webmock(req, proxy, conn, &block)
37
- do_get_with_webmock(req, proxy, conn, true, &block)
38
+ def do_get_stream(req, proxy, conn, &block)
39
+ do_get(req, proxy, conn, true, &block)
38
40
  end
39
41
 
40
- def do_get_with_webmock(req, proxy, conn, stream = false, &block)
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 do_request_async_with_webmock(method, uri, query, body, extheader)
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
- do_request_async_without_webmock(method, uri, query, body, extheader)
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.query_values = req.header.request_query if req.header.request_query
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 socket_type_with_webmock
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 request_with_webmock(request, body = nil, &block)
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 = request_without_webmock(request, nil)
89
+ response = super(request, nil, &nil)
92
90
  after_request.call(response)
93
91
  }
94
92
  else
95
- response = request_without_webmock(request, nil)
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
- def start_with_conditional_connect(&block)
115
+ alias_method :start_with_connect, :start
116
+
117
+ def start(&block)
120
118
  if WebMock::Config.instance.net_http_connect_on_start
121
- start_with_connect(&block)
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 initialize_with_webmock(io, debug_output = nil)
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 handle_request_with_webmock(req)
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 = handle_request_without_webmock(req)
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
- Addressable::URI.parse('?' + query_params).query_values
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.query_values)
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 && (@query_params.nil? || @query_params == uri.query_values)
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
- @pattern.query_values = (@pattern.query_values || {}).merge(@query_params)
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
- Addressable::URI.parse('?' + body).query_values
204
+ WebMock::Util::QueryMapper.query_to_values(body)
203
205
  end
204
206
  end
205
207
 
@@ -81,7 +81,7 @@ module WebMock
81
81
 
82
82
  if signature.body.to_s != ''
83
83
  body = if signature.url_encoded?
84
- Addressable::URI.parse('?' + signature.body).query_values
84
+ WebMock::Util::QueryMapper.query_to_values(signature.body)
85
85
  else
86
86
  signature.body
87
87
  end
@@ -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
@@ -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
- normalized_uri.query_values = sort_query_values(normalized_uri.query_values) if normalized_uri.query_values
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, Addressable::URI::CharacterClasses::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)
@@ -1,3 +1,3 @@
1
1
  module WebMock
2
- VERSION = '1.8.8' unless defined?(::WebMock::VERSION)
2
+ VERSION = '1.8.9' unless defined?(::WebMock::VERSION)
3
3
  end
@@ -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.query_values, options[:body], options[:headers] || {}]
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
@@ -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.query_values.should == {"a"=>["b", "c"]}
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.query_values.should == {"a"=>{"d"=>"b", "e"=>"c", "b"=>{"c"=>"1"}}}
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.query_values.should == {"one"=>{"two"=>{"three" => ["four", "five"]}}}
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
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = 'webmock'
16
16
 
17
- s.add_dependency 'addressable', '~> 2.2.8'
17
+ s.add_dependency 'addressable', '>= 2.2.7'
18
18
  s.add_dependency 'crack', '>=0.1.7'
19
19
 
20
20
  s.add_development_dependency 'rspec', '~> 2.10'
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: 39
4
+ hash: 37
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 8
9
- - 8
10
- version: 1.8.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-07-23 00:00:00 Z
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: 23
27
+ hash: 9
28
28
  segments:
29
29
  - 2
30
30
  - 2
31
- - 8
32
- version: 2.2.8
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