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