require "uri-test"
local URI = require "uri"

module("test.generic", lunit.testcase, package.seeall)

function test_normalize_percent_encoding ()
    -- Don't use unnecessary percent encoding for unreserved characters.
    test_norm("x:ABCDEFGHIJKLM", "x:%41%42%43%44%45%46%47%48%49%4A%4b%4C%4d")
    test_norm("x:NOPQRSTUVWXYZ", "x:%4E%4f%50%51%52%53%54%55%56%57%58%59%5A")
    test_norm("x:abcdefghijklm", "x:%61%62%63%64%65%66%67%68%69%6A%6b%6C%6d")
    test_norm("x:nopqrstuvwxyz", "x:%6E%6f%70%71%72%73%74%75%76%77%78%79%7A")
    test_norm("x:0123456789", "x:%30%31%32%33%34%35%36%37%38%39")
    test_norm("x:-._~", "x:%2D%2e%5F%7e")

    -- Keep percent encoding for other characters in US-ASCII.
    test_norm_already("x:%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F")
    test_norm_already("x:%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F")
    test_norm_already("x:%20%21%22%23%24%25%26%27%28%29%2A%2B%2C")
    test_norm_already("x:%2F")
    test_norm_already("x:%3A%3B%3C%3D%3E%3F%40")
    test_norm_already("x:%5B%5C%5D%5E")
    test_norm_already("x:%60")
    test_norm_already("x:%7B%7C%7D")
    test_norm_already("x:%7F")

    -- Normalize hex digits in percent encoding to uppercase.
    test_norm("x:%0A%0B%0C%0D%0E%0F", "x:%0a%0b%0c%0d%0e%0f")
    test_norm("x:%AA%BB%CC%DD%EE%FF", "x:%aA%bB%cC%dD%eE%fF")

    -- Keep percent encoding, and normalize hex digit case, for all characters
    -- outside US-ASCII.
    for i = 0x80, 0xFF do
        test_norm_already(string.format("x:%%%02X", i))
        test_norm(string.format("x:%%%02X", i), string.format("x:%%%02x", i))
    end
end

function test_bad_percent_encoding ()
    assert_error("double percent", function () URI:new("x:foo%%2525") end)
    assert_error("no hex digits", function () URI:new("x:foo%") end)
    assert_error("no hex digits 2nd time", function () URI:new("x:f%20o%") end)
    assert_error("1 hex digit", function () URI:new("x:foo%2") end)
    assert_error("1 hex digit 2nd time", function () URI:new("x:f%20o%2") end)
    assert_error("bad hex digit 1", function () URI:new("x:foo%G2bar") end)
    assert_error("bad hex digit 2", function () URI:new("x:foo%2Gbar") end)
    assert_error("bad hex digit both", function () URI:new("x:foo%GGbar") end)
end

function test_scheme ()
    test_norm_already("foo:")
    test_norm_already("foo:-+.:")
    test_norm_already("foo:-+.0123456789:")
    test_norm_already("x:")
    test_norm("example:FooBar:Baz", "ExAMplE:FooBar:Baz")

    local uri = assert(URI:new("Foo-Bar:Baz%20Quux"))
    is("foo-bar", uri:scheme())
end

function test_change_scheme ()
    local uri = assert(URI:new("x-foo://example.com/blah"))
    is("x-foo://example.com/blah", tostring(uri))
    is("x-foo", uri:scheme())
    is("uri", uri._NAME)

    -- x-foo -> x-bar
    is("x-foo", uri:scheme("x-bar"))
    is("x-bar", uri:scheme())
    is("x-bar://example.com/blah", tostring(uri))
    is("uri", uri._NAME)

    -- x-bar -> http
    is("x-bar", uri:scheme("http"))
    is("http", uri:scheme())
    is("http://example.com/blah", tostring(uri))
    is("uri.http", uri._NAME)

    -- http -> x-foo
    is("http", uri:scheme("x-foo"))
    is("x-foo", uri:scheme())
    is("x-foo://example.com/blah", tostring(uri))
    is("uri", uri._NAME)
end

