require_relative "spec_helper"

describe "capturing" do
  it "doesn't yield the verb for verb matcher" do
    app do |r|
      r.get do |*args|
        args.size.to_s
      end
    end

    body.must_equal '0'
  end

  it "doesn't yield the path for string matcher" do
    app do |r|
      r.get "home" do |*args|
        args.size.to_s
      end
    end

    body('/home').must_equal '0'
  end

  it "yields the segment for symbol matcher" do
    app do |r|
      r.get "user", :id do |id|
        id
      end
    end

    body("/user/johndoe").must_equal 'johndoe'
  end

  it "yields an integer segment as a string when using symbol matcher" do
    app do |r|
      r.get "user", :id do |id|
        id
      end
    end

    body("/user/101").must_equal '101'
  end

  it "yields a segment per nested block for symbol matcher" do
    app do |r|
      r.on :one do |one|
        r.on :two do |two|
          r.on :three do |three|
            one + two + three
          end
        end
      end
    end

    body("/one/two/three").must_equal "onetwothree"
  end

  it "yields a segment per argument for symbol matcher" do
    app do |r|
      r.on :one, :two, :three do |one, two, three|
        one + two + three
      end
    end

    body("/one/two/three").must_equal "onetwothree"
  end

  it "yields regex captures as separate arguments" do
    app do |r|
      r.get %r{posts/(\d+)-(.*)} do |id, slug|
        id + slug
      end
    end

    body("/posts/123-postal-service").must_equal "123postal-service"
  end

  it "yields an integer segment as an integer segment over 100 bytes when using Integer matcher" do
    app do |r|
      r.get "user", Integer do |id|
        "#{id}-#{id.is_a?(Integer)}"
      end
      "b"
    end

    body("/user/101").must_equal '101-true'
    body("/user/a").must_equal 'b'

    body("/user/"+"1"*100).must_equal '1'*100+'-true'
    body("/user/"+"1"*101).must_equal 'b'
  end

  it "yields the segment for String class matcher" do
    app do |r|
      r.get "user", String do |id|
        id
      end
    end

    body("/user/johndoe").must_equal 'johndoe'
  end

  it "raises error for unsupported class matcher" do
    app do |r|
      r.get Hash do |id|
        id
      end
    end

    proc{status}.must_raise Roda::RodaError
  end
end

describe "r.is" do 
  it "ensures the patch is matched fully" do
    app do |r|
      r.is "" do
        "+1"
      end
    end

    body.must_equal '+1'
    status('//').must_equal 404
  end

  it "handles no arguments" do
    app do |r|
      r.on "" do
        r.is do
          "+1"
        end
      end
    end

    body.must_equal '+1'
    status('//').must_equal 404
  end

  it "matches strings" do
    app do |r|
      r.is "123" do
        "+1"
      end
    end

    body("/123").must_equal '+1'
    status("/123/").must_equal 404
  end

  it "matches regexps" do
    app do |r|
      r.is(/(\w+)/) do |id|
        id
      end
    end

    body("/123").must_equal '123'
    status("/123/").must_equal 404
  end

  it "matches regexps" do
    app do |r|
      r.on(/foo/) do |id|
        'a'
      end
    end

    body("/foo").must_equal 'a'
    body("/foo/").must_equal 'a'
    status("/food").must_equal 404
  end

  it "matches segments" do
    app do |r|
      r.is :id do |id|
        id
      end
    end

    body("/123").must_equal '123'
    status("/123/").must_equal 404
  end
end

