1Code.require_file("test_helper.exs", __DIR__) 2 3defmodule URITest do 4 use ExUnit.Case, async: true 5 6 doctest URI 7 8 test "encode/1,2" do 9 assert URI.encode("4_test.is-s~") == "4_test.is-s~" 10 11 assert URI.encode("\r\n&<%>\" ゆ", &URI.char_unreserved?/1) == 12 "%0D%0A%26%3C%25%3E%22%20%E3%82%86" 13 end 14 15 test "encode_www_form/1" do 16 assert URI.encode_www_form("4test ~1.x") == "4test+~1.x" 17 assert URI.encode_www_form("poll:146%") == "poll%3A146%25" 18 assert URI.encode_www_form("/\n+/ゆ") == "%2F%0A%2B%2F%E3%82%86" 19 end 20 21 test "encode_query/1,2" do 22 assert URI.encode_query([{:foo, :bar}, {:baz, :quux}]) == "foo=bar&baz=quux" 23 assert URI.encode_query([{"foo", "bar"}, {"baz", "quux"}]) == "foo=bar&baz=quux" 24 25 assert URI.encode_query([{"foo z", :bar}]) == "foo+z=bar" 26 assert URI.encode_query([{"foo z", :bar}], :rfc3986) == "foo%20z=bar" 27 assert URI.encode_query([{"foo z", :bar}], :www_form) == "foo+z=bar" 28 29 assert URI.encode_query([{"foo[]", "+=/?&# Ñ"}]) == 30 "foo%5B%5D=%2B%3D%2F%3F%26%23+%C3%91" 31 32 assert URI.encode_query([{"foo[]", "+=/?&# Ñ"}], :rfc3986) == 33 "foo%5B%5D=%2B%3D%2F%3F%26%23%20%C3%91" 34 35 assert URI.encode_query([{"foo[]", "+=/?&# Ñ"}], :www_form) == 36 "foo%5B%5D=%2B%3D%2F%3F%26%23+%C3%91" 37 38 assert_raise ArgumentError, fn -> 39 URI.encode_query([{"foo", 'bar'}]) 40 end 41 42 assert_raise ArgumentError, fn -> 43 URI.encode_query([{'foo', "bar"}]) 44 end 45 end 46 47 test "decode_query/1,2,3" do 48 assert URI.decode_query("", %{}) == %{} 49 50 assert URI.decode_query("safe=off", %{"cookie" => "foo"}) == 51 %{"safe" => "off", "cookie" => "foo"} 52 53 assert URI.decode_query("q=search%20query&cookie=ab%26cd&block+buster=") == 54 %{"block buster" => "", "cookie" => "ab&cd", "q" => "search query"} 55 56 assert URI.decode_query("q=search%20query&cookie=ab%26cd&block+buster=", %{}, :rfc3986) == 57 %{"block+buster" => "", "cookie" => "ab&cd", "q" => "search query"} 58 59 assert URI.decode_query("something=weird%3Dhappening") == %{"something" => "weird=happening"} 60 61 assert URI.decode_query("=") == %{"" => ""} 62 assert URI.decode_query("key") == %{"key" => ""} 63 assert URI.decode_query("key=") == %{"key" => ""} 64 assert URI.decode_query("=value") == %{"" => "value"} 65 assert URI.decode_query("something=weird=happening") == %{"something" => "weird=happening"} 66 end 67 68 test "query_decoder/1,2" do 69 decoder = URI.query_decoder("q=search%20query&cookie=ab%26cd&block+buster=") 70 expected = [{"q", "search query"}, {"cookie", "ab&cd"}, {"block buster", ""}] 71 assert Enum.map(decoder, & &1) == expected 72 73 decoder = URI.query_decoder("q=search%20query&cookie=ab%26cd&block+buster=", :rfc3986) 74 expected = [{"q", "search query"}, {"cookie", "ab&cd"}, {"block+buster", ""}] 75 assert Enum.map(decoder, & &1) == expected 76 end 77 78 test "decode/1" do 79 assert URI.decode("%0D%0A%26%3C%25%3E%22%20%E3%82%86") == "\r\n&<%>\" ゆ" 80 assert URI.decode("%2f%41%4a%55") == "/AJU" 81 assert URI.decode("4_t+st.is-s~") == "4_t+st.is-s~" 82 assert URI.decode("% invalid") == "% invalid" 83 assert URI.decode("invalid %") == "invalid %" 84 assert URI.decode("%%") == "%%" 85 end 86 87 test "decode_www_form/1" do 88 assert URI.decode_www_form("%3Eval+ue%2B") == ">val ue+" 89 assert URI.decode_www_form("%E3%82%86+") == "ゆ " 90 assert URI.decode_www_form("% invalid") == "% invalid" 91 assert URI.decode_www_form("invalid %") == "invalid %" 92 assert URI.decode_www_form("%%") == "%%" 93 end 94 95 describe "new/1" do 96 test "empty" do 97 assert URI.new("") == {:ok, %URI{}} 98 end 99 100 test "errors on bad URIs" do 101 assert URI.new("/>") == {:error, ">"} 102 assert URI.new(":https") == {:error, ":"} 103 assert URI.new("ht\0tps://foo.com") == {:error, "\0"} 104 end 105 end 106 107 describe "new!/1" do 108 test "returns the given URI if a %URI{} struct is given" do 109 assert URI.new!(uri = %URI{scheme: "http", host: "foo.com"}) == uri 110 end 111 112 test "works with HTTP scheme" do 113 expected_uri = %URI{ 114 scheme: "http", 115 host: "foo.com", 116 path: "/path/to/something", 117 query: "foo=bar&bar=foo", 118 fragment: "fragment", 119 port: 80, 120 userinfo: nil 121 } 122 123 assert URI.new!("http://foo.com/path/to/something?foo=bar&bar=foo#fragment") == 124 expected_uri 125 end 126 127 test "works with HTTPS scheme" do 128 expected_uri = %URI{ 129 scheme: "https", 130 host: "foo.com", 131 query: nil, 132 fragment: nil, 133 port: 443, 134 path: nil, 135 userinfo: nil 136 } 137 138 assert URI.new!("https://foo.com") == expected_uri 139 end 140 141 test "works with file scheme" do 142 expected_uri = %URI{ 143 scheme: "file", 144 host: "", 145 path: "/foo/bar/baz", 146 userinfo: nil, 147 query: nil, 148 fragment: nil, 149 port: nil 150 } 151 152 assert URI.new!("file:///foo/bar/baz") == expected_uri 153 end 154 155 test "works with FTP scheme" do 156 expected_uri = %URI{ 157 scheme: "ftp", 158 host: "private.ftp-server.example.com", 159 userinfo: "user001:password", 160 path: "/my_directory/my_file.txt", 161 query: nil, 162 fragment: nil, 163 port: 21 164 } 165 166 ftp = "ftp://user001:password@private.ftp-server.example.com/my_directory/my_file.txt" 167 assert URI.new!(ftp) == expected_uri 168 end 169 170 test "works with SFTP scheme" do 171 expected_uri = %URI{ 172 scheme: "sftp", 173 host: "private.ftp-server.example.com", 174 userinfo: "user001:password", 175 path: "/my_directory/my_file.txt", 176 query: nil, 177 fragment: nil, 178 port: 22 179 } 180 181 sftp = "sftp://user001:password@private.ftp-server.example.com/my_directory/my_file.txt" 182 assert URI.new!(sftp) == expected_uri 183 end 184 185 test "works with TFTP scheme" do 186 expected_uri = %URI{ 187 scheme: "tftp", 188 host: "private.ftp-server.example.com", 189 userinfo: "user001:password", 190 path: "/my_directory/my_file.txt", 191 query: nil, 192 fragment: nil, 193 port: 69 194 } 195 196 tftp = "tftp://user001:password@private.ftp-server.example.com/my_directory/my_file.txt" 197 assert URI.new!(tftp) == expected_uri 198 end 199 200 test "works with LDAP scheme" do 201 expected_uri = %URI{ 202 scheme: "ldap", 203 host: "", 204 userinfo: nil, 205 path: "/dc=example,dc=com", 206 query: "?sub?(givenName=John)", 207 fragment: nil, 208 port: 389 209 } 210 211 assert URI.new!("ldap:///dc=example,dc=com??sub?(givenName=John)") == expected_uri 212 213 expected_uri = %URI{ 214 scheme: "ldap", 215 host: "ldap.example.com", 216 userinfo: nil, 217 path: "/cn=John%20Doe,dc=foo,dc=com", 218 fragment: nil, 219 port: 389, 220 query: nil 221 } 222 223 assert URI.new!("ldap://ldap.example.com/cn=John%20Doe,dc=foo,dc=com") == expected_uri 224 end 225 226 test "can parse IPv6 addresses" do 227 addresses = [ 228 # undefined 229 "::", 230 # loopback 231 "::1", 232 # unicast 233 "1080::8:800:200C:417A", 234 # multicast 235 "FF01::101", 236 # link-local 237 "fe80::", 238 # abbreviated 239 "2607:f3f0:2:0:216:3cff:fef0:174a", 240 # mixed hex case 241 "2607:f3F0:2:0:216:3cFf:Fef0:174A", 242 # complete 243 "2051:0db8:2d5a:3521:8313:ffad:1242:8e2e", 244 # embedded IPv4 245 "::00:192.168.10.184" 246 ] 247 248 Enum.each(addresses, fn addr -> 249 simple_uri = URI.new!("http://[#{addr}]/") 250 assert simple_uri.host == addr 251 252 userinfo_uri = URI.new!("http://user:pass@[#{addr}]/") 253 assert userinfo_uri.host == addr 254 assert userinfo_uri.userinfo == "user:pass" 255 256 port_uri = URI.new!("http://[#{addr}]:2222/") 257 assert port_uri.host == addr 258 assert port_uri.port == 2222 259 260 userinfo_port_uri = URI.new!("http://user:pass@[#{addr}]:2222/") 261 assert userinfo_port_uri.host == addr 262 assert userinfo_port_uri.userinfo == "user:pass" 263 assert userinfo_port_uri.port == 2222 264 end) 265 end 266 267 test "downcases the scheme" do 268 assert URI.new!("hTtP://google.com").scheme == "http" 269 end 270 271 test "preserves empty fragments" do 272 assert URI.new!("http://example.com#").fragment == "" 273 assert URI.new!("http://example.com/#").fragment == "" 274 assert URI.new!("http://example.com/test#").fragment == "" 275 end 276 277 test "preserves an empty query" do 278 assert URI.new!("http://foo.com/?").query == "" 279 end 280 end 281 282 test "default_port/1,2" do 283 assert URI.default_port("http") == 80 284 285 try do 286 URI.default_port("http", 8000) 287 assert URI.default_port("http") == 8000 288 after 289 URI.default_port("http", 80) 290 end 291 292 assert URI.default_port("unknown") == nil 293 URI.default_port("unknown", 13) 294 assert URI.default_port("unknown") == 13 295 end 296 297 test "to_string/1 and Kernel.to_string/1" do 298 assert to_string(URI.new!("http://google.com")) == "http://google.com" 299 assert to_string(URI.new!("http://google.com:443")) == "http://google.com:443" 300 assert to_string(URI.new!("https://google.com:443")) == "https://google.com" 301 assert to_string(URI.new!("file:/path")) == "file:/path" 302 assert to_string(URI.new!("file:///path")) == "file:///path" 303 assert to_string(URI.new!("file://///path")) == "file://///path" 304 assert to_string(URI.new!("http://lol:wut@google.com")) == "http://lol:wut@google.com" 305 assert to_string(URI.new!("http://google.com/elixir")) == "http://google.com/elixir" 306 assert to_string(URI.new!("http://google.com?q=lol")) == "http://google.com?q=lol" 307 assert to_string(URI.new!("http://google.com?q=lol#omg")) == "http://google.com?q=lol#omg" 308 assert to_string(URI.new!("//google.com/elixir")) == "//google.com/elixir" 309 assert to_string(URI.new!("//google.com:8080/elixir")) == "//google.com:8080/elixir" 310 assert to_string(URI.new!("//user:password@google.com/")) == "//user:password@google.com/" 311 assert to_string(URI.new!("http://[2001:db8::]:8080")) == "http://[2001:db8::]:8080" 312 assert to_string(URI.new!("http://[2001:db8::]")) == "http://[2001:db8::]" 313 314 assert URI.to_string(URI.new!("http://google.com")) == "http://google.com" 315 assert URI.to_string(URI.new!("gid:hello/123")) == "gid:hello/123" 316 317 assert URI.to_string(URI.new!("//user:password@google.com/")) == 318 "//user:password@google.com/" 319 320 assert_raise ArgumentError, 321 ~r":path in URI must be empty or an absolute path if URL has a :host", 322 fn -> %URI{host: "foo.com", path: "hello/123"} |> URI.to_string() end 323 end 324 325 test "merge/2" do 326 assert_raise ArgumentError, "you must merge onto an absolute URI", fn -> 327 URI.merge("/relative", "") 328 end 329 330 assert URI.merge("http://google.com/foo", "http://example.com/baz") 331 |> to_string == "http://example.com/baz" 332 333 assert URI.merge("http://google.com/foo", "http://example.com/.././bar/../../baz") 334 |> to_string == "http://example.com/baz" 335 336 assert URI.merge("http://google.com/foo", "//example.com/baz") 337 |> to_string == "http://example.com/baz" 338 339 assert URI.merge("http://google.com/foo", "//example.com/.././bar/../../../baz") 340 |> to_string == "http://example.com/baz" 341 342 assert URI.merge("http://example.com", URI.new!("/foo")) 343 |> to_string == "http://example.com/foo" 344 345 assert URI.merge("http://example.com", URI.new!("/.././bar/../../../baz")) 346 |> to_string == "http://example.com/baz" 347 348 base = URI.new!("http://example.com/foo/bar") 349 assert URI.merge(base, "") |> to_string == "http://example.com/foo/bar" 350 assert URI.merge(base, "#fragment") |> to_string == "http://example.com/foo/bar#fragment" 351 assert URI.merge(base, "?query") |> to_string == "http://example.com/foo/bar?query" 352 assert URI.merge(base, %URI{}) |> to_string == "http://example.com/foo/bar" 353 354 assert URI.merge(base, %URI{fragment: "fragment"}) 355 |> to_string == "http://example.com/foo/bar#fragment" 356 357 base = URI.new!("http://example.com") 358 assert URI.merge(base, "/foo") |> to_string == "http://example.com/foo" 359 assert URI.merge(base, "foo") |> to_string == "http://example.com/foo" 360 361 base = URI.new!("http://example.com/foo/bar") 362 assert URI.merge(base, "/baz") |> to_string == "http://example.com/baz" 363 assert URI.merge(base, "baz") |> to_string == "http://example.com/foo/baz" 364 assert URI.merge(base, "../baz") |> to_string == "http://example.com/baz" 365 assert URI.merge(base, ".././baz") |> to_string == "http://example.com/baz" 366 assert URI.merge(base, "./baz") |> to_string == "http://example.com/foo/baz" 367 assert URI.merge(base, "bar/./baz") |> to_string == "http://example.com/foo/bar/baz" 368 369 base = URI.new!("http://example.com/foo/bar/") 370 assert URI.merge(base, "/baz") |> to_string == "http://example.com/baz" 371 assert URI.merge(base, "baz") |> to_string == "http://example.com/foo/bar/baz" 372 assert URI.merge(base, "../baz") |> to_string == "http://example.com/foo/baz" 373 assert URI.merge(base, ".././baz") |> to_string == "http://example.com/foo/baz" 374 assert URI.merge(base, "./baz") |> to_string == "http://example.com/foo/bar/baz" 375 assert URI.merge(base, "bar/./baz") |> to_string == "http://example.com/foo/bar/bar/baz" 376 377 base = URI.new!("http://example.com/foo/bar/baz") 378 assert URI.merge(base, "../../foobar") |> to_string == "http://example.com/foobar" 379 assert URI.merge(base, "../../../foobar") |> to_string == "http://example.com/foobar" 380 assert URI.merge(base, "../../../../../../foobar") |> to_string == "http://example.com/foobar" 381 382 base = URI.new!("http://example.com/foo/../bar") 383 assert URI.merge(base, "baz") |> to_string == "http://example.com/baz" 384 385 base = URI.new!("http://example.com/foo/./bar") 386 assert URI.merge(base, "baz") |> to_string == "http://example.com/foo/baz" 387 388 base = URI.new!("http://example.com/foo?query1") 389 assert URI.merge(base, "?query2") |> to_string == "http://example.com/foo?query2" 390 assert URI.merge(base, "") |> to_string == "http://example.com/foo?query1" 391 392 base = URI.new!("http://example.com/foo#fragment1") 393 assert URI.merge(base, "#fragment2") |> to_string == "http://example.com/foo#fragment2" 394 assert URI.merge(base, "") |> to_string == "http://example.com/foo" 395 396 page_url = "https://example.com/guide/" 397 image_url = "https://images.example.com/t/1600x/https://images.example.com/foo.jpg" 398 399 assert URI.merge(URI.new!(page_url), URI.new!(image_url)) |> to_string == 400 "https://images.example.com/t/1600x/https://images.example.com/foo.jpg" 401 end 402 403 ## Deprecate API 404 405 describe "authority" do 406 test "to_string" do 407 assert URI.to_string(%URI{authority: "foo@example.com:80"}) == 408 "//foo@example.com:80" 409 410 assert URI.to_string(%URI{userinfo: "bar", host: "example.org", port: 81}) == 411 "//bar@example.org:81" 412 413 assert URI.to_string(%URI{ 414 authority: "foo@example.com:80", 415 userinfo: "bar", 416 host: "example.org", 417 port: 81 418 }) == 419 "//bar@example.org:81" 420 end 421 end 422 423 describe "parse/1" do 424 test "returns the given URI if a %URI{} struct is given" do 425 assert URI.parse(uri = %URI{scheme: "http", host: "foo.com"}) == uri 426 end 427 428 test "works with HTTP scheme" do 429 expected_uri = %URI{ 430 scheme: "http", 431 host: "foo.com", 432 path: "/path/to/something", 433 query: "foo=bar&bar=foo", 434 fragment: "fragment", 435 port: 80, 436 authority: "foo.com", 437 userinfo: nil 438 } 439 440 assert URI.parse("http://foo.com/path/to/something?foo=bar&bar=foo#fragment") == 441 expected_uri 442 end 443 444 test "works with HTTPS scheme" do 445 expected_uri = %URI{ 446 scheme: "https", 447 host: "foo.com", 448 authority: "foo.com", 449 query: nil, 450 fragment: nil, 451 port: 443, 452 path: nil, 453 userinfo: nil 454 } 455 456 assert URI.parse("https://foo.com") == expected_uri 457 end 458 459 test "works with \"file\" scheme" do 460 expected_uri = %URI{ 461 scheme: "file", 462 host: "", 463 path: "/foo/bar/baz", 464 userinfo: nil, 465 query: nil, 466 fragment: nil, 467 port: nil, 468 authority: "" 469 } 470 471 assert URI.parse("file:///foo/bar/baz") == expected_uri 472 end 473 474 test "works with FTP scheme" do 475 expected_uri = %URI{ 476 scheme: "ftp", 477 host: "private.ftp-server.example.com", 478 userinfo: "user001:password", 479 authority: "user001:password@private.ftp-server.example.com", 480 path: "/my_directory/my_file.txt", 481 query: nil, 482 fragment: nil, 483 port: 21 484 } 485 486 ftp = "ftp://user001:password@private.ftp-server.example.com/my_directory/my_file.txt" 487 assert URI.parse(ftp) == expected_uri 488 end 489 490 test "works with SFTP scheme" do 491 expected_uri = %URI{ 492 scheme: "sftp", 493 host: "private.ftp-server.example.com", 494 userinfo: "user001:password", 495 authority: "user001:password@private.ftp-server.example.com", 496 path: "/my_directory/my_file.txt", 497 query: nil, 498 fragment: nil, 499 port: 22 500 } 501 502 sftp = "sftp://user001:password@private.ftp-server.example.com/my_directory/my_file.txt" 503 assert URI.parse(sftp) == expected_uri 504 end 505 506 test "works with TFTP scheme" do 507 expected_uri = %URI{ 508 scheme: "tftp", 509 host: "private.ftp-server.example.com", 510 userinfo: "user001:password", 511 authority: "user001:password@private.ftp-server.example.com", 512 path: "/my_directory/my_file.txt", 513 query: nil, 514 fragment: nil, 515 port: 69 516 } 517 518 tftp = "tftp://user001:password@private.ftp-server.example.com/my_directory/my_file.txt" 519 assert URI.parse(tftp) == expected_uri 520 end 521 522 test "works with LDAP scheme" do 523 expected_uri = %URI{ 524 scheme: "ldap", 525 host: "", 526 authority: "", 527 userinfo: nil, 528 path: "/dc=example,dc=com", 529 query: "?sub?(givenName=John)", 530 fragment: nil, 531 port: 389 532 } 533 534 assert URI.parse("ldap:///dc=example,dc=com??sub?(givenName=John)") == expected_uri 535 536 expected_uri = %URI{ 537 scheme: "ldap", 538 host: "ldap.example.com", 539 authority: "ldap.example.com", 540 userinfo: nil, 541 path: "/cn=John%20Doe,dc=foo,dc=com", 542 fragment: nil, 543 port: 389, 544 query: nil 545 } 546 547 assert URI.parse("ldap://ldap.example.com/cn=John%20Doe,dc=foo,dc=com") == expected_uri 548 end 549 550 test "splits authority" do 551 expected_uri = %URI{ 552 scheme: "http", 553 host: "foo.com", 554 path: nil, 555 query: nil, 556 fragment: nil, 557 port: 4444, 558 authority: "foo:bar@foo.com:4444", 559 userinfo: "foo:bar" 560 } 561 562 assert URI.parse("http://foo:bar@foo.com:4444") == expected_uri 563 564 expected_uri = %URI{ 565 scheme: "https", 566 host: "foo.com", 567 path: nil, 568 query: nil, 569 fragment: nil, 570 port: 443, 571 authority: "foo:bar@foo.com", 572 userinfo: "foo:bar" 573 } 574 575 assert URI.parse("https://foo:bar@foo.com") == expected_uri 576 577 expected_uri = %URI{ 578 scheme: "http", 579 host: "foo.com", 580 path: nil, 581 query: nil, 582 fragment: nil, 583 port: 4444, 584 authority: "foo.com:4444", 585 userinfo: nil 586 } 587 588 assert URI.parse("http://foo.com:4444") == expected_uri 589 end 590 591 test "can parse bad URIs" do 592 assert URI.parse("") 593 assert URI.parse("https:??@?F?@#>F//23/") 594 595 assert URI.parse(":https").path == ":https" 596 assert URI.parse("https").path == "https" 597 assert URI.parse("ht\0tps://foo.com").path == "ht\0tps://foo.com" 598 end 599 600 test "can parse IPv6 addresses" do 601 addresses = [ 602 # undefined 603 "::", 604 # loopback 605 "::1", 606 # unicast 607 "1080::8:800:200C:417A", 608 # multicast 609 "FF01::101", 610 # link-local 611 "fe80::", 612 # abbreviated 613 "2607:f3f0:2:0:216:3cff:fef0:174a", 614 # mixed hex case 615 "2607:f3F0:2:0:216:3cFf:Fef0:174A", 616 # complete 617 "2051:0db8:2d5a:3521:8313:ffad:1242:8e2e", 618 # embedded IPv4 619 "::00:192.168.10.184" 620 ] 621 622 Enum.each(addresses, fn addr -> 623 simple_uri = URI.parse("http://[#{addr}]/") 624 assert simple_uri.authority == "[#{addr}]" 625 assert simple_uri.host == addr 626 627 userinfo_uri = URI.parse("http://user:pass@[#{addr}]/") 628 assert userinfo_uri.authority == "user:pass@[#{addr}]" 629 assert userinfo_uri.host == addr 630 assert userinfo_uri.userinfo == "user:pass" 631 632 port_uri = URI.parse("http://[#{addr}]:2222/") 633 assert port_uri.authority == "[#{addr}]:2222" 634 assert port_uri.host == addr 635 assert port_uri.port == 2222 636 637 userinfo_port_uri = URI.parse("http://user:pass@[#{addr}]:2222/") 638 assert userinfo_port_uri.authority == "user:pass@[#{addr}]:2222" 639 assert userinfo_port_uri.host == addr 640 assert userinfo_port_uri.userinfo == "user:pass" 641 assert userinfo_port_uri.port == 2222 642 end) 643 end 644 645 test "downcases the scheme" do 646 assert URI.parse("hTtP://google.com").scheme == "http" 647 end 648 649 test "preserves empty fragments" do 650 assert URI.parse("http://example.com#").fragment == "" 651 assert URI.parse("http://example.com/#").fragment == "" 652 assert URI.parse("http://example.com/test#").fragment == "" 653 end 654 655 test "preserves an empty query" do 656 assert URI.parse("http://foo.com/?").query == "" 657 end 658 659 test "merges empty path" do 660 base = URI.parse("http://example.com") 661 assert URI.merge(base, "/foo") |> to_string == "http://example.com/foo" 662 assert URI.merge(base, "foo") |> to_string == "http://example.com/foo" 663 end 664 end 665end 666