function test_change_scheme_bad ()
    local uri = assert(URI:new("x-foo://foo@bar/"))

    -- Try changing the scheme to something invalid
    assert_error("bad scheme '-x-foo'", function () uri:scheme("-x-foo") end)
    assert_error("bad scheme 'x,foo'", function () uri:scheme("x,foo") end)
    assert_error("bad scheme 'x:foo'", function () uri:scheme("x:foo") end)
    assert_error("bad scheme 'x-foo:'", function () uri:scheme("x-foo:") end)

    -- Change to valid scheme, but where the rest of the URI is not valid for it
    assert_error("bad HTTP URI", function () uri:scheme("http") end)

    -- Original URI should be left unchanged
    is("x-foo://foo@bar/", tostring(uri))
    is("x-foo", uri:scheme())
    is("uri", uri._NAME)
end

function test_auth_userinfo ()
    local uri = assert(URI:new("X://a-zA-Z09!$:&%40@FOO.com:80/"))
    is("x://a-zA-Z09!$:&%40@foo.com:80/", tostring(uri))
    is("x", uri:scheme())
    is("a-zA-Z09!$:&%40", uri:userinfo())
    is("foo.com", uri:host())
    is(80, uri:port())
end

function test_auth_userinfo_bad ()
    is_bad_uri("bad character in userinfo", "x-a://foo^bar@example.com/")
end

function test_auth_set_userinfo ()
    local uri = assert(URI:new("X-foo://user:pass@FOO.com:80/"))
    is("user:pass", uri:userinfo("newuserinfo"))
    is("newuserinfo", uri:userinfo())
    is("x-foo://newuserinfo@foo.com:80/", tostring(uri))

    -- Userinfo should be supplied already percent-encoded, but the percent
    -- encoding should be normalized.
    is("newuserinfo", uri:userinfo("foo%3abar%3A:%78"))
    is("foo%3Abar%3A:x", uri:userinfo())

    -- It should be OK to use more than one colon in userinfo for generic URIs,
    -- although not for ones which specificly divide it into username:password.
    is("foo%3Abar%3A:x", uri:userinfo("foo:bar:baz::"))
    is("foo:bar:baz::", uri:userinfo())
end

function test_auth_set_bad_userinfo ()
    local uri = assert(URI:new("X-foo://user:pass@FOO.com:80/"))
    assert_error("/ in userinfo", function () uri:userinfo("foo/bar") end)
    assert_error("@ in userinfo", function () uri:userinfo("foo@bar") end)
    is("user:pass", uri:userinfo())
    is("x-foo://user:pass@foo.com:80/", tostring(uri))
end

function test_auth_reg_name ()
    local uri = assert(URI:new("x://azAZ0-9--foo.bqr_baz~%20!$;/"))
    -- TODO - %20 should probably be rejected.  Apparently only UTF-8 pctenc
    -- should be produced, so after unescaping unreserved chars there should
    -- be nothing left percent encoded other than valid UTF-8 sequences.  If
    -- that's right I could safely decode the host before returning it.
    is("azaz0-9--foo.bqr_baz~%20!$;", uri:host())
end

function test_auth_ip4 ()
    local uri = assert(URI:new("x://0.0.0.0/path"))
    is("0.0.0.0", uri:host())
    uri = assert(URI:new("x://192.168.0.1/path"))
    is("192.168.0.1", uri:host())
    uri = assert(URI:new("x://255.255.255.255/path"))
    is("255.255.255.255", uri:host())
end

function test_auth_ip4_or_reg_name_bad ()
    is_bad_uri("bad character in host part", "x://foo:bar/")
end