describe "matchers" do
  it "should not handle string with embedded param if :verbatim_string_matcher option is set" do
    app do |r|
      r.on "posts/:id" do
        '1'
      end

      r.on "responses-:id" do
        '2'
      end
    end

    status('/post/123').must_equal 404
    status('/posts/123').must_equal 404
    body('/posts/:id').must_equal '1'
    status('/responses-123').must_equal 404
    body('/responses-:id').must_equal '2'
  end

  it "should handle regexes and nesting" do
    app do |r|
      r.on(/u\/(\w+)/) do |uid|
        r.on(/posts\/(\d+)/) do |id|
          uid + id
        end
      end
    end

    body("/u/jdoe/posts/123").must_equal 'jdoe123'
    status("/u/jdoe/pots/123").must_equal 404
  end

  it "should handle regex nesting colon param style" do
    app do |r|
      r.on(/u:(\w+)/) do |uid|
        r.on(/posts:(\d+)/) do |id|
          uid + id
        end
      end
    end

    body("/u:jdoe/posts:123").must_equal 'jdoe123'
    status("/u:jdoe/poss:123").must_equal 404
  end unless ENV['LINT']

  it "symbol matching" do
    app do |r|
      r.on "user", :id do |uid|
        r.on "posts", :pid do |id|
          uid + id
        end
      end
    end

    body("/user/jdoe/posts/123").must_equal 'jdoe123'
    status("/user/jdoe/pots/123").must_equal 404
  end

  it "paths and numbers" do
    app do |r|
      r.on "about" do
        r.on :one, :two do |one, two|
          one + two
        end
      end
    end

    body("/about/1/2").must_equal '12'
    status("/about/1").must_equal 404
  end

  it "paths and decimals" do
    app do |r|
     r.on "about" do
        r.on(/(\d+)/) do |one|
          one
        end
      end
    end

    body("/about/1").must_equal '1'
    status("/about/1.2").must_equal 404
  end

  it "should allow arrays to match any value" do
    app do |r|
      r.on [/(\d+)/, /\d+(bar)?/] do |id|
        id
      end
    end

    body('/123').must_equal '123'
    body('/123bar').must_equal 'bar'
    status('/123bard').must_equal 404
  end

  it "should allow arrays to match optional segments with splats" do
    app do |r|
      r.on :foo, [:bar, true] do |*m|
        m.inspect
      end
    end

    body('/123').must_equal '["123"]'
    body('/123/456').must_equal '["123", "456"]'
  end

  it "should allow arrays to match optional segments with separate arguments" do
    app do |r|
      r.on :foo, [:bar, true] do |f, b|
        [f, b].inspect
      end
    end

    body('/123').must_equal '["123", nil]'
    body('/123/456').must_equal '["123", "456"]'
  end

  it "should allow regexp to match optional segments with separate arguments" do
    app do |r|
      r.on(/(\d+)(?:\/(\d+))?/) do |f, b|
        [f, b].inspect
      end
    end

    body('/123').must_equal '["123", nil]'
    body('/123/456').must_equal '["123", "456"]'
  end

  it "should have array capture match string if match" do
    app do |r|
      r.on %w'p q' do |id|
        id
      end
    end

    body('/p').must_equal 'p'
    body('/q').must_equal 'q'
    status('/r').must_equal 404
  end

  it "should have set return next segment if one of the set's elements" do
    app do |r|
      r.on Set['p', 'q'] do |id|
        id
      end
    end

    body('/p').must_equal 'p'
    body('/q').must_equal 'q'
    status('/r').must_equal 404
  end
end

