|
| 1 | +#-- |
| 2 | +# This source code is available under the MIT license. |
| 3 | +# See the file LICENSE.txt for details. |
| 4 | +#++ |
| 5 | + |
| 6 | +require File.expand_path('../../spec_helper', File.dirname(__FILE__)) |
| 7 | + |
| 8 | +require 'rack' |
| 9 | +require 'rack/handler/servlet' |
| 10 | + |
| 11 | +# Differential coverage: the "pure" ServletEnv maps servlet-parsed parameters |
| 12 | +# (HttpServletRequest#getParameterMap) into the Rack env, whereas DefaultEnv |
| 13 | +# lets Rack itself parse the QUERY_STRING. ServletEnv's whole reason to exist |
| 14 | +# is to produce the *same* params Rack would - so here we drive the same query |
| 15 | +# string through both and assert Rack::Request#GET comes out identical. |
| 16 | +# |
| 17 | +# The decoded values handed to addParameter are written as literals (simulating |
| 18 | +# what a servlet container decodes getParameterMap to), NOT computed via Rack's |
| 19 | +# unescaper - otherwise the escaping comparison would be circular. |
| 20 | +describe 'Rack::Handler::Servlet ServletEnv vs DefaultEnv (parsing parity)' do |
| 21 | + |
| 22 | + before do |
| 23 | + @servlet_context = mock_servlet_context |
| 24 | + end |
| 25 | + |
| 26 | + # Build Rack::Request#GET from a query string using the given env class. |
| 27 | + # +params+ are the (already url-decoded) name/value pairs the servlet |
| 28 | + # container would expose via getParameterMap; DefaultEnv ignores them. |
| 29 | + def get_params(env_class, query_string, params = []) |
| 30 | + request = org.springframework.mock.web.MockHttpServletRequest.new(@servlet_context) |
| 31 | + request.setMethod('GET') |
| 32 | + request.setRequestURI('/path') |
| 33 | + request.setQueryString(query_string) |
| 34 | + params.each { |name, value| request.addParameter(name, value) } |
| 35 | + response = org.springframework.mock.web.MockHttpServletResponse.new |
| 36 | + servlet_env = org.jruby.rack.servlet.ServletRackEnvironment.new(request, response, @rack_context) |
| 37 | + Rack::Request.new(env_class.create(servlet_env)).GET |
| 38 | + end |
| 39 | + |
| 40 | + DefaultEnv = Rack::Handler::Servlet::DefaultEnv |
| 41 | + ServletEnv = Rack::Handler::Servlet::ServletEnv |
| 42 | + |
| 43 | + # [ description, query_string, decoded getParameterMap pairs ] |
| 44 | + PARITY_CASES = [ |
| 45 | + [ 'plain params', 'a=1&b=2', [ %w(a 1), %w(b 2) ] ], |
| 46 | + [ 'space via +', 'a=x+y', [ [ 'a', 'x y' ] ] ], |
| 47 | + [ 'space via %20', 'a=x%20y', [ [ 'a', 'x y' ] ] ], |
| 48 | + [ 'encoded & = #', 'a=%26%3D%23', [ [ 'a', '&=#' ] ] ], |
| 49 | + [ 'encoded = in value', 'a=b%3Dc', [ [ 'a', 'b=c' ] ] ], |
| 50 | + [ 'utf-8 name and value', 'caf%C3%A9=%C3%BC', [ [ "café", "ü" ] ] ], |
| 51 | + [ 'repeated flat key', 'a=1&a=2', [ %w(a 1), %w(a 2) ] ], |
| 52 | + [ 'array [] key', 'a%5B%5D=1&a%5B%5D=2', [ [ 'a[]', '1' ], [ 'a[]', '2' ] ] ], |
| 53 | + [ 'hash [k] key', 'a%5Bb%5D=1', [ [ 'a[b]', '1' ] ] ], |
| 54 | + [ 'empty value', 'a=', [ [ 'a', '' ] ] ], |
| 55 | + ] |
| 56 | + |
| 57 | + PARITY_CASES.each do |desc, query_string, params| |
| 58 | + it "matches DefaultEnv (real Rack) for #{desc}" do |
| 59 | + default_get = get_params(DefaultEnv, query_string) |
| 60 | + servlet_get = get_params(ServletEnv, query_string, params) |
| 61 | + expect(servlet_get).to eq(default_get) |
| 62 | + end |
| 63 | + end |
| 64 | + |
| 65 | + # Known divergences of the hand-rolled ServletEnv#store_parameter from Rack's |
| 66 | + # own nested-query parsing. Documented here so the gap is explicit (and any |
| 67 | + # future change to store_parameter can turn these into parity cases). |
| 68 | + describe 'known divergences from Rack' do |
| 69 | + |
| 70 | + it 'hash-in-array (a[][b]) is not parsed the same as Rack' do |
| 71 | + query_string = 'a%5B%5D%5Bb%5D=1' |
| 72 | + default_get = get_params(DefaultEnv, query_string) # Rack: {"a"=>[{"b"=>"1"}]} |
| 73 | + servlet_get = get_params(ServletEnv, query_string, [ [ 'a[][b]', '1' ] ]) |
| 74 | + |
| 75 | + expect(default_get).to eq({ 'a' => [ { 'b' => '1' } ] }) |
| 76 | + expect(servlet_get).to_not eq(default_get) # documents the current gap |
| 77 | + end |
| 78 | + |
| 79 | + it 'deep nesting (a[b][c]) is only nested one level by store_parameter' do |
| 80 | + query_string = 'a%5Bb%5D%5Bc%5D=x' |
| 81 | + default_get = get_params(DefaultEnv, query_string) # Rack: {"a"=>{"b"=>{"c"=>"x"}}} |
| 82 | + servlet_get = get_params(ServletEnv, query_string, [ [ 'a[b][c]', 'x' ] ]) |
| 83 | + |
| 84 | + expect(default_get).to eq({ 'a' => { 'b' => { 'c' => 'x' } } }) |
| 85 | + expect(servlet_get).to eq({ 'a' => { 'b][c' => 'x' } }) # current (broken) behavior |
| 86 | + end |
| 87 | + |
| 88 | + end |
| 89 | + |
| 90 | +end |
0 commit comments