function test_auth_ip6 ()
    -- The example addresses in here are all from RFC 4291 section 2.2, except
    -- that they get normalized to lowercase here in the results.
    local uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]"))
    is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
    uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]/"))
    is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
    uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:"))
    is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
    uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:/"))
    is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
    uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:0/"))
    is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
    uri = assert(URI:new("x://y:z@[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:80/"))
    is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
    uri = assert(URI:new("x://[2001:DB8:0:0:8:800:200C:417A]/"))
    is("[2001:db8:0:0:8:800:200c:417a]", uri:host())
    uri = assert(URI:new("x://[FF01:0:0:0:0:0:0:101]/"))
    is("[ff01:0:0:0:0:0:0:101]", uri:host())
    uri = assert(URI:new("x://[ff01::101]/"))
    is("[ff01::101]", uri:host())
    uri = assert(URI:new("x://[0:0:0:0:0:0:0:1]/"))
    is("[0:0:0:0:0:0:0:1]", uri:host())
    uri = assert(URI:new("x://[::1]/"))
    is("[::1]", uri:host())
    uri = assert(URI:new("x://[0:0:0:0:0:0:0:0]/"))
    is("[0:0:0:0:0:0:0:0]", uri:host())
    uri = assert(URI:new("x://[0:0:0:0:0:0:13.1.68.3]/"))
    is("[0:0:0:0:0:0:13.1.68.3]", uri:host())
    uri = assert(URI:new("x://[::13.1.68.3]/"))
    is("[::13.1.68.3]", uri:host())
    uri = assert(URI:new("x://[0:0:0:0:0:FFFF:129.144.52.38]/"))
    is("[0:0:0:0:0:ffff:129.144.52.38]", uri:host())
    uri = assert(URI:new("x://[::FFFF:129.144.52.38]/"))
    is("[::ffff:129.144.52.38]", uri:host())

    -- These try all the cominations of abbreviating using '::'.
    uri = assert(URI:new("x://[08:19:2a:3B:4c:5D:6e:7F]/"))
    is("[08:19:2a:3b:4c:5d:6e:7f]", uri:host())
    uri = assert(URI:new("x://[::19:2a:3B:4c:5D:6e:7F]/"))
    is("[::19:2a:3b:4c:5d:6e:7f]", uri:host())
    uri = assert(URI:new("x://[::2a:3B:4c:5D:6e:7F]/"))
    is("[::2a:3b:4c:5d:6e:7f]", uri:host())
    uri = assert(URI:new("x://[::3B:4c:5D:6e:7F]/"))
    is("[::3b:4c:5d:6e:7f]", uri:host())
    uri = assert(URI:new("x://[::4c:5D:6e:7F]/"))
    is("[::4c:5d:6e:7f]", uri:host())
    uri = assert(URI:new("x://[::5D:6e:7F]/"))
    is("[::5d:6e:7f]", uri:host())
    uri = assert(URI:new("x://[::6e:7F]/"))
    is("[::6e:7f]", uri:host())
    uri = assert(URI:new("x://[::7F]/"))
    is("[::7f]", uri:host())
    uri = assert(URI:new("x://[::]/"))
    is("[::]", uri:host())
    uri = assert(URI:new("x://[08::]/"))
    is("[08::]", uri:host())
    uri = assert(URI:new("x://[08:19::]/"))
    is("[08:19::]", uri:host())
    uri = assert(URI:new("x://[08:19:2a::]/"))
    is("[08:19:2a::]", uri:host())
    uri = assert(URI:new("x://[08:19:2a:3B::]/"))
    is("[08:19:2a:3b::]", uri:host())
    uri = assert(URI:new("x://[08:19:2a:3B:4c::]/"))
    is("[08:19:2a:3b:4c::]", uri:host())
    uri = assert(URI:new("x://[08:19:2a:3B:4c:5D::]/"))
    is("[08:19:2a:3b:4c:5d::]", uri:host())
    uri = assert(URI:new("x://[08:19:2a:3B:4c:5D:6e::]/"))
    is("[08:19:2a:3b:4c:5d:6e::]", uri:host())

    -- Try extremes of good IPv4 addresses mapped to IPv6.
    uri = assert(URI:new("x://[::FFFF:0.0.0.0]/path"))
    is("[::ffff:0.0.0.0]", uri:host())
    uri = assert(URI:new("x://[::ffff:255.255.255.255]/path"))
    is("[::ffff:255.255.255.255]", uri:host())
end

