webmock icon indicating copy to clipboard operation
webmock copied to clipboard

Query string with multiple values for the same field parsed incorrectly

Open alexandergitter opened this issue 9 years ago • 4 comments

I ran into an issue that might or might be related to #365, at least it seems to be caused by WebMock's way of parsing query parameters.

Consider the following URL: http://example.org/?key=value1&key=value2

Stubbing requests with URLs like this leads to WebMock discarding all values except one. Which means in this case it will gladly accept requests to http://example.org/?key=value2 which it really shouldn't.

Example code (based on the one in #365):

require "net/http"
require "cgi"
require "webmock"

WebMock.stub_request(:any, /.*/).
      with { |req| puts "AFTER : #{URI::decode(req.uri.query)}"; true }

uri = URI.parse("http://example.org/?key=value1&key=value2")
puts "BEFORE: #{uri.query}"
req = Net::HTTP::Get.new(uri.to_s)
Net::HTTP.new(uri.host, uri.port).request(req)

produces

BEFORE: key=value1&key=value2
AFTER : key=value2

alexandergitter avatar Jun 25 '15 08:06 alexandergitter

Default subscript notation when serializing query params doesn't support multiple keys with the same name. Please try flat_array

WebMock::Config.instance.query_values_notation = :flat_array

bblimke avatar Jun 25 '15 09:06 bblimke

Thanks, that works great for query parameters. However it seems that this options breaks how the expected body is being handled.

Example:

require "net/http"
require "cgi"
require "webmock"

WebMock::Config.instance.query_values_notation = :flat_array
WebMock.stub_request(:any, /.*/).
      with(:body => {'bodykey' => 'bodyval'}) { |req| puts "AFTER : #{URI::decode(req.uri.query)}"; true }

uri = URI.parse("http://example.org/?key=value1&key=value2")
puts "BEFORE: #{uri.query}"
req = Net::HTTP::Post.new(uri.to_s)
req.set_form_data('bodykey' => 'bodyval')
Net::HTTP.new(uri.host, uri.port).request(req)

produces

Real HTTP connections are disabled. Unregistered request: POST http://example.org/?key=value1&key=value2 with body 'bodykey=bodyval' with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Ruby'} (WebMock::NetConnectNotAllowedError)

You can stub this request with the following snippet:

stub_request(:post, "http://example.org/?key=value1&key=value2").
  with(:body => [["bodykey", "bodyval"]],
       :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Ruby'}).
  to_return(:status => 200, :body => "", :headers => {})

registered request stubs:

stub_request(:any, "/.*/").
  with(:body => {"bodykey"=>"bodyval"})

This example works fine if I set query_values_notation back to subscript.

It also works when I replace the hash in the expected body {'bodykey' => 'bodyval'} with a string 'bodykey=bodyval'

alexandergitter avatar Jun 25 '15 11:06 alexandergitter

IMO, for array parameters it should be passed as key[]=val1&key[]=val

isaiah avatar Oct 07 '15 09:10 isaiah

I'm having the same issue. I'm stubbing an API that expects query parameters as key=value1&key=value2.

In Faraday HTTP library this is implemented by FlatParamsEncoder. But there is no equivalent in WebMock.

semaperepelitsa avatar Aug 18 '22 20:08 semaperepelitsa