describe "r.on" do 
  it "executes on no arguments" do
    app do |r|
      r.on do
        "+1"
      end
    end

    body.must_equal '+1'
  end

  it "executes on true" do
    app do |r|
      r.on true do
        "+1"
      end
    end

    body.must_equal '+1'
  end

  it "does not execute on false" do
    app do |r|
      r.on false do
        "+1"
      end
    end

    status.must_equal 404
  end

  it "does not execute on nil" do
    app do |r|
      r.on nil do
        "+1"
      end
    end

    status.must_equal 404
  end

  it "raises on arbitrary object" do
    app(:bare) do 
      route do |r|
        r.on Object.new do
          "+1"
        end
      end
    end

    proc{body}.must_raise Roda::RodaError
  end

  it "executes on non-false" do
    app do |r|
      r.on "123" do
        "+1"
      end
    end

    body("/123").must_equal '+1'
  end

  it "does not modify SCRIPT_NAME/PATH_INFO during routing" do
    app(:pass) do |r|
      r.on "foo" do
        r.is "bar" do
          "bar|#{env['SCRIPT_NAME']}|#{env['PATH_INFO']}"
        end
        r.is "baz" do
          r.pass
        end
        "foo|#{env['SCRIPT_NAME']}|#{env['PATH_INFO']}"
      end
      "#{env['SCRIPT_NAME']}|#{env['PATH_INFO']}"
    end

    body.must_equal '|/'
    body('SCRIPT_NAME'=>'/a').must_equal '/a|/'
    body('/foo').must_equal 'foo||/foo'
    body('/foo', 'SCRIPT_NAME'=>'/a').must_equal 'foo|/a|/foo'
    body('/foo/bar').must_equal 'bar||/foo/bar'
    body('/foo/bar', 'SCRIPT_NAME'=>'/a').must_equal 'bar|/a|/foo/bar'
    body('/foo/baz').must_equal 'foo||/foo/baz'
    body('/foo/baz', 'SCRIPT_NAME'=>'/a').must_equal 'foo|/a|/foo/baz'
  end

  it "should have path/matched_path/remaining_path work correctly" do
    app do |r|
      r.on "foo" do
        "#{r.path}:#{r.matched_path}:#{r.remaining_path}"
      end
    end

    body("/foo/bar").must_equal  "/foo/bar:/foo:/bar"
  end

  it "ensures remaining_path is reverted if modified in failing matcher" do
    app do |r|
      r.on lambda { @remaining_path = "/blah"; false } do
        "Unreachable"
      end
      
      r.on do
        r.matched_path + ':' + r.remaining_path
      end
    end

    body("/hello").must_equal ':/hello'
  end

  it "modifies matched_path/remaining_path during routing" do
    app do |r|
      r.on 'login', 'foo' do 
        "Unreachable"
      end
      
      r.on 'hello' do
        r.matched_path + ':' + r.remaining_path
      end
    end

    body("/hello/you").must_equal '/hello:/you'
  end

  it "doesn't modify SCRIPT_NAME/PATH_INFO during routing" do
    app do |r|
      r.on 'login', 'foo' do 
        "Unreachable"
      end
      
      r.on 'hello' do
        r.env["SCRIPT_NAME"] + ':' + r.env["PATH_INFO"]
      end
    end

    body("/hello/you").must_equal ':/hello/you'
  end

  it "doesn't mutate SCRIPT_NAME or PATH_INFO after request is returned" do
    app do |r|
      r.on 'login', 'foo' do 
        "Unreachable"
      end
      
      r.on do
        r.env["SCRIPT_NAME"] + ':' + r.env["PATH_INFO"]
      end
    end

    pi, sn = '/login', ''
    env = {"REQUEST_METHOD" => "GET", "PATH_INFO" => pi, "SCRIPT_NAME" => sn}
    _req(app, env)[2].join.must_equal ":/login"
    env["PATH_INFO"].must_equal pi 
    env["SCRIPT_NAME"].must_equal sn
  end

  it "skips consecutive matches" do
    app do |r|
      r.on do
        "foo"
      end

      r.on do
        "bar"
      end
    end

    body.must_equal "foo"
  end

  it "finds first match available" do
    app do |r|
      r.on false do
        "foo"
      end

      r.on do
        "bar"
      end
    end

    body.must_equal "bar"
  end

  it "reverts a half-met matcher" do
    app do |r|
      r.on "post", false do
        "Should be unmet"
      end

      r.on do
        r.env["SCRIPT_NAME"] + ':' + r.env["PATH_INFO"]
      end
    end

    body("/hello").must_equal ':/hello'
  end

  it "doesn't write to body if body already written to" do
    app do |r|
      r.on do
        response.write "a"
        "b"
      end
    end

    body.must_equal 'a'
  end
end

describe "path matchers" do 
  it "one level path" do
    app do |r|
      r.on "about" do
        "About"
      end
    end

    body('/about').must_equal "About"
    status("/abot").must_equal 404
  end

  it "two level nested paths" do
    app do |r|
      r.on "about" do
        r.on "1" do
          "+1"
        end

        r.on "2" do
          "+2"
        end
      end
    end

    body('/about/1').must_equal "+1"
    body('/about/2').must_equal "+2"
    status('/about/3').must_equal 404
  end

  it "two level inlined paths" do
    app do |r|
      r.on "a/b" do
        "ab"
      end
    end

    body('/a/b').must_equal "ab"
    status('/a/d').must_equal 404
  end

  it "a path with some regex captures" do
    app do |r|
      r.on(/user(\d+)/) do |uid|
        uid
      end
    end

    body('/user123').must_equal "123"
    status('/useradf').must_equal 404
  end

  it "matching the root with a string" do
    app do |r|
      r.is "" do
        "Home"
      end
    end

    body.must_equal 'Home'
    status("//").must_equal 404
    status("/foo").must_equal 404
  end

  it "matching the root with the root method" do
    app do |r|
      r.root do
        "Home"
      end
    end

    body.must_equal 'Home'
    status('REQUEST_METHOD'=>'POST').must_equal 404
    status("//").must_equal 404
    status("/foo").must_equal 404
  end
end