function test_auth_ip6_bad ()
    is_bad_uri("empty brackets", "x://[]")
    is_bad_uri("just colon", "x://[:]")
    is_bad_uri("3 colons only", "x://[:::]")
    is_bad_uri("3 colons at start", "x://[:::1234]")
    is_bad_uri("3 colons at end", "x://[1234:::]")
    is_bad_uri("3 colons in middle", "x://[1234:::5678]")
    is_bad_uri("non-hex char", "x://[ABCD:EF01:2345:6789:ABCD:EG01:2345:6789]")
    is_bad_uri("chunk too big",
               "x://[ABCD:EF01:2345:6789:ABCD:EFF01:2345:6789]")
    is_bad_uri("too many chunks",
               "x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789:1]")
    is_bad_uri("not enough chunks", "x://[ABCD:EF01:2345:6789:ABCD:EF01:2345]")
    is_bad_uri("too many chunks with ellipsis in middle",
               "x://[ABCD:EF01:2345:6789:ABCD::EF01:2345:6789]")
    is_bad_uri("too many chunks with ellipsis at end",
               "x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789::]")
    is_bad_uri("too many chunks with ellipsis at start",
               "x://[::ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]")
    is_bad_uri("two elipses, middle and end",
               "x://[EF01:2345::6789:ABCD:EF01:2345::]")
    is_bad_uri("two elipses, start and middle",
               "x://[::EF01:2345::6789:ABCD:EF01:2345]")
    is_bad_uri("two elipses, both ends",
               "x://[::EF01:2345:6789:ABCD:EF01:2345::]")
    is_bad_uri("two elipses, both middle",
               "x://[EF01:2345::6789:ABCD:::EF01:2345]")
    is_bad_uri("extra colon at start",
               "x://[:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]")
    is_bad_uri("missing chunk at start",
               "x://[:EF01:2345:6789:ABCD:EF01:2345:6789]")
    is_bad_uri("extra colon at end",
               "x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789:]")
    is_bad_uri("missing chunk at end",
               "x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:]")

    -- Bad IPv4 addresses mapped to IPv6.
    is_bad_uri("octet 1 too big", "x://[::FFFF:256.2.3.4]/")
    is_bad_uri("octet 2 too big", "x://[::FFFF:1.256.3.4]/")
    is_bad_uri("octet 3 too big", "x://[::FFFF:1.2.256.4]/")
    is_bad_uri("octet 4 too big", "x://[::FFFF:1.2.3.256]/")
    is_bad_uri("octet 1 leading zeroes", "x://[::FFFF:01.2.3.4]/")
    is_bad_uri("octet 2 leading zeroes", "x://[::FFFF:1.02.3.4]/")
    is_bad_uri("octet 3 leading zeroes", "x://[::FFFF:1.2.03.4]/")
    is_bad_uri("octet 4 leading zeroes", "x://[::FFFF:1.2.3.04]/")
    is_bad_uri("only 2 octets", "x://[::FFFF:1.2]/")
    is_bad_uri("only 3 octets", "x://[::FFFF:1.2.3]/")
    is_bad_uri("5 octets", "x://[::FFFF:1.2.3.4.5]/")
end

function test_auth_ipvfuture ()
    local uri = assert(URI:new("x://[v123456789ABCdef.foo=bar]/"))
    is("[v123456789abcdef.foo=bar]", uri:host())
end

function test_auth_ipvfuture_bad ()
    is_bad_uri("missing dot", "x://[v999]")
    is_bad_uri("missing hex num", "x://[v.foo]")
    is_bad_uri("missing bit after dot", "x://[v999.]")
    is_bad_uri("bad character in hex num", "x://[v99g.foo]")
    is_bad_uri("bad character after dot", "x://[v999.foo:bar]")
end

function test_auth_set_host ()
    local uri = assert(URI:new("x-a://host/path"))
    is("host", uri:host("FOO.BAR"))
    is("x-a://foo.bar/path", tostring(uri))
    is("foo.bar", uri:host("[::6e:7F]"))
    is("x-a://[::6e:7f]/path", tostring(uri))
    is("[::6e:7f]", uri:host("[v7F.foo=BAR]"))
    is("x-a://[v7f.foo=bar]/path", tostring(uri))
    is("[v7f.foo=bar]", uri:host(""))
    is("x-a:///path", tostring(uri))
    is("", uri:host(nil))
    is(nil, uri:host())
    is("x-a:/path", tostring(uri))
end

