require_relative 'helper'

class FooError < RuntimeError; end

class RegexpLookAlike
  # RegexpLookAlike#to_s, RegexpLookAlike#names and MatchData#names must be defined.
  class MatchData
    def captures
      %w[this is a test]
    end

    def names
      %w[one two three four]
    end
  end

  def names
    %w[one two three four]
  end

  def to_s
    '/this/is/a/test/'
  end

  def match(string)
    ::RegexpLookAlike::MatchData.new if string == '/this/is/a/test/'
  end
end

describe 'Routing' do
  before do
    Padrino.clear!
    ENV['RACK_BASE_URI'] = nil
  end

  it 'should serve static files with simple cache control' do
    mock_app do
      set :static_cache_control, :public
      set :public_folder, __dir__
    end
    get "/#{File.basename(__FILE__)}"
    assert headers.key?('Cache-Control')
    assert_equal headers['Cache-Control'], 'public'
  end # static simple

  it 'should serve static files with cache control and max_age' do
    mock_app do
      set :static_cache_control, [:public, :must_revalidate, { max_age: 300 }]
      set :public_folder, __dir__
    end
    get "/#{File.basename(__FILE__)}"
    assert headers.key?('Cache-Control')
    assert_equal headers['Cache-Control'], 'public, must-revalidate, max-age=300'
  end # static max_age

  it 'should render static files with custom status via options' do
    mock_app do
      set :static, true
      set :public_folder, __dir__

      post '/*' do
        static!(status: params[:status])
      end
    end

    post "/#{File.basename(__FILE__)}?status=422"
    assert_equal response.status, 422
    assert_equal File.size(__FILE__).to_s, response['Content-Length']
    assert_includes response.headers, 'Last-Modified'
  end

  it 'should ignore trailing delimiters for basic route' do
    mock_app do
      get('/foo') { 'okey' }
      get(:test) { 'tester' }
    end
    get '/foo'
    assert_equal 'okey', body
    get '/foo/'
    assert_equal 'okey', body
    get '/test'
    assert_equal 'tester', body
    get '/test/'
    assert_equal 'tester', body
  end

  it 'should recognize route even if paths are duplicated' do
    mock_app do
      get(:index) {}
      get(:index, with: :id) {}
      get(:index, with: :id, provides: :json) {}
    end
    assert_equal '/', @app.url_for(:index)
    assert_equal '/1234', @app.url_for(:index, id: '1234')
    assert_equal '/1234.json?baz=baz', @app.url_for(:index, id: '1234', format: 'json', baz: 'baz')
  end

  it 'should recognize route even if paths are duplicated, in reverse order' do
    mock_app do
      get(:index, with: :id, provides: :json) {}
      get(:index, with: :id) {}
      get(:index) {}
    end
    assert_equal '/', @app.url_for(:index)
    assert_equal '/1234', @app.url_for(:index, id: '1234')
    assert_equal '/1234.json?baz=baz', @app.url_for(:index, id: '1234', format: 'json', baz: 'baz')
  end

  it 'should fail with unrecognized route exception when not found' do
    mock_app do
      get(:index) { 'okey' }
    end
    get @app.url_for(:index)
    assert_equal 'okey', body
    assert_raises(Padrino::Routing::UnrecognizedException) do
      get @app.url_for(:fake)
    end
  end

  it 'should fail with unrecognized route exception when namespace is invalid' do
    mock_app do
      controller :foo_bar do
        get(:index) { 'okey' }
        get(:test_baz) { 'okey' }
      end
    end
    assert_equal '/foo_bar', @app.url_for(:foo_bar, :index)
    assert_raises(Padrino::Routing::UnrecognizedException) do
      get @app.url_for(:foo, :bar, :index)
    end
    assert_raises(Padrino::Routing::UnrecognizedException) do
      get @app.url_for(:foo, :bar_index)
    end
    assert_equal '/foo_bar/test_baz', @app.url_for(:foo_bar, :test_baz)
    assert_raises(Padrino::Routing::UnrecognizedException) do
      get @app.url_for(:foo_bar, :test, :baz)
    end
    assert_raises(Padrino::Routing::UnrecognizedException) do
      get @app.url_for(:foo, :bar_test, :baz)
    end
    assert_raises(Padrino::Routing::UnrecognizedException) do
      get @app.url_for(:foo, :bar_test_baz)
    end
  end

  it 'should accept regexp routes' do
    mock_app do
      get(%r{/fob|/baz}) { 'regexp' }
      get('/foo')        { 'str' }
      get %r{/([0-9]+)/} do |num|
        "Your lucky number: #{num} #{params[:captures].first}"
      end
      get %r{/page/([0-9]+)|/} do |num|
        "My lucky number: #{num} #{params[:captures].first}"
      end
    end
    get '/foo'
    assert_equal 'str', body
    get '/fob'
    assert_equal 'regexp', body
    get '/baz'
    assert_equal 'regexp', body
    get '/321/'
    assert_equal 'Your lucky number: 321 321', body
    get '/page/99'
    assert_equal 'My lucky number: 99 99', body
  end

  it 'should ignore trailing slashes' do
    mock_app do
      get(%r{/trailing}) { 'slash' }
    end
    get '/trailing'
    assert_equal 'slash', body
    get '/trailing/'
    assert_equal 'slash', body
  end

  it 'should accept regexp routes with generate with :generate_with' do
    mock_app do
      get(%r{/fob|/baz}, name: :foo, generate_with: '/fob') { 'regexp' }
    end
    assert_equal '/fob', @app.url(:foo)
  end

  it 'should parse routes with question marks' do
    mock_app do
      get('/foo/?') { 'okey' }
      post('/unauthenticated/?') { 'no access' }
    end
    get '/foo'
    assert_equal 'okey', body
    get '/foo/'
    assert_equal 'okey', body
    post '/unauthenticated'
    assert_equal 'no access', body
    post '/unauthenticated/'
    assert_equal 'no access', body
  end

  it 'should parse routes that are encoded' do
    mock_app do
      get('/щч') { 'success!' }
    end
    get("/#{CGI.escape('щч')}")
    assert_equal 'success!', body
  end

  it 'should parse routes that include encoded slash' do
    mock_app do
      get('/:drive_alias/:path', path: /.*/) do
        "Show #{params[:drive_alias]} and #{params[:path]}"
      end
    end
    get('/drive%2Ffoo/some/path')
    assert_equal 'Show drive/foo and some/path', body
  end

  it 'should encode params using UTF-8' do
    mock_app do
      get('/:foo') { params[:foo].encoding.name }
    end
    get '/bar'
    assert_equal 'UTF-8', body
  end

  it 'should match correctly similar paths' do
    mock_app do
      get('/my/:foo_id') { params[:foo_id] }
      get('/my/:bar_id/bar') { params[:bar_id] }
    end
    get '/my/1'
    assert_equal '1', body
    get '/my/2/bar'
    assert_equal '2', body
  end

  it 'should match user agents' do
    mock_app do
      get('/main', agent: /IE/) { 'hello IE' }
      get('/main') { 'hello' }
    end
    get '/main'
    assert_equal 'hello', body
    get '/main', {}, { 'HTTP_USER_AGENT' => 'This is IE' }
    assert_equal 'hello IE', body
  end

  it 'should use regex for parts of a route' do
    mock_app do
      get('/main/:id', id: /\d+/) { "hello #{params[:id]}" }
    end
    get '/main/123'
    assert_equal 'hello 123', body
    get '/main/asd'
    assert_equal 404, status
  end

  it 'should parse params when use regex for parts of a route' do
    mock_app do
      post :index, with: %i[foo bar], bar: /.+/ do
        "show #{params[:foo]}"
      end

      get :index, map: '/mystuff/:a_id/boing/:boing_id' do
        "show #{params[:a_id]} and #{params[:boing_id]}"
      end
    end
    get '/mystuff/5/boing/2'
    assert_equal 'show 5 and 2', body
  end

  it 'should not generate overlapping head urls' do
    app = mock_app do
      get('/main') { 'hello' }
      post('/main') { 'hello' }
    end
    assert_equal 3, app.routes.size, 'should generate GET, HEAD and PUT'
    assert_equal 'GET',  app.routes[0].request_methods.first
    assert_equal 'HEAD', app.routes[1].request_methods.first
    assert_equal 'POST', app.routes[2].request_methods.first
  end

  it 'should generate basic urls' do
    mock_app do
      get(:foo) { '/foo' }
      get(:foo, with: :id) { |id| "/foo/#{id}" }
      get(%i[foo id]) { |id| "/foo/#{id}" }
      get(:hash, with: :id) { url(:hash, id: 1) }
      get(:anchor) { url(:anchor, anchor: 'comments') }
      get(:fragment) { url(:anchor, fragment: 'comments') }
      get(:fragment2) { url(:anchor, fragment: :comments) }
      get(:gangsta) { url(:gangsta, foo: { bar: :baz }, hoge: :fuga) }
      get(%i[hash id]) { url(:hash, id: 1) }
      get(:array, with: :id) { url(:array, 23) }
      get(%i[array id]) { url(:array, 23) }
      get(:hash_with_extra, with: :id) { url(:hash_with_extra, id: 1, query: 'string') }
      get(%i[hash_with_extra id]) { url(:hash_with_extra, id: 1, query: 'string') }
      get(:array_with_extra, with: :id) { url(:array_with_extra, 23, query: 'string') }
      get(%i[array_with_extra id]) { url(:array_with_extra, 23, query: 'string') }
      get('/old-bar/:id') { params[:id] }
      post(:mix, map: '/mix-bar/:id') { params[:id] }
      get(:mix, map: '/mix-bar/:id') { params[:id] }
      get(:foo, '', with: :id) { |id| "/#{id}" }
      post(:foo, '', with: :id) { |id| "/#{id}" }
      delete(:drugs, with: [:id, 'destroy']) { |id| "/drugs/#{id}/destroy" }
      delete(:drugs, '', with: [:id, 'destroy']) { |id| "/#{id}/destroy" }
      get(:splatter, '/splatter/*/*') { |_a, _b| url(:splatter, splat: %w[123 456]) }
    end
    get '/foo'
    assert_equal '/foo', body
    get '/foo/123'
    assert_equal '/foo/123', body
    get '/hash/2'
    assert_equal '/hash/1', body
    get '/anchor'
    assert_equal '/anchor#comments', body
    get '/fragment'
    assert_equal '/anchor#comments', body
    get '/fragment2'
    assert_equal '/anchor#comments', body
    get '/array/23'
    assert_equal '/array/23', body
    get '/hash_with_extra/1'
    assert_equal '/hash_with_extra/1?query=string', body
    get '/array_with_extra/23'
    assert_equal '/array_with_extra/23?query=string', body
    get '/old-bar/3'
    assert_equal '3', body
    post '/mix-bar/4'
    assert_equal '4', body
    get '/mix-bar/4'
    assert_equal '4', body
    get '/123'
    assert_equal '/123', body
    post '/123'
    assert_equal '/123', body
    delete '/drugs/123/destroy'
    assert_equal '/drugs/123/destroy', body
    delete '/123/destroy'
    assert_equal '/123/destroy', body
    get '/gangsta'
    assert_equal '/gangsta?foo%5Bbar%5D=baz&hoge=fuga', body
    get '/splatter/123/456'
    assert_equal '/splatter/123/456', body
  end

  it 'should generate url with format' do
    mock_app do
      get(:a, provides: :any) { url(:a, format: :json) }
      get(:b, provides: :js) { url(:b, format: :js) }
      get(:c, provides: %i[js json]) { url(:c, format: :json) }
      get(:d, provides: %i[html js]) { url(:d, format: :js, foo: :bar) }
    end
    get '/a.js'
    assert_equal '/a.json', body
    get '/b.js'
    assert_equal '/b.js', body
    get '/b.ru'
    assert_equal 404, status
    get '/c.js'
    assert_equal '/c.json', body
    get '/c.json'
    assert_equal '/c.json', body
    get '/c.ru'
    assert_equal 404, status
    get '/d'
    assert_equal '/d.js?foo=bar', body
    get '/d.js'
    assert_equal '/d.js?foo=bar', body
    get '/e.xml'
    assert_equal 404, status
  end

  it 'should generate absolute urls' do
    mock_app do
      get(:hash, with: :id) { absolute_url(:hash, id: 1) }
    end
    get '/hash/2'
    assert_equal 'http://example.org/hash/1', body
    get 'https://example.org/hash/2'
    assert_equal 'https://example.org/hash/1', body
  end

  it 'should generate absolute urls from stringified keys' do
    mock_app do
      get(:hash, with: :id) { absolute_url(:hash, 'id' => 1) }
    end
    get '/hash/2'
    assert_equal 'http://example.org/hash/1', body
  end

  it 'should generate proper absolute urls for mounted apps' do
    class Test < Padrino::Application
      get :foo do
        absolute_url(:foo, id: 1)
      end
    end
    Padrino.mount('Test').to('/test')
    @app = Padrino.application
    get('/test/foo')
    assert_equal 'http://example.org/test/foo?id=1', body
  end

  it 'should rebase simple string urls to app uri_root' do
    mock_app do
      set :uri_root, '/app'
      get(:a) { url('/foo') }
      get(:b) { url('bar') }
      get(:c) { absolute_url('/foo') }
      get(:d, map: '/d/e/f') { absolute_url('bar') }
    end
    get '/a'
    assert_equal '/app/foo', body
    get '/b'
    assert_equal 'bar', body
    get '/c'
    assert_equal 'http://example.org/app/foo', body
    get '/d/e/f'
    assert_equal 'http://example.org/app/d/e/bar', body
  end

  it 'should allow regex url with format' do
    mock_app do
      get(/.*/, provides: :any) { 'regexp' }
    end
    get '/anything'
    assert_equal 'regexp', body
  end

  it 'should use padrino url method' do
    mock_app do
    end

    assert_equal @app.method(:url).owner, Padrino::Routing::ClassMethods
  end

  it 'should work correctly with sinatra redirects' do
    mock_app do
      get(:index)  { redirect url(:index) }
      get(:google) { redirect 'http://google.com' }
      get('/foo')  { redirect '/bar' }
      get('/bar')  { 'Bar' }
    end

    get '/'
    assert_equal 'http://example.org/', headers['Location']
    get '/google'
    assert_equal 'http://google.com', headers['Location']
    get '/foo'
    assert_equal 'http://example.org/bar', headers['Location']
  end

  it 'should return 406 on Accept-Headers it does not provide' do
    mock_app do
      get(:a, provides: %i[html js]) { content_type }
    end

    get '/a', {}, { 'HTTP_ACCEPT' => 'application/yaml' }
    assert_equal 406, status
  end

  it 'should return 406 on file extensions it does not provide and flag is set' do
    mock_app do
      enable :treat_format_as_accept
      get(:a, provides: %i[html js]) { content_type }
    end

    get '/a.xml', {}, {}
    assert_equal 406, status
  end

  it 'should provide proper content when :provides is specified and Accept: `*/*` requested' do
    mock_app do
      get(:text, provides: :text) { 'text' }
    end
    header 'Accept', '*/*'
    get '/text'
    assert_equal 200, status
    assert_equal 'text', body
  end

  it 'should return 404 on file extensions it does not provide and flag is not set' do
    mock_app do
      get(:a, provides: %i[html js]) { content_type }
    end

    get '/a.xml', {}, {}
    assert_equal 404, status
  end

  it 'should not set content_type to :html if Accept */* and html not in provides' do
    mock_app do
      get('/foo', provides: %i[json xml]) { content_type.to_s }
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => '*/*;q=0.5' }
    assert_equal 'json', body
  end

  it 'should set content_type to :json if Accept contains */*' do
    mock_app do
      get('/foo', provides: [:json]) { content_type.to_s }
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' }
    assert_equal 'json', body
  end

  it 'should set and get content_type' do
    mock_app do
      get('/foo') { content_type(:json); content_type.to_s }
    end
    get '/foo'
    assert_equal 'application/json', content_type
    assert_equal 'json', body
  end

  it 'should send the appropriate number of params' do
    mock_app do
      get('/id/:user_id', provides: [:json]) { |user_id, _format| user_id }
    end
    get '/id/5.json'
    assert_equal '5', body
  end

  it 'should allow "." in param values' do
    mock_app do
      get('/id/:email', provides: [:json]) { |email, format| [email, format].join('/') }
    end
    get '/id/foo@bar.com.json'
    assert_equal 'foo@bar.com/json', body
  end

  it 'should set correct content_type for Accept not equal to */* even if */* also provided' do
    mock_app do
      get('/foo', provides: %i[html js xml]) { content_type.to_s }
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/javascript, */*;q=0.5' }
    assert_equal 'js', body
  end

  it 'should return the first content type in provides if accept header is empty' do
    mock_app do
      get(:a, provides: [:js]) { content_type.to_s }
    end

    get '/a', {}, {}
    assert_equal 'js', body
  end

  it 'should not default to HTML if HTML is not provided and no type is given' do
    mock_app do
      get(:a, provides: [:js]) { content_type }
    end

    get '/a', {}, {}
    assert_equal 'text/javascript;charset=utf-8', content_type
  end

  it 'should not match routes if url_format and http_accept is provided but not included' do
    mock_app do
      get(:a, provides: %i[js html]) { content_type }
    end

    get '/a.xml', {}, { 'HTTP_ACCEPT' => 'text/html' }
    assert_equal 404, status
  end

  it 'should generate routes for format simple' do
    mock_app do
      get(:foo, provides: %i[html rss]) { "Test\n" }
    end
    get '/foo'
    assert_equal "Test\n", body
    get '/foo.rss'
    assert_equal "Test\n", body
  end

  it 'should inject the controller name into the request' do
    mock_app do
      controller :posts do
        get(:index) { request.controller }
        controller :mini do
          get(:index) { request.controller }
        end
      end
    end
    get '/posts'
    assert_equal 'posts', body
    get '/mini'
    assert_equal 'mini', body
  end

  it 'should inject the action name into the request' do
    mock_app do
      controller :posts do
        get('/omnomnom(/:id)?') { request.action.inspect }
        controller :mini do
          get(%i[a b c]) { request.action.inspect }
        end
      end
    end
    get '/posts/omnomnom'
    assert_equal '"/omnomnom(/:id)?"', body
    get '/mini/a/b/c'
    assert_equal ':a', body
  end

  it 'should support not_found' do
    mock_app do
      not_found { 'whatever' }

      get :index, map: '/' do
        'index'
      end
    end
    get '/wrong'
    assert_equal 404, status
    assert_equal 'whatever', body
    get '/'
    assert_equal 'index', body
    assert_equal 200, status
  end

  it 'should inject the route into the request' do
    mock_app do
      controller :posts do
        get(:index) { request.route_obj.name.to_s }
      end
    end
    get '/posts'
    assert_equal 'posts index', body
  end

  it 'should preserve the format if you set it manually' do
    mock_app do
      before do
        params[:format] = 'json'
      end

      get 'test', provides: %i[html json] do
        content_type.inspect
      end
    end
    get '/test'
    assert_equal ':json', body
    get '/test.html'
    assert_equal ':json', body
    get '/test.php'
    assert_equal ':json', body
  end

  it 'should correctly accept "." in the route' do
    mock_app do
      get 'test.php', provides: %i[html json] do
        content_type.inspect
      end
    end
    get '/test.php'
    assert_equal ':html', body
    get '/test.php.json'
    assert_equal ':json', body
  end

  it 'should correctly accept priority of format' do
    mock_app do
      get 'test.php', provides: %i[html json xml] do
        content_type.inspect
      end
    end

    get '/test.php'
    assert_equal ':html', body
    get '/test.php', {}, { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal ':xml', body
    get '/test.php?format=json', { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal ':json', body
    get '/test.php.json?format=html', { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal ':json', body
  end

  it 'should generate routes for format with controller' do
    mock_app do
      controller :posts do
        get(:index, provides: %i[html rss atom js]) { "Index.#{content_type}\n" }
        get(:show,  with: :id, provides: %i[html rss atom]) { "Show.#{content_type}\n" }
      end
    end
    get '/posts'
    assert_equal "Index.html\n", body
    get '/posts.rss'
    assert_equal "Index.rss\n", body
    get '/posts.atom'
    assert_equal "Index.atom\n", body
    get '/posts.js'
    assert_equal "Index.js\n", body
    get '/posts/show/5'
    assert_equal "Show.html\n", body
    get '/posts/show/5.rss'
    assert_equal "Show.rss\n", body
    get '/posts/show/10.atom'
    assert_equal "Show.atom\n", body
  end

  it 'should map routes' do
    mock_app do
      get(:bar) { 'bar' }
    end
    get '/bar'
    assert_equal 'bar', body
    assert_equal '/bar', @app.url(:bar)
  end

  it 'should remove index from path' do
    mock_app do
      get(:index) { 'index' }
      get('/accounts/index') { 'accounts' }
    end
    get '/'
    assert_equal 'index', body
    assert_equal '/', @app.url(:index)
    get '/accounts/index'
    assert_equal 'accounts', body
  end

  it 'should remove index from path with params' do
    mock_app do
      get(:index, with: :name) { "index with #{params[:name]}" }
    end
    get '/bobby'
    assert_equal 'index with bobby', body
    assert_equal '/john', @app.url(:index, name: 'john')
  end

  it 'should parse named params' do
    mock_app do
      get(:print, with: :id) { "Im #{params[:id]}" }
    end
    get '/print/9'
    assert_equal 'Im 9', body
    assert_equal '/print/9', @app.url(:print, id: 9)
  end

  it 'should 405 on wrong request_method' do
    mock_app do
      post('/bar') { 'bar' }
    end
    get '/bar'
    assert_equal 405, status
  end

  it 'should respond to' do
    mock_app do
      get(:a, provides: :js) { 'js' }
      get(:b, provides: :any) { 'any' }
      get(:c, provides: %i[js json]) { 'js,json' }
      get(:d, provides: %i[html js]) { 'html,js' }
    end
    get '/a'
    assert_equal 200, status
    assert_equal 'js', body
    get '/a.js'
    assert_equal 'js', body
    get '/b'
    assert_equal 'any', body
    # TODO: randomly fails in minitest :(
    # assert_raises(RuntimeError) { get "/b.foo" }
    get '/c'
    assert_equal 200, status
    assert_equal 'js,json', body
    get '/c.js'
    assert_equal 'js,json', body
    get '/c.json'
    assert_equal 'js,json', body
    get '/d'
    assert_equal 'html,js', body
    get '/d.js'
    assert_equal 'html,js', body
  end

  it 'should respond_to and set content_type' do
    Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
    mock_app do
      get :a, provides: :any do
        content_type.to_s if %i[js json foo html].include?(content_type)
      end
    end
    get '/a.js'
    assert_equal 'js', body
    assert_equal 'text/javascript;charset=utf-8', response['Content-Type']
    get '/a.json'
    assert_equal 'json', body
    assert_equal 'application/json', response['Content-Type']
    get '/a.foo'
    assert_equal 'foo', body
    assert_equal 'application/foo', response['Content-Type']
    get '/a'
    assert_equal 'html', body
    assert_equal 'text/html;charset=utf-8', response['Content-Type']
  end

  it 'should not drop json charset' do
    mock_app do
      get '/' do
        content_type :json, charset: 'utf-16'
      end
      get '/a' do
        content_type :json, 'charset' => 'utf-16'
      end
    end
    get '/'
    assert_equal 'application/json;charset=utf-16', response['Content-Type']
    get '/a'
    assert_equal 'application/json;charset=utf-16', response['Content-Type']
  end

  it 'should use controllers' do
    mock_app do
      controller '/admin' do
        get('/') { 'index' }
        get('/show/:id') { "show #{params[:id]}" }
      end
    end
    get '/admin'
    assert_equal 'index', body
    get '/admin/show/1'
    assert_equal 'show 1', body
  end

  it 'should use named controllers' do
    mock_app do
      controller :admin do
        get(:index, with: :id) { params[:id] }
        get(:show, with: :id) { "show #{params[:id]}" }
      end
      controllers :foo, :bar do
        get(:index) { 'foo_bar_index' }
      end
    end
    get '/admin/1'
    assert_equal '1', body
    get '/admin/show/1'
    assert_equal 'show 1', body
    assert_equal '/admin/1', @app.url(:admin, :index, id: 1)
    assert_equal '/admin/show/1', @app.url(:admin, :show, id: 1)
    get '/foo/bar'
    assert_equal 'foo_bar_index', body
  end

  it 'should use map and with' do
    mock_app do
      get :index, map: '/bugs', with: :id do
        params[:id]
      end
    end
    get '/bugs/4'
    assert_equal '4', body
    assert_equal '/bugs/4', @app.url(:index, id: 4)
  end

  it 'should ignore trailing delimiters within a named controller' do
    mock_app do
      controller :posts do
        get(:index, provides: %i[html js]) { 'index' }
        get(:new)  { 'new' }
        get(:show, with: :id) { "show #{params[:id]}" }
      end
    end
    get '/posts'
    assert_equal 'index', body
    get '/posts/'
    assert_equal 'index', body
    get '/posts.js'
    assert_equal 'index', body
    get '/posts.js/'
    assert_equal 'index', body
    get '/posts/new'
    assert_equal 'new', body
    get '/posts/new/'
    assert_equal 'new', body
  end

  it 'should ignore trailing delimiters within a named controller for unnamed actions' do
    mock_app do
      controller :accounts do
        get('/') { 'account_index' }
        get('/new') { 'new' }
      end
      controller :votes do
        get('/') { 'vote_index' }
      end
    end
    get '/accounts'
    assert_equal 'account_index', body
    get '/accounts/'
    assert_equal 'account_index', body
    get '/accounts/new'
    assert_equal 'new', body
    get '/accounts/new/'
    assert_equal 'new', body
    get '/votes'
    assert_equal 'vote_index', body
    get '/votes/'
    assert_equal 'vote_index', body
  end

  it 'should use named controllers with array routes' do
    mock_app do
      controller :admin do
        get(:index) { 'index' }
        get(:show, with: :id) { "show #{params[:id]}" }
      end
      controllers :foo, :bar do
        get(:index) { 'foo_bar_index' }
      end
    end
    get '/admin'
    assert_equal 'index', body
    get '/admin/show/1'
    assert_equal 'show 1', body
    assert_equal '/admin', @app.url(:admin, :index)
    assert_equal '/admin/show/1', @app.url(:admin, :show, id: 1)
    get '/foo/bar'
    assert_equal 'foo_bar_index', body
  end

  it 'should support a reindex action and remove index inside controller' do
    mock_app do
      controller :posts do
        get(:index) { 'index' }
        get(:reindex) { 'reindex' }
      end
    end
    get '/posts'
    assert_equal 'index', body
    get '/posts/reindex'
    assert_equal '/posts/reindex', @app.url(:posts, :reindex)
    assert_equal 'reindex', body
  end

  it 'should use uri_root' do
    mock_app do
      get(:foo) { 'foo' }
    end
    @app.uri_root = '/'
    assert_equal '/foo', @app.url(:foo)
    @app.uri_root = '/testing'
    assert_equal '/testing/foo', @app.url(:foo)
    @app.uri_root = '/testing/'
    assert_equal '/testing/foo', @app.url(:foo)
    @app.uri_root = 'testing/bar///'
    assert_equal '/testing/bar/foo', @app.url(:foo)
  end

  it 'should use uri_root with controllers' do
    mock_app do
      controller :foo do
        get(:bar) { 'bar' }
      end
    end
    @app.uri_root = '/testing'
    assert_equal '/testing/foo/bar', @app.url(:foo, :bar)
  end

  it 'should use RACK_BASE_URI' do
    mock_app do
      get(:foo) { 'foo' }
    end
    # Wish there was a side-effect free way to test this...
    ENV['RACK_BASE_URI'] = '/'
    assert_equal '/foo', @app.url(:foo)
    ENV['RACK_BASE_URI'] = '/testing'
    assert_equal '/testing/foo', @app.url(:foo)
    ENV['RACK_BASE_URI'] = nil
  end

  it 'should use uri_root and RACK_BASE_URI' do
    mock_app do
      controller :foo do
        get(:bar) { 'bar' }
      end
    end
    ENV['RACK_BASE_URI'] = '/base'
    @app.uri_root = 'testing'
    assert_equal '/base/testing/foo/bar', @app.url(:foo, :bar)
    ENV['RACK_BASE_URI'] = nil
  end

  it 'should reset routes' do
    mock_app do
      get('/') { 'foo' }
      reset_router!
    end
    get '/'
    assert_equal 404, status
  end

  it 'should match params and format' do
    mock_app do
      get '/:id', provides: %i[json html] do |id, _|
        id
      end

      get 'format/:id', provides: %i[json html] do |_id, format|
        format
      end
    end

    get '/123.html'
    assert_equal '123', body

    get 'format/123.html'
    assert_equal 'html', body
  end

  it 'should respect priorities' do
    route_order = []
    mock_app do
      get(:index, priority: :normal) { route_order << :normal; pass }
      get(:index, priority: :low)    { route_order << :low; 'hello' }
      get(:index, priority: :high)   { route_order << :high; pass }
    end
    get '/'
    assert_equal %i[high normal low], route_order
    assert_equal 'hello', body
  end

  it 'should set the params correctly even if using prioritized routes' do
    mock_app do
      get('*__sinatra__/:image.png') {}
      get '/:primary/:secondary', priority: :low do
        "#{params[:primary]} #{params[:secondary]}"
      end
    end
    get '/abc/def'
    assert_equal 'abc def', body
  end

  it 'should catch all after controllers' do
    mock_app do
      get(:index, with: :slug, priority: :low) { 'catch all' }
      controllers :contact do
        get(:index) { 'contact' }
      end
    end
    get '/contact'
    assert_equal 'contact', body
    get '/foo'
    assert_equal 'catch all', body
  end

  it 'should allow optionals' do
    mock_app do
      get(:show, map: '/stories/:type(/:category)?') do
        "#{params[:type]}/#{params[:category]}"
      end
    end
    get '/stories/foo'
    assert_equal 'foo/', body
    get '/stories/foo/bar'
    assert_equal 'foo/bar', body
  end

  it 'should apply maps' do
    mock_app do
      controllers :admin do
        get(:index, map: '/') { 'index' }
        get(:show, with: :id, map: '/show') { "show #{params[:id]}" }
        get(:edit, map: '/edit/:id/product') { "edit #{params[:id]}" }
        get(:wacky, map: '/wacky-:id-:product_id') { "wacky #{params[:id]}-#{params[:product_id]}" }
      end
    end
    get '/'
    assert_equal 'index', body
    get @app.url(:admin, :index)
    assert_equal 'index', body
    get '/show/1'
    assert_equal 'show 1', body
    get '/edit/1/product'
    assert_equal 'edit 1', body
    get '/wacky-1-2'
    assert_equal 'wacky 1-2', body
  end

  it 'should apply maps when given path is kind of hash' do
    mock_app do
      controllers :admin do
        get(:foobar, '/foo/bar') { 'foobar' }
      end
    end
    get '/foo/bar'
    assert_equal 'foobar', body
  end

  it 'should apply parent to route' do
    mock_app do
      controllers :project do
        get(:index, parent: :user) { "index #{params[:user_id]}" }
        get(:index, parent: %i[user section]) { "index #{params[:user_id]} #{params[:section_id]}" }
        get(:edit, with: :id, parent: :user) { "edit #{params[:id]} #{params[:user_id]}" }
        get(:show, with: :id, parent: %i[user product]) { "show #{params[:id]} #{params[:user_id]} #{params[:product_id]}" }
      end
    end
    get '/user/1/project'
    assert_equal 'index 1', body
    get '/user/1/section/3/project'
    assert_equal 'index 1 3', body
    get '/user/1/project/edit/2'
    assert_equal 'edit 2 1', body
    get '/user/1/product/2/project/show/3'
    assert_equal 'show 3 1 2', body
  end

  it 'should respect parent precedence: controllers parents go before route parents' do
    mock_app do
      controllers :project do
        get(:index, parent: :user) { "index #{params[:user_id]}" }
      end

      controllers :bar, parent: :foo do
        get(:index) { "index on foo #{params[:foo_id]} @ bar" }
        get(:index, parent: :baz) { "index on foo #{params[:foo_id]} @ baz #{params[:baz_id]} @ bar" }
      end
    end

    get '/user/1/project'
    assert_equal 'index 1', body
    get '/foo/1/bar'
    assert_equal 'index on foo 1 @ bar', body
    get '/foo/1/baz/2/bar'
    assert_equal 'index on foo 1 @ baz 2 @ bar', body
  end

  it 'should keep a reference to the parent on the route' do
    mock_app do
      controllers :project do
        get(:index, parent: :user) { "index #{params[:user_id]}" }
        get(:index, parent: %i[user section]) { "index #{params[:user_id]} #{params[:section_id]}" }
        get(:edit, with: :id, parent: :user) { "edit #{params[:id]} #{params[:user_id]}" }
        get(:show, with: :id, parent: %i[user product]) { "show #{params[:id]} #{params[:user_id]} #{params[:product_id]}" }
      end

      controllers :bar, parent: :foo do
        get(:index) { 'index on foo/bar' }
        get(:index, parent: :baz) { 'index on foo/baz/bar' }
      end
    end

    # get "/user/1/project"
    assert_equal :user, @app.routes[0].parent
    # get "/user/1/section/3/project"
    assert_equal %i[user section], @app.routes[2].parent
    # get "/user/1/project/edit/2"
    assert_equal :user, @app.routes[4].parent
    # get "/user/1/product/2/project/show/3"
    assert_equal %i[user product], @app.routes[6].parent
    # get "/foo/1/bar"
    assert_equal :foo, @app.routes[8].parent
    # get "/foo/1/baz/2/bar"
    assert_equal %i[foo baz], @app.routes[10].parent
  end

  it 'should apply parent to controller' do
    mock_app do
      controller :project, parent: :user do
        get(:index) { "index #{params[:user_id]}" }
        get(:edit, with: :id, parent: :user) { "edit #{params[:id]} #{params[:user_id]}" }
        get(:show, with: :id, parent: :product) { "show #{params[:id]} #{params[:user_id]} #{params[:product_id]}" }
      end
    end

    user_project_url = '/user/1/project'
    get user_project_url
    assert_equal 'index 1', body
    assert_equal user_project_url, @app.url(:project, :index, user_id: 1)

    user_project_edit_url = '/user/1/project/edit/2'
    get user_project_edit_url
    assert_equal 'edit 2 1', body
    assert_equal user_project_edit_url, @app.url(:project, :edit, user_id: 1, id: 2)

    user_product_project_url = '/user/1/product/2/project/show/3'
    get user_product_project_url
    assert_equal 'show 3 1 2', body
    assert_equal user_product_project_url, @app.url(:project, :show, user_id: 1, product_id: 2, id: 3)
  end

  it 'should apply parent with shallowing to controller' do
    mock_app do
      controller :project do
        parent :user
        parent :shop, optional: true
        get(:index) { "index #{params[:user_id]} #{params[:shop_id]}" }
        get(:edit, with: :id) { "edit #{params[:id]} #{params[:user_id]} #{params[:shop_id]}" }
        get(:show, with: :id, parent: :product) { "show #{params[:id]} #{params[:user_id]} #{params[:product_id]} #{params[:shop_id]}" }
      end
    end

    assert_equal '/user/1/project', @app.url(:project, :index, user_id: 1, shop_id: nil)
    assert_equal '/user/1/shop/23/project', @app.url(:project, :index, user_id: 1, shop_id: 23)

    user_project_url = '/user/1/project'
    get user_project_url
    assert_equal 'index 1 ', body
    assert_equal user_project_url, @app.url(:project, :index, user_id: 1)

    user_project_edit_url = '/user/1/project/edit/2'
    get user_project_edit_url
    assert_equal 'edit 2 1 ', body
    assert_equal user_project_edit_url, @app.url(:project, :edit, user_id: 1, id: 2)

    user_product_project_url = '/user/1/product/2/project/show/3'
    get user_product_project_url
    assert_equal 'show 3 1 2 ', body
    assert_equal user_product_project_url, @app.url(:project, :show, user_id: 1, product_id: 2, id: 3)

    user_project_url = '/user/1/shop/1/project'
    get user_project_url
    assert_equal 'index 1 1', body
    assert_equal user_project_url, @app.url(:project, :index, user_id: 1, shop_id: 1)

    user_project_edit_url = '/user/1/shop/1/project/edit/2'
    get user_project_edit_url
    assert_equal 'edit 2 1 1', body
    assert_equal user_project_edit_url, @app.url(:project, :edit, user_id: 1, id: 2, shop_id: 1)

    user_product_project_url = '/user/1/shop/1/product/2/project/show/3'
    get user_product_project_url
    assert_equal 'show 3 1 2 1', body
    assert_equal user_product_project_url, @app.url(:project, :show, user_id: 1, product_id: 2, id: 3, shop_id: 1)
  end

  it 'should respect map in parents with shallowing' do
    mock_app do
      controller :project do
        parent :shop, map: '/foo/bar'
        get(:index) { "index #{params[:shop_id]}" }
      end
    end

    shop_project_url = '/foo/bar/1/project'
    get shop_project_url
    assert_equal 'index 1', body
    assert_equal shop_project_url, @app.url(:project, :index, shop_id: 1)
  end

  it 'should use default values' do
    mock_app do
      controller lang: :it do
        get(:index, map: '/:lang') { "lang is #{params[:lang]}" }
      end
      # This is only for be sure that default values
      # work only for the given controller
      get(:foo, map: '/foo') {}
    end
    assert_equal '/it',  @app.url(:index)
    assert_equal '/foo', @app.url(:foo)
    get '/en'
    assert_equal 'lang is en', body
  end

  it 'should override default values when parameters are passed' do
    mock_app do
      controller lang: :it do
        get(:index, map: '/:lang') { "lang is #{params[:lang]}" }
      end
    end
    assert_equal '/pt', @app.url(:index, lang: 'pt')
    get '/pt'
    assert_equal 'lang is pt', body
  end

  it 'should transitions to the next matching route on pass' do
    mock_app do
      get '/:foo' do
        pass
        'Hello Foo'
      end
      get '/:bar' do
        'Hello World'
      end
    end

    get '/za'
    assert_equal 'Hello World', body
  end

  it 'should filters by media type' do
    mock_app do
      get '/foo', accepts: %i[xml json] do
        request.env['CONTENT_TYPE']
      end
    end

    get '/foo', {}, { 'CONTENT_TYPE' => 'application/xml' }
    assert ok?
    assert_equal 'application/xml', body
    assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
    get '/foo'
    assert_equal 406, status
    get '/foo.xml'
    assert_equal 404, status

    get '/foo', {}, { 'CONTENT_TYPE' => 'application/json' }
    assert ok?
    assert_equal 'application/json', body
    assert_equal 'application/json', response.headers['Content-Type']
  end

  it 'should filters by media type when using :accepts as controller option' do
    mock_app do
      controller accepts: %i[xml js] do
        get '/foo' do
          request.env['CONTENT_TYPE']
        end
      end
    end

    get '/foo', {}, { 'CONTENT_TYPE' => 'text/javascript' }
    assert ok?
    assert_equal 'text/javascript', body
  end

  it 'should filters by accept header' do
    mock_app do
      get '/foo', provides: %i[xml js] do
        request.env['HTTP_ACCEPT']
      end
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' }
    assert ok?
    assert_equal 'application/xml', body
    assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']

    get '/foo.xml'
    assert ok?
    assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']

    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/javascript' }
    assert ok?
    assert_equal 'text/javascript', body
    assert_equal 'text/javascript;charset=utf-8', response.headers['Content-Type']

    get '/foo.js'
    assert ok?
    assert_equal 'text/javascript;charset=utf-8', response.headers['Content-Type']

    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html' }
    assert_equal 406, status
  end

  it 'should does not allow global provides' do
    mock_app do
      provides :xml

      get('/foo') { "Foo in #{content_type.inspect}" }
      get('/bar') { "Bar in #{content_type.inspect}" }
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal 'Foo in :xml', body
    get '/foo'
    assert_equal 'Foo in :xml', body

    get '/bar', {}, { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal 'Bar in nil', body
  end

  it 'should does not allow global provides in controller' do
    mock_app do
      controller :base do
        provides :xml

        get(:foo, '/foo') { "Foo in #{content_type.inspect}" }
        get(:bar, '/bar') { "Bar in #{content_type.inspect}" }
      end
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal 'Foo in :xml', body
    get '/foo'
    assert_equal 'Foo in :xml', body

    get '/bar', {}, { 'HTTP_ACCEPT' => 'application/xml' }
    assert_equal 'Bar in nil', body
  end

  it 'should map non named routes in controllers' do
    mock_app do
      controller :base do
        get('/foo') { 'ok' }
        get('/bar') { 'ok' }
      end
    end

    get '/base/foo'
    assert ok?
    get '/base/bar'
    assert ok?
  end

  it 'should set content_type to :html for both empty Accept as well as Accept text/html' do
    mock_app do
      provides :html

      get('/foo') { content_type.to_s }
    end

    get '/foo', {}, {}
    assert_equal 'html', body

    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html' }
    assert_equal 'html', body
  end

  it 'should set content_type to :html if Accept */*' do
    mock_app do
      get('/foo', provides: %i[html js]) { content_type.to_s }
    end
    get '/foo', {}, {}
    assert_equal 'html', body

    get '/foo', {}, { 'HTTP_ACCEPT' => '*/*;q=0.5' }
    assert_equal 'html', body
  end

  it 'should set content_type to :js if Accept includes both text/javascript and */*;q=0.5' do
    mock_app do
      get('/foo', provides: %i[html js]) { content_type.to_s }
    end
    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/javascript, */*;q=0.5' }
    assert_equal 'js', body
  end

  it 'should set content_type to :html if Accept */* and provides of :any' do
    mock_app do
      get('/foo', provides: :any) { content_type.to_s }
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => '*/*' }
    assert_equal 'html', body
  end

  it 'should set content_type to :js if Accept includes both text/javascript, */*;q=0.5 and provides of :any' do
    mock_app do
      get('/foo', provides: :any) { content_type.to_s }
    end

    get '/foo', {}, { 'HTTP_ACCEPT' => 'text/javascript, */*;q=0.5' }
    assert_equal 'js', body
  end

  it 'should allows custom route-conditions to be set via route options and halt' do
    protector = Module.new do
      def protect(*args)
        condition { halt(403, 'go away') unless authorize(params['user'], params['password']) }
      end
    end

    mock_app do
      register protector

      helpers do
        def authorize(username, password)
          username == 'foo' && password == 'bar'
        end
      end

      get '/', protect: true do
        'hey'
      end
    end

    get '/'
    assert forbidden?
    assert_equal 'go away', body

    get '/', user: 'foo', password: 'bar'
    assert ok?
    assert_equal 'hey', body
  end

  it 'should allows custom route-conditions to be set via route options using two routes' do
    protector = Module.new do
      def protect(*args)
        condition { authorize(params['user'], params['password']) }
      end
    end

    mock_app do
      register protector

      helpers do
        def authorize(username, password)
          username == 'foo' && password == 'bar'
        end
      end

      get '/', protect: true do
        'hey'
      end

      get '/' do
        'go away'
      end
    end

    get '/'
    assert_equal 'go away', body

    get '/', user: 'foo', password: 'bar'
    assert ok?
    assert_equal 'hey', body
  end

  it 'should allow concise routing' do
    mock_app do
      get :index, ':id' do
        params[:id]
      end

      get :map, 'route/:id' do
        params[:id]
      end
    end

    get '/123'
    assert_equal '123', body

    get '/route/123'
    assert_equal '123', body
  end

  it 'should support halting with 404 and message' do
    mock_app do
      controller do
        get :index do
          halt 404, 'not found'
        end
      end
    end

    get '/'
    assert_equal 404, status
    assert_equal 'not found', body
  end

  it 'should allow passing & halting in before filters' do
    mock_app do
      controller do
        before { env['QUERY_STRING'] == 'secret' or pass }
        get :index do
          'secret index'
        end
      end

      controller do
        before { env['QUERY_STRING'] == 'halt' and halt 401, 'go away!' }
        get :index do
          'index'
        end
      end
    end

    get '/?secret'
    assert_equal 'secret index', body

    get '/?halt'
    assert_equal 'go away!', body
    assert_equal 401, status

    get '/'
    assert_equal 'index', body
  end

  it 'should scope filters in the given controller' do
    mock_app do
      before { @global = 'global' }
      after { @global = nil }

      controller :foo do
        before { @foo = :foo }
        after { @foo = nil }
        get('/') { [@foo, @bar, @global].compact.join(' ') }
      end

      get('/') { [@foo, @bar, @global].compact.join(' ') }

      controller :bar do
        before { @bar = :bar }
        after { @bar = nil }
        get('/') { [@foo, @bar, @global].compact.join(' ') }
      end
    end

    get '/bar'
    assert_equal 'bar global', body

    get '/foo'
    assert_equal 'foo global', body

    get '/'
    assert_equal 'global', body
  end

  it 'should works with optionals params' do
    mock_app do
      get('/foo(/:bar)?') { params[:bar] }
    end

    get '/foo/bar'
    assert_equal 'bar', body

    get '/foo'
    assert_equal '', body
  end

  it 'should work with multiple dashed params' do
    mock_app do
      get '/route/:foo/:bar/:baz', provides: :html do
        "#{params[:foo]};#{params[:bar]};#{params[:baz]}"
      end
    end

    get '/route/foo/bar/baz'
    assert_equal 'foo;bar;baz', body

    get '/route/foo/bar-whatever/baz'
    assert_equal 'foo;bar-whatever;baz', body
  end

  it 'should work with arbitrary params' do
    mock_app do
      get(:testing) { params[:foo] }
    end

    url = @app.url(:testing, foo: 'bar')
    assert_equal '/testing?foo=bar', url
    get url
    assert_equal 'bar', body
  end

  it 'should ignore nil params' do
    mock_app do
      get(:testing, provides: %i[html json]) do
      end
    end
    assert_equal '/testing.html', @app.url(:testing, format: :html)
    assert_equal '/testing', @app.url(:testing, format: nil)
  end

  it 'should be able to access params in a before filter' do
    username_from_before_filter = nil

    mock_app do
      before do
        username_from_before_filter = params[:username]
      end

      get :users, with: :username do
      end
    end
    get '/users/josh'
    assert_equal 'josh', username_from_before_filter
  end

  it 'should be able to access params normally when a before filter is specified' do
    mock_app do
      before {}
      get :index do
        params.to_json
      end
    end
    get '/?test=what'
    assert_equal '{"test":"what"}', body
  end

  it 'should work only for the given controller and route when using before-filter with route name' do
    mock_app do
      controller :foo do
        before(:index) { @a = 'only to :index' }
        get(:index) { @a }
        get(:main) { @a }
      end
    end
    get '/foo/'
    assert_equal 'only to :index', body
    get '/foo/main'
    assert_equal '', body
  end

  it 'should work only for the given controller and route when using after-filter with route name' do
    mock_app do
      controller :after_controller do
        global = 'global variable'
        get(:index) { global }
        get(:main) { global }
        after(:index) { global = nil }
      end
    end
    get '/after_controller'
    assert_equal 'global variable', body
    get '/after_controller'
    assert_equal '', body
  end

  it 'should execute the before/after filters when they are inserted after the target route' do
    mock_app do
      controller :after_test do
        global = 'global variable'
        get(:index) { global }
        get(:foo) { global }
        before(:index) { global.delete!(' ') }
        after(:index) { global = 'after' }
      end
    end
    get '/after_test'
    assert_equal 'globalvariable', body
    get '/after_test/foo'
    assert_equal 'after', body
  end

  it 'should work with controller and arbitrary params' do
    mock_app do
      get(:testing) { params[:foo] }
      controller :test1 do
        get(:url1) { params[:foo] }
        get(:url2, provides: %i[html json]) { params[:foo] }
      end
    end

    url = @app.url(:test1, :url1, foo: 'bar1')
    assert_equal '/test1/url1?foo=bar1', url
    get url
    assert_equal 'bar1', body

    url = @app.url(:test1, :url2, foo: 'bar2')
    assert_equal '/test1/url2?foo=bar2', url
    get url
    assert_equal 'bar2', body
  end

  it 'should parse two routes with the same path but different http verbs' do
    mock_app do
      get(:index) { 'This is the get index' }
      post(:index) { 'This is the post index' }
    end
    get '/'
    assert_equal 'This is the get index', body
    post '/'
    assert_equal 'This is the post index', body
  end

  it 'should use optionals params' do
    mock_app do
      get(:index, map: '/:foo(/:bar)?') { "#{params[:foo]}-#{params[:bar]}" }
    end
    get '/foo'
    assert_equal 'foo-', body
    get '/foo/bar'
    assert_equal 'foo-bar', body
  end

  it 'should parse two routes with the same path but different http verbs and provides' do
    mock_app do
      get(:index, provides: %i[html json]) { "This is the get index.#{content_type}" }
      post(:index, provides: %i[html json]) { "This is the post index.#{content_type}" }
    end
    get '/'
    assert_equal 'This is the get index.html', body
    post '/'
    assert_equal 'This is the post index.html', body
    get '/.json'
    assert_equal 'This is the get index.json', body
    get '/.js'
    assert_equal 404, status
    post '/.json'
    assert_equal 'This is the post index.json', body
    post '/.js'
    assert_equal 404, status
  end

  it 'should allow controller level mapping' do
    mock_app do
      controller map: 'controller-:id' do
        get(:url3) { params[:id].to_s }
        get(:url4, map: 'test-:id2') { "#{params[:id]}, #{params[:id2]}" }
      end
    end

    url = @app.url(:url3, id: 1)
    assert_equal '/controller-1/url3', url
    get url
    assert_equal '1', body

    url = @app.url(:url4, 1, 2)
    assert_equal '/controller-1/test-2', url
    get url
    assert_equal '1, 2', body
  end

  it 'should replace name of named controller with mapping path' do
    mock_app do
      controller :ugly, map: '/pretty/:id' do
        get(:url3) { params[:id].to_s }
        get(:url4, map: 'test-:id2') { "#{params[:id]}, #{params[:id2]}" }
      end
      controller :voldemort, map: '' do
        get(:url5) { 'okay' }
      end
    end

    url = @app.url(:ugly, :url3, id: 1)
    assert_equal '/pretty/1/url3', url
    get url
    assert_equal '1', body

    url = @app.url(:ugly, :url4, 3, 5)
    assert_equal '/pretty/3/test-5', url
    get url
    assert_equal '3, 5', body

    url = @app.url(:voldemort, :url5)
    assert_equal '/url5', url
    get url
    assert_equal 'okay', body
  end

  it 'should use absolute and relative maps' do
    mock_app do
      controller :one do
        parent :three
        get :index, map: 'one' do; end
        get :index2, map: '/one' do; end
      end

      controller :two, map: 'two' do
        parent :three
        get :index, map: 'two' do; end
        get :index2, map: '/two', with: :id do; end
      end
    end
    assert_equal '/three/three_id/one', @app.url(:one, :index, 'three_id')
    assert_equal '/one', @app.url(:one, :index2)
    assert_equal '/two/three/three_id/two', @app.url(:two, :index, 'three_id')
    assert_equal '/two/four_id', @app.url(:two, :index2, 'four_id')
  end

  it 'should work with params and parent options' do
    mock_app do
      controller :test2, parent: :parent1, parent1_id: 1 do
        get(:url3) { params[:foo] }
        get(:url4, with: :with1) { params[:foo] }
        get(:url5, with: :with2, provides: [:html]) { params[:foo] }
      end
    end

    url = @app.url(:test2, :url3, foo: 'bar3')
    assert_equal '/parent1/1/test2/url3?foo=bar3', url
    get url
    assert_equal 'bar3', body

    url = @app.url(:test2, :url4, with1: 'awith1', foo: 'bar4')
    assert_equal '/parent1/1/test2/url4/awith1?foo=bar4', url
    get url
    assert_equal 'bar4', body

    url = @app.url(:test2, :url5, with2: 'awith1', foo: 'bar5')
    assert_equal '/parent1/1/test2/url5/awith1?foo=bar5', url
    get url
    assert_equal 'bar5', body
  end

  it 'should parse params without explicit provides for every matching route' do
    mock_app do
      get(:index, map: '/foos/:bar') { "get bar = #{params[:bar]}" }
      post :create, map: '/foos/:bar', provides: %i[html js] do
        "post bar = #{params[:bar]}"
      end
    end

    get '/foos/hello'
    assert_equal 'get bar = hello', body
    post '/foos/hello'
    assert_equal 'post bar = hello', body
    post '/foos/hello.js'
    assert_equal 'post bar = hello', body
  end

  it 'should properly route to first foo with two similar routes' do
    mock_app do
      controllers do
        get('/foo/') { 'this is foo' }
        get(:show, map: '/foo/:bar/:id') { "/foo/#{params[:bar]}/#{params[:id]}" }
      end
    end
    get '/foo'
    assert_equal 'this is foo', body
    get '/foo/'
    assert_equal 'this is foo', body
    get '/foo/5/10'
    assert_equal '/foo/5/10', body
  end

  it 'should index routes should be optional when nested' do
    mock_app do
      controller '/users', provides: [:json] do
        get '/' do
          'foo'
        end
      end
    end
    get '/users.json'
    assert_equal 'foo', body
  end

  it 'should use provides as conditional' do
    mock_app do
      provides :json
      get '/' do
        'foo'
      end
    end
    get '/.json'
    assert_equal 'foo', body
  end

  it 'should reset provides for routes that did not use it' do
    mock_app do
      get('/foo', provides: :js) {}
      get('/bar') {}
    end
    get '/foo'
    assert ok?
    get '/foo.js'
    assert ok?
    get '/bar'
    assert ok?
    get '/bar.js'
    assert_equal 404, status
  end

  it 'should pass controller conditions to each route' do
    counter = 0

    mock_app do
      self.class.send(:define_method, :increment!) do |*_args|
        condition { counter += 1 }
      end

      controller :posts, conditions: { increment!: true } do
        get('/foo') { 'foo' }
        get('/bar') { 'bar' }
      end
    end

    get '/posts/foo'
    get '/posts/bar'
    assert_equal 2, counter
  end

  it 'should allow controller conditions to be overridden' do
    counter = 0

    mock_app do
      self.class.send(:define_method, :increment!) do |increment|
        condition { counter += 1 } if increment
      end

      controller :posts, conditions: { increment!: true } do
        get('/foo') { 'foo' }
        get('/bar', increment!: false) { 'bar' }
      end
    end

    get '/posts/foo'
    get '/posts/bar'
    assert_equal 1, counter
  end

  it 'should parse params with class level provides' do
    mock_app do
      controllers :posts, provides: %i[html js] do
        post(:create, map: '/foo/:bar/:baz/:id') do
          "POST CREATE #{params[:bar]} - #{params[:baz]} - #{params[:id]}"
        end
      end
      controllers :topics, provides: %i[js html] do
        get(:show, map: '/foo/:bar/:baz/:id') { render 'topics/show' }
        post(:create, map: '/foo/:bar/:baz') { "TOPICS CREATE #{params[:bar]} - #{params[:baz]}" }
      end
    end
    post '/foo/bar/baz.js'
    assert_equal 'TOPICS CREATE bar - baz', body, 'should parse params with explicit .js'
    post @app.url(:topics, :create, format: :js, bar: 'bar', baz: 'baz')
    assert_equal 'TOPICS CREATE bar - baz', body, 'should parse params from generated url'
    post '/foo/bar/baz/5.js'
    assert_equal 'POST CREATE bar - baz - 5', body
    post @app.url(:posts, :create, format: :js, bar: 'bar', baz: 'baz', id: 5)
    assert_equal 'POST CREATE bar - baz - 5', body
  end

  it 'should parse params properly with inline provides' do
    mock_app do
      controllers :posts do
        post(:create, map: '/foo/:bar/:baz/:id', provides: %i[html js]) do
          "POST CREATE #{params[:bar]} - #{params[:baz]} - #{params[:id]}"
        end
      end
      controllers :topics do
        get(:show, map: '/foo/:bar/:baz/:id', provides: %i[html js]) { render 'topics/show' }
        post(:create, map: '/foo/:bar/:baz', provides: %i[html js]) { "TOPICS CREATE #{params[:bar]} - #{params[:baz]}" }
      end
    end
    post @app.url(:topics, :create, format: :js, bar: 'bar', baz: 'baz')
    assert_equal 'TOPICS CREATE bar - baz', body, 'should properly post to topics create action'
    post @app.url(:posts, :create, format: :js, bar: 'bar', baz: 'baz', id: 5)
    assert_equal 'POST CREATE bar - baz - 5', body, 'should properly post to create action'
  end

  it 'should have overideable format' do
    ::Rack::Mime::MIME_TYPES['.other'] = 'text/html'
    mock_app do
      before do
        params[:format] ||= :other
      end
      get('/format_test', provides: %i[html other]) { content_type.to_s }
    end
    get '/format_test'
    assert_equal 'other', body
    ::Rack::Mime::MIME_TYPES.delete('.other')
  end

  it 'should invokes handlers registered with ::error when raised' do
    mock_app do
      set :raise_errors, false
      error(FooError) { 'Foo!' }
      get '/' do
        raise FooError
      end
    end
    get '/'
    assert_equal 500, status
    assert_equal 'Foo!', body
  end

  it 'should have MethodOverride middleware' do
    mock_app do
      put('/') { 'okay' }
    end
    assert @app.method_override?
    post '/', { '_method' => 'PUT' }, {}
    assert_equal 200, status
    assert_equal 'okay', body
  end

  it 'should return value from params' do
    mock_app do
      get('/foo/:bar') { raise "'bar' should be a string" unless params[:bar].is_a? String }
    end
    get '/foo/50'
    assert ok?
  end

  it 'should return params as a HashWithIndifferentAccess object via GET' do
    mock_app do
      get('/foo/:bar') { "#{params['bar']} #{params[:bar]}" }
      get(:foo, map: '/prefix/:var') { "#{params['var']} #{params[:var]}" }
    end

    get('/foo/some_text')
    assert_equal 'some_text some_text', body

    get('/prefix/var')
    assert_equal 'var var', body
  end

  it 'should return params as a HashWithIndifferentAccess object via POST' do
    mock_app do
      post('/user') do
        "#{params['user']['full_name']} #{params[:user][:full_name]}"
      end
    end

    post '/user', { user: { full_name: 'example user' } }
    assert_equal 'example user example user', body

    post '/user', { 'user' => { 'full_name' => 'example user' } }
    assert_equal 'example user example user', body
  end

  it 'should have MethodOverride middleware with more options' do
    mock_app do
      put('/hi', provides: [:json]) { 'hi' }
    end
    post '/hi', { '_method' => 'PUT' }
    assert_equal 200, status
    assert_equal 'hi', body
    post '/hi.json', { '_method' => 'PUT' }
    assert_equal 200, status
    assert_equal 'hi', body
    post '/hi.json'
    assert_equal 405, status
  end

  it 'should parse nested params' do
    mock_app do
      get(:index) { format('%s %s', params[:account][:name], params[:account][:surname]) }
    end
    get "/?#{Padrino::Utils.build_uri_query(account: { name: 'foo', surname: 'bar' })}"
    assert_equal 'foo bar', body
    get @app.url(:index, 'account[name]' => 'foo', 'account[surname]' => 'bar')
    assert_equal 'foo bar', body
  end

  it 'should render sinatra NotFound page' do
    mock_app { set :environment, :development }
    get '/'
    assert_equal 404, status
    assert_match(/Not Found/, body)
  end

  it 'should render a custom NotFound page' do
    mock_app do
      error(Sinatra::NotFound) { 'not found' }
    end
    get '/'
    assert_equal 404, status
    assert_match(/not found/, body)
  end

  it 'should render a custom 404 page using not_found' do
    mock_app do
      not_found { 'custom 404 not found' }
    end
    get '/'
    assert_equal 404, status
    assert_equal 'custom 404 not found', body
  end

  it 'should render a custom error page using error method' do
    mock_app do
      error(404) { 'custom 404 error' }
    end
    get '/'
    assert_equal 404, status
    assert_equal 'custom 404 error', body
  end

  it 'should render a custom 403 page' do
    mock_app do
      error(403) { 'custom 403 not found' }
      get('/') { status 403 }
    end
    get '/'
    assert_equal 403, status
    assert_equal 'custom 403 not found', body
  end

  it 'should recognize paths' do
    mock_app do
      controller :foo do
        get(:bar, map: '/my/:id/custom-route') {}
      end
      get(:simple, map: '/simple/:id') {}
      get(:with_format, with: :id, provides: :js) {}
    end
    assert_equal [:"foo bar", { 'id' => 'fantastic' }], @app.recognize_path(@app.url(:foo, :bar, id: :fantastic))
    assert_equal [:"foo bar", { 'id' => '18' }], @app.recognize_path(@app.url(:foo, :bar, id: 18))
    assert_equal [:simple, { 'id' => 'bar' }], @app.recognize_path(@app.url(:simple, id: 'bar'))
    assert_equal [:simple, { 'id' => 'true' }], @app.recognize_path(@app.url(:simple, id: true))
    assert_equal [:simple, { 'id' => '9' }], @app.recognize_path(@app.url(:simple, id: 9))
    assert_equal [:with_format, { 'id' => 'bar', 'format' => 'js' }], @app.recognize_path(@app.url(:with_format, id: 'bar', format: :js))
    assert_equal [:with_format, { 'id' => 'true', 'format' => 'js' }], @app.recognize_path(@app.url(:with_format, id: true, format: 'js'))
    assert_equal [:with_format, { 'id' => '9', 'format' => 'js' }], @app.recognize_path(@app.url(:with_format, id: 9, format: :js))
  end

  it 'should have current_path' do
    mock_app do
      controller :foo do
        get(:index) { current_path }
        get :bar, map: '/paginate/:page' do
          current_path
        end
        get(:after) { current_path }
      end
    end
    get '/paginate/10'
    assert_equal '/paginate/10', body
    get '/foo/after'
    assert_equal '/foo/after', body
    get '/foo'
    assert_equal '/foo', body
  end

  it 'should accept :map and :parent' do
    mock_app do
      controller :posts do
        get :show, parent: :users, map: 'posts/:id' do
          "#{params[:user_id]}-#{params[:id]}"
        end
      end
    end
    get '/users/123/posts/321'
    assert_equal '123-321', body
  end

  it 'should change params in current_path' do
    mock_app do
      get :index, map: '/paginate/:page' do
        current_path(page: 66)
      end
    end
    get @app.url(:index, page: 10)
    assert_equal '/paginate/66', body
  end

  it 'should not route get :users, with: :id to /users//' do
    mock_app do
      get(:users, with: :id) { 'boo' }
    end
    get '/users//'
    assert_equal 404, status
  end

  it 'should support splat params' do
    mock_app do
      get '/say/*/to/*' do
        params[:splat].inspect
      end
    end
    get '/say/hello/to/world'
    assert_equal %(["hello", "world"]), body
  end

  it 'should recognize the route containing splat params if path is ended with slash' do
    mock_app do
      get '/splat/*' do
        'slash!'
      end
    end
    get '/splat'
    assert_equal 404, status
    get '/splat/'
    assert_equal 'slash!', body
  end

  it 'should match correctly paths even if the free regex route exists' do
    mock_app do
      get %r{/b/(?<aa>\w+)/(?<bb>\w+)} do
        'free regex'
      end

      put '/b/:b/:c', csrf_protection: false do
        params.to_json
      end
    end
    put '/b/x/y'
    assert_equal '{"b":"x","c":"y"}', body
  end

  it 'should support named captures like %r{/hello/(?<person>[^/?#]+)} on Ruby >= 1.9' do
    mock_app do
      get Regexp.new('/hello/(?<person>[^/?#]+)') do
        "Hello #{params['person']}"
      end
    end
    get '/hello/Frank'
    assert_equal 'Hello Frank', body
  end

  it 'supports regular expression look-alike routes' do
    mock_app do
      get(RegexpLookAlike.new) do
        [params[:one], params[:two], params[:three], params[:four]].join(' ')
      end
    end

    get '/this/is/a/test/'
    assert ok?
    assert_equal 'this is a test', body
  end

  it 'uses optional block passed to pass as route block if no other route is found' do
    mock_app do
      get '/' do
        pass do
          'this'
        end
        'not this'
      end
    end

    get '/'
    assert ok?
    assert_equal 'this', body
  end

  it 'supports mixing multiple splat params like /*/foo/*/* as block parameters' do
    mock_app do
      get '/*/foo/*/*' do |foo, bar, baz|
        "#{foo}, #{bar}, #{baz}"
      end
    end

    get '/bar/foo/bling/baz/boom'
    assert ok?
    assert_equal 'bar, bling, baz/boom', body
  end

  it 'should be able to use PathRouter#recognize to recognize routes' do
    mock_app do
      get(:sample) {}
    end
    env = Rack::MockRequest.env_for('/sample')
    request = Rack::Request.new(env)
    assert_equal :sample, @app.router.recognize(request).first.name
  end

  it 'should be able to use PathRouter#recognize to recognize routes by using Rack::MockRequest' do
    mock_app do
      get(:mock_sample) {}
    end
    env = Rack::MockRequest.env_for('/mock_sample')
    assert_equal :mock_sample, @app.router.recognize(env).first.name
    env = Rack::MockRequest.env_for('/invalid')
    assert_empty @app.router.recognize(env)
  end

  it 'should be able to use params after sending request' do
    mock_app do
      get('/foo/:id') { params.inspect }
    end
    get '/foo/123'
    assert_equal({ 'id' => '123' }, Thread.current['padrino.instance'].instance_variable_get(:@params))
  end

  it 'should raise an exception if block arity is not same with captured params size' do
    assert_raises(Padrino::Routing::BlockArityError) do
      mock_app do
        get('/sample/:a/:b') { |a| }
      end
    end
  end

  it 'should pass format value as a block parameter' do
    mock_app do
      get '/sample/:a/:b', provides: :xml do |a, b, format|
        "#{a}, #{b}, #{format}"
      end
    end
    get '/sample/foo/bar'
    assert_equal 'foo, bar, ', body
    get '/sample/foo/bar.xml'
    assert_equal 'foo, bar, xml', body
  end

  it 'should allow negative arity in route block' do
    mock_app do
      get('/:a/sample/*/*') { |*all| }
    end
  end

  it 'should be able to use splat and named captues' do
    mock_app do
      get('/:a/:b/*/*/*') { |a, b, *splats| "#{a}, #{b}, (#{splats * ','})" }
    end
    get '/123/456/a/b/c'
    assert_equal '123, 456, (a,b,c)', body
  end

  it 'can modify the request' do
    mock_app do
      get('/foo') { request.path_info = '/bar'; pass }
      get('/bar') { 'bar' }
    end

    get '/foo'
    assert ok?
    assert_equal 'bar', body
  end

  it 'should generate urls and absolute urls' do
    mock_app do
      get(:index) { settings.url(:index) }
      get(:absolute) { settings.absolute_url(:absolute) }
    end
    get '/'
    assert_equal '/', body
    get '/absolute'
    assert_equal 'http://localhost/absolute', body
    @app.set :base_url, 'http://example.com'
    get '/absolute'
    assert_equal 'http://example.com/absolute', body
  end

  it 'should not match if route regexps matches with incorrect_path[0..2]' do
    mock_app do
      get(:index) { 'bork' }
      get('/foo') { 'foo' }
    end
    get '/'
    assert_equal 200, status
    get '/a'
    assert_equal 404, status
    get '/foo'
    assert_equal 200, status
    get '/fo'
    assert_equal 404, status
  end

  it "should maintain Sinatra's params indifference" do
    mock_app do
      get '/update', with: :id do
        "#{params[:product]['title']}==#{params[:product][:title]}"
      end
    end
    get '/update/1?product[title]=test'
    assert_equal 'test==test', body
  end

  it 'prevent overwriting params by given query' do
    mock_app do
      get '/prohibit/:id' do
        params[:id]
      end
    end
    get '/prohibit/123?id=456'
    assert_equal '123', body
  end

  it 'functions in a standalone app' do
    mock_app(Sinatra::Application) do
      register Padrino::Routing
      get(:index) { 'Standalone' }
    end
    get '/'
    assert_equal 200, status
  end
end