describe "root/empty segment matching" do
  it "matching an empty segment" do
    app do |r|
      r.on "" do
        r.path
      end
    end

    body.must_equal '/'
    status("/foo").must_equal 404
  end

  it "nested empty segments" do
    app do |r|
      r.on "" do
        r.on "" do
          r.on "1" do
            r.path
          end
        end
      end
    end

    body("///1").must_equal '///1'
    status("/1").must_equal 404
    status("//1").must_equal 404
  end

  it "/events/? scenario" do
    a = app do |r|
      r.on "" do
        "Hooray"
      end

      r.is do
        "Foo"
      end
    end

    app(:new) do |r|
      r.on "events" do
        r.run a
      end
    end

    unless_lint do
      body("/events").must_equal 'Foo'
    end
    body("/events/").must_equal 'Hooray'
    status("/events/foo").must_equal 404
  end
end

describe "segment handling" do
  before do
    app do |r|
      r.on "post" do
        r.on :id do |id|
          id
        end
      end
    end
  end

  it "matches numeric ids" do
    body('/post/1').must_equal '1'
  end

  it "matches decimal numbers" do
    body('/post/1.1').must_equal '1.1'
  end

  it "matches slugs" do
    body('/post/my-blog-post-about-cuba').must_equal 'my-blog-post-about-cuba'
  end

  it "matches only the first segment available" do
    body('/post/one/two/three').must_equal 'one'
  end
end

describe "request verb methods" do 
  it "executes if verb matches" do
    app do |r|
      r.get do
        "g"
      end
      r.post do
        "p"
      end
    end

    body.must_equal 'g'
    body('REQUEST_METHOD'=>'POST').must_equal 'p'
  end

  it "requires exact match if given arguments" do
    app do |r|
      r.get "" do
        "g"
      end
      r.post "" do
        "p"
      end
    end

    body.must_equal 'g'
    body('REQUEST_METHOD'=>'POST').must_equal 'p'
    status("/a").must_equal 404
    status("/a", 'REQUEST_METHOD'=>'POST').must_equal 404
  end

  it "does not require exact match if given arguments" do
    app do |r|
      r.get do
        r.is "" do
          "g"
        end

        "get"
      end
      r.post do
        r.is "" do
          "p"
        end

        "post"
      end
    end

    body.must_equal 'g'
    body('REQUEST_METHOD'=>'POST').must_equal 'p'
    body("/a").must_equal 'get'
    body("/a", 'REQUEST_METHOD'=>'POST').must_equal 'post'
  end
end

describe "all matcher" do
  it "should match only all all arguments match" do
    app do |r|
      r.is :all=>['foo', :y] do |file|
        file
      end
    end

    body("/foo/bar").must_equal 'bar'
    status.must_equal 404
    status("/foo").must_equal 404
    status("/foo/").must_equal 404
    status("/foo/bar/baz").must_equal 404
  end
end

describe "method matcher" do
  it "should match given request types" do
    app do |r|
      r.is "", :method=>:get do
        "foo"
      end
      r.is "", :method=>[:patch, :post] do
        "bar"
      end
    end

    body("REQUEST_METHOD"=>"GET").must_equal 'foo'
    body("REQUEST_METHOD"=>"PATCH").must_equal 'bar'
    body("REQUEST_METHOD"=>"POST").must_equal 'bar'
    status("REQUEST_METHOD"=>"DELETE").must_equal 404
  end
end

describe "route block that returns string" do
  it "should be treated as if an on block returned string" do
    app do |r|
      "+1"
    end

    body.must_equal '+1'
  end
end

describe "app with :unsupported_block_result => :raise option" do
  def app_value(v)
    app(:bare) do
      opts[:unsupported_block_result] = :raise
      route do |r|
        r.is 'a' do v end
        v
      end
    end
  end

  it "should handle String as body" do
    app_value '1'
    status.must_equal 200
    body.must_equal '1'
    status('/a').must_equal 200
    body('/a').must_equal '1'
  end

  it "should handle nil and false as not found" do
    app_value nil
    status.must_equal 404
    body.must_equal ''
    status('/a').must_equal 404
    body('/a').must_equal ''
  end

  it "should handle false as not found" do
    app_value false
    status.must_equal 404
    body.must_equal ''
    status('/a').must_equal 404
    body('/a').must_equal ''
  end

  it "should raise RodaError for other types" do
    app_value Object.new
    proc{body}.must_raise Roda::RodaError
    proc{body('/a')}.must_raise Roda::RodaError
  end
end