function test_auth_set_host_bad ()
    local uri = assert(URI:new("x-a://host/path"))
    assert_error("bad char in host", function () uri:host("foo^bar") end)
    assert_error("invalid IPv6 host", function () uri:host("[::3G]") end)
    assert_error("invalid IPvFuture host", function () uri:host("[v7.]") end)
    is("host", uri:host())
    is("x-a://host/path", tostring(uri))
    -- There must be a hsot when there is a userinfo or port.
    uri = assert(URI:new("x-a://foo@/"))
    assert_error("userinfo but no host", function () uri:host(nil) end)
    is("x-a://foo@/", tostring(uri))
    uri = assert(URI:new("x-a://:123/"))
    assert_error("port but no host", function () uri:host(nil) end)
    is("x-a://:123/", tostring(uri))
end

function test_auth_port ()
    local uri = assert(URI:new("x://localhost:0/path"))
    is(0, uri:port())
    uri = assert(URI:new("x://localhost:0"))
    is(0, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost:0"))
    is(0, uri:port())
    uri = assert(URI:new("x://localhost:00/path"))
    is(0, uri:port())
    uri = assert(URI:new("x://localhost:00"))
    is(0, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost:00"))
    is(0, uri:port())
    uri = assert(URI:new("x://localhost:54321/path"))
    is(54321, uri:port())
    uri = assert(URI:new("x://localhost:54321"))
    is(54321, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost:54321"))
    is(54321, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost:"))
    is(nil, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost:/"))
    is(nil, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost"))
    is(nil, uri:port())
    uri = assert(URI:new("x://foo:bar@localhost/"))
    is(nil, uri:port())
end

function test_auth_set_port ()
    -- Test unusual but valid values for port.
    local uri = assert(URI:new("x://localhost/path"))
    is(nil, uri:port("12345"))  -- string
    is(12345, uri:port())
    is("x://localhost:12345/path", tostring(uri))
    uri = assert(URI:new("x://localhost/path"))
    is(nil, uri:port(12345.0))  -- float
    is(12345, uri:port())
    is("x://localhost:12345/path", tostring(uri))
end

function test_auth_set_port_without_host ()
    local uri = assert(URI:new("x:///path"))
    is(nil, uri:port(80))
    is(80, uri:port())
    is("", uri:host())
    is("x://:80/path", tostring(uri))
    uri = assert(URI:new("x:/path"))
    is(nil, uri:port(80))
    is(80, uri:port())
    is("", uri:host())
    is("x://:80/path", tostring(uri))
end

function test_auth_set_port_bad ()
    local uri = assert(URI:new("x://localhost:54321/path"))
    assert_error("negative port number", function () uri:port(-23) end)
    assert_error("port not integer", function () uri:port(23.00001) end)
    assert_error("string not number", function () uri:port("x") end)
    assert_error("string not all number", function () uri:port("x23") end)
    assert_error("string negative number", function () uri:port("-23") end)
    assert_error("string empty", function () uri:port("") end)
    is(54321, uri:port())
    is("x://localhost:54321/path", tostring(uri))
end

function test_path ()
    local uri = assert(URI:new("x:"))
    is("", uri:path())
    uri = assert(URI:new("x:?"))
    is("", uri:path())
    uri = assert(URI:new("x:#"))
    is("", uri:path())
    uri = assert(URI:new("x:/"))
    is("/", uri:path())
    uri = assert(URI:new("x://"))
    is("", uri:path())
    uri = assert(URI:new("x://?"))
    is("", uri:path())
    uri = assert(URI:new("x://#"))
    is("", uri:path())
    uri = assert(URI:new("x:///"))
    is("/", uri:path())
    uri = assert(URI:new("x:////"))
    is("//", uri:path())
    uri = assert(URI:new("x:foo"))
    is("foo", uri:path())
    uri = assert(URI:new("x:/foo"))
    is("/foo", uri:path())
    uri = assert(URI:new("x://foo"))
    is("", uri:path())
    uri = assert(URI:new("x://foo?"))
    is("", uri:path())
    uri = assert(URI:new("x://foo#"))
    is("", uri:path())
    uri = assert(URI:new("x:///foo"))
    is("/foo", uri:path())
    uri = assert(URI:new("x:////foo"))
    is("//foo", uri:path())
    uri = assert(URI:new("x://foo/"))
    is("/", uri:path())
    uri = assert(URI:new("x://foo/bar"))
    is("/bar", uri:path())
end

function test_path_bad ()
    is_bad_uri("bad character in path", "x-a://host/^/")
end

function test_set_path_without_auth ()
    local uri = assert(URI:new("x:blah"))
    is("blah", uri:path("frob%25%3a%78/%2F"))
    is("frob%25%3Ax/%2F", uri:path("/foo/bar"))
    is("/foo/bar", uri:path("//foo//bar"))
    is("/%2Ffoo//bar", uri:path("x ?#\"\0\127\255"))
    is("x%20%3F%23%22%00%7F%FF", uri:path(""))
    is("", uri:path(nil))
    is("", uri:path())
    is("x:", tostring(uri))
end

function test_set_path_with_auth ()
    local uri = assert(URI:new("x://host/wibble"))
    is("/wibble", uri:path("/foo/bar"))
    is("/foo/bar", uri:path("//foo//bar"))
    is("//foo//bar", uri:path(nil))
    is("", uri:path(""))
    is("", uri:path())
    is("x://host", tostring(uri))
end

function test_set_path_bad ()
    local uri = assert(URI:new("x://host/wibble"))
    tostring(uri)
    assert_error("with authority, path must start with /",
                 function () uri:path("foo") end)
    assert_error("bad %-encoding, % at end", function () uri:path("foo%") end)
    assert_error("bad %-encoding, %2 at end", function () uri:path("foo%2") end)
    assert_error("bad %-encoding, %gf", function () uri:path("%gf") end)
    assert_error("bad %-encoding, %fg", function () uri:path("%fg") end)
    is("/wibble", uri:path())
    is("x://host/wibble", tostring(uri))
end

function test_query ()
    local uri = assert(URI:new("x:?"))
    is("", uri:query())
    uri = assert(URI:new("x:"))
    is(nil, uri:query())
    uri = assert(URI:new("x:/foo"))
    is(nil, uri:query())
    uri = assert(URI:new("x:/foo#"))
    is(nil, uri:query())
    uri = assert(URI:new("x:/foo#bar?baz"))
    is(nil, uri:query())
    uri = assert(URI:new("x:/foo?"))
    is("", uri:query())
    uri = assert(URI:new("x://foo?"))
    is("", uri:query())
    uri = assert(URI:new("x://foo/?"))
    is("", uri:query())
    uri = assert(URI:new("x:/foo?bar"))
    is("bar", uri:query())
    uri = assert(URI:new("x:?foo?bar?"))
    is("foo?bar?", uri:query())
    uri = assert(URI:new("x:?foo?bar?#quux?frob"))
    is("foo?bar?", uri:query())
    uri = assert(URI:new("x://foo/bar%3Fbaz?"))
    is("", uri:query())
    uri = assert(URI:new("x:%3F?foo"))
    is("%3F", uri:path())
    is("foo", uri:query())
end

function test_query_bad ()
    is_bad_uri("bad character in query", "x-a://host/path/?foo^bar")
end

function test_set_query ()
    local uri = assert(URI:new("x://host/path"))
    is(nil, uri:query("foo/bar?baz"))
    is("x://host/path?foo/bar?baz", tostring(uri))
    is("foo/bar?baz", uri:query(""))
    is("x://host/path?", tostring(uri))
    is("", uri:query("foo^bar#baz"))
    is("x://host/path?foo%5Ebar%23baz", tostring(uri))
    is("foo%5Ebar%23baz", uri:query(nil))
    is(nil, uri:query())
    is("x://host/path", tostring(uri))
end

function test_fragment ()
    local uri = assert(URI:new("x:"))
    is(nil, uri:fragment())
    uri = assert(URI:new("x:#"))
    is("", uri:fragment())
    uri = assert(URI:new("x://#"))
    is("", uri:fragment())
    uri = assert(URI:new("x:///#"))
    is("", uri:fragment())
    uri = assert(URI:new("x:////#"))
    is("", uri:fragment())
    uri = assert(URI:new("x:#foo"))
    is("foo", uri:fragment())
    uri = assert(URI:new("x:%23#foo"))
    is("%23", uri:path())
    is("foo", uri:fragment())
    uri = assert(URI:new("x:?foo?bar?#quux?frob"))
    is("quux?frob", uri:fragment())
end

function test_fragment_bad ()
    is_bad_uri("bad character in fragment", "x-a://host/path/#foo^bar")
end

function test_set_fragment ()
    local uri = assert(URI:new("x://host/path"))
    is(nil, uri:fragment("foo/bar#baz"))
    is("x://host/path#foo/bar%23baz", tostring(uri))
    is("foo/bar%23baz", uri:fragment(""))
    is("x://host/path#", tostring(uri))
    is("", uri:fragment("foo^bar?baz"))
    is("x://host/path#foo%5Ebar?baz", tostring(uri))
    is("foo%5Ebar?baz", uri:fragment(nil))
    is(nil, uri:fragment())
    is("x://host/path", tostring(uri))
end

function test_bad_usage ()
    assert_error("missing uri arg", function () URI:new() end)
    assert_error("nil uri arg", function () URI:new(nil) end)
end

function test_clone_with_new ()
    -- Test cloning with as many components set as possible.
    local uri = assert(URI:new("x-foo://user:pass@bar.com:123/blah?q#frag"))
    tostring(uri)
    local clone = URI:new(uri)
    assert_table(clone)
    is("x-foo://user:pass@bar.com:123/blah?q#frag", tostring(uri))
    is("x-foo://user:pass@bar.com:123/blah?q#frag", tostring(clone))
    is("uri", getmetatable(uri)._NAME)
    is("uri", getmetatable(clone)._NAME)

    -- Test cloning with less stuff specified, but not in the base class.
    uri = assert(URI:new("http://example.com/"))
    clone = URI:new(uri)
    assert_table(clone)
    is("http://example.com/", tostring(uri))
    is("http://example.com/", tostring(clone))
    is("uri.http", getmetatable(uri)._NAME)
    is("uri.http", getmetatable(clone)._NAME)
end

function test_set_uri ()
    local uri = assert(URI:new("x-foo://user:pass@bar.com:123/blah?q#frag"))
    is("x-foo://user:pass@bar.com:123/blah?q#frag",
       uri:uri("http://example.com:81/blah2?q2#frag2"))
    is("http://example.com:81/blah2?q2#frag2", uri:uri())
    is("uri.http", getmetatable(uri)._NAME)
    is("http", uri:scheme())
    is("q2", uri:query())
    is("http://example.com:81/blah2?q2#frag2", uri:uri("Urn:X-FOO:bar"))
    is("uri.urn", getmetatable(uri)._NAME)
    is("x-foo", uri:nid())
    is("urn:x-foo:bar", tostring(uri))
end

function test_set_uri_bad ()
    local uri = assert(URI:new("x-foo://user:pass@bar.com:123/blah?q#frag"))
    assert_error("can't set URI to nil", function () uri:uri(nil) end)
    assert_error("invalid authority", function () uri:uri("foo://@@") end)
    is("x-foo://user:pass@bar.com:123/blah?q#frag", uri:uri())
    is("uri", getmetatable(uri)._NAME)
    is("x-foo", uri:scheme())
end

function test_eq ()
    local uri1str, uri2str = "x-a://host/foo", "x-a://host/bar"
    local uri1obj, uri2obj = assert(URI:new(uri1str)), assert(URI:new(uri2str))
    assert_true(URI.eq(uri1str, uri1str), "str == str")
    assert_false(URI.eq(uri1str, uri2str), "str ~= str")
    assert_true(URI.eq(uri1str, uri1obj), "str == obj")
    assert_false(URI.eq(uri1str, uri2obj), "str ~= obj")
    assert_true(URI.eq(uri1obj, uri1str), "obj == str")
    assert_false(URI.eq(uri1obj, uri2str), "obj ~= str")
    assert_true(URI.eq(uri1obj, uri1obj), "obj == obj")
    assert_false(URI.eq(uri1obj, uri2obj), "obj ~= obj")
end

function test_eq_bad_uri ()
    -- Check that an exception is thrown when 'eq' is given a bad URI string,
    -- and also that it's not just the error from trying to call the 'uri'
    -- method on nil, because that won't be very helpful to the caller.
    local ok, err = pcall(URI.eq, "^", "x-a://x/")
    assert_false(ok)
    assert_not_match("a nil value", err)
    ok, err = pcall(URI.eq, "x-a://x/", "^")
    assert_false(ok)
    assert_not_match("a nil value", err)
end

-- vi:ts=4 sw=4 expandtab
