1# -*- coding: utf-8 -*- 2 3# 4# furl - URL manipulation made simple. 5# 6# Ansgar Grunseid 7# grunseid.com 8# grunseid@gmail.com 9# 10# License: Build Amazing Things (Unlicense) 11# 12 13from __future__ import division 14 15import warnings 16from abc import ABCMeta, abstractmethod 17 18import six 19from six.moves import zip 20from six.moves.urllib.parse import ( 21 quote, quote_plus, parse_qsl, urlsplit, SplitResult) 22 23import furl 24from furl.omdict1D import omdict1D 25from furl.compat import OrderedDict as odict 26 27import unittest 28 29 30# 31# TODO(grun): Add tests for furl objects with strict=True. Make sure 32# UserWarnings are raised when improperly encoded path, query, and 33# fragment strings are provided. 34# 35 36 37@six.add_metaclass(ABCMeta) 38class itemcontainer(object): 39 40 """ 41 Utility list subclasses to expose allitems() and iterallitems() 42 methods on different kinds of item containers - lists, dictionaries, 43 multivalue dictionaries, and query strings. This provides a common 44 iteration interface for looping through their items (including items 45 with repeated keys). original() is also provided to get access to a 46 copy of the original container. 47 """ 48 49 @abstractmethod 50 def allitems(self): 51 pass 52 53 @abstractmethod 54 def iterallitems(self): 55 pass 56 57 @abstractmethod 58 def original(self): 59 """ 60 Returns: A copy of the original data type. For example, an 61 itemlist would return a list, itemdict a dict, etc. 62 """ 63 pass 64 65 66class itemlist(list, itemcontainer): 67 68 def allitems(self): 69 return list(self.iterallitems()) 70 71 def iterallitems(self): 72 return iter(self) 73 74 def original(self): 75 return list(self) 76 77 78class itemdict(odict, itemcontainer): 79 80 def allitems(self): 81 return list(self.items()) 82 83 def iterallitems(self): 84 return iter(self.items()) 85 86 def original(self): 87 return dict(self) 88 89 90class itemomdict1D(omdict1D, itemcontainer): 91 92 def original(self): 93 return omdict1D(self) 94 95 96class itemstr(str, itemcontainer): 97 98 def allitems(self): 99 # Keys and values get unquoted. i.e. 'a=a%20a' -> ['a', 'a a']. Empty 100 # values without '=' have value None. 101 items = [] 102 parsed = parse_qsl(self, keep_blank_values=True) 103 pairstrs = [ 104 s2 for s1 in self.split('&') for s2 in s1.split(';')] 105 for (key, value), pairstr in zip(parsed, pairstrs): 106 if key == pairstr: 107 value = None 108 items.append((key, value)) 109 return items 110 111 def iterallitems(self): 112 return iter(self.allitems()) 113 114 def original(self): 115 return str(self) 116 117 118class TestPath(unittest.TestCase): 119 120 def test_none(self): 121 p = furl.Path(None) 122 assert str(p) == '' 123 124 p = furl.Path('/a/b/c') 125 assert str(p) == '/a/b/c' 126 p.load(None) 127 assert str(p) == '' 128 129 def test_isdir_isfile(self): 130 for path in ['', '/']: 131 p = furl.Path(path) 132 assert p.isdir 133 assert not p.isfile 134 135 paths = ['dir1/', 'd1/d2/', 'd/d/d/d/d/', '/', '/dir1/', '/d1/d2/d3/'] 136 for path in paths: 137 p = furl.Path(path) 138 assert p.isdir 139 assert not p.isfile 140 141 for path in ['dir1', 'd1/d2', 'd/d/d/d/d', '/dir1', '/d1/d2/d3']: 142 p = furl.Path(path) 143 assert p.isfile 144 assert not p.isdir 145 146 def test_leading_slash(self): 147 p = furl.Path('') 148 assert not p.isabsolute 149 assert not p.segments 150 assert p.isdir and p.isdir != p.isfile 151 assert str(p) == '' 152 153 p = furl.Path('/') 154 assert p.isabsolute 155 assert p.segments == [''] 156 assert p.isdir and p.isdir != p.isfile 157 assert str(p) == '/' 158 159 p = furl.Path('sup') 160 assert not p.isabsolute 161 assert p.segments == ['sup'] 162 assert p.isfile and p.isdir != p.isfile 163 assert str(p) == 'sup' 164 165 p = furl.Path('/sup') 166 assert p.isabsolute 167 assert p.segments == ['sup'] 168 assert p.isfile and p.isdir != p.isfile 169 assert str(p) == '/sup' 170 171 p = furl.Path('a/b/c') 172 assert not p.isabsolute 173 assert p.segments == ['a', 'b', 'c'] 174 assert p.isfile and p.isdir != p.isfile 175 assert str(p) == 'a/b/c' 176 177 p = furl.Path('/a/b/c') 178 assert p.isabsolute 179 assert p.segments == ['a', 'b', 'c'] 180 assert p.isfile and p.isdir != p.isfile 181 assert str(p) == '/a/b/c' 182 183 p = furl.Path('a/b/c/') 184 assert not p.isabsolute 185 assert p.segments == ['a', 'b', 'c', ''] 186 assert p.isdir and p.isdir != p.isfile 187 assert str(p) == 'a/b/c/' 188 189 p.isabsolute = True 190 assert p.isabsolute 191 assert str(p) == '/a/b/c/' 192 193 def test_encoding(self): 194 decoded = ['a+a', '/#haypepps/', 'a/:@/a', 'a/b'] 195 encoded = ['a%20a', '/%23haypepps/', 'a/:@/a', 'a%2Fb'] 196 197 for path in encoded: 198 assert str(furl.Path(path)) == path 199 200 safe = furl.Path.SAFE_SEGMENT_CHARS + '/' 201 for path in decoded: 202 assert str(furl.Path(path)) == quote(path, safe) 203 204 # Valid path segment characters should not be encoded. 205 for char in ":@-._~!$&'()*+,;=": 206 f = furl.furl().set(path=char) 207 assert str(f.path) == f.url == char 208 assert f.path.segments == [char] 209 210 # Invalid path segment characters should be encoded. 211 for char in ' ^`<>[]"?': 212 f = furl.furl().set(path=char) 213 assert str(f.path) == f.url == quote(char) 214 assert f.path.segments == [char] 215 216 # Encode '/' within a path segment. 217 segment = 'a/b' # One path segment that includes the '/' character. 218 f = furl.furl().set(path=[segment]) 219 assert str(f.path) == 'a%2Fb' 220 assert f.path.segments == [segment] 221 assert f.url == 'a%2Fb' 222 223 # Encode percent signs in path segment stings. 224 assert str(furl.Path(['a%20d'])) == 'a%2520d' 225 assert str(furl.Path(['a%zzd'])) == 'a%25zzd' 226 227 # Percent-encodings should be capitalized, as per RFC 3986. 228 assert str(furl.Path('a%2fd')) == str(furl.Path('a%2Fd')) == 'a%2Fd' 229 230 def test_load(self): 231 self._test_set_load(furl.Path.load) 232 233 def test_set(self): 234 self._test_set_load(furl.Path.set) 235 236 def _test_set_load(self, path_set_or_load): 237 p = furl.Path('a/b/c/') 238 assert path_set_or_load(p, furl.Path('asdf/asdf/')) == p 239 assert not p.isabsolute and str(p) == 'asdf/asdf/' 240 241 assert path_set_or_load(p, 'asdf/asdf/') == p 242 assert not p.isabsolute and str(p) == 'asdf/asdf/' 243 244 assert path_set_or_load(p, ['a', 'b', 'c', '']) == p 245 assert not p.isabsolute and str(p) == 'a/b/c/' 246 247 assert path_set_or_load(p, ['', 'a', 'b', 'c', '']) == p 248 assert p.isabsolute and str(p) == '/a/b/c/' 249 250 def test_add(self): 251 # URL paths. 252 p = furl.furl('a/b/c/').path 253 assert p.add('d') == p 254 assert not p.isabsolute 255 assert str(p) == 'a/b/c/d' 256 assert p.add('/') == p 257 assert not p.isabsolute 258 assert str(p) == 'a/b/c/d/' 259 assert p.add(['e', 'f', 'e e', '']) == p 260 assert not p.isabsolute 261 assert str(p) == 'a/b/c/d/e/f/e%20e/' 262 263 p = furl.furl().path 264 assert not p.isabsolute 265 assert p.add('/') == p 266 assert p.isabsolute 267 assert str(p) == '/' 268 assert p.add('pump') == p 269 assert p.isabsolute 270 assert str(p) == '/pump' 271 272 p = furl.furl().path 273 assert not p.isabsolute 274 assert p.add(['', '']) == p 275 assert p.isabsolute 276 assert str(p) == '/' 277 assert p.add(['pump', 'dump', '']) == p 278 assert p.isabsolute 279 assert str(p) == '/pump/dump/' 280 281 p = furl.furl('http://sprop.ru/a/b/c/').path 282 assert p.add('d') == p 283 assert p.isabsolute 284 assert str(p) == '/a/b/c/d' 285 assert p.add('/') == p 286 assert p.isabsolute 287 assert str(p) == '/a/b/c/d/' 288 assert p.add(['e', 'f', 'e e', '']) == p 289 assert p.isabsolute 290 assert str(p) == '/a/b/c/d/e/f/e%20e/' 291 292 f = furl.furl('http://sprop.ru') 293 assert not f.path.isabsolute 294 f.path.add('sup') 295 assert f.path.isabsolute and str(f.path) == '/sup' 296 297 f = furl.furl('/mrp').add(path='sup') 298 assert str(f.path) == '/mrp/sup' 299 300 f = furl.furl('/').add(path='/sup') 301 assert f.path.isabsolute and str(f.path) == '/sup' 302 303 f = furl.furl('/hi').add(path='sup') 304 assert f.path.isabsolute and str(f.path) == '/hi/sup' 305 306 f = furl.furl('/hi').add(path='/sup') 307 assert f.path.isabsolute and str(f.path) == '/hi/sup' 308 309 f = furl.furl('/hi/').add(path='/sup') 310 assert f.path.isabsolute and str(f.path) == '/hi//sup' 311 312 # Fragment paths. 313 f = furl.furl('http://sprop.ru#mrp') 314 assert not f.fragment.path.isabsolute 315 f.fragment.path.add('sup') 316 assert not f.fragment.path.isabsolute 317 assert str(f.fragment.path) == 'mrp/sup' 318 319 f = furl.furl('http://sprop.ru#/mrp') 320 assert f.fragment.path.isabsolute 321 f.fragment.path.add('sup') 322 assert f.fragment.path.isabsolute 323 assert str(f.fragment.path) == '/mrp/sup' 324 325 def test_remove(self): 326 # Remove lists of path segments. 327 p = furl.Path('a/b/s%20s/') 328 assert p.remove(['b', 's s']) == p 329 assert str(p) == 'a/b/s%20s/' 330 assert p.remove(['b', 's s', '']) == p 331 assert str(p) == 'a/' 332 assert p.remove(['', 'a']) == p 333 assert str(p) == 'a/' 334 assert p.remove(['a']) == p 335 assert str(p) == 'a/' 336 assert p.remove(['a', '']) == p 337 assert str(p) == '' 338 339 p = furl.Path('a/b/s%20s/') 340 assert p.remove(['', 'b', 's s']) == p 341 assert str(p) == 'a/b/s%20s/' 342 assert p.remove(['', 'b', 's s', '']) == p 343 assert str(p) == 'a' 344 assert p.remove(['', 'a']) == p 345 assert str(p) == 'a' 346 assert p.remove(['a', '']) == p 347 assert str(p) == 'a' 348 assert p.remove(['a']) == p 349 assert str(p) == '' 350 351 p = furl.Path('a/b/s%20s/') 352 assert p.remove(['a', 'b', 's%20s', '']) == p 353 assert str(p) == 'a/b/s%20s/' 354 assert p.remove(['a', 'b', 's s', '']) == p 355 assert str(p) == '' 356 357 # Remove a path string. 358 p = furl.Path('a/b/s%20s/') 359 assert p.remove('b/s s/') == p # Encoding Warning. 360 assert str(p) == 'a/' 361 362 p = furl.Path('a/b/s%20s/') 363 assert p.remove('b/s%20s/') == p 364 assert str(p) == 'a/' 365 assert p.remove('a') == p 366 assert str(p) == 'a/' 367 assert p.remove('/a') == p 368 assert str(p) == 'a/' 369 assert p.remove('a/') == p 370 assert str(p) == '' 371 372 p = furl.Path('a/b/s%20s/') 373 assert p.remove('b/s s') == p # Encoding Warning. 374 assert str(p) == 'a/b/s%20s/' 375 376 p = furl.Path('a/b/s%20s/') 377 assert p.remove('b/s%20s') == p 378 assert str(p) == 'a/b/s%20s/' 379 assert p.remove('s%20s') == p 380 assert str(p) == 'a/b/s%20s/' 381 assert p.remove('s s') == p # Encoding Warning. 382 assert str(p) == 'a/b/s%20s/' 383 assert p.remove('b/s%20s/') == p 384 assert str(p) == 'a/' 385 assert p.remove('/a') == p 386 assert str(p) == 'a/' 387 assert p.remove('a') == p 388 assert str(p) == 'a/' 389 assert p.remove('a/') == p 390 assert str(p) == '' 391 392 p = furl.Path('a/b/s%20s/') 393 assert p.remove('a/b/s s/') == p # Encoding Warning. 394 assert str(p) == '' 395 396 # Remove True. 397 p = furl.Path('a/b/s%20s/') 398 assert p.remove(True) == p 399 assert str(p) == '' 400 401 def test_isabsolute(self): 402 paths = ['', '/', 'pump', 'pump/dump', '/pump/dump', '/pump/dump'] 403 for path in paths: 404 # A URL path's isabsolute attribute is mutable if there's no 405 # netloc. 406 mutable = [ 407 {}, # No scheme or netloc -> isabsolute is mutable. 408 {'scheme': 'nonempty'}] # Scheme, no netloc -> isabs mutable. 409 for kwargs in mutable: 410 f = furl.furl().set(path=path, **kwargs) 411 if path and path.startswith('/'): 412 assert f.path.isabsolute 413 else: 414 assert not f.path.isabsolute 415 f.path.isabsolute = False # No exception. 416 assert not f.path.isabsolute and not str( 417 f.path).startswith('/') 418 f.path.isabsolute = True # No exception. 419 assert f.path.isabsolute and str(f.path).startswith('/') 420 421 # A URL path's isabsolute attribute is read-only if there's 422 # a netloc. 423 readonly = [ 424 # Netloc, no scheme -> isabsolute is read-only if path 425 # is non-empty. 426 {'netloc': 'nonempty'}, 427 # Netloc and scheme -> isabsolute is read-only if path 428 # is non-empty. 429 {'scheme': 'nonempty', 'netloc': 'nonempty'}] 430 for kwargs in readonly: 431 f = furl.furl().set(path=path, **kwargs) 432 if path: # Exception raised. 433 with self.assertRaises(AttributeError): 434 f.path.isabsolute = False 435 with self.assertRaises(AttributeError): 436 f.path.isabsolute = True 437 else: # No exception raised. 438 f.path.isabsolute = False 439 assert not f.path.isabsolute and not str( 440 f.path).startswith('/') 441 f.path.isabsolute = True 442 assert f.path.isabsolute and str(f.path).startswith('/') 443 444 # A Fragment path's isabsolute attribute is never read-only. 445 f = furl.furl().set(fragment_path=path) 446 if path and path.startswith('/'): 447 assert f.fragment.path.isabsolute 448 else: 449 assert not f.fragment.path.isabsolute 450 f.fragment.path.isabsolute = False # No exception. 451 assert (not f.fragment.path.isabsolute and 452 not str(f.fragment.path).startswith('/')) 453 f.fragment.path.isabsolute = True # No exception. 454 assert f.fragment.path.isabsolute and str( 455 f.fragment.path).startswith('/') 456 457 # Sanity checks. 458 f = furl.furl().set(scheme='mailto', path='dad@pumps.biz') 459 assert str(f) == 'mailto:dad@pumps.biz' and not f.path.isabsolute 460 f.path.isabsolute = True # No exception. 461 assert str(f) == 'mailto:/dad@pumps.biz' and f.path.isabsolute 462 463 f = furl.furl().set(scheme='sup', fragment_path='/dad@pumps.biz') 464 assert str( 465 f) == 'sup:#/dad@pumps.biz' and f.fragment.path.isabsolute 466 f.fragment.path.isabsolute = False # No exception. 467 assert str( 468 f) == 'sup:#dad@pumps.biz' and not f.fragment.path.isabsolute 469 470 def test_normalize(self): 471 # Path not modified. 472 for path in ['', 'a', '/a', '/a/', '/a/b%20b/c', '/a/b%20b/c/']: 473 p = furl.Path(path) 474 assert p.normalize() is p and str(p) == str(p.normalize()) == path 475 476 # Path modified. 477 to_normalize = [ 478 ('//', '/'), ('//a', '/a'), ('//a/', '/a/'), ('//a///', '/a/'), 479 ('////a/..//b', '/b'), ('/a/..//b//./', '/b/')] 480 for path, normalized in to_normalize: 481 p = furl.Path(path) 482 assert p.normalize() is p and str(p.normalize()) == normalized 483 484 def test_equality(self): 485 assert furl.Path() == furl.Path() 486 487 p1 = furl.furl('http://sprop.ru/a/b/c/').path 488 p11 = furl.furl('http://spep.ru/a/b/c/').path 489 p2 = furl.furl('http://sprop.ru/a/b/c/d/').path 490 491 assert p1 == p11 and str(p1) == str(p11) 492 assert p1 != p2 and str(p1) != str(p2) 493 494 def test_nonzero(self): 495 p = furl.Path() 496 assert not p 497 498 p = furl.Path('') 499 assert not p 500 501 p = furl.Path('') 502 assert not p 503 p.segments = [''] 504 assert p 505 506 p = furl.Path('asdf') 507 assert p 508 509 p = furl.Path('/asdf') 510 assert p 511 512 def test_unicode(self): 513 paths = ['/wiki/ロリポップ', u'/wiki/ロリポップ'] 514 path_encoded = '/wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 515 for path in paths: 516 p = furl.Path(path) 517 assert str(p) == path_encoded 518 519 def test_itruediv(self): 520 p = furl.Path() 521 522 p /= 'a' 523 assert str(p) == 'a' 524 525 p /= 'b' 526 assert str(p) == 'a/b' 527 528 p /= 'c d/' 529 assert str(p) == 'a/b/c%20d/' 530 531 p /= furl.Path('e') 532 assert str(p) == 'a/b/c%20d/e' 533 534 def test_truediv(self): 535 p = furl.Path() 536 537 p1 = p / 'a' 538 assert p1 is not p 539 assert str(p1) == 'a' 540 541 p2 = p / 'a' / 'b' 542 assert p2 is not p 543 assert str(p) == '' 544 assert str(p2) == 'a/b' 545 546 # Path objects should be joinable with other Path objects. 547 p3 = furl.Path('e') 548 p4 = furl.Path('f') 549 assert p3 / p4 == furl.Path('e/f') 550 551 def test_asdict(self): 552 segments = ['wiki', 'ロリポップ'] 553 path_encoded = 'wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 554 p = furl.Path(path_encoded) 555 d = { 556 'isdir': False, 557 'isfile': True, 558 'isabsolute': False, 559 'segments': segments, 560 'encoded': path_encoded, 561 } 562 assert p.asdict() == d 563 564 565class TestQuery(unittest.TestCase): 566 567 def setUp(self): 568 # All interaction with parameters is unquoted unless that 569 # interaction is through an already encoded query string. In the 570 # case of an already encoded query string, like 'a=a%20a&b=b', 571 # its keys and values will be unquoted. 572 self.itemlists = list(map(itemlist, [ 573 [], [(1, 1)], [(1, 1), (2, 2)], [ 574 (1, 1), (1, 11), (2, 2), (3, 3)], [('', '')], 575 [('a', 1), ('b', 2), ('a', 3)], [ 576 ('a', 1), ('b', 'b'), ('a', 0.23)], 577 [(0.1, -0.9), (-0.1231, 12312.3123)], [ 578 (None, None), (None, 'pumps')], 579 [('', ''), ('', '')], [('', 'a'), ('', 'b'), 580 ('b', ''), ('b', 'b')], [('<', '>')], 581 [('=', '><^%'), ('><^%', '=')], [ 582 ("/?:@-._~!$'()*+,", "/?:@-._~!$'()*+,=")], 583 [('+', '-')], [('a%20a', 'a%20a')], [('/^`<>[]"', '/^`<>[]"=')], 584 [("/?:@-._~!$'()*+,", "/?:@-._~!$'()*+,=")], 585 ])) 586 self.itemdicts = list(map(itemdict, [ 587 {}, {1: 1, 2: 2}, {'1': '1', '2': '2', 588 '3': '3'}, {None: None}, {5.4: 4.5}, 589 {'': ''}, {'': 'a', 'b': ''}, { 590 'pue': 'pue', 'a': 'a&a'}, {'=': '====='}, 591 {'pue': 'pue', 'a': 'a%26a'}, {'%': '`', '`': '%'}, {'+': '-'}, 592 {"/?:@-._~!$'()*+,": "/?:@-._~!$'()*+,="}, { 593 '%25': '%25', '%60': '%60'}, 594 ])) 595 self.itemomdicts = list(map(itemomdict1D, self.itemlists)) 596 self.itemstrs = list(map(itemstr, [ 597 # Basics. 598 '', 'a=a', 'a=a&b=b', 'q=asdf&check_keywords=yes&area=default', 599 '=asdf', 600 # Various quoted and unquoted parameters and values that 601 # will be unquoted. 602 'space=a+a&=a%26a', 'a a=a a&no encoding=sup', 'a+a=a+a', 603 'a%20=a+a', 'a%20a=a%20a', 'a+a=a%20a', 'space=a a&=a^a', 604 'a=a&s=s#s', '+=+', "/?:@-._~!$&'()*+,=/?:@-._~!$'()*+,=", 605 'a=a&c=c%5Ec', '<=>&^="', '%3C=%3E&%5E=%22', '%=%;`=`', 606 '%25=%25&%60=%60', 607 # Only keys, no values. 608 'asdfasdf', '/asdf/asdf/sdf', '*******', '!@#(*&@!#(*@!#', 'a&b', 609 'a;b', 610 # Repeated parameters. 611 'a=a&a=a', 'space=a+a&space=b+b', 612 # Empty keys and/or values. 613 '=', 'a=', 'a=a&a=', '=a&=b', 614 # Semicolon delimiter, like 'a=a;b=b'. 615 'a=a;a=a', 'space=a+a;space=b+b', 616 ])) 617 self.items = (self.itemlists + self.itemdicts + self.itemomdicts + 618 self.itemstrs) 619 620 def test_none(self): 621 q = furl.Query(None) 622 assert str(q) == '' 623 624 q = furl.Query('a=b&c=d') 625 assert str(q) == 'a=b&c=d' 626 q.load(None) 627 assert str(q) == '' 628 629 def test_various(self): 630 for items in self.items: 631 q = furl.Query(items.original()) 632 assert q.params.allitems() == items.allitems() 633 634 # encode() accepts both 'delimiter' and 'delimeter'. The 635 # latter was incorrectly used until furl v0.4.6. 636 e = q.encode 637 assert e(';') == e(delimiter=';') == e(delimeter=';') 638 639 # __nonzero__(). 640 if items.allitems(): 641 assert q 642 else: 643 assert not q 644 645 def test_load(self): 646 for items in self.items: 647 q = furl.Query(items.original()) 648 for update in self.items: 649 assert q.load(update) == q 650 assert q.params.allitems() == update.allitems() 651 652 def test_add(self): 653 for items in self.items: 654 q = furl.Query(items.original()) 655 runningsum = list(items.allitems()) 656 for itemupdate in self.items: 657 assert q.add(itemupdate.original()) == q 658 for item in itemupdate.iterallitems(): 659 runningsum.append(item) 660 assert q.params.allitems() == runningsum 661 662 def test_set(self): 663 for items in self.items: 664 q = furl.Query(items.original()) 665 items_omd = omdict1D(items.allitems()) 666 for update in self.items: 667 q.set(update) 668 items_omd.updateall(update) 669 assert q.params.allitems() == items_omd.allitems() 670 671 # The examples. 672 q = furl.Query({1: 1}).set([(1, None), (2, 2)]) 673 assert q.params.allitems() == [(1, None), (2, 2)] 674 675 q = furl.Query({1: None, 2: None}).set([(1, 1), (2, 2), (1, 11)]) 676 assert q.params.allitems() == [(1, 1), (2, 2), (1, 11)] 677 678 q = furl.Query({1: None}).set([(1, [1, 11, 111])]) 679 assert q.params.allitems() == [(1, 1), (1, 11), (1, 111)] 680 681 # Further manual tests. 682 q = furl.Query([(2, None), (3, None), (1, None)]) 683 q.set([(1, [1, 11]), (2, 2), (3, [3, 33])]) 684 assert q.params.allitems() == [ 685 (2, 2), (3, 3), (1, 1), (1, 11), (3, 33)] 686 687 def test_remove(self): 688 for items in self.items: 689 # Remove one key at a time. 690 q = furl.Query(items.original()) 691 for key in dict(items.iterallitems()): 692 assert key in q.params 693 assert q.remove(key) == q 694 assert key not in q.params 695 696 # Remove multiple keys at a time (in this case all of them). 697 q = furl.Query(items.original()) 698 if items.allitems(): 699 assert q.params 700 allkeys = [key for key, value in items.allitems()] 701 assert q.remove(allkeys) == q 702 assert len(q.params) == 0 703 704 # Remove the whole query string with True. 705 q = furl.Query(items.original()) 706 if items.allitems(): 707 assert q.params 708 assert q.remove(True) == q 709 assert len(q.params) == 0 710 711 # List of keys to remove. 712 q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')]) 713 q.remove(['a', 'b']) 714 assert not list(q.params.items()) 715 716 # List of items to remove. 717 q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')]) 718 q.remove([('a', '1'), ('b', '3')]) 719 assert list(q.params.allitems()) == [('b', '2'), ('a', '4')] 720 721 # Dictionary of items to remove. 722 q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')]) 723 q.remove({'b': '3', 'a': '1'}) 724 assert q.params.allitems() == [('b', '2'), ('a', '4')] 725 726 # Multivalue dictionary of items to remove. 727 q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')]) 728 omd = omdict1D([('a', '4'), ('b', '3'), ('b', '2')]) 729 q.remove(omd) 730 assert q.params.allitems() == [('a', '1')] 731 732 def test_params(self): 733 # Basics. 734 q = furl.Query('a=a&b=b') 735 assert q.params == {'a': 'a', 'b': 'b'} 736 q.params['sup'] = 'sup' 737 assert q.params == {'a': 'a', 'b': 'b', 'sup': 'sup'} 738 del q.params['a'] 739 assert q.params == {'b': 'b', 'sup': 'sup'} 740 q.params['b'] = 'BLROP' 741 assert q.params == {'b': 'BLROP', 'sup': 'sup'} 742 743 # Blanks keys and values are kept. 744 q = furl.Query('=') 745 assert q.params == {'': ''} and str(q) == '=' 746 q = furl.Query('=&=') 747 assert q.params.allitems() == [('', ''), ('', '')] and str(q) == '=&=' 748 q = furl.Query('a=&=b') 749 assert q.params == {'a': '', '': 'b'} and str(q) == 'a=&=b' 750 751 # ';' is a valid query delimiter. 752 q = furl.Query('=;=') 753 assert q.params.allitems() == [('', ''), ('', '')] and str(q) == '=&=' 754 q = furl.Query('a=a;b=b;c=') 755 assert q.params == { 756 'a': 'a', 'b': 'b', 'c': ''} and str(q) == 'a=a&b=b&c=' 757 758 # Non-string parameters are coerced to strings in the final 759 # query string. 760 q.params.clear() 761 q.params[99] = 99 762 q.params[None] = -1 763 q.params['int'] = 1 764 q.params['float'] = 0.39393 765 assert str(q) == '99=99&None=-1&int=1&float=0.39393' 766 767 # Spaces are encoded as '+'s. '+'s are encoded as '%2B'. 768 q.params.clear() 769 q.params['s s'] = 's s' 770 q.params['p+p'] = 'p+p' 771 assert str(q) == 's+s=s+s&p%2Bp=p%2Bp' 772 773 # Params is an omdict (ordered multivalue dictionary). 774 q.params.clear() 775 q.params.add('1', '1').set('2', '4').add('1', '11').addlist( 776 3, [3, 3, '3']) 777 assert q.params.getlist('1') == ['1', '11'] and q.params['1'] == '1' 778 assert q.params.getlist(3) == [3, 3, '3'] 779 780 # Assign various things to Query.params and make sure 781 # Query.params is reinitialized, not replaced. 782 for items in self.items: 783 q.params = items.original() 784 assert isinstance(q.params, omdict1D) 785 786 pairs = zip(q.params.iterallitems(), items.iterallitems()) 787 for item1, item2 in pairs: 788 assert item1 == item2 789 790 # Value of '' -> '?param='. Value of None -> '?param'. 791 q = furl.Query('slrp') 792 assert str(q) == 'slrp' and q.params['slrp'] is None 793 q = furl.Query('slrp=') 794 assert str(q) == 'slrp=' and q.params['slrp'] == '' 795 q = furl.Query('prp=&slrp') 796 assert q.params['prp'] == '' and q.params['slrp'] is None 797 q.params['slrp'] = '' 798 assert str(q) == 'prp=&slrp=' and q.params['slrp'] == '' 799 800 def test_unicode(self): 801 pairs = [('ロリポップ', 'testä'), (u'ロリポップ', u'testä')] 802 key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 803 value_encoded = 'test%C3%A4' 804 805 for key, value in pairs: 806 q = furl.Query('%s=%s' % (key, value)) 807 assert q.params[key] == value 808 assert str(q) == '%s=%s' % (key_encoded, value_encoded) 809 810 q = furl.Query() 811 q.params[key] = value 812 assert q.params[key] == value 813 assert str(q) == '%s=%s' % (key_encoded, value_encoded) 814 815 def test_equality(self): 816 assert furl.Query() == furl.Query() 817 818 q1 = furl.furl('http://sprop.ru/?a=1&b=2').query 819 q11 = furl.furl('http://spep.ru/path/?a=1&b=2').query 820 q2 = furl.furl('http://sprop.ru/?b=2&a=1').query 821 822 assert q1 == q11 and str(q1) == str(q11) 823 assert q1 != q2 and str(q1) != str(q2) 824 825 def test_encode(self): 826 for items in self.items: 827 q = furl.Query(items.original()) 828 # encode() and __str__(). 829 assert str(q) == q.encode() == q.encode('&') 830 831 # Accept both percent-encoded ('a=b%20c') and 832 # application/x-www-form-urlencoded ('a=b+c') pairs as input. 833 query = furl.Query('a=b%20c&d=e+f') 834 assert query.encode(';') == 'a=b+c;d=e+f' 835 assert query.encode(';', quote_plus=False) == 'a=b%20c;d=e%20f' 836 837 # Encode '/' consistently across quote_plus=True and quote_plus=False. 838 query = furl.Query('a /b') 839 assert query.encode(quote_plus=True) == 'a+%2Fb' 840 assert query.encode(quote_plus=False) == 'a%20%2Fb' 841 842 # dont_quote= accepts both True and a string of safe characters not to 843 # percent-encode. Unsafe query characters, like '^' and '#', are always 844 # percent-encoded. 845 query = furl.Query('a %2B/b?#') 846 assert query.encode(dont_quote='^') == 'a+%2B%2Fb%3F%23' 847 assert query.encode(quote_plus=True, dont_quote=True) == 'a++/b?%23' 848 assert query.encode(quote_plus=False, dont_quote=True) == 'a%20+/b?%23' 849 850 def test_asdict(self): 851 pairs = [('a', '1'), ('ロリポップ', 'testä')] 852 key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 853 value_encoded = 'test%C3%A4' 854 query_encoded = 'a=1&' + key_encoded + '=' + value_encoded 855 p = furl.Query(query_encoded) 856 d = { 857 'params': pairs, 858 'encoded': query_encoded, 859 } 860 assert p.asdict() == d 861 862 def test_value_encoding_empty_vs_nonempty_key(self): 863 pair = ('=', '=') 864 pair_encoded = '%3D=%3D' 865 assert furl.Query(pair_encoded).params.allitems() == [pair] 866 867 q = furl.Query() 868 q.params = [pair] 869 assert q.encode() == pair_encoded 870 871 empty_key_pair = ('', '==3===') 872 empty_key_encoded = '===3===' 873 assert furl.Query(empty_key_encoded).params.items() == [empty_key_pair] 874 875 def test_special_characters(self): 876 q = furl.Query('==3==') 877 assert q.params.allitems() == [('', '=3==')] and str(q) == '==3==' 878 879 f = furl.furl('https://www.google.com????') 880 assert f.args.allitems() == [('???', None)] 881 882 q = furl.Query('&=&') 883 assert q.params.allitems() == [('', None), ('', ''), ('', None)] 884 assert str(q) == '&=&' 885 886 url = 'https://www.google.com?&&%3F=&%3F' 887 f = furl.furl(url) 888 assert f.args.allitems() == [ 889 ('', None), ('', None), ('?', ''), ('?', None)] 890 assert f.url == url 891 892 def _quote_items(self, items): 893 # Calculate the expected querystring with proper query encoding. 894 # Valid query key characters: "/?:@-._~!$'()*,;" 895 # Valid query value characters: "/?:@-._~!$'()*,;=" 896 allitems_quoted = [] 897 for key, value in items.iterallitems(): 898 pair = ( 899 quote_plus(str(key), "/?:@-._~!$'()*,;"), 900 quote_plus(str(value), "/?:@-._~!$'()*,;=")) 901 allitems_quoted.append(pair) 902 return allitems_quoted 903 904 905class TestQueryCompositionInterface(unittest.TestCase): 906 907 def test_interface(self): 908 class tester(furl.QueryCompositionInterface): 909 910 def __init__(self): 911 furl.QueryCompositionInterface.__init__(self) 912 913 def __setattr__(self, attr, value): 914 fqci = furl.QueryCompositionInterface 915 if not fqci.__setattr__(self, attr, value): 916 object.__setattr__(self, attr, value) 917 918 t = tester() 919 assert isinstance(t.query, furl.Query) 920 assert str(t.query) == '' 921 922 t.args = {'55': '66'} 923 assert t.args == {'55': '66'} and str(t.query) == '55=66' 924 925 t.query = 'a=a&s=s s' 926 assert isinstance(t.query, furl.Query) 927 assert str(t.query) == 'a=a&s=s+s' 928 assert t.args == t.query.params == {'a': 'a', 's': 's s'} 929 930 931class TestFragment(unittest.TestCase): 932 933 def test_basics(self): 934 f = furl.Fragment() 935 assert str(f.path) == '' and str(f.query) == '' and str(f) == '' 936 937 f.args['sup'] = 'foo' 938 assert str(f) == 'sup=foo' 939 f.path = 'yasup' 940 assert str(f) == 'yasup?sup=foo' 941 f.path = '/yasup' 942 assert str(f) == '/yasup?sup=foo' 943 assert str(f.query) == 'sup=foo' 944 f.query.params['sup'] = 'kwlpumps' 945 assert str(f) == '/yasup?sup=kwlpumps' 946 f.query = '' 947 assert str(f) == '/yasup' 948 f.path = '' 949 assert str(f) == '' 950 f.args['no'] = 'dads' 951 f.query.params['hi'] = 'gr8job' 952 assert str(f) == 'no=dads&hi=gr8job' 953 954 def test_none(self): 955 f = furl.Fragment(None) 956 assert str(f) == '' 957 958 f = furl.Fragment('sup') 959 assert str(f) == 'sup' 960 f.load(None) 961 assert str(f) == '' 962 963 def test_load(self): 964 comps = [('', '', {}), 965 ('?', '%3F', {}), 966 ('??a??', '%3F%3Fa%3F%3F', {}), 967 ('??a??=', '', {'?a??': ''}), 968 ('schtoot', 'schtoot', {}), 969 ('sch/toot/YOEP', 'sch/toot/YOEP', {}), 970 ('/sch/toot/YOEP', '/sch/toot/YOEP', {}), 971 ('schtoot?', 'schtoot%3F', {}), 972 ('schtoot?NOP', 'schtoot%3FNOP', {}), 973 ('schtoot?NOP=', 'schtoot', {'NOP': ''}), 974 ('schtoot?=PARNT', 'schtoot', {'': 'PARNT'}), 975 ('schtoot?NOP=PARNT', 'schtoot', {'NOP': 'PARNT'}), 976 ('dog?machine?yes', 'dog%3Fmachine%3Fyes', {}), 977 ('dog?machine=?yes', 'dog', {'machine': '?yes'}), 978 ('schtoot?a=a&hok%20sprm', 'schtoot', 979 {'a': 'a', 'hok sprm': None}), 980 ('schtoot?a=a&hok sprm', 'schtoot', 981 {'a': 'a', 'hok sprm': None}), 982 ('sch/toot?a=a&hok sprm', 'sch/toot', 983 {'a': 'a', 'hok sprm': None}), 984 ('/sch/toot?a=a&hok sprm', '/sch/toot', 985 {'a': 'a', 'hok sprm': None}), 986 ] 987 988 for fragment, path, query in comps: 989 f = furl.Fragment() 990 f.load(fragment) 991 assert str(f.path) == path 992 assert f.query.params == query 993 994 def test_add(self): 995 f = furl.Fragment('') 996 assert f is f.add(path='one two', args=[('a', 'a'), ('s', 's s')]) 997 assert str(f) == 'one%20two?a=a&s=s+s' 998 999 f = furl.Fragment('break?legs=broken') 1000 assert f is f.add(path='horse bones', args=[('a', 'a'), ('s', 's s')]) 1001 assert str(f) == 'break/horse%20bones?legs=broken&a=a&s=s+s' 1002 1003 def test_set(self): 1004 f = furl.Fragment('asdf?lol=sup&foo=blorp') 1005 assert f is f.set(path='one two', args=[('a', 'a'), ('s', 's s')]) 1006 assert str(f) == 'one%20two?a=a&s=s+s' 1007 1008 assert f is f.set(path='!', separator=False) 1009 assert f.separator is False 1010 assert str(f) == '!a=a&s=s+s' 1011 1012 def test_remove(self): 1013 f = furl.Fragment('a/path/great/job?lol=sup&foo=blorp') 1014 assert f is f.remove(path='job', args=['lol']) 1015 assert str(f) == 'a/path/great/?foo=blorp' 1016 1017 assert f is f.remove(path=['path', 'great'], args=['foo']) 1018 assert str(f) == 'a/path/great/' 1019 assert f is f.remove(path=['path', 'great', '']) 1020 assert str(f) == 'a/' 1021 1022 assert f is f.remove(fragment=True) 1023 assert str(f) == '' 1024 1025 def test_encoding(self): 1026 f = furl.Fragment() 1027 f.path = "/?:@-._~!$&'()*+,;=" 1028 assert str(f) == "/?:@-._~!$&'()*+,;=" 1029 f.query = [('a', 'a'), ('b b', 'NOPE')] 1030 assert str(f) == "/%3F:@-._~!$&'()*+,;=?a=a&b+b=NOPE" 1031 f.separator = False 1032 assert str(f) == "/?:@-._~!$&'()*+,;=a=a&b+b=NOPE" 1033 1034 f = furl.Fragment() 1035 f.path = "/?:@-._~!$&'()*+,;= ^`<>[]" 1036 assert str(f) == "/?:@-._~!$&'()*+,;=%20%5E%60%3C%3E%5B%5D" 1037 f.query = [('a', 'a'), ('b b', 'NOPE')] 1038 assert str( 1039 f) == "/%3F:@-._~!$&'()*+,;=%20%5E%60%3C%3E%5B%5D?a=a&b+b=NOPE" 1040 f.separator = False 1041 assert str(f) == "/?:@-._~!$&'()*+,;=%20%5E%60%3C%3E%5B%5Da=a&b+b=NOPE" 1042 1043 f = furl.furl() 1044 f.fragment = 'a?b?c?d?' 1045 assert f.url == '#a?b?c?d?' 1046 assert str(f.fragment) == 'a?b?c?d?' 1047 1048 def test_unicode(self): 1049 for fragment in ['ロリポップ', u'ロリポップ']: 1050 f = furl.furl('http://sprop.ru/#ja').set(fragment=fragment) 1051 assert str(f.fragment) == ( 1052 '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97') 1053 1054 def test_equality(self): 1055 assert furl.Fragment() == furl.Fragment() 1056 1057 f1 = furl.furl('http://sprop.ru/#ja').fragment 1058 f11 = furl.furl('http://spep.ru/#ja').fragment 1059 f2 = furl.furl('http://sprop.ru/#nein').fragment 1060 1061 assert f1 == f11 and str(f1) == str(f11) 1062 assert f1 != f2 and str(f1) != str(f2) 1063 1064 def test_nonzero(self): 1065 f = furl.Fragment() 1066 assert not f 1067 1068 f = furl.Fragment('') 1069 assert not f 1070 1071 f = furl.Fragment('asdf') 1072 assert f 1073 1074 f = furl.Fragment() 1075 f.path = 'sup' 1076 assert f 1077 1078 f = furl.Fragment() 1079 f.query = 'a=a' 1080 assert f 1081 1082 f = furl.Fragment() 1083 f.path = 'sup' 1084 f.query = 'a=a' 1085 assert f 1086 1087 f = furl.Fragment() 1088 f.path = 'sup' 1089 f.query = 'a=a' 1090 f.separator = False 1091 assert f 1092 1093 def test_asdict(self): 1094 path_encoded = '/wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 1095 1096 key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 1097 value_encoded = 'test%C3%A4' 1098 query_encoded = 'a=1&' + key_encoded + '=' + value_encoded 1099 1100 fragment_encoded = path_encoded + '?' + query_encoded 1101 p = furl.Path(path_encoded) 1102 q = furl.Query(query_encoded) 1103 f = furl.Fragment(fragment_encoded) 1104 d = { 1105 'separator': True, 1106 'path': p.asdict(), 1107 'query': q.asdict(), 1108 'encoded': fragment_encoded, 1109 } 1110 assert f.asdict() == d 1111 1112 1113class TestFragmentCompositionInterface(unittest.TestCase): 1114 1115 def test_interface(self): 1116 class tester(furl.FragmentCompositionInterface): 1117 1118 def __init__(self): 1119 furl.FragmentCompositionInterface.__init__(self) 1120 1121 def __setattr__(self, attr, value): 1122 ffci = furl.FragmentCompositionInterface 1123 if not ffci.__setattr__(self, attr, value): 1124 object.__setattr__(self, attr, value) 1125 1126 t = tester() 1127 assert isinstance(t.fragment, furl.Fragment) 1128 assert isinstance(t.fragment.path, furl.Path) 1129 assert isinstance(t.fragment.query, furl.Query) 1130 assert str(t.fragment) == '' 1131 assert t.fragment.separator 1132 assert str(t.fragment.path) == '' 1133 assert str(t.fragment.query) == '' 1134 1135 t.fragment = 'animal meats' 1136 assert isinstance(t.fragment, furl.Fragment) 1137 t.fragment.path = 'pump/dump' 1138 t.fragment.query = 'a=a&s=s+s' 1139 assert isinstance(t.fragment.path, furl.Path) 1140 assert isinstance(t.fragment.query, furl.Query) 1141 assert str(t.fragment.path) == 'pump/dump' 1142 assert t.fragment.path.segments == ['pump', 'dump'] 1143 assert not t.fragment.path.isabsolute 1144 assert str(t.fragment.query) == 'a=a&s=s+s' 1145 assert t.fragment.args == t.fragment.query.params == { 1146 'a': 'a', 's': 's s'} 1147 1148 1149class TestFurl(unittest.TestCase): 1150 1151 def setUp(self): 1152 # Don't hide duplicate Warnings. Test for all of them. 1153 warnings.simplefilter("always") 1154 1155 def _param(self, url, key, val): 1156 # urlsplit() only parses the query for schemes in urlparse.uses_query, 1157 # so switch to 'http' (a scheme in urlparse.uses_query) for 1158 # urlparse.urlsplit(). 1159 if '://' in url: 1160 url = 'http://%s' % url.split('://', 1)[1] 1161 1162 # Note: urlparse.urlsplit() doesn't separate the query from the path 1163 # for all schemes, only those schemes in the list urlparse.uses_query. 1164 # So, as a result of using urlparse.urlsplit(), this little helper 1165 # function only works when provided URLs whos schemes are also in 1166 # urlparse.uses_query. 1167 items = parse_qsl(urlsplit(url).query, True) 1168 return (key, val) in items 1169 1170 def test_constructor_and_set(self): 1171 f = furl.furl( 1172 'http://user:pass@pumps.ru/', args={'hi': 'bye'}, 1173 scheme='scrip', path='prorp', host='horp', fragment='fraggg') 1174 assert f.url == 'scrip://user:pass@horp/prorp?hi=bye#fraggg' 1175 1176 def test_none(self): 1177 f = furl.furl(None) 1178 assert str(f) == '' 1179 1180 f = furl.furl('http://user:pass@pumps.ru/') 1181 assert str(f) == 'http://user:pass@pumps.ru/' 1182 f.load(None) 1183 assert str(f) == '' 1184 1185 def test_idna(self): 1186 decoded_host = u'ドメイン.テスト' 1187 encoded_url = 'http://user:pass@xn--eckwd4c7c.xn--zckzah/' 1188 1189 f = furl.furl(encoded_url) 1190 assert f.username == 'user' and f.password == 'pass' 1191 assert f.host == decoded_host 1192 1193 f = furl.furl(encoded_url) 1194 assert f.host == decoded_host 1195 1196 f = furl.furl('http://user:pass@pumps.ru/') 1197 f.set(host=decoded_host) 1198 assert f.url == encoded_url 1199 1200 f = furl.furl().set(host=u'ロリポップ') 1201 assert f.url == '//xn--9ckxbq5co' 1202 1203 def test_unicode(self): 1204 paths = ['ロリポップ', u'ロリポップ'] 1205 pairs = [('testö', 'testä'), (u'testö', u'testä')] 1206 key_encoded, value_encoded = u'test%C3%B6', u'test%C3%A4' 1207 path_encoded = u'%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 1208 1209 base_url = 'http://pumps.ru' 1210 full_url_utf8_str = '%s/%s?%s=%s' % ( 1211 base_url, paths[0], pairs[0][0], pairs[0][1]) 1212 full_url_unicode = u'%s/%s?%s=%s' % ( 1213 base_url, paths[1], pairs[1][0], pairs[1][1]) 1214 full_url_encoded = '%s/%s?%s=%s' % ( 1215 base_url, path_encoded, key_encoded, value_encoded) 1216 1217 f = furl.furl(full_url_utf8_str) 1218 assert f.url == full_url_encoded 1219 1220 # Accept unicode without raising an exception. 1221 f = furl.furl(full_url_unicode) 1222 assert f.url == full_url_encoded 1223 1224 # Accept unicode paths. 1225 for path in paths: 1226 f = furl.furl(base_url) 1227 f.path = path 1228 assert f.url == '%s/%s' % (base_url, path_encoded) 1229 1230 # Accept unicode queries. 1231 for key, value in pairs: 1232 f = furl.furl(base_url).set(path=path) 1233 f.args[key] = value 1234 assert f.args[key] == value # Unicode values aren't modified. 1235 assert key not in f.url 1236 assert value not in f.url 1237 assert quote_plus(furl.utf8(key)) in f.url 1238 assert quote_plus(furl.utf8(value)) in f.url 1239 f.path.segments = [path] 1240 assert f.path.segments == [path] # Unicode values aren't modified. 1241 assert f.url == full_url_encoded 1242 1243 def test_scheme(self): 1244 assert furl.furl().scheme is None 1245 assert furl.furl('').scheme is None 1246 1247 # Lowercase. 1248 assert furl.furl('/sup/').set(scheme='PrOtO').scheme == 'proto' 1249 1250 # No scheme. 1251 for url in ['sup.txt', '/d/sup', '#flarg']: 1252 f = furl.furl(url) 1253 assert f.scheme is None and f.url == url 1254 1255 # Protocol relative URLs. 1256 for url in ['//', '//sup.txt', '//arc.io/d/sup']: 1257 f = furl.furl(url) 1258 assert f.scheme is None and f.url == url 1259 1260 f = furl.furl('//sup.txt') 1261 assert f.scheme is None and f.url == '//sup.txt' 1262 f.scheme = '' 1263 assert f.scheme == '' and f.url == '://sup.txt' 1264 1265 # Schemes without slashes, like 'mailto:'. 1266 assert furl.furl('mailto:sup@sprp.ru').url == 'mailto:sup@sprp.ru' 1267 assert furl.furl('mailto://sup@sprp.ru').url == 'mailto://sup@sprp.ru' 1268 1269 f = furl.furl('mailto:sproop:spraps@sprp.ru') 1270 assert f.scheme == 'mailto' 1271 assert f.path == 'sproop:spraps@sprp.ru' 1272 1273 f = furl.furl('mailto:') 1274 assert f.url == 'mailto:' and f.scheme == 'mailto' and f.netloc is None 1275 1276 f = furl.furl('tel:+1-555-555-1234') 1277 assert f.scheme == 'tel' and str(f.path) == '+1-555-555-1234' 1278 1279 f = furl.furl('urn:srp.com/ferret?query') 1280 assert f.scheme == 'urn' and str(f.path) == 'srp.com/ferret' 1281 assert str(f.query) == 'query' 1282 1283 # Ignore invalid schemes. 1284 assert furl.furl('+invalid$scheme://lolsup').scheme is None 1285 assert furl.furl('/api/test?url=http://a.com').scheme is None 1286 1287 # Empty scheme. 1288 f = furl.furl(':') 1289 assert f.scheme == '' and f.netloc is None and f.url == ':' 1290 1291 def test_username_and_password(self): 1292 # Empty usernames and passwords. 1293 for url in ['', 'http://www.pumps.com/']: 1294 f = furl.furl(url) 1295 assert f.username is f.password is None 1296 1297 baseurl = 'http://www.google.com/' 1298 usernames = ['', 'user', '@user', ' a-user_NAME$%^&09@:/'] 1299 passwords = ['', 'pass', ':pass', ' a-PASS_word$%^&09@:/'] 1300 1301 # Username only. 1302 for username in usernames: 1303 encoded_username = quote(username, safe='') 1304 encoded_url = 'http://%s@www.google.com/' % encoded_username 1305 1306 f = furl.furl(encoded_url) 1307 assert f.username == username and f.password is None 1308 1309 f = furl.furl(baseurl) 1310 f.username = username 1311 assert f.username == username and f.password is None 1312 assert f.url == encoded_url 1313 1314 f = furl.furl(baseurl) 1315 f.set(username=username) 1316 assert f.username == username and f.password is None 1317 assert f.url == encoded_url 1318 1319 f.remove(username=True) 1320 assert f.username is f.password is None and f.url == baseurl 1321 1322 # Password only. 1323 for password in passwords: 1324 encoded_password = quote(password, safe='') 1325 encoded_url = 'http://:%s@www.google.com/' % encoded_password 1326 1327 f = furl.furl(encoded_url) 1328 assert f.password == password and f.username == '' 1329 1330 f = furl.furl(baseurl) 1331 f.password = password 1332 assert f.password == password and not f.username 1333 assert f.url == encoded_url 1334 1335 f = furl.furl(baseurl) 1336 f.set(password=password) 1337 assert f.password == password and not f.username 1338 assert f.url == encoded_url 1339 1340 f.remove(password=True) 1341 assert f.username is f.password is None and f.url == baseurl 1342 1343 # Username and password. 1344 for username in usernames: 1345 for password in passwords: 1346 encoded_username = quote(username, safe='') 1347 encoded_password = quote(password, safe='') 1348 encoded_url = 'http://%s:%s@www.google.com/' % ( 1349 encoded_username, encoded_password) 1350 1351 f = furl.furl(encoded_url) 1352 assert f.username == username and f.password == password 1353 1354 f = furl.furl(baseurl) 1355 f.username = username 1356 f.password = password 1357 assert f.username == username and f.password == password 1358 assert f.url == encoded_url 1359 1360 f = furl.furl(baseurl) 1361 f.set(username=username, password=password) 1362 assert f.username == username and f.password == password 1363 assert f.url == encoded_url 1364 1365 f = furl.furl(baseurl) 1366 f.remove(username=True, password=True) 1367 assert f.username is f.password is None and f.url == baseurl 1368 1369 # Username and password in the network location string. 1370 f = furl.furl() 1371 f.netloc = 'user@domain.com' 1372 assert f.username == 'user' and not f.password 1373 assert f.netloc == 'user@domain.com' 1374 1375 f = furl.furl() 1376 f.netloc = ':pass@domain.com' 1377 assert not f.username and f.password == 'pass' 1378 assert f.netloc == ':pass@domain.com' 1379 1380 f = furl.furl() 1381 f.netloc = 'user:pass@domain.com' 1382 assert f.username == 'user' and f.password == 'pass' 1383 assert f.netloc == 'user:pass@domain.com' 1384 f = furl.furl() 1385 assert f.username is f.password is None 1386 f.username = 'uu' 1387 assert f.username == 'uu' and f.password is None and f.url == '//uu@' 1388 f.password = 'pp' 1389 assert f.username == 'uu' and f.password == 'pp' 1390 assert f.url == '//uu:pp@' 1391 f.username = '' 1392 assert f.username == '' and f.password == 'pp' and f.url == '//:pp@' 1393 f.password = '' 1394 assert f.username == f.password == '' and f.url == '//:@' 1395 f.password = None 1396 assert f.username == '' and f.password is None and f.url == '//@' 1397 f.username = None 1398 assert f.username is f.password is None and f.url == '' 1399 f.password = '' 1400 assert f.username is None and f.password == '' and f.url == '//:@' 1401 1402 # Unicode. 1403 username = u'kødp' 1404 password = u'ålæg' 1405 f = furl.furl(u'https://%s:%s@example.com/' % (username, password)) 1406 assert f.username == username and f.password == password 1407 assert f.url == 'https://k%C3%B8dp:%C3%A5l%C3%A6g@example.com/' 1408 1409 def test_basics(self): 1410 url = 'hTtP://www.pumps.com/' 1411 f = furl.furl(url) 1412 assert f.scheme == 'http' 1413 assert f.netloc == 'www.pumps.com' 1414 assert f.host == 'www.pumps.com' 1415 assert f.port == 80 1416 assert str(f.path) == '/' 1417 assert str(f.query) == '' 1418 assert f.args == f.query.params == {} 1419 assert str(f.fragment) == '' 1420 assert f.url == str(f) == url.lower() 1421 assert f.url == furl.furl(f).url == furl.furl(f.url).url 1422 assert f is not f.copy() and f.url == f.copy().url 1423 1424 url = 'HTTPS://wWw.YAHOO.cO.UK/one/two/three?a=a&b=b&m=m%26m#fragment' 1425 f = furl.furl(url) 1426 assert f.scheme == 'https' 1427 assert f.netloc == 'www.yahoo.co.uk' 1428 assert f.host == 'www.yahoo.co.uk' 1429 assert f.port == 443 1430 assert str(f.path) == '/one/two/three' 1431 assert str(f.query) == 'a=a&b=b&m=m%26m' 1432 assert f.args == f.query.params == {'a': 'a', 'b': 'b', 'm': 'm&m'} 1433 assert str(f.fragment) == 'fragment' 1434 assert f.url == str(f) == url.lower() 1435 assert f.url == furl.furl(f).url == furl.furl(f.url).url 1436 assert f is not f.copy() and f.url == f.copy().url 1437 1438 url = 'sup://192.168.1.102:8080///one//a%20b////?s=kwl%20string#frag' 1439 f = furl.furl(url) 1440 assert f.scheme == 'sup' 1441 assert f.netloc == '192.168.1.102:8080' 1442 assert f.host == '192.168.1.102' 1443 assert f.port == 8080 1444 assert str(f.path) == '///one//a%20b////' 1445 assert str(f.query) == 's=kwl+string' 1446 assert f.args == f.query.params == {'s': 'kwl string'} 1447 assert str(f.fragment) == 'frag' 1448 quoted = 'sup://192.168.1.102:8080///one//a%20b////?s=kwl+string#frag' 1449 assert f.url == str(f) == quoted 1450 assert f.url == furl.furl(f).url == furl.furl(f.url).url 1451 assert f is not f.copy() and f.url == f.copy().url 1452 1453 # URL paths are optionally absolute if scheme and netloc are 1454 # empty. 1455 f = furl.furl() 1456 f.path.segments = ['pumps'] 1457 assert str(f.path) == 'pumps' 1458 f.path = 'pumps' 1459 assert str(f.path) == 'pumps' 1460 1461 # Fragment paths are optionally absolute, and not absolute by 1462 # default. 1463 f = furl.furl() 1464 f.fragment.path.segments = ['pumps'] 1465 assert str(f.fragment.path) == 'pumps' 1466 f.fragment.path = 'pumps' 1467 assert str(f.fragment.path) == 'pumps' 1468 1469 # URLs comprised of a netloc string only should not be prefixed 1470 # with '//', as-is the default behavior of 1471 # urlparse.urlunsplit(). 1472 f = furl.furl() 1473 assert f.set(host='foo').url == '//foo' 1474 assert f.set(host='pumps.com').url == '//pumps.com' 1475 assert f.set(host='pumps.com', port=88).url == '//pumps.com:88' 1476 assert f.set(netloc='pumps.com:88').url == '//pumps.com:88' 1477 1478 # furl('...') and furl.url = '...' are functionally identical. 1479 url = 'https://www.pumps.com/path?query#frag' 1480 f1 = furl.furl(url) 1481 f2 = furl.furl() 1482 f2.url = url 1483 assert f1 == f2 1484 1485 # Empty scheme and netloc. 1486 f = furl.furl('://') 1487 assert f.scheme == f.netloc == '' and f.url == '://' 1488 1489 def test_basic_manipulation(self): 1490 f = furl.furl('http://www.pumps.com/') 1491 1492 f.args.setdefault('foo', 'blah') 1493 assert str(f) == 'http://www.pumps.com/?foo=blah' 1494 f.query.params['foo'] = 'eep' 1495 assert str(f) == 'http://www.pumps.com/?foo=eep' 1496 1497 f.port = 99 1498 assert str(f) == 'http://www.pumps.com:99/?foo=eep' 1499 1500 f.netloc = 'www.yahoo.com:220' 1501 assert str(f) == 'http://www.yahoo.com:220/?foo=eep' 1502 1503 f.netloc = 'www.yahoo.com' 1504 assert f.port == 80 1505 assert str(f) == 'http://www.yahoo.com/?foo=eep' 1506 1507 f.scheme = 'sup' 1508 assert str(f) == 'sup://www.yahoo.com:80/?foo=eep' 1509 1510 f.port = None 1511 assert str(f) == 'sup://www.yahoo.com/?foo=eep' 1512 1513 f.fragment = 'sup' 1514 assert str(f) == 'sup://www.yahoo.com/?foo=eep#sup' 1515 1516 f.path = 'hay supppp' 1517 assert str(f) == 'sup://www.yahoo.com/hay%20supppp?foo=eep#sup' 1518 1519 f.args['space'] = '1 2' 1520 assert str( 1521 f) == 'sup://www.yahoo.com/hay%20supppp?foo=eep&space=1+2#sup' 1522 1523 del f.args['foo'] 1524 assert str(f) == 'sup://www.yahoo.com/hay%20supppp?space=1+2#sup' 1525 1526 f.host = 'ohay.com' 1527 assert str(f) == 'sup://ohay.com/hay%20supppp?space=1+2#sup' 1528 1529 def test_path_itruediv(self): 1530 f = furl.furl('http://www.pumps.com/') 1531 1532 f /= 'a' 1533 assert f.url == 'http://www.pumps.com/a' 1534 1535 f /= 'b' 1536 assert f.url == 'http://www.pumps.com/a/b' 1537 1538 f /= 'c d/' 1539 assert f.url == 'http://www.pumps.com/a/b/c%20d/' 1540 1541 def test_path_truediv(self): 1542 f = furl.furl('http://www.pumps.com/') 1543 1544 f1 = f / 'a' 1545 assert f.url == 'http://www.pumps.com/' 1546 assert f1.url == 'http://www.pumps.com/a' 1547 1548 f2 = f / 'c' / 'd e/' 1549 assert f2.url == 'http://www.pumps.com/c/d%20e/' 1550 1551 f3 = f / furl.Path('f') 1552 assert f3.url == 'http://www.pumps.com/f' 1553 1554 def test_odd_urls(self): 1555 # Empty. 1556 f = furl.furl('') 1557 assert f.username is f.password is None 1558 assert f.scheme is f.host is f.port is f.netloc is None 1559 assert str(f.path) == '' 1560 assert str(f.query) == '' 1561 assert f.args == f.query.params == {} 1562 assert str(f.fragment) == '' 1563 assert f.url == '' 1564 1565 # Keep in mind that ';' is a query delimiter for both the URL 1566 # query and the fragment query, resulting in the str(path), 1567 # str(query), and str(fragment) values below. 1568 url = ( 1569 "sup://example.com/:@-._~!$&'()*+,=;:@-._~!$&'()*+,=:@-._~!$&'()*+" 1570 ",==?/?:@-._~!$'()*+,;=/?:@-._~!$'()*+,;==#/?:@-._~!$&'()*+,;=") 1571 pathstr = "/:@-._~!$&'()*+,=;:@-._~!$&'()*+,=:@-._~!$&'()*+,==" 1572 querystr = ( 1573 quote_plus("/?:@-._~!$'()* ,") + '&' + 1574 '=' + quote_plus("/?:@-._~!$'()* ,") + '&' + 1575 '==') 1576 fragmentstr = ( 1577 '/?' + quote_plus(':@-._~!$') + '&' + 1578 quote_plus("'()* ,") + '&' + '=') 1579 f = furl.furl(url) 1580 assert f.scheme == 'sup' 1581 assert f.host == 'example.com' 1582 assert f.port is None 1583 assert f.netloc == 'example.com' 1584 assert str(f.path) == pathstr 1585 assert str(f.query) == querystr 1586 assert str(f.fragment) == fragmentstr 1587 1588 # Scheme only. 1589 f = furl.furl('sup://') 1590 assert f.scheme == 'sup' 1591 assert f.host == f.netloc == '' 1592 assert f.port is None 1593 assert str(f.path) == str(f.query) == str(f.fragment) == '' 1594 assert f.args == f.query.params == {} 1595 assert f.url == 'sup://' 1596 f.scheme = None 1597 assert f.scheme is None and f.netloc == '' and f.url == '//' 1598 f.scheme = '' 1599 assert f.scheme == '' and f.netloc == '' and f.url == '://' 1600 1601 f = furl.furl('sup:') 1602 assert f.scheme == 'sup' 1603 assert f.host is f.port is f.netloc is None 1604 assert str(f.path) == str(f.query) == str(f.fragment) == '' 1605 assert f.args == f.query.params == {} 1606 assert f.url == 'sup:' 1607 f.scheme = None 1608 assert f.url == '' and f.netloc is None 1609 f.scheme = '' 1610 assert f.url == ':' and f.netloc is None 1611 1612 # Host only. 1613 f = furl.furl().set(host='pumps.meat') 1614 assert f.url == '//pumps.meat' and f.netloc == f.host == 'pumps.meat' 1615 f.host = None 1616 assert f.url == '' and f.host is f.netloc is None 1617 f.host = '' 1618 assert f.url == '//' and f.host == f.netloc == '' 1619 1620 # Port only. 1621 f = furl.furl() 1622 f.port = 99 1623 assert f.url == '//:99' and f.netloc is not None 1624 f.port = None 1625 assert f.url == '' and f.netloc is None 1626 1627 # urlparse.urlsplit() treats the first two '//' as the beginning 1628 # of a netloc, even if the netloc is empty. 1629 f = furl.furl('////path') 1630 assert f.netloc == '' and str(f.path) == '//path' 1631 assert f.url == '////path' 1632 1633 # TODO(grun): Test more odd URLs. 1634 1635 def test_hosts(self): 1636 # No host. 1637 url = 'http:///index.html' 1638 f = furl.furl(url) 1639 assert f.host == '' and furl.furl(url).url == url 1640 1641 # Valid IPv4 and IPv6 addresses. 1642 f = furl.furl('http://192.168.1.101') 1643 f = furl.furl('http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/') 1644 1645 # Host strings are always lowercase. 1646 f = furl.furl('http://wWw.PuMpS.com') 1647 assert f.host == 'www.pumps.com' 1648 f.host = 'yEp.NoPe' 1649 assert f.host == 'yep.nope' 1650 f.set(host='FeE.fIe.FoE.fUm') 1651 assert f.host == 'fee.fie.foe.fum' 1652 1653 # Invalid IPv4 addresses shouldn't raise an exception because 1654 # urlparse.urlsplit() doesn't raise an exception on invalid IPv4 1655 # addresses. 1656 f = furl.furl('http://1.2.3.4.5.6/') 1657 1658 # Invalid, but well-formed, IPv6 addresses shouldn't raise an 1659 # exception because urlparse.urlsplit() doesn't raise an 1660 # exception on invalid IPv6 addresses. 1661 furl.furl('http://[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]/') 1662 1663 # Malformed IPv6 should raise an exception because urlparse.urlsplit() 1664 # raises an exception on malformed IPv6 addresses. 1665 with self.assertRaises(ValueError): 1666 furl.furl('http://[0:0:0:0:0:0:0:1/') 1667 with self.assertRaises(ValueError): 1668 furl.furl('http://0:0:0:0:0:0:0:1]/') 1669 1670 # Invalid host strings should raise ValueError. 1671 invalid_hosts = ['.', '..', 'a..b', '.a.b', '.a.b.', '$', 'a$b'] 1672 for host in invalid_hosts: 1673 with self.assertRaises(ValueError): 1674 f = furl.furl('http://%s/' % host) 1675 for host in invalid_hosts + ['a/b']: 1676 with self.assertRaises(ValueError): 1677 f = furl.furl('http://google.com/').set(host=host) 1678 1679 def test_netloc(self): 1680 f = furl.furl('http://pumps.com/') 1681 netloc = '1.2.3.4.5.6:999' 1682 f.netloc = netloc 1683 assert f.netloc == netloc 1684 assert f.host == '1.2.3.4.5.6' 1685 assert f.port == 999 1686 1687 netloc = '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]:888' 1688 f.netloc = netloc 1689 assert f.netloc == netloc 1690 assert f.host == '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]' 1691 assert f.port == 888 1692 1693 # Malformed IPv6 should raise an exception because 1694 # urlparse.urlsplit() raises an exception 1695 with self.assertRaises(ValueError): 1696 f.netloc = '[0:0:0:0:0:0:0:1' 1697 with self.assertRaises(ValueError): 1698 f.netloc = '0:0:0:0:0:0:0:1]' 1699 1700 # Invalid ports should raise an exception. 1701 with self.assertRaises(ValueError): 1702 f.netloc = '[0:0:0:0:0:0:0:1]:alksdflasdfasdf' 1703 with self.assertRaises(ValueError): 1704 f.netloc = 'pump2pump.org:777777777777' 1705 1706 # No side effects. 1707 assert f.host == '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]' 1708 assert f.port == 888 1709 1710 # Empty netloc. 1711 f = furl.furl('//') 1712 assert f.scheme is None and f.netloc == '' and f.url == '//' 1713 1714 def test_origin(self): 1715 assert furl.furl().origin == '://' 1716 assert furl.furl().set(host='slurp.ru').origin == '://slurp.ru' 1717 assert furl.furl('http://pep.ru:83/yep').origin == 'http://pep.ru:83' 1718 assert furl.furl().set(origin='pep://yep.ru').origin == 'pep://yep.ru' 1719 f = furl.furl('http://user:pass@pumps.com/path?query#fragemtn') 1720 assert f.origin == 'http://pumps.com' 1721 1722 f = furl.furl('none://ignored/lol?sup').set(origin='sup://yep.biz:99') 1723 assert f.url == 'sup://yep.biz:99/lol?sup' 1724 1725 # Username and password are unaffected. 1726 f = furl.furl('http://user:pass@slurp.com') 1727 f.origin = 'ssh://horse-machine.de' 1728 assert f.url == 'ssh://user:pass@horse-machine.de' 1729 1730 # Malformed IPv6 should raise an exception because urlparse.urlsplit() 1731 # raises an exception. 1732 with self.assertRaises(ValueError): 1733 f.origin = '[0:0:0:0:0:0:0:1' 1734 with self.assertRaises(ValueError): 1735 f.origin = 'http://0:0:0:0:0:0:0:1]' 1736 1737 # Invalid ports should raise an exception. 1738 with self.assertRaises(ValueError): 1739 f.origin = '[0:0:0:0:0:0:0:1]:alksdflasdfasdf' 1740 with self.assertRaises(ValueError): 1741 f.origin = 'http://pump2pump.org:777777777777' 1742 1743 def test_ports(self): 1744 # Default port values. 1745 assert furl.furl('http://www.pumps.com/').port == 80 1746 assert furl.furl('https://www.pumps.com/').port == 443 1747 assert furl.furl('undefined://www.pumps.com/').port is None 1748 1749 # Override default port values. 1750 assert furl.furl('http://www.pumps.com:9000/').port == 9000 1751 assert furl.furl('https://www.pumps.com:9000/').port == 9000 1752 assert furl.furl('undefined://www.pumps.com:9000/').port == 9000 1753 1754 # Reset the port. 1755 f = furl.furl('http://www.pumps.com:9000/') 1756 f.port = None 1757 assert f.url == 'http://www.pumps.com/' 1758 assert f.port == 80 1759 1760 f = furl.furl('undefined://www.pumps.com:9000/') 1761 f.port = None 1762 assert f.url == 'undefined://www.pumps.com/' 1763 assert f.port is None 1764 1765 # Invalid port raises ValueError with no side effects. 1766 with self.assertRaises(ValueError): 1767 furl.furl('http://www.pumps.com:invalid/') 1768 1769 url = 'http://www.pumps.com:400/' 1770 f = furl.furl(url) 1771 assert f.port == 400 1772 with self.assertRaises(ValueError): 1773 f.port = 'asdf' 1774 assert f.url == url 1775 f.port = 9999 1776 with self.assertRaises(ValueError): 1777 f.port = [] 1778 with self.assertRaises(ValueError): 1779 f.port = -1 1780 with self.assertRaises(ValueError): 1781 f.port = 77777777777 1782 assert f.port == 9999 1783 assert f.url == 'http://www.pumps.com:9999/' 1784 1785 # The port is inferred from scheme changes, if possible, but 1786 # only if the port is otherwise unset (self.port is None). 1787 assert furl.furl('unknown://pump.com').set(scheme='http').port == 80 1788 assert furl.furl('unknown://pump.com:99').set(scheme='http').port == 99 1789 assert furl.furl('http://pump.com:99').set(scheme='unknown').port == 99 1790 1791 # Hostnames are always lowercase. 1792 f = furl.furl('http://wWw.PuMpS.com:9999') 1793 assert f.netloc == 'www.pumps.com:9999' 1794 f.netloc = 'yEp.NoPe:9999' 1795 assert f.netloc == 'yep.nope:9999' 1796 f.set(netloc='FeE.fIe.FoE.fUm:9999') 1797 assert f.netloc == 'fee.fie.foe.fum:9999' 1798 1799 def test_add(self): 1800 f = furl.furl('http://pumps.com/') 1801 1802 assert f is f.add(args={'a': 'a', 'm': 'm&m'}, path='sp ace', 1803 fragment_path='1', fragment_args={'f': 'frp'}) 1804 assert self._param(f.url, 'a', 'a') 1805 assert self._param(f.url, 'm', 'm&m') 1806 assert str(f.fragment) == '1?f=frp' 1807 assert str(f.path) == urlsplit(f.url).path == '/sp%20ace' 1808 1809 assert f is f.add(path='dir', fragment_path='23', args={'b': 'b'}, 1810 fragment_args={'b': 'bewp'}) 1811 assert self._param(f.url, 'a', 'a') 1812 assert self._param(f.url, 'm', 'm&m') 1813 assert self._param(f.url, 'b', 'b') 1814 assert str(f.path) == '/sp%20ace/dir' 1815 assert str(f.fragment) == '1/23?f=frp&b=bewp' 1816 1817 # Supplying both <args> and <query_params> should raise a 1818 # warning. 1819 with warnings.catch_warnings(record=True) as w1: 1820 f.add(args={'a': '1'}, query_params={'a': '2'}) 1821 assert len(w1) == 1 and issubclass(w1[0].category, UserWarning) 1822 assert self._param( 1823 f.url, 'a', '1') and self._param(f.url, 'a', '2') 1824 params = f.args.allitems() 1825 assert params.index(('a', '1')) < params.index(('a', '2')) 1826 1827 def test_set(self): 1828 f = furl.furl('http://pumps.com/sp%20ace/dir') 1829 assert f is f.set(args={'no': 'nope'}, fragment='sup') 1830 assert 'a' not in f.args 1831 assert 'b' not in f.args 1832 assert f.url == 'http://pumps.com/sp%20ace/dir?no=nope#sup' 1833 1834 # No conflict warnings between <host>/<port> and <netloc>, or 1835 # <query> and <params>. 1836 assert f is f.set(args={'a': 'a a'}, path='path path/dir', port='999', 1837 fragment='moresup', scheme='sup', host='host') 1838 assert str(f.path) == '/path%20path/dir' 1839 assert f.url == 'sup://host:999/path%20path/dir?a=a+a#moresup' 1840 1841 # Path as a list of path segments to join. 1842 assert f is f.set(path=['d1', 'd2']) 1843 assert f.url == 'sup://host:999/d1/d2?a=a+a#moresup' 1844 assert f is f.add(path=['/d3/', '/d4/']) 1845 assert f.url == 'sup://host:999/d1/d2/%2Fd3%2F/%2Fd4%2F?a=a+a#moresup' 1846 1847 # Set a lot of stuff (but avoid conflicts, which are tested 1848 # below). 1849 f.set( 1850 query_params={'k': 'k'}, fragment_path='no scrubs', scheme='morp', 1851 host='myhouse', port=69, path='j$j*m#n', fragment_args={'f': 'f'}) 1852 assert f.url == 'morp://myhouse:69/j$j*m%23n?k=k#no%20scrubs?f=f' 1853 1854 # No side effects. 1855 oldurl = f.url 1856 with self.assertRaises(ValueError): 1857 f.set(args={'a': 'a a'}, path='path path/dir', port='INVALID_PORT', 1858 fragment='moresup', scheme='sup', host='host') 1859 assert f.url == oldurl 1860 with warnings.catch_warnings(record=True) as w1: 1861 self.assertRaises( 1862 ValueError, f.set, netloc='nope.com:99', port='NOPE') 1863 assert len(w1) == 1 and issubclass(w1[0].category, UserWarning) 1864 assert f.url == oldurl 1865 1866 # Separator isn't reset with set(). 1867 f = furl.Fragment() 1868 f.separator = False 1869 f.set(path='flush', args={'dad': 'nope'}) 1870 assert str(f) == 'flushdad=nope' 1871 1872 # Test warnings for potentially overlapping parameters. 1873 f = furl.furl('http://pumps.com') 1874 warnings.simplefilter("always") 1875 1876 # Scheme, origin overlap. Scheme takes precedence. 1877 with warnings.catch_warnings(record=True) as w1: 1878 f.set(scheme='hi', origin='bye://sup.sup') 1879 assert len(w1) == 1 and issubclass(w1[0].category, UserWarning) 1880 assert f.scheme == 'hi' 1881 1882 # Netloc, origin, host and/or port. Host and port take precedence. 1883 with warnings.catch_warnings(record=True) as w1: 1884 f.set(netloc='dumps.com:99', origin='sup://pumps.com:88') 1885 assert len(w1) == 1 and issubclass(w1[0].category, UserWarning) 1886 with warnings.catch_warnings(record=True) as w1: 1887 f.set(netloc='dumps.com:99', host='ohay.com') 1888 assert len(w1) == 1 and issubclass(w1[0].category, UserWarning) 1889 assert f.host == 'ohay.com' 1890 assert f.port == 99 1891 with warnings.catch_warnings(record=True) as w2: 1892 f.set(netloc='dumps.com:99', port=88) 1893 assert len(w2) == 1 and issubclass(w2[0].category, UserWarning) 1894 assert f.port == 88 1895 with warnings.catch_warnings(record=True) as w2: 1896 f.set(origin='http://dumps.com:99', port=88) 1897 assert len(w2) == 1 and issubclass(w2[0].category, UserWarning) 1898 assert f.port == 88 1899 with warnings.catch_warnings(record=True) as w3: 1900 f.set(netloc='dumps.com:99', host='ohay.com', port=88) 1901 assert len(w3) == 1 and issubclass(w3[0].category, UserWarning) 1902 1903 # Query, args, and query_params overlap - args and query_params 1904 # take precedence. 1905 with warnings.catch_warnings(record=True) as w4: 1906 f.set(query='yosup', args={'a': 'a', 'b': 'b'}) 1907 assert len(w4) == 1 and issubclass(w4[0].category, UserWarning) 1908 assert self._param(f.url, 'a', 'a') 1909 assert self._param(f.url, 'b', 'b') 1910 with warnings.catch_warnings(record=True) as w5: 1911 f.set(query='yosup', query_params={'a': 'a', 'b': 'b'}) 1912 assert len(w5) == 1 and issubclass(w5[0].category, UserWarning) 1913 assert self._param(f.url, 'a', 'a') 1914 assert self._param(f.url, 'b', 'b') 1915 with warnings.catch_warnings(record=True) as w6: 1916 f.set(args={'a': 'a', 'b': 'b'}, query_params={'c': 'c', 'd': 'd'}) 1917 assert len(w6) == 1 and issubclass(w6[0].category, UserWarning) 1918 assert self._param(f.url, 'c', 'c') 1919 assert self._param(f.url, 'd', 'd') 1920 1921 # Fragment, fragment_path, fragment_args, and fragment_separator 1922 # overlap - fragment_separator, fragment_path, and fragment_args 1923 # take precedence. 1924 with warnings.catch_warnings(record=True) as w7: 1925 f.set(fragment='hi', fragment_path='!', fragment_args={'a': 'a'}, 1926 fragment_separator=False) 1927 assert len(w7) == 1 and issubclass(w7[0].category, UserWarning) 1928 assert str(f.fragment) == '!a=a' 1929 with warnings.catch_warnings(record=True) as w8: 1930 f.set(fragment='hi', fragment_path='bye') 1931 assert len(w8) == 1 and issubclass(w8[0].category, UserWarning) 1932 assert str(f.fragment) == 'bye' 1933 with warnings.catch_warnings(record=True) as w9: 1934 f.set(fragment='hi', fragment_args={'a': 'a'}) 1935 assert len(w9) == 1 and issubclass(w9[0].category, UserWarning) 1936 assert str(f.fragment) == 'hia=a' 1937 with warnings.catch_warnings(record=True) as w10: 1938 f.set(fragment='!?a=a', fragment_separator=False) 1939 assert len(w10) == 1 and issubclass(w10[0].category, UserWarning) 1940 assert str(f.fragment) == '!a=a' 1941 1942 def test_remove(self): 1943 url = ('http://u:p@host:69/a/big/path/?a=a&b=b&s=s+s#a frag?with=args' 1944 '&a=a') 1945 f = furl.furl(url) 1946 1947 # Remove without parameters removes nothing. 1948 assert f.url == f.remove().url 1949 1950 # username, password, and port must be True. 1951 assert f == f.copy().remove( 1952 username='nope', password='nope', port='nope') 1953 1954 # Basics. 1955 assert f is f.remove(fragment=True, args=['a', 'b'], path='path/', 1956 username=True, password=True, port=True) 1957 assert f.url == 'http://host/a/big/?s=s+s' 1958 1959 # No errors are thrown when removing URL components that don't exist. 1960 f = furl.furl(url) 1961 assert f is f.remove(fragment_path=['asdf'], fragment_args=['asdf'], 1962 args=['asdf'], path=['ppp', 'ump']) 1963 assert self._param(f.url, 'a', 'a') 1964 assert self._param(f.url, 'b', 'b') 1965 assert self._param(f.url, 's', 's s') 1966 assert str(f.path) == '/a/big/path/' 1967 assert str(f.fragment.path) == 'a%20frag' 1968 assert f.fragment.args == {'a': 'a', 'with': 'args'} 1969 1970 # Path as a list of paths to join before removing. 1971 assert f is f.remove(fragment_path='a frag', fragment_args=['a'], 1972 query_params=['a', 'b'], path=['big', 'path', ''], 1973 port=True) 1974 assert f.url == 'http://u:p@host/a/?s=s+s#with=args' 1975 1976 assert f is f.remove( 1977 path=True, query=True, fragment=True, username=True, 1978 password=True) 1979 assert f.url == 'http://host' 1980 1981 def test_join(self): 1982 empty_tests = ['', '/meat', '/meat/pump?a=a&b=b#fragsup', 1983 'sup://www.pumps.org/brg/pap/mrf?a=b&c=d#frag?sup', ] 1984 run_tests = [ 1985 # Join full URLs. 1986 ('unknown://pepp.ru', 'unknown://pepp.ru'), 1987 ('unknown://pepp.ru?one=two&three=four', 1988 'unknown://pepp.ru?one=two&three=four'), 1989 ('unknown://pepp.ru/new/url/?one=two#blrp', 1990 'unknown://pepp.ru/new/url/?one=two#blrp'), 1991 1992 # Absolute paths ('/foo'). 1993 ('/pump', 'unknown://pepp.ru/pump'), 1994 ('/pump/2/dump', 'unknown://pepp.ru/pump/2/dump'), 1995 ('/pump/2/dump/', 'unknown://pepp.ru/pump/2/dump/'), 1996 1997 # Relative paths ('../foo'). 1998 ('./crit/', 'unknown://pepp.ru/pump/2/dump/crit/'), 1999 ('.././../././././srp', 'unknown://pepp.ru/pump/2/srp'), 2000 ('../././../nop', 'unknown://pepp.ru/nop'), 2001 2002 # Query included. 2003 ('/erp/?one=two', 'unknown://pepp.ru/erp/?one=two'), 2004 ('morp?three=four', 'unknown://pepp.ru/erp/morp?three=four'), 2005 ('/root/pumps?five=six', 2006 'unknown://pepp.ru/root/pumps?five=six'), 2007 2008 # Fragment included. 2009 ('#sup', 'unknown://pepp.ru/root/pumps?five=six#sup'), 2010 ('/reset?one=two#yepYEP', 2011 'unknown://pepp.ru/reset?one=two#yepYEP'), 2012 ('./slurm#uwantpump?', 'unknown://pepp.ru/slurm#uwantpump?'), 2013 2014 # Unicode. 2015 ('/?kødpålæg=4', 'unknown://pepp.ru/?k%C3%B8dp%C3%A5l%C3%A6g=4'), 2016 (u'/?kødpålæg=4', 'unknown://pepp.ru/?k%C3%B8dp%C3%A5l%C3%A6g=4'), 2017 ] 2018 2019 for test in empty_tests: 2020 f = furl.furl().join(test) 2021 assert f.url == test 2022 2023 f = furl.furl('') 2024 for join, result in run_tests: 2025 assert f is f.join(join) and f.url == result 2026 2027 # Join other furl object, which serialize to strings with str(). 2028 f = furl.furl('') 2029 for join, result in run_tests: 2030 tojoin = furl.furl(join) 2031 assert f is f.join(tojoin) and f.url == result 2032 2033 # Join multiple URLs. 2034 f = furl.furl('') 2035 f.join('path', 'tcp://blorp.biz', 'http://pepp.ru/', 'a/b/c', 2036 '#uwantpump?') 2037 assert f.url == 'http://pepp.ru/a/b/c#uwantpump?' 2038 2039 # In edge cases (e.g. URLs without an authority/netloc), behave 2040 # identically to urllib.parse.urljoin(). 2041 f = furl.furl('wss://slrp.com/').join('foo:1') 2042 assert f.url == 'wss://slrp.com/foo:1' 2043 f = furl.furl('wss://slrp.com/').join('foo:1:rip') 2044 assert f.url == 'foo:1:rip' 2045 f = furl.furl('scheme:path').join('foo:blah') 2046 assert f.url == 'foo:blah' 2047 2048 def test_tostr(self): 2049 f = furl.furl('http://blast.off/?a+b=c+d&two%20tap=cat%20nap%24%21') 2050 assert f.tostr() == f.url 2051 assert (f.tostr(query_delimiter=';') == 2052 'http://blast.off/?a+b=c+d;two+tap=cat+nap%24%21') 2053 assert (f.tostr(query_quote_plus=False) == 2054 'http://blast.off/?a%20b=c%20d&two%20tap=cat%20nap%24%21') 2055 assert (f.tostr(query_delimiter=';', query_quote_plus=False) == 2056 'http://blast.off/?a%20b=c%20d;two%20tap=cat%20nap%24%21') 2057 assert (f.tostr(query_quote_plus=False, query_dont_quote=True) == 2058 'http://blast.off/?a%20b=c%20d&two%20tap=cat%20nap$!') 2059 # query_dont_quote ignores invalid query characters, like '$'. 2060 assert (f.tostr(query_quote_plus=False, query_dont_quote='$') == 2061 'http://blast.off/?a%20b=c%20d&two%20tap=cat%20nap$%21') 2062 2063 url = 'https://klugg.com/?hi=*' 2064 url_encoded = 'https://klugg.com/?hi=%2A&url=' 2065 f = furl.furl(url).set(args=[('hi', '*'), ('url', url)]) 2066 assert f.tostr() == url_encoded + quote_plus(url) 2067 assert f.tostr(query_dont_quote=True) == url + '&url=' + url 2068 assert f.tostr(query_dont_quote='*') == ( 2069 url + '&url=' + quote_plus(url, '*')) 2070 2071 def test_equality(self): 2072 assert furl.furl() is not furl.furl() and furl.furl() == furl.furl() 2073 2074 assert furl.furl() is not None 2075 2076 url = 'https://www.yahoo.co.uk/one/two/three?a=a&b=b&m=m%26m#fragment' 2077 assert furl.furl(url) != url # No furl to string comparisons. 2078 assert furl.furl(url) == furl.furl(url) 2079 assert furl.furl(url).remove(path=True) != furl.furl(url) 2080 2081 def test_urlsplit(self): 2082 # Without any delimiters like '://' or '/', the input should be 2083 # treated as a path. 2084 urls = ['sup', '127.0.0.1', 'www.google.com', '192.168.1.1:8000'] 2085 for url in urls: 2086 assert isinstance(furl.urlsplit(url), SplitResult) 2087 assert furl.urlsplit(url).path == urlsplit(url).path 2088 2089 # No changes to existing urlsplit() behavior for known schemes. 2090 url = 'http://www.pumps.com/' 2091 assert isinstance(furl.urlsplit(url), SplitResult) 2092 assert furl.urlsplit(url) == urlsplit(url) 2093 2094 url = 'https://www.yahoo.co.uk/one/two/three?a=a&b=b&m=m%26m#fragment' 2095 assert isinstance(furl.urlsplit(url), SplitResult) 2096 assert furl.urlsplit(url) == urlsplit(url) 2097 2098 # Properly split the query from the path for unknown schemes. 2099 url = 'unknown://www.yahoo.com?one=two&three=four' 2100 correct = ('unknown', 'www.yahoo.com', '', 'one=two&three=four', '') 2101 assert isinstance(furl.urlsplit(url), SplitResult) 2102 assert furl.urlsplit(url) == correct 2103 2104 url = 'sup://192.168.1.102:8080///one//two////?s=kwl%20string#frag' 2105 correct = ('sup', '192.168.1.102:8080', '///one//two////', 2106 's=kwl%20string', 'frag') 2107 assert isinstance(furl.urlsplit(url), SplitResult) 2108 assert furl.urlsplit(url) == correct 2109 2110 url = 'crazyyy://www.yahoo.co.uk/one/two/three?a=a&b=b&m=m%26m#frag' 2111 correct = ('crazyyy', 'www.yahoo.co.uk', '/one/two/three', 2112 'a=a&b=b&m=m%26m', 'frag') 2113 assert isinstance(furl.urlsplit(url), SplitResult) 2114 assert furl.urlsplit(url) == correct 2115 2116 def test_join_path_segments(self): 2117 jps = furl.join_path_segments 2118 2119 # Empty. 2120 assert jps() == [] 2121 assert jps([]) == [] 2122 assert jps([], [], [], []) == [] 2123 2124 # Null strings. 2125 # [''] means nothing, or an empty string, in the final path 2126 # segments. 2127 # ['', ''] is preserved as a slash in the final path segments. 2128 assert jps(['']) == [] 2129 assert jps([''], ['']) == [] 2130 assert jps([''], [''], ['']) == [] 2131 assert jps([''], ['', '']) == ['', ''] 2132 assert jps([''], [''], [''], ['']) == [] 2133 assert jps(['', ''], ['', '']) == ['', '', ''] 2134 assert jps(['', '', ''], ['', '']) == ['', '', '', ''] 2135 assert jps(['', '', '', '', '', '']) == ['', '', '', '', '', ''] 2136 assert jps(['', '', '', ''], ['', '']) == ['', '', '', '', ''] 2137 assert jps(['', '', '', ''], ['', ''], ['']) == ['', '', '', '', ''] 2138 assert jps(['', '', '', ''], ['', '', '']) == ['', '', '', '', '', ''] 2139 2140 # Basics. 2141 assert jps(['a']) == ['a'] 2142 assert jps(['a', 'b']) == ['a', 'b'] 2143 assert jps(['a'], ['b']) == ['a', 'b'] 2144 assert jps(['1', '2', '3'], ['4', '5']) == ['1', '2', '3', '4', '5'] 2145 2146 # A trailing slash is preserved if no new slash is being added. 2147 # ex: ['a', ''] + ['b'] == ['a', 'b'], or 'a/' + 'b' == 'a/b' 2148 assert jps(['a', ''], ['b']) == ['a', 'b'] 2149 assert jps(['a'], [''], ['b']) == ['a', 'b'] 2150 assert jps(['', 'a', ''], ['b']) == ['', 'a', 'b'] 2151 assert jps(['', 'a', ''], ['b', '']) == ['', 'a', 'b', ''] 2152 2153 # A new slash is preserved if no trailing slash exists. 2154 # ex: ['a'] + ['', 'b'] == ['a', 'b'], or 'a' + '/b' == 'a/b' 2155 assert jps(['a'], ['', 'b']) == ['a', 'b'] 2156 assert jps(['a'], [''], ['b']) == ['a', 'b'] 2157 assert jps(['', 'a'], ['', 'b']) == ['', 'a', 'b'] 2158 assert jps(['', 'a', ''], ['b', '']) == ['', 'a', 'b', ''] 2159 assert jps(['', 'a', ''], ['b'], ['']) == ['', 'a', 'b'] 2160 assert jps(['', 'a', ''], ['b'], ['', '']) == ['', 'a', 'b', ''] 2161 2162 # A trailing slash and a new slash means that an extra slash 2163 # will exist afterwords. 2164 # ex: ['a', ''] + ['', 'b'] == ['a', '', 'b'], or 'a/' + '/b' 2165 # == 'a//b' 2166 assert jps(['a', ''], ['', 'b']) == ['a', '', 'b'] 2167 assert jps(['a'], [''], [''], ['b']) == ['a', 'b'] 2168 assert jps(['', 'a', ''], ['', 'b']) == ['', 'a', '', 'b'] 2169 assert jps(['', 'a'], [''], ['b', '']) == ['', 'a', 'b', ''] 2170 assert jps(['', 'a'], [''], [''], ['b'], ['']) == ['', 'a', 'b'] 2171 assert jps(['', 'a'], [''], [''], ['b'], ['', '']) == [ 2172 '', 'a', 'b', ''] 2173 assert jps(['', 'a'], ['', ''], ['b'], ['', '']) == ['', 'a', 'b', ''] 2174 assert jps(['', 'a'], ['', '', ''], ['b']) == ['', 'a', '', 'b'] 2175 assert jps(['', 'a', ''], ['', '', ''], ['', 'b']) == [ 2176 '', 'a', '', '', '', 'b'] 2177 assert jps(['a', '', ''], ['', '', ''], ['', 'b']) == [ 2178 'a', '', '', '', '', 'b'] 2179 2180 # Path segments blocks without slashes, are combined as 2181 # expected. 2182 assert jps(['a', 'b'], ['c', 'd']) == ['a', 'b', 'c', 'd'] 2183 assert jps(['a'], ['b'], ['c'], ['d']) == ['a', 'b', 'c', 'd'] 2184 assert jps(['a', 'b', 'c', 'd'], ['e']) == ['a', 'b', 'c', 'd', 'e'] 2185 assert jps(['a', 'b', 'c'], ['d'], ['e', 'f']) == [ 2186 'a', 'b', 'c', 'd', 'e', 'f'] 2187 2188 # Putting it all together. 2189 assert jps(['a', '', 'b'], ['', 'c', 'd']) == ['a', '', 'b', 'c', 'd'] 2190 assert jps(['a', '', 'b', ''], ['c', 'd']) == ['a', '', 'b', 'c', 'd'] 2191 assert jps(['a', '', 'b', ''], ['c', 'd'], ['', 'e']) == [ 2192 'a', '', 'b', 'c', 'd', 'e'] 2193 assert jps(['', 'a', '', 'b', ''], ['', 'c']) == [ 2194 '', 'a', '', 'b', '', 'c'] 2195 assert jps(['', 'a', ''], ['', 'b', ''], ['', 'c']) == [ 2196 '', 'a', '', 'b', '', 'c'] 2197 2198 def test_remove_path_segments(self): 2199 rps = furl.remove_path_segments 2200 2201 # [''] represents a slash, equivalent to ['','']. 2202 2203 # Basics. 2204 assert rps([], []) == [] 2205 assert rps([''], ['']) == [] 2206 assert rps(['a'], ['a']) == [] 2207 assert rps(['a'], ['', 'a']) == ['a'] 2208 assert rps(['a'], ['a', '']) == ['a'] 2209 assert rps(['a'], ['', 'a', '']) == ['a'] 2210 2211 # Slash manipulation. 2212 assert rps([''], ['', '']) == [] 2213 assert rps(['', ''], ['']) == [] 2214 assert rps(['', ''], ['', '']) == [] 2215 assert rps(['', 'a', 'b', 'c'], ['b', 'c']) == ['', 'a', ''] 2216 assert rps(['', 'a', 'b', 'c'], ['', 'b', 'c']) == ['', 'a'] 2217 assert rps(['', 'a', '', ''], ['']) == ['', 'a', ''] 2218 assert rps(['', 'a', '', ''], ['', '']) == ['', 'a', ''] 2219 assert rps(['', 'a', '', ''], ['', '', '']) == ['', 'a'] 2220 2221 # Remove a portion of the path from the tail of the original 2222 # path. 2223 assert rps(['', 'a', 'b', ''], ['', 'a', 'b', '']) == [] 2224 assert rps(['', 'a', 'b', ''], ['a', 'b', '']) == ['', ''] 2225 assert rps(['', 'a', 'b', ''], ['b', '']) == ['', 'a', ''] 2226 assert rps(['', 'a', 'b', ''], ['', 'b', '']) == ['', 'a'] 2227 assert rps(['', 'a', 'b', ''], ['', '']) == ['', 'a', 'b'] 2228 assert rps(['', 'a', 'b', ''], ['']) == ['', 'a', 'b'] 2229 assert rps(['', 'a', 'b', ''], []) == ['', 'a', 'b', ''] 2230 2231 assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b', 'c']) == [] 2232 assert rps(['', 'a', 'b', 'c'], ['a', 'b', 'c']) == ['', ''] 2233 assert rps(['', 'a', 'b', 'c'], ['b', 'c']) == ['', 'a', ''] 2234 assert rps(['', 'a', 'b', 'c'], ['', 'b', 'c']) == ['', 'a'] 2235 assert rps(['', 'a', 'b', 'c'], ['c']) == ['', 'a', 'b', ''] 2236 assert rps(['', 'a', 'b', 'c'], ['', 'c']) == ['', 'a', 'b'] 2237 assert rps(['', 'a', 'b', 'c'], []) == ['', 'a', 'b', 'c'] 2238 assert rps(['', 'a', 'b', 'c'], ['']) == ['', 'a', 'b', 'c'] 2239 2240 # Attempt to remove valid subsections, but subsections not from 2241 # the end of the original path. 2242 assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b', '']) == [ 2243 '', 'a', 'b', 'c'] 2244 assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b']) == ['', 'a', 'b', 'c'] 2245 assert rps(['', 'a', 'b', 'c'], ['a', 'b']) == ['', 'a', 'b', 'c'] 2246 assert rps(['', 'a', 'b', 'c'], ['a', 'b', '']) == ['', 'a', 'b', 'c'] 2247 assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b']) == ['', 'a', 'b', 'c'] 2248 assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b', '']) == [ 2249 '', 'a', 'b', 'c'] 2250 2251 assert rps(['', 'a', 'b', 'c'], ['a']) == ['', 'a', 'b', 'c'] 2252 assert rps(['', 'a', 'b', 'c'], ['', 'a']) == ['', 'a', 'b', 'c'] 2253 assert rps(['', 'a', 'b', 'c'], ['a', '']) == ['', 'a', 'b', 'c'] 2254 assert rps(['', 'a', 'b', 'c'], ['', 'a', '']) == ['', 'a', 'b', 'c'] 2255 assert rps(['', 'a', 'b', 'c'], ['', 'a', '', '']) == [ 2256 '', 'a', 'b', 'c'] 2257 assert rps(['', 'a', 'b', 'c'], ['', '', 'a', '', '']) == [ 2258 '', 'a', 'b', 'c'] 2259 2260 assert rps(['', 'a', 'b', 'c'], ['']) == ['', 'a', 'b', 'c'] 2261 assert rps(['', 'a', 'b', 'c'], ['', '']) == ['', 'a', 'b', 'c'] 2262 assert rps(['', 'a', 'b', 'c'], ['c', '']) == ['', 'a', 'b', 'c'] 2263 2264 # Attempt to remove segments longer than the original. 2265 assert rps([], ['a']) == [] 2266 assert rps([], ['a', 'b']) == [] 2267 assert rps(['a'], ['a', 'b']) == ['a'] 2268 assert rps(['a', 'a'], ['a', 'a', 'a']) == ['a', 'a'] 2269 2270 def test_is_valid_port(self): 2271 valids = [1, 2, 3, 65535, 119, 2930] 2272 invalids = [-1, -9999, 0, 'a', [], (0), {1: 1}, 65536, 99999, {}, None] 2273 2274 for port in valids: 2275 assert furl.is_valid_port(port) 2276 for port in invalids: 2277 assert not furl.is_valid_port(port) 2278 2279 def test_is_valid_scheme(self): 2280 valids = ['a', 'ab', 'a-b', 'a.b', 'a+b', 'a----b', 'a123', 'a-b123', 2281 'a+b.1-+'] 2282 invalids = ['1', '12', '12+', '-', '.', '+', '1a', '+a', '.b.'] 2283 2284 for valid in valids: 2285 assert furl.is_valid_scheme(valid) 2286 for invalid in invalids: 2287 assert not furl.is_valid_scheme(invalid) 2288 2289 def test_is_valid_encoded_path_segment(self): 2290 valids = [('abcdefghijklmnopqrstuvwxyz' 2291 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 2292 '0123456789' '-._~' ":@!$&'()*+,;="), 2293 '', 'a', 'asdf', 'a%20a', '%3F', ] 2294 invalids = [' ^`<>[]"#/?', ' ', '%3Z', '/', '?'] 2295 2296 for valid in valids: 2297 assert furl.is_valid_encoded_path_segment(valid) 2298 for invalid in invalids: 2299 assert not furl.is_valid_encoded_path_segment(invalid) 2300 2301 def test_is_valid_encoded_query_key(self): 2302 valids = [('abcdefghijklmnopqrstuvwxyz' 2303 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 2304 '0123456789' '-._~' ":@!$&'()*+,;" '/?'), 2305 '', 'a', 'asdf', 'a%20a', '%3F', 'a+a', '/', '?', ] 2306 invalids = [' ^`<>[]"#', ' ', '%3Z', '#'] 2307 2308 for valid in valids: 2309 assert furl.is_valid_encoded_query_key(valid) 2310 for invalid in invalids: 2311 assert not furl.is_valid_encoded_query_key(invalid) 2312 2313 def test_is_valid_encoded_query_value(self): 2314 valids = [('abcdefghijklmnopqrstuvwxyz' 2315 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 2316 '0123456789' '-._~' ":@!$&'()*+,;" '/?='), 2317 '', 'a', 'asdf', 'a%20a', '%3F', 'a+a', '/', '?', '='] 2318 invalids = [' ^`<>[]"#', ' ', '%3Z', '#'] 2319 2320 for valid in valids: 2321 assert furl.is_valid_encoded_query_value(valid) 2322 for invalid in invalids: 2323 assert not furl.is_valid_encoded_query_value(invalid) 2324 2325 def test_asdict(self): 2326 path_encoded = '/wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 2327 2328 key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97' 2329 value_encoded = 'test%C3%A4' 2330 query_encoded = 'a=1&' + key_encoded + '=' + value_encoded 2331 2332 host = u'ドメイン.テスト' 2333 host_encoded = 'xn--eckwd4c7c.xn--zckzah' 2334 2335 fragment_encoded = path_encoded + '?' + query_encoded 2336 url = ('https://user:pass@%s%s?%s#%s' % ( 2337 host_encoded, path_encoded, query_encoded, fragment_encoded)) 2338 2339 p = furl.Path(path_encoded) 2340 q = furl.Query(query_encoded) 2341 f = furl.Fragment(fragment_encoded) 2342 u = furl.furl(url) 2343 d = { 2344 'url': url, 2345 'scheme': 'https', 2346 'username': 'user', 2347 'password': 'pass', 2348 'host': host, 2349 'host_encoded': host_encoded, 2350 'port': 443, 2351 'netloc': 'user:pass@xn--eckwd4c7c.xn--zckzah', 2352 'origin': 'https://xn--eckwd4c7c.xn--zckzah', 2353 'path': p.asdict(), 2354 'query': q.asdict(), 2355 'fragment': f.asdict(), 2356 } 2357 assert u.asdict() == d 2358