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&amp=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&amp=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