# -*- coding: utf-8 -*-

#
# furl - URL manipulation made simple.
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: Build Amazing Things (Unlicense)
#

from __future__ import division

import warnings
from abc import ABCMeta, abstractmethod
import sys

import six
from six.moves import zip
from six.moves.urllib.parse import (
    quote, quote_plus, parse_qsl, urlsplit, SplitResult)

import furl
from furl.omdict1D import omdict1D
from furl.compat import string_types, OrderedDict as odict

import unittest


#
# TODO(grun): Add tests for furl objects with strict=True. Make sure
# UserWarnings are raised when improperly encoded path, query, and
# fragment strings are provided.
#


@six.add_metaclass(ABCMeta)
class itemcontainer(object):

    """
    Utility list subclasses to expose allitems() and iterallitems()
    methods on different kinds of item containers - lists, dictionaries,
    multivalue dictionaries, and query strings. This provides a common
    iteration interface for looping through their items (including items
    with repeated keys).  original() is also provided to get access to a
    copy of the original container.
    """

    @abstractmethod
    def allitems(self):
        pass

    @abstractmethod
    def iterallitems(self):
        pass

    @abstractmethod
    def original(self):
        """
        Returns: A copy of the original data type. For example, an
        itemlist would return a list, itemdict a dict, etc.
        """
        pass


class itemlist(list, itemcontainer):

    def allitems(self):
        return list(self.iterallitems())

    def iterallitems(self):
        return iter(self)

    def original(self):
        return list(self)


class itemdict(odict, itemcontainer):

    def allitems(self):
        return list(self.items())

    def iterallitems(self):
        return iter(self.items())

    def original(self):
        return dict(self)


class itemomdict1D(omdict1D, itemcontainer):

    def original(self):
        return omdict1D(self)


class itemstr(str, itemcontainer):

    def allitems(self):
        # Keys and values get unquoted. i.e. 'a=a%20a' -> ['a', 'a a']. Empty
        # values without '=' have value None.
        items = []
        parsed = parse_qsl(self, keep_blank_values=True)
        pairstrs = [
            s2 for s1 in self.split('&') for s2 in s1.split(';')]
        for (key, value), pairstr in zip(parsed, pairstrs):
            if key == pairstr:
                value = None
            items.append((key, value))
        return items

    def iterallitems(self):
        return iter(self.allitems())

    def original(self):
        return str(self)


class TestPath(unittest.TestCase):

    def test_none(self):
        p = furl.Path(None)
        assert str(p) == ''

        p = furl.Path('/a/b/c')
        assert str(p) == '/a/b/c'
        p.load(None)
        assert str(p) == ''

    def test_isdir_isfile(self):
        for path in ['', '/']:
            p = furl.Path(path)
            assert p.isdir
            assert not p.isfile

        paths = ['dir1/', 'd1/d2/', 'd/d/d/d/d/', '/', '/dir1/', '/d1/d2/d3/']
        for path in paths:
            p = furl.Path(path)
            assert p.isdir
            assert not p.isfile

        for path in ['dir1', 'd1/d2', 'd/d/d/d/d', '/dir1', '/d1/d2/d3']:
            p = furl.Path(path)
            assert p.isfile
            assert not p.isdir

    def test_leading_slash(self):
        p = furl.Path('')
        assert not p.isabsolute
        assert not p.segments
        assert p.isdir and p.isdir != p.isfile
        assert str(p) == ''

        p = furl.Path('/')
        assert p.isabsolute
        assert p.segments == ['']
        assert p.isdir and p.isdir != p.isfile
        assert str(p) == '/'

        p = furl.Path('sup')
        assert not p.isabsolute
        assert p.segments == ['sup']
        assert p.isfile and p.isdir != p.isfile
        assert str(p) == 'sup'

        p = furl.Path('/sup')
        assert p.isabsolute
        assert p.segments == ['sup']
        assert p.isfile and p.isdir != p.isfile
        assert str(p) == '/sup'

        p = furl.Path('a/b/c')
        assert not p.isabsolute
        assert p.segments == ['a', 'b', 'c']
        assert p.isfile and p.isdir != p.isfile
        assert str(p) == 'a/b/c'

        p = furl.Path('/a/b/c')
        assert p.isabsolute
        assert p.segments == ['a', 'b', 'c']
        assert p.isfile and p.isdir != p.isfile
        assert str(p) == '/a/b/c'

        p = furl.Path('a/b/c/')
        assert not p.isabsolute
        assert p.segments == ['a', 'b', 'c', '']
        assert p.isdir and p.isdir != p.isfile
        assert str(p) == 'a/b/c/'

        p.isabsolute = True
        assert p.isabsolute
        assert str(p) == '/a/b/c/'

    def test_encoding(self):
        decoded = ['a+a', '/#haypepps/', 'a/:@/a', 'a/b']
        encoded = ['a%20a', '/%23haypepps/', 'a/:@/a', 'a%2Fb']

        for path in encoded:
            assert str(furl.Path(path)) == path

        safe = furl.Path.SAFE_SEGMENT_CHARS + '/'
        for path in decoded:
            assert str(furl.Path(path)) == quote(path, safe)

        # Valid path segment characters should not be encoded.
        for char in ":@-._~!$&'()*+,;=":
            f = furl.furl().set(path=char)
            assert str(f.path) == f.url == char
            assert f.path.segments == [char]

        # Invalid path segment characters should be encoded.
        for char in ' ^`<>[]"?':
            f = furl.furl().set(path=char)
            assert str(f.path) == f.url == quote(char)
            assert f.path.segments == [char]

        # Encode '/' within a path segment.
        segment = 'a/b'  # One path segment that includes the '/' character.
        f = furl.furl().set(path=[segment])
        assert str(f.path) == 'a%2Fb'
        assert f.path.segments == [segment]
        assert f.url == 'a%2Fb'

        # Encode percent signs in path segment stings.
        assert str(furl.Path(['a%20d'])) == 'a%2520d'
        assert str(furl.Path(['a%zzd'])) == 'a%25zzd'

        # Percent-encodings should be capitalized, as per RFC 3986.
        assert str(furl.Path('a%2fd')) == str(furl.Path('a%2Fd')) == 'a%2Fd'

    def test_load(self):
        self._test_set_load(furl.Path.load)

    def test_set(self):
        self._test_set_load(furl.Path.set)

    def _test_set_load(self, path_set_or_load):
        p = furl.Path('a/b/c/')
        assert path_set_or_load(p, furl.Path('asdf/asdf/')) == p
        assert not p.isabsolute and str(p) == 'asdf/asdf/'

        assert path_set_or_load(p, 'asdf/asdf/') == p
        assert not p.isabsolute and str(p) == 'asdf/asdf/'

        assert path_set_or_load(p, ['a', 'b', 'c', '']) == p
        assert not p.isabsolute and str(p) == 'a/b/c/'

        assert path_set_or_load(p, ['', 'a', 'b', 'c', '']) == p
        assert p.isabsolute and str(p) == '/a/b/c/'

    def test_add(self):
        # URL paths.
        p = furl.furl('a/b/c/').path
        assert p.add('d') == p
        assert not p.isabsolute
        assert str(p) == 'a/b/c/d'
        assert p.add('/') == p
        assert not p.isabsolute
        assert str(p) == 'a/b/c/d/'
        assert p.add(['e', 'f', 'e e', '']) == p
        assert not p.isabsolute
        assert str(p) == 'a/b/c/d/e/f/e%20e/'

        p = furl.furl().path
        assert not p.isabsolute
        assert p.add('/') == p
        assert p.isabsolute
        assert str(p) == '/'
        assert p.add('pump') == p
        assert p.isabsolute
        assert str(p) == '/pump'

        p = furl.furl().path
        assert not p.isabsolute
        assert p.add(['', '']) == p
        assert p.isabsolute
        assert str(p) == '/'
        assert p.add(['pump', 'dump', '']) == p
        assert p.isabsolute
        assert str(p) == '/pump/dump/'

        p = furl.furl('http://sprop.ru/a/b/c/').path
        assert p.add('d') == p
        assert p.isabsolute
        assert str(p) == '/a/b/c/d'
        assert p.add('/') == p
        assert p.isabsolute
        assert str(p) == '/a/b/c/d/'
        assert p.add(['e', 'f', 'e e', '']) == p
        assert p.isabsolute
        assert str(p) == '/a/b/c/d/e/f/e%20e/'

        f = furl.furl('http://sprop.ru')
        assert not f.path.isabsolute
        f.path.add('sup')
        assert f.path.isabsolute and str(f.path) == '/sup'

        f = furl.furl('/mrp').add(path='sup')
        assert str(f.path) == '/mrp/sup'

        f = furl.furl('/').add(path='/sup')
        assert f.path.isabsolute and str(f.path) == '/sup'

        f = furl.furl('/hi').add(path='sup')
        assert f.path.isabsolute and str(f.path) == '/hi/sup'

        f = furl.furl('/hi').add(path='/sup')
        assert f.path.isabsolute and str(f.path) == '/hi/sup'

        f = furl.furl('/hi/').add(path='/sup')
        assert f.path.isabsolute and str(f.path) == '/hi//sup'

        # Fragment paths.
        f = furl.furl('http://sprop.ru#mrp')
        assert not f.fragment.path.isabsolute
        f.fragment.path.add('sup')
        assert not f.fragment.path.isabsolute
        assert str(f.fragment.path) == 'mrp/sup'

        f = furl.furl('http://sprop.ru#/mrp')
        assert f.fragment.path.isabsolute
        f.fragment.path.add('sup')
        assert f.fragment.path.isabsolute
        assert str(f.fragment.path) == '/mrp/sup'

    def test_remove(self):
        # Remove lists of path segments.
        p = furl.Path('a/b/s%20s/')
        assert p.remove(['b', 's s']) == p
        assert str(p) == 'a/b/s%20s/'
        assert p.remove(['b', 's s', '']) == p
        assert str(p) == 'a/'
        assert p.remove(['', 'a']) == p
        assert str(p) == 'a/'
        assert p.remove(['a']) == p
        assert str(p) == 'a/'
        assert p.remove(['a', '']) == p
        assert str(p) == ''

        p = furl.Path('a/b/s%20s/')
        assert p.remove(['', 'b', 's s']) == p
        assert str(p) == 'a/b/s%20s/'
        assert p.remove(['', 'b', 's s', '']) == p
        assert str(p) == 'a'
        assert p.remove(['', 'a']) == p
        assert str(p) == 'a'
        assert p.remove(['a', '']) == p
        assert str(p) == 'a'
        assert p.remove(['a']) == p
        assert str(p) == ''

        p = furl.Path('a/b/s%20s/')
        assert p.remove(['a', 'b', 's%20s', '']) == p
        assert str(p) == 'a/b/s%20s/'
        assert p.remove(['a', 'b', 's s', '']) == p
        assert str(p) == ''

        # Remove a path string.
        p = furl.Path('a/b/s%20s/')
        assert p.remove('b/s s/') == p  # Encoding Warning.
        assert str(p) == 'a/'

        p = furl.Path('a/b/s%20s/')
        assert p.remove('b/s%20s/') == p
        assert str(p) == 'a/'
        assert p.remove('a') == p
        assert str(p) == 'a/'
        assert p.remove('/a') == p
        assert str(p) == 'a/'
        assert p.remove('a/') == p
        assert str(p) == ''

        p = furl.Path('a/b/s%20s/')
        assert p.remove('b/s s') == p  # Encoding Warning.
        assert str(p) == 'a/b/s%20s/'

        p = furl.Path('a/b/s%20s/')
        assert p.remove('b/s%20s') == p
        assert str(p) == 'a/b/s%20s/'
        assert p.remove('s%20s') == p
        assert str(p) == 'a/b/s%20s/'
        assert p.remove('s s') == p  # Encoding Warning.
        assert str(p) == 'a/b/s%20s/'
        assert p.remove('b/s%20s/') == p
        assert str(p) == 'a/'
        assert p.remove('/a') == p
        assert str(p) == 'a/'
        assert p.remove('a') == p
        assert str(p) == 'a/'
        assert p.remove('a/') == p
        assert str(p) == ''

        p = furl.Path('a/b/s%20s/')
        assert p.remove('a/b/s s/') == p  # Encoding Warning.
        assert str(p) == ''

        # Remove True.
        p = furl.Path('a/b/s%20s/')
        assert p.remove(True) == p
        assert str(p) == ''

    def test_isabsolute(self):
        paths = ['', '/', 'pump', 'pump/dump', '/pump/dump', '/pump/dump']
        for path in paths:
            # A URL path's isabsolute attribute is mutable if there's no
            # netloc.
            mutable = [
                {},  # No scheme or netloc -> isabsolute is mutable.
                {'scheme': 'nonempty'}]  # Scheme, no netloc -> isabs mutable.
            for kwargs in mutable:
                f = furl.furl().set(path=path, **kwargs)
                if path and path.startswith('/'):
                    assert f.path.isabsolute
                else:
                    assert not f.path.isabsolute
                f.path.isabsolute = False  # No exception.
                assert not f.path.isabsolute and not str(
                    f.path).startswith('/')
                f.path.isabsolute = True  # No exception.
                assert f.path.isabsolute and str(f.path).startswith('/')

            # A URL path's isabsolute attribute is read-only if there's
            # a netloc.
            readonly = [
                # Netloc, no scheme -> isabsolute is read-only if path
                # is non-empty.
                {'netloc': 'nonempty'},
                # Netloc and scheme -> isabsolute is read-only if path
                # is non-empty.
                {'scheme': 'nonempty', 'netloc': 'nonempty'}]
            for kwargs in readonly:
                f = furl.furl().set(path=path, **kwargs)
                if path:  # Exception raised.
                    with self.assertRaises(AttributeError):
                        f.path.isabsolute = False
                    with self.assertRaises(AttributeError):
                        f.path.isabsolute = True
                else:  # No exception raised.
                    f.path.isabsolute = False
                    assert not f.path.isabsolute and not str(
                        f.path).startswith('/')
                    f.path.isabsolute = True
                    assert f.path.isabsolute and str(f.path).startswith('/')

            # A Fragment path's isabsolute attribute is never read-only.
            f = furl.furl().set(fragment_path=path)
            if path and path.startswith('/'):
                assert f.fragment.path.isabsolute
            else:
                assert not f.fragment.path.isabsolute
            f.fragment.path.isabsolute = False  # No exception.
            assert (not f.fragment.path.isabsolute and
                    not str(f.fragment.path).startswith('/'))
            f.fragment.path.isabsolute = True  # No exception.
            assert f.fragment.path.isabsolute and str(
                f.fragment.path).startswith('/')

            # Sanity checks.
            f = furl.furl().set(scheme='mailto', path='dad@pumps.biz')
            assert str(f) == 'mailto:dad@pumps.biz' and not f.path.isabsolute
            f.path.isabsolute = True  # No exception.
            assert str(f) == 'mailto:/dad@pumps.biz' and f.path.isabsolute

            f = furl.furl().set(scheme='sup', fragment_path='/dad@pumps.biz')
            assert str(
                f) == 'sup:#/dad@pumps.biz' and f.fragment.path.isabsolute
            f.fragment.path.isabsolute = False  # No exception.
            assert str(
                f) == 'sup:#dad@pumps.biz' and not f.fragment.path.isabsolute

    def test_normalize(self):
        # Path not modified.
        for path in ['', 'a', '/a', '/a/', '/a/b%20b/c', '/a/b%20b/c/']:
            p = furl.Path(path)
            assert p.normalize() is p and str(p) == str(p.normalize()) == path

        # Path modified.
        to_normalize = [
            ('//', '/'), ('//a', '/a'), ('//a/', '/a/'), ('//a///', '/a/'),
            ('////a/..//b', '/b'), ('/a/..//b//./', '/b/')]
        for path, normalized in to_normalize:
            p = furl.Path(path)
            assert p.normalize() is p and str(p.normalize()) == normalized

    def test_equality(self):
        assert furl.Path() == furl.Path()

        p1 = furl.furl('http://sprop.ru/a/b/c/').path
        p11 = furl.furl('http://spep.ru/a/b/c/').path
        p2 = furl.furl('http://sprop.ru/a/b/c/d/').path

        assert p1 == p11 and str(p1) == str(p11)
        assert p1 != p2 and str(p1) != str(p2)

    def test_nonzero(self):
        p = furl.Path()
        assert not p

        p = furl.Path('')
        assert not p

        p = furl.Path('')
        assert not p
        p.segments = ['']
        assert p

        p = furl.Path('asdf')
        assert p

        p = furl.Path('/asdf')
        assert p

    def test_unicode(self):
        paths = ['/wiki/ロリポップ', u'/wiki/ロリポップ']
        path_encoded = '/wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'
        for path in paths:
            p = furl.Path(path)
            assert str(p) == path_encoded

    def test_itruediv(self):
        p = furl.Path()

        p /= 'a'
        assert str(p) == 'a'

        p /= 'b'
        assert str(p) == 'a/b'

        p /= 'c d/'
        assert str(p) == 'a/b/c%20d/'

        p /= furl.Path('e')
        assert str(p) == 'a/b/c%20d/e'

    def test_truediv(self):
        p = furl.Path()

        p1 = p / 'a'
        assert p1 is not p
        assert str(p1) == 'a'

        p2 = p / 'a' / 'b'
        assert p2 is not p
        assert str(p) == ''
        assert str(p2) == 'a/b'

        # Path objects should be joinable with other Path objects.
        p3 = furl.Path('e')
        p4 = furl.Path('f')
        assert p3 / p4 == furl.Path('e/f')

        # Joining paths with __truediv__ should not modify the original, even
        # if <isabsolute> is True.
        p5 = furl.Path(['a', 'b'], force_absolute=lambda _: True)
        p6 = p5 / 'c'
        assert str(p5) == '/a/b'
        assert str(p6) == '/a/b/c'

    def test_asdict(self):
        segments = ['wiki', 'ロリポップ']
        path_encoded = 'wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'
        p = furl.Path(path_encoded)
        d = {
            'isdir': False,
            'isfile': True,
            'isabsolute': False,
            'segments': segments,
            'encoded': path_encoded,
            }
        assert p.asdict() == d


class TestQuery(unittest.TestCase):

    def setUp(self):
        # All interaction with parameters is unquoted unless that
        # interaction is through an already encoded query string. In the
        # case of an already encoded query string, like 'a=a%20a&b=b',
        # its keys and values will be unquoted.
        self.itemlists = list(map(itemlist, [
            [], [(1, 1)], [(1, 1), (2, 2)], [
                (1, 1), (1, 11), (2, 2), (3, 3)], [('', '')],
            [('a', 1), ('b', 2), ('a', 3)], [
                ('a', 1), ('b', 'b'), ('a', 0.23)],
            [(0.1, -0.9), (-0.1231, 12312.3123)], [
                (None, None), (None, 'pumps')],
            [('', ''), ('', '')], [('', 'a'), ('', 'b'),
                                   ('b', ''), ('b', 'b')], [('<', '>')],
            [('=', '><^%'), ('><^%', '=')], [
                ("/?:@-._~!$'()*+,", "/?:@-._~!$'()*+,=")],
            [('+', '-')], [('a%20a', 'a%20a')], [('/^`<>[]"', '/^`<>[]"=')],
            [("/?:@-._~!$'()*+,", "/?:@-._~!$'()*+,=")],
        ]))
        self.itemdicts = list(map(itemdict, [
            {}, {1: 1, 2: 2}, {'1': '1', '2': '2',
                               '3': '3'}, {None: None}, {5.4: 4.5},
            {'': ''}, {'': 'a', 'b': ''}, {
                'pue': 'pue', 'a': 'a&a'}, {'=': '====='},
            {'pue': 'pue', 'a': 'a%26a'}, {'%': '`', '`': '%'}, {'+': '-'},
            {"/?:@-._~!$'()*+,": "/?:@-._~!$'()*+,="}, {
                '%25': '%25', '%60': '%60'},
        ]))
        self.itemomdicts = list(map(itemomdict1D, self.itemlists))
        self.itemstrs = list(map(itemstr, [
            # Basics.
            '', 'a=a', 'a=a&b=b', 'q=asdf&check_keywords=yes&area=default',
            '=asdf',
            # Various quoted and unquoted parameters and values that
            # will be unquoted.
            'space=a+a&amp=a%26a', 'a a=a a&no encoding=sup', 'a+a=a+a',
            'a%20=a+a', 'a%20a=a%20a', 'a+a=a%20a', 'space=a a&amp=a^a',
            'a=a&s=s#s', '+=+', "/?:@-._~!$&'()*+,=/?:@-._~!$'()*+,=",
            'a=a&c=c%5Ec', '<=>&^="', '%3C=%3E&%5E=%22', '%=%&`=`',
            '%25=%25&%60=%60',
            # Only keys, no values.
            'asdfasdf', '/asdf/asdf/sdf', '*******', '!@#(*&@!#(*@!#', 'a&b',
            'a&b',
            # Repeated parameters.
            'a=a&a=a', 'space=a+a&space=b+b',
            # Empty keys and/or values.
            '=', 'a=', 'a=a&a=', '=a&=b',
            # Semicolon delimiter is not allowed per default after bpo#42967
            'a=a&a=a', 'space=a+a&space=b+b',
        ]))
        self.items = (self.itemlists + self.itemdicts + self.itemomdicts +
                      self.itemstrs)

    def test_none(self):
        q = furl.Query(None)
        assert str(q) == ''

        q = furl.Query('a=b&c=d')
        assert str(q) == 'a=b&c=d'
        q.load(None)
        assert str(q) == ''

    def test_various(self):
        for items in self.items:
            q = furl.Query(items.original())
            assert q.params.allitems() == items.allitems()

            # encode() accepts both 'delimiter' and 'delimeter'. The
            # latter was incorrectly used until furl v0.4.6.
            e = q.encode
            assert e(';') == e(delimiter=';') == e(delimeter=';')

            # __nonzero__().
            if items.allitems():
                assert q
            else:
                assert not q

    def test_load(self):
        for items in self.items:
            q = furl.Query(items.original())
            for update in self.items:
                assert q.load(update) == q
                assert q.params.allitems() == update.allitems()

    def test_add(self):
        for items in self.items:
            q = furl.Query(items.original())
            runningsum = list(items.allitems())
            for itemupdate in self.items:
                assert q.add(itemupdate.original()) == q
                for item in itemupdate.iterallitems():
                    runningsum.append(item)
                assert q.params.allitems() == runningsum

    def test_set(self):
        for items in self.items:
            q = furl.Query(items.original())
            items_omd = omdict1D(items.allitems())
            for update in self.items:
                q.set(update)
                items_omd.updateall(update)
                assert q.params.allitems() == items_omd.allitems()

        # The examples.
        q = furl.Query({1: 1}).set([(1, None), (2, 2)])
        assert q.params.allitems() == [(1, None), (2, 2)]

        q = furl.Query({1: None, 2: None}).set([(1, 1), (2, 2), (1, 11)])
        assert q.params.allitems() == [(1, 1), (2, 2), (1, 11)]

        q = furl.Query({1: None}).set([(1, [1, 11, 111])])
        assert q.params.allitems() == [(1, 1), (1, 11), (1, 111)]

        # Further manual tests.
        q = furl.Query([(2, None), (3, None), (1, None)])
        q.set([(1, [1, 11]), (2, 2), (3, [3, 33])])
        assert q.params.allitems() == [
            (2, 2), (3, 3), (1, 1), (1, 11), (3, 33)]

    def test_remove(self):
        for items in self.items:
            # Remove one key at a time.
            q = furl.Query(items.original())
            for key in dict(items.iterallitems()):
                assert key in q.params
                assert q.remove(key) == q
                assert key not in q.params

            # Remove multiple keys at a time (in this case all of them).
            q = furl.Query(items.original())
            if items.allitems():
                assert q.params
            allkeys = [key for key, value in items.allitems()]
            assert q.remove(allkeys) == q
            assert len(q.params) == 0

            # Remove the whole query string with True.
            q = furl.Query(items.original())
            if items.allitems():
                assert q.params
            assert q.remove(True) == q
            assert len(q.params) == 0

        # List of keys to remove.
        q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')])
        q.remove(['a', 'b'])
        assert not list(q.params.items())

        # List of items to remove.
        q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')])
        q.remove([('a', '1'), ('b', '3')])
        assert list(q.params.allitems()) == [('b', '2'), ('a', '4')]

        # Dictionary of items to remove.
        q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')])
        q.remove({'b': '3', 'a': '1'})
        assert q.params.allitems() == [('b', '2'), ('a', '4')]

        # Multivalue dictionary of items to remove.
        q = furl.Query([('a', '1'), ('b', '2'), ('b', '3'), ('a', '4')])
        omd = omdict1D([('a', '4'), ('b', '3'), ('b', '2')])
        q.remove(omd)
        assert q.params.allitems() == [('a', '1')]

    def test_params(self):
        # Basics.
        q = furl.Query('a=a&b=b')
        assert q.params == {'a': 'a', 'b': 'b'}
        q.params['sup'] = 'sup'
        assert q.params == {'a': 'a', 'b': 'b', 'sup': 'sup'}
        del q.params['a']
        assert q.params == {'b': 'b', 'sup': 'sup'}
        q.params['b'] = 'BLROP'
        assert q.params == {'b': 'BLROP', 'sup': 'sup'}

        # Blanks keys and values are kept.
        q = furl.Query('=')
        assert q.params == {'': ''} and str(q) == '='
        q = furl.Query('=&=')
        assert q.params.allitems() == [('', ''), ('', '')] and str(q) == '=&='
        q = furl.Query('a=&=b')
        assert q.params == {'a': '', '': 'b'} and str(q) == 'a=&=b'

        # ';' is no longer a valid query delimiter, though it was prior to furl
        # v2.1.3. See https://bugs.python.org/issue42967.
        q = furl.Query('=;=')
        assert q.params.allitems() == [('', ';=')] and str(q) == '=%3B='

        # Non-string parameters are coerced to strings in the final
        # query string.
        q.params.clear()
        q.params[99] = 99
        q.params[None] = -1
        q.params['int'] = 1
        q.params['float'] = 0.39393
        assert str(q) == '99=99&None=-1&int=1&float=0.39393'

        # Spaces are encoded as '+'s. '+'s are encoded as '%2B'.
        q.params.clear()
        q.params['s s'] = 's s'
        q.params['p+p'] = 'p+p'
        assert str(q) == 's+s=s+s&p%2Bp=p%2Bp'

        # Params is an omdict (ordered multivalue dictionary).
        q.params.clear()
        q.params.add('1', '1').set('2', '4').add('1', '11').addlist(
            3, [3, 3, '3'])
        assert q.params.getlist('1') == ['1', '11'] and q.params['1'] == '1'
        assert q.params.getlist(3) == [3, 3, '3']

        # Assign various things to Query.params and make sure
        # Query.params is reinitialized, not replaced.
        for items in self.items:
            q.params = items.original()
            assert isinstance(q.params, omdict1D)

            pairs = zip(q.params.iterallitems(), items.iterallitems())
            for item1, item2 in pairs:
                assert item1 == item2

        # Value of '' -> '?param='. Value of None -> '?param'.
        q = furl.Query('slrp')
        assert str(q) == 'slrp' and q.params['slrp'] is None
        q = furl.Query('slrp=')
        assert str(q) == 'slrp=' and q.params['slrp'] == ''
        q = furl.Query('prp=&slrp')
        assert q.params['prp'] == '' and q.params['slrp'] is None
        q.params['slrp'] = ''
        assert str(q) == 'prp=&slrp=' and q.params['slrp'] == ''

    def test_unicode(self):
        pairs = [('ロリポップ', 'testä'), (u'ロリポップ', u'testä')]
        key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'
        value_encoded = 'test%C3%A4'

        for key, value in pairs:
            q = furl.Query('%s=%s' % (key, value))
            assert q.params[key] == value
            assert str(q) == '%s=%s' % (key_encoded, value_encoded)

            q = furl.Query()
            q.params[key] = value
            assert q.params[key] == value
            assert str(q) == '%s=%s' % (key_encoded, value_encoded)

    def test_equality(self):
        assert furl.Query() == furl.Query()

        q1 = furl.furl('http://sprop.ru/?a=1&b=2').query
        q11 = furl.furl('http://spep.ru/path/?a=1&b=2').query
        q2 = furl.furl('http://sprop.ru/?b=2&a=1').query

        assert q1 == q11 and str(q1) == str(q11)
        assert q1 != q2 and str(q1) != str(q2)

    def test_encode(self):
        for items in self.items:
            q = furl.Query(items.original())
            # encode() and __str__().
            assert str(q) == q.encode() == q.encode('&')

        # Accept both percent-encoded ('a=b%20c') and
        # application/x-www-form-urlencoded ('a=b+c') pairs as input.
        query = furl.Query('a=b%20c&d=e+f')
        assert query.encode(';') == 'a=b+c;d=e+f'
        assert query.encode(';', quote_plus=False) == 'a=b%20c;d=e%20f'

        # Encode '/' consistently across quote_plus=True and quote_plus=False.
        query = furl.Query('a /b')
        assert query.encode(quote_plus=True) == 'a+%2Fb'
        assert query.encode(quote_plus=False) == 'a%20%2Fb'

        # dont_quote= accepts both True and a string of safe characters not to
        # percent-encode. Unsafe query characters, like '^' and '#', are always
        # percent-encoded.
        query = furl.Query('a %2B/b?#')
        assert query.encode(dont_quote='^') == 'a+%2B%2Fb%3F%23'
        assert query.encode(quote_plus=True, dont_quote=True) == 'a++/b?%23'
        assert query.encode(quote_plus=False, dont_quote=True) == 'a%20+/b?%23'

    def test_asdict(self):
        pairs = [('a', '1'), ('ロリポップ', 'testä')]
        key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'
        value_encoded = 'test%C3%A4'
        query_encoded = 'a=1&' + key_encoded + '=' + value_encoded
        p = furl.Query(query_encoded)
        d = {
            'params': pairs,
            'encoded': query_encoded,
            }
        assert p.asdict() == d

    def test_value_encoding_empty_vs_nonempty_key(self):
        pair = ('=', '=')
        pair_encoded = '%3D=%3D'
        assert furl.Query(pair_encoded).params.allitems() == [pair]

        q = furl.Query()
        q.params = [pair]
        assert q.encode() == pair_encoded

        empty_key_pair = ('', '==3===')
        empty_key_encoded = '===3==='
        assert furl.Query(empty_key_encoded).params.items() == [empty_key_pair]

    def test_special_characters(self):
        q = furl.Query('==3==')
        assert q.params.allitems() == [('', '=3==')] and str(q) == '==3=='

        f = furl.furl('https://www.google.com????')
        assert f.args.allitems() == [('???', None)]

        q = furl.Query('&=&')
        assert q.params.allitems() == [('', None), ('', ''), ('', None)]
        assert str(q) == '&=&'

        url = 'https://www.google.com?&&%3F=&%3F'
        f = furl.furl(url)
        assert f.args.allitems() == [
            ('', None), ('', None), ('?', ''), ('?', None)]
        assert f.url == url

    def _quote_items(self, items):
        # Calculate the expected querystring with proper query encoding.
        #   Valid query key characters: "/?:@-._~!$'()*,;"
        #   Valid query value characters: "/?:@-._~!$'()*,;="
        allitems_quoted = []
        for key, value in items.iterallitems():
            pair = (
                quote_plus(str(key), "/?:@-._~!$'()*,;"),
                quote_plus(str(value), "/?:@-._~!$'()*,;="))
            allitems_quoted.append(pair)
        return allitems_quoted


class TestQueryCompositionInterface(unittest.TestCase):

    def test_interface(self):
        class tester(furl.QueryCompositionInterface):

            def __init__(self):
                furl.QueryCompositionInterface.__init__(self)

            def __setattr__(self, attr, value):
                fqci = furl.QueryCompositionInterface
                if not fqci.__setattr__(self, attr, value):
                    object.__setattr__(self, attr, value)

        t = tester()
        assert isinstance(t.query, furl.Query)
        assert str(t.query) == ''

        t.args = {'55': '66'}
        assert t.args == {'55': '66'} and str(t.query) == '55=66'

        t.query = 'a=a&s=s s'
        assert isinstance(t.query, furl.Query)
        assert str(t.query) == 'a=a&s=s+s'
        assert t.args == t.query.params == {'a': 'a', 's': 's s'}


class TestFragment(unittest.TestCase):

    def test_basics(self):
        f = furl.Fragment()
        assert str(f.path) == '' and str(f.query) == '' and str(f) == ''

        f.args['sup'] = 'foo'
        assert str(f) == 'sup=foo'
        f.path = 'yasup'
        assert str(f) == 'yasup?sup=foo'
        f.path = '/yasup'
        assert str(f) == '/yasup?sup=foo'
        assert str(f.query) == 'sup=foo'
        f.query.params['sup'] = 'kwlpumps'
        assert str(f) == '/yasup?sup=kwlpumps'
        f.query = ''
        assert str(f) == '/yasup'
        f.path = ''
        assert str(f) == ''
        f.args['no'] = 'dads'
        f.query.params['hi'] = 'gr8job'
        assert str(f) == 'no=dads&hi=gr8job'

    def test_none(self):
        f = furl.Fragment(None)
        assert str(f) == ''

        f = furl.Fragment('sup')
        assert str(f) == 'sup'
        f.load(None)
        assert str(f) == ''

    def test_load(self):
        comps = [('', '', {}),
                 ('?', '%3F', {}),
                 ('??a??', '%3F%3Fa%3F%3F', {}),
                 ('??a??=', '', {'?a??': ''}),
                 ('schtoot', 'schtoot', {}),
                 ('sch/toot/YOEP', 'sch/toot/YOEP', {}),
                 ('/sch/toot/YOEP', '/sch/toot/YOEP', {}),
                 ('schtoot?', 'schtoot%3F', {}),
                 ('schtoot?NOP', 'schtoot%3FNOP', {}),
                 ('schtoot?NOP=', 'schtoot', {'NOP': ''}),
                 ('schtoot?=PARNT', 'schtoot', {'': 'PARNT'}),
                 ('schtoot?NOP=PARNT', 'schtoot', {'NOP': 'PARNT'}),
                 ('dog?machine?yes', 'dog%3Fmachine%3Fyes', {}),
                 ('dog?machine=?yes', 'dog', {'machine': '?yes'}),
                 ('schtoot?a=a&hok%20sprm', 'schtoot',
                  {'a': 'a', 'hok sprm': None}),
                 ('schtoot?a=a&hok sprm', 'schtoot',
                  {'a': 'a', 'hok sprm': None}),
                 ('sch/toot?a=a&hok sprm', 'sch/toot',
                  {'a': 'a', 'hok sprm': None}),
                 ('/sch/toot?a=a&hok sprm', '/sch/toot',
                  {'a': 'a', 'hok sprm': None}),
                 ]

        for fragment, path, query in comps:
            f = furl.Fragment()
            f.load(fragment)
            assert str(f.path) == path
            assert f.query.params == query

    def test_add(self):
        f = furl.Fragment('')
        assert f is f.add(path='one two', args=[('a', 'a'), ('s', 's s')])
        assert str(f) == 'one%20two?a=a&s=s+s'

        f = furl.Fragment('break?legs=broken')
        assert f is f.add(path='horse bones', args=[('a', 'a'), ('s', 's s')])
        assert str(f) == 'break/horse%20bones?legs=broken&a=a&s=s+s'

    def test_set(self):
        f = furl.Fragment('asdf?lol=sup&foo=blorp')
        assert f is f.set(path='one two', args=[('a', 'a'), ('s', 's s')])
        assert str(f) == 'one%20two?a=a&s=s+s'

        assert f is f.set(path='!', separator=False)
        assert f.separator is False
        assert str(f) == '!a=a&s=s+s'

    def test_remove(self):
        f = furl.Fragment('a/path/great/job?lol=sup&foo=blorp')
        assert f is f.remove(path='job', args=['lol'])
        assert str(f) == 'a/path/great/?foo=blorp'

        assert f is f.remove(path=['path', 'great'], args=['foo'])
        assert str(f) == 'a/path/great/'
        assert f is f.remove(path=['path', 'great', ''])
        assert str(f) == 'a/'

        assert f is f.remove(fragment=True)
        assert str(f) == ''

    def test_encoding(self):
        f = furl.Fragment()
        f.path = "/?:@-._~!$&'()*+,;="
        assert str(f) == "/?:@-._~!$&'()*+,;="
        f.query = [('a', 'a'), ('b b', 'NOPE')]
        assert str(f) == "/%3F:@-._~!$&'()*+,;=?a=a&b+b=NOPE"
        f.separator = False
        assert str(f) == "/?:@-._~!$&'()*+,;=a=a&b+b=NOPE"

        f = furl.Fragment()
        f.path = "/?:@-._~!$&'()*+,;= ^`<>[]"
        assert str(f) == "/?:@-._~!$&'()*+,;=%20%5E%60%3C%3E%5B%5D"
        f.query = [('a', 'a'), ('b b', 'NOPE')]
        assert str(
            f) == "/%3F:@-._~!$&'()*+,;=%20%5E%60%3C%3E%5B%5D?a=a&b+b=NOPE"
        f.separator = False
        assert str(f) == "/?:@-._~!$&'()*+,;=%20%5E%60%3C%3E%5B%5Da=a&b+b=NOPE"

        f = furl.furl()
        f.fragment = 'a?b?c?d?'
        assert f.url == '#a?b?c?d?'
        assert str(f.fragment) == 'a?b?c?d?'

    def test_unicode(self):
        for fragment in ['ロリポップ', u'ロリポップ']:
            f = furl.furl('http://sprop.ru/#ja').set(fragment=fragment)
            assert str(f.fragment) == (
                '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97')

    def test_equality(self):
        assert furl.Fragment() == furl.Fragment()

        f1 = furl.furl('http://sprop.ru/#ja').fragment
        f11 = furl.furl('http://spep.ru/#ja').fragment
        f2 = furl.furl('http://sprop.ru/#nein').fragment

        assert f1 == f11 and str(f1) == str(f11)
        assert f1 != f2 and str(f1) != str(f2)

    def test_nonzero(self):
        f = furl.Fragment()
        assert not f

        f = furl.Fragment('')
        assert not f

        f = furl.Fragment('asdf')
        assert f

        f = furl.Fragment()
        f.path = 'sup'
        assert f

        f = furl.Fragment()
        f.query = 'a=a'
        assert f

        f = furl.Fragment()
        f.path = 'sup'
        f.query = 'a=a'
        assert f

        f = furl.Fragment()
        f.path = 'sup'
        f.query = 'a=a'
        f.separator = False
        assert f

    def test_asdict(self):
        path_encoded = '/wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'

        key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'
        value_encoded = 'test%C3%A4'
        query_encoded = 'a=1&' + key_encoded + '=' + value_encoded

        fragment_encoded = path_encoded + '?' + query_encoded
        p = furl.Path(path_encoded)
        q = furl.Query(query_encoded)
        f = furl.Fragment(fragment_encoded)
        d = {
            'separator': True,
            'path': p.asdict(),
            'query': q.asdict(),
            'encoded': fragment_encoded,
            }
        assert f.asdict() == d


class TestFragmentCompositionInterface(unittest.TestCase):

    def test_interface(self):
        class tester(furl.FragmentCompositionInterface):

            def __init__(self):
                furl.FragmentCompositionInterface.__init__(self)

            def __setattr__(self, attr, value):
                ffci = furl.FragmentCompositionInterface
                if not ffci.__setattr__(self, attr, value):
                    object.__setattr__(self, attr, value)

        t = tester()
        assert isinstance(t.fragment, furl.Fragment)
        assert isinstance(t.fragment.path, furl.Path)
        assert isinstance(t.fragment.query, furl.Query)
        assert str(t.fragment) == ''
        assert t.fragment.separator
        assert str(t.fragment.path) == ''
        assert str(t.fragment.query) == ''

        t.fragment = 'animal meats'
        assert isinstance(t.fragment, furl.Fragment)
        t.fragment.path = 'pump/dump'
        t.fragment.query = 'a=a&s=s+s'
        assert isinstance(t.fragment.path, furl.Path)
        assert isinstance(t.fragment.query, furl.Query)
        assert str(t.fragment.path) == 'pump/dump'
        assert t.fragment.path.segments == ['pump', 'dump']
        assert not t.fragment.path.isabsolute
        assert str(t.fragment.query) == 'a=a&s=s+s'
        assert t.fragment.args == t.fragment.query.params == {
            'a': 'a', 's': 's s'}


class TestFurl(unittest.TestCase):

    def setUp(self):
        # Don't hide duplicate Warnings. Test for all of them.
        warnings.simplefilter("always")

    def _param(self, url, key, val):
        # urlsplit() only parses the query for schemes in urlparse.uses_query,
        # so switch to 'http' (a scheme in urlparse.uses_query) for
        # urlparse.urlsplit().
        if '://' in url:
            url = 'http://%s' % url.split('://', 1)[1]

        # Note: urlparse.urlsplit() doesn't separate the query from the path
        # for all schemes, only those schemes in the list urlparse.uses_query.
        # So, as a result of using urlparse.urlsplit(), this little helper
        # function only works when provided URLs whos schemes are also in
        # urlparse.uses_query.
        items = parse_qsl(urlsplit(url).query, True)
        return (key, val) in items

    def test_constructor_and_set(self):
        f = furl.furl(
            'http://user:pass@pumps.ru/', args={'hi': 'bye'},
            scheme='scrip', path='prorp', host='horp', fragment='fraggg')
        assert f.url == 'scrip://user:pass@horp/prorp?hi=bye#fraggg'

    def test_none(self):
        f = furl.furl(None)
        assert str(f) == ''

        f = furl.furl('http://user:pass@pumps.ru/')
        assert str(f) == 'http://user:pass@pumps.ru/'
        f.load(None)
        assert str(f) == ''

    def test_idna(self):
        decoded_host = u'ドメイン.テスト'
        encoded_url = 'http://user:pass@xn--eckwd4c7c.xn--zckzah/'

        f = furl.furl(encoded_url)
        assert f.username == 'user' and f.password == 'pass'
        assert f.host == decoded_host

        f = furl.furl(encoded_url)
        assert f.host == decoded_host

        f = furl.furl('http://user:pass@pumps.ru/')
        f.set(host=decoded_host)
        assert f.url == encoded_url

        f = furl.furl().set(host=u'ロリポップ')
        assert f.url == '//xn--9ckxbq5co'

    def test_unicode(self):
        paths = ['ロリポップ', u'ロリポップ']
        pairs = [('testö', 'testä'), (u'testö', u'testä')]
        key_encoded, value_encoded = u'test%C3%B6', u'test%C3%A4'
        path_encoded = u'%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'

        base_url = 'http://pumps.ru'
        full_url_utf8_str = '%s/%s?%s=%s' % (
            base_url, paths[0], pairs[0][0], pairs[0][1])
        full_url_unicode = u'%s/%s?%s=%s' % (
            base_url, paths[1], pairs[1][0], pairs[1][1])
        full_url_encoded = '%s/%s?%s=%s' % (
            base_url, path_encoded, key_encoded, value_encoded)

        f = furl.furl(full_url_utf8_str)
        assert f.url == full_url_encoded

        # Accept unicode without raising an exception.
        f = furl.furl(full_url_unicode)
        assert f.url == full_url_encoded

        # Accept unicode paths.
        for path in paths:
            f = furl.furl(base_url)
            f.path = path
            assert f.url == '%s/%s' % (base_url, path_encoded)

        # Accept unicode queries.
        for key, value in pairs:
            f = furl.furl(base_url).set(path=path)
            f.args[key] = value
            assert f.args[key] == value  # Unicode values aren't modified.
            assert key not in f.url
            assert value not in f.url
            assert quote_plus(furl.utf8(key)) in f.url
            assert quote_plus(furl.utf8(value)) in f.url
            f.path.segments = [path]
            assert f.path.segments == [path]  # Unicode values aren't modified.
            assert f.url == full_url_encoded

    def test_scheme(self):
        assert furl.furl().scheme is None
        assert furl.furl('').scheme is None

        # Lowercase.
        assert furl.furl('/sup/').set(scheme='PrOtO').scheme == 'proto'

        # No scheme.
        for url in ['sup.txt', '/d/sup', '#flarg']:
            f = furl.furl(url)
            assert f.scheme is None and f.url == url

        # Protocol relative URLs.
        for url in ['//', '//sup.txt', '//arc.io/d/sup']:
            f = furl.furl(url)
            assert f.scheme is None and f.url == url

        f = furl.furl('//sup.txt')
        assert f.scheme is None and f.url == '//sup.txt'
        f.scheme = ''
        assert f.scheme == '' and f.url == '://sup.txt'

        # Schemes without slashes, like 'mailto:'.
        assert furl.furl('mailto:sup@sprp.ru').url == 'mailto:sup@sprp.ru'
        assert furl.furl('mailto://sup@sprp.ru').url == 'mailto://sup@sprp.ru'

        f = furl.furl('mailto:sproop:spraps@sprp.ru')
        assert f.scheme == 'mailto'
        assert f.path == 'sproop:spraps@sprp.ru'

        f = furl.furl('mailto:')
        assert f.url == 'mailto:' and f.scheme == 'mailto' and f.netloc is None

        f = furl.furl('tel:+1-555-555-1234')
        assert f.scheme == 'tel' and str(f.path) == '+1-555-555-1234'

        f = furl.furl('urn:srp.com/ferret?query')
        assert f.scheme == 'urn' and str(f.path) == 'srp.com/ferret'
        assert str(f.query) == 'query'

        # Ignore invalid schemes.
        assert furl.furl('+invalid$scheme://lolsup').scheme is None
        assert furl.furl('/api/test?url=http://a.com').scheme is None

        # Empty scheme.
        f = furl.furl(':')
        assert f.scheme == '' and f.netloc is None and f.url == ':'

    def test_username_and_password(self):
        # Empty usernames and passwords.
        for url in ['', 'http://www.pumps.com/']:
            f = furl.furl(url)
            assert f.username is f.password is None

        baseurl = 'http://www.google.com/'
        usernames = ['', 'user', '@user', ' a-user_NAME$%^&09@:/']
        passwords = ['', 'pass', ':pass', ' a-PASS_word$%^&09@:/']

        # Username only.
        for username in usernames:
            encoded_username = quote(username, safe='')
            encoded_url = 'http://%s@www.google.com/' % encoded_username

            f = furl.furl(encoded_url)
            assert f.username == username and f.password is None

            f = furl.furl(baseurl)
            f.username = username
            assert f.username == username and f.password is None
            assert f.url == encoded_url

            f = furl.furl(baseurl)
            f.set(username=username)
            assert f.username == username and f.password is None
            assert f.url == encoded_url

            f.remove(username=True)
            assert f.username is f.password is None and f.url == baseurl

        # Password only.
        for password in passwords:
            encoded_password = quote(password, safe='')
            encoded_url = 'http://:%s@www.google.com/' % encoded_password

            f = furl.furl(encoded_url)
            assert f.password == password and f.username == ''

            f = furl.furl(baseurl)
            f.password = password
            assert f.password == password and not f.username
            assert f.url == encoded_url

            f = furl.furl(baseurl)
            f.set(password=password)
            assert f.password == password and not f.username
            assert f.url == encoded_url

            f.remove(password=True)
            assert f.username is f.password is None and f.url == baseurl

        # Username and password.
        for username in usernames:
            for password in passwords:
                encoded_username = quote(username, safe='')
                encoded_password = quote(password, safe='')
                encoded_url = 'http://%s:%s@www.google.com/' % (
                    encoded_username, encoded_password)

                f = furl.furl(encoded_url)
                assert f.username == username and f.password == password

                f = furl.furl(baseurl)
                f.username = username
                f.password = password
                assert f.username == username and f.password == password
                assert f.url == encoded_url

                f = furl.furl(baseurl)
                f.set(username=username, password=password)
                assert f.username == username and f.password == password
                assert f.url == encoded_url

                f = furl.furl(baseurl)
                f.remove(username=True, password=True)
                assert f.username is f.password is None and f.url == baseurl

        # Username and password in the network location string.
        f = furl.furl()
        f.netloc = 'user@domain.com'
        assert f.username == 'user' and not f.password
        assert f.netloc == 'user@domain.com'

        f = furl.furl()
        f.netloc = ':pass@domain.com'
        assert not f.username and f.password == 'pass'
        assert f.netloc == ':pass@domain.com'

        f = furl.furl()
        f.netloc = 'user:pass@domain.com'
        assert f.username == 'user' and f.password == 'pass'
        assert f.netloc == 'user:pass@domain.com'
        f = furl.furl()
        assert f.username is f.password is None
        f.username = 'uu'
        assert f.username == 'uu' and f.password is None and f.url == '//uu@'
        f.password = 'pp'
        assert f.username == 'uu' and f.password == 'pp'
        assert f.url == '//uu:pp@'
        f.username = ''
        assert f.username == '' and f.password == 'pp' and f.url == '//:pp@'
        f.password = ''
        assert f.username == f.password == '' and f.url == '//:@'
        f.password = None
        assert f.username == '' and f.password is None and f.url == '//@'
        f.username = None
        assert f.username is f.password is None and f.url == ''
        f.password = ''
        assert f.username is None and f.password == '' and f.url == '//:@'

        # Unicode.
        username = u'kødp'
        password = u'ålæg'
        f = furl.furl(u'https://%s:%s@example.com/' % (username, password))
        assert f.username == username and f.password == password
        assert f.url == 'https://k%C3%B8dp:%C3%A5l%C3%A6g@example.com/'

    def test_basics(self):
        url = 'hTtP://www.pumps.com/'
        f = furl.furl(url)
        assert f.scheme == 'http'
        assert f.netloc == 'www.pumps.com'
        assert f.host == 'www.pumps.com'
        assert f.port == 80
        assert str(f.path) == '/'
        assert str(f.query) == ''
        assert f.args == f.query.params == {}
        assert str(f.fragment) == ''
        assert f.url == str(f) == url.lower()
        assert f.url == furl.furl(f).url == furl.furl(f.url).url
        assert f is not f.copy() and f.url == f.copy().url

        url = 'HTTPS://wWw.YAHOO.cO.UK/one/two/three?a=a&b=b&m=m%26m#fragment'
        f = furl.furl(url)
        assert f.scheme == 'https'
        assert f.netloc == 'www.yahoo.co.uk'
        assert f.host == 'www.yahoo.co.uk'
        assert f.port == 443
        assert str(f.path) == '/one/two/three'
        assert str(f.query) == 'a=a&b=b&m=m%26m'
        assert f.args == f.query.params == {'a': 'a', 'b': 'b', 'm': 'm&m'}
        assert str(f.fragment) == 'fragment'
        assert f.url == str(f) == url.lower()
        assert f.url == furl.furl(f).url == furl.furl(f.url).url
        assert f is not f.copy() and f.url == f.copy().url

        url = 'sup://192.168.1.102:8080///one//a%20b////?s=kwl%20string#frag'
        f = furl.furl(url)
        assert f.scheme == 'sup'
        assert f.netloc == '192.168.1.102:8080'
        assert f.host == '192.168.1.102'
        assert f.port == 8080
        assert str(f.path) == '///one//a%20b////'
        assert str(f.query) == 's=kwl+string'
        assert f.args == f.query.params == {'s': 'kwl string'}
        assert str(f.fragment) == 'frag'
        quoted = 'sup://192.168.1.102:8080///one//a%20b////?s=kwl+string#frag'
        assert f.url == str(f) == quoted
        assert f.url == furl.furl(f).url == furl.furl(f.url).url
        assert f is not f.copy() and f.url == f.copy().url

        # URL paths are optionally absolute if scheme and netloc are
        # empty.
        f = furl.furl()
        f.path.segments = ['pumps']
        assert str(f.path) == 'pumps'
        f.path = 'pumps'
        assert str(f.path) == 'pumps'

        # Fragment paths are optionally absolute, and not absolute by
        # default.
        f = furl.furl()
        f.fragment.path.segments = ['pumps']
        assert str(f.fragment.path) == 'pumps'
        f.fragment.path = 'pumps'
        assert str(f.fragment.path) == 'pumps'

        # URLs comprised of a netloc string only should not be prefixed
        # with '//', as-is the default behavior of
        # urlparse.urlunsplit().
        f = furl.furl()
        assert f.set(host='foo').url == '//foo'
        assert f.set(host='pumps.com').url == '//pumps.com'
        assert f.set(host='pumps.com', port=88).url == '//pumps.com:88'
        assert f.set(netloc='pumps.com:88').url == '//pumps.com:88'

        # furl('...') and furl.url = '...' are functionally identical.
        url = 'https://www.pumps.com/path?query#frag'
        f1 = furl.furl(url)
        f2 = furl.furl()
        f2.url = url
        assert f1 == f2

        # Empty scheme and netloc.
        f = furl.furl('://')
        assert f.scheme == f.netloc == '' and f.url == '://'

    def test_basic_manipulation(self):
        f = furl.furl('http://www.pumps.com/')

        f.args.setdefault('foo', 'blah')
        assert str(f) == 'http://www.pumps.com/?foo=blah'
        f.query.params['foo'] = 'eep'
        assert str(f) == 'http://www.pumps.com/?foo=eep'

        f.port = 99
        assert str(f) == 'http://www.pumps.com:99/?foo=eep'

        f.netloc = 'www.yahoo.com:220'
        assert str(f) == 'http://www.yahoo.com:220/?foo=eep'

        f.netloc = 'www.yahoo.com'
        assert f.port == 80
        assert str(f) == 'http://www.yahoo.com/?foo=eep'

        f.scheme = 'sup'
        assert str(f) == 'sup://www.yahoo.com:80/?foo=eep'

        f.port = None
        assert str(f) == 'sup://www.yahoo.com/?foo=eep'

        f.fragment = 'sup'
        assert str(f) == 'sup://www.yahoo.com/?foo=eep#sup'

        f.path = 'hay supppp'
        assert str(f) == 'sup://www.yahoo.com/hay%20supppp?foo=eep#sup'

        f.args['space'] = '1 2'
        assert str(
            f) == 'sup://www.yahoo.com/hay%20supppp?foo=eep&space=1+2#sup'

        del f.args['foo']
        assert str(f) == 'sup://www.yahoo.com/hay%20supppp?space=1+2#sup'

        f.host = 'ohay.com'
        assert str(f) == 'sup://ohay.com/hay%20supppp?space=1+2#sup'

    def test_path_itruediv(self):
        f = furl.furl('http://www.pumps.com/')

        f /= 'a'
        assert f.url == 'http://www.pumps.com/a'

        f /= 'b'
        assert f.url == 'http://www.pumps.com/a/b'

        f /= 'c d/'
        assert f.url == 'http://www.pumps.com/a/b/c%20d/'

    def test_path_truediv(self):
        f = furl.furl('http://www.pumps.com/')

        f1 = f / 'a'
        assert f.url == 'http://www.pumps.com/'
        assert f1.url == 'http://www.pumps.com/a'

        f2 = f / 'c' / 'd e/'
        assert f2.url == 'http://www.pumps.com/c/d%20e/'

        f3 = f / furl.Path('f')
        assert f3.url == 'http://www.pumps.com/f'

    def test_odd_urls(self):
        # Empty.
        f = furl.furl('')
        assert f.username is f.password is None
        assert f.scheme is f.host is f.port is f.netloc is None
        assert str(f.path) == ''
        assert str(f.query) == ''
        assert f.args == f.query.params == {}
        assert str(f.fragment) == ''
        assert f.url == ''

        url = (
            "sup://example.com/:@-._~!$&'()*+,=;:@-._~!$&'()*+,=:@-._~!$&'()*+"
            ",==?/?:@-._~!$'()*+,;=/?:@-._~!$'()*+,;==#/?:@-._~!$&'()*+,;=")
        pathstr = "/:@-._~!$&'()*+,=;:@-._~!$&'()*+,=:@-._~!$&'()*+,=="
        querystr = (
            quote_plus("/?:@-._~!$'()* ,;") + '=' +
            quote_plus("/?:@-._~!$'()* ,;=="))
        fragmentstr = quote_plus("/?:@-._~!$&'()* ,;=", '/?&=')
        f = furl.furl(url)
        assert f.scheme == 'sup'
        assert f.host == 'example.com'
        assert f.port is None
        assert f.netloc == 'example.com'
        assert str(f.path) == pathstr
        assert str(f.query) == querystr
        assert str(f.fragment) == fragmentstr

        # Scheme only.
        f = furl.furl('sup://')
        assert f.scheme == 'sup'
        assert f.host == f.netloc == ''
        assert f.port is None
        assert str(f.path) == str(f.query) == str(f.fragment) == ''
        assert f.args == f.query.params == {}
        assert f.url == 'sup://'
        f.scheme = None
        assert f.scheme is None and f.netloc == '' and f.url == '//'
        f.scheme = ''
        assert f.scheme == '' and f.netloc == '' and f.url == '://'

        f = furl.furl('sup:')
        assert f.scheme == 'sup'
        assert f.host is f.port is f.netloc is None
        assert str(f.path) == str(f.query) == str(f.fragment) == ''
        assert f.args == f.query.params == {}
        assert f.url == 'sup:'
        f.scheme = None
        assert f.url == '' and f.netloc is None
        f.scheme = ''
        assert f.url == ':' and f.netloc is None

        # Host only.
        f = furl.furl().set(host='pumps.meat')
        assert f.url == '//pumps.meat' and f.netloc == f.host == 'pumps.meat'
        f.host = None
        assert f.url == '' and f.host is f.netloc is None
        f.host = ''
        assert f.url == '//' and f.host == f.netloc == ''

        # Port only.
        f = furl.furl()
        f.port = 99
        assert f.url == '//:99' and f.netloc is not None
        f.port = None
        assert f.url == '' and f.netloc is None

        # urlparse.urlsplit() treats the first two '//' as the beginning
        # of a netloc, even if the netloc is empty.
        f = furl.furl('////path')
        assert f.netloc == '' and str(f.path) == '//path'
        #assert f.url == '////path'

        # TODO(grun): Test more odd URLs.

    def test_hosts(self):
        # No host.
        url = 'http:///index.html'
        f = furl.furl(url)
        assert f.host == '' and furl.furl(url).url == url

        # Valid IPv4 and IPv6 addresses.
        f = furl.furl('http://192.168.1.101')
        f = furl.furl('http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/')

        # Host strings are always lowercase.
        f = furl.furl('http://wWw.PuMpS.com')
        assert f.host == 'www.pumps.com'
        f.host = 'yEp.NoPe'
        assert f.host == 'yep.nope'
        f.set(host='FeE.fIe.FoE.fUm')
        assert f.host == 'fee.fie.foe.fum'

        # Invalid IPv4 addresses shouldn't raise an exception because
        # urlparse.urlsplit() doesn't raise an exception on invalid IPv4
        # addresses.
        f = furl.furl('http://1.2.3.4.5.6/')

        # Invalid, but well-formed, IPv6 addresses shouldn't raise an
        # exception because urlparse.urlsplit() doesn't raise an
        # exception on invalid IPv6 addresses.
        with self.assertRaises(ValueError):
            furl.furl('http://[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]/')

        # Malformed IPv6 should raise an exception because urlparse.urlsplit()
        # raises an exception on malformed IPv6 addresses.
        with self.assertRaises(ValueError):
            furl.furl('http://[0:0:0:0:0:0:0:1/')
        with self.assertRaises(ValueError):
            furl.furl('http://0:0:0:0:0:0:0:1]/')

        # Invalid host strings should raise ValueError.
        invalid_hosts = ['.', '..', 'a..b', '.a.b', '.a.b.', '$', 'a$b']
        for host in invalid_hosts:
            with self.assertRaises(ValueError):
                f = furl.furl('http://%s/' % host)
        for host in invalid_hosts + ['a/b']:
            with self.assertRaises(ValueError):
                f = furl.furl('http://google.com/').set(host=host)

    def test_netloc(self):
        f = furl.furl('http://pumps.com/')
        netloc = '1.2.3.4.5.6:999'
        f.netloc = netloc
        assert f.netloc == netloc
        assert f.host == '1.2.3.4.5.6'
        assert f.port == 999

        with self.assertRaises(ValueError):
            netloc = '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]:888'
            f.netloc = netloc
            assert f.netloc == netloc
            assert f.host == '[0:0:0:0:0:0:0:1:1:1:1:1:1:1:1:9999999999999]'
            assert f.port == 888

        # Malformed IPv6 should raise an exception because
        # urlparse.urlsplit() raises an exception
        with self.assertRaises(ValueError):
            f.netloc = '[0:0:0:0:0:0:0:1'
        with self.assertRaises(ValueError):
            f.netloc = '0:0:0:0:0:0:0:1]'

        # Invalid ports should raise an exception.
        with self.assertRaises(ValueError):
            f.netloc = '[0:0:0:0:0:0:0:1]:alksdflasdfasdf'
        with self.assertRaises(ValueError):
            f.netloc = 'pump2pump.org:777777777777'

    def test_origin(self):
        assert furl.furl().origin == '://'
        assert furl.furl().set(host='slurp.ru').origin == '://slurp.ru'
        assert furl.furl('http://pep.ru:83/yep').origin == 'http://pep.ru:83'
        assert furl.furl().set(origin='pep://yep.ru').origin == 'pep://yep.ru'
        f = furl.furl('http://user:pass@pumps.com/path?query#fragemtn')
        assert f.origin == 'http://pumps.com'

        f = furl.furl('none://ignored/lol?sup').set(origin='sup://yep.biz:99')
        assert f.url == 'sup://yep.biz:99/lol?sup'

        # Username and password are unaffected.
        f = furl.furl('http://user:pass@slurp.com')
        f.origin = 'ssh://horse-machine.de'
        assert f.url == 'ssh://user:pass@horse-machine.de'

        # Malformed IPv6 should raise an exception because urlparse.urlsplit()
        # raises an exception.
        with self.assertRaises(ValueError):
            f.origin = '[0:0:0:0:0:0:0:1'
        with self.assertRaises(ValueError):
            f.origin = 'http://0:0:0:0:0:0:0:1]'

        # Invalid ports should raise an exception.
        with self.assertRaises(ValueError):
            f.origin = '[0:0:0:0:0:0:0:1]:alksdflasdfasdf'
        with self.assertRaises(ValueError):
            f.origin = 'http://pump2pump.org:777777777777'

    def test_ports(self):
        # Default port values.
        assert furl.furl('http://www.pumps.com/').port == 80
        assert furl.furl('https://www.pumps.com/').port == 443
        assert furl.furl('undefined://www.pumps.com/').port is None

        # Override default port values.
        assert furl.furl('http://www.pumps.com:9000/').port == 9000
        assert furl.furl('https://www.pumps.com:9000/').port == 9000
        assert furl.furl('undefined://www.pumps.com:9000/').port == 9000

        # Reset the port.
        f = furl.furl('http://www.pumps.com:9000/')
        f.port = None
        assert f.url == 'http://www.pumps.com/'
        assert f.port == 80

        f = furl.furl('undefined://www.pumps.com:9000/')
        f.port = None
        assert f.url == 'undefined://www.pumps.com/'
        assert f.port is None

        # Invalid port raises ValueError with no side effects.
        with self.assertRaises(ValueError):
            furl.furl('http://www.pumps.com:invalid/')

        url = 'http://www.pumps.com:400/'
        f = furl.furl(url)
        assert f.port == 400
        with self.assertRaises(ValueError):
            f.port = 'asdf'
        assert f.url == url
        f.port = 9999
        with self.assertRaises(ValueError):
            f.port = []
        with self.assertRaises(ValueError):
            f.port = -1
        with self.assertRaises(ValueError):
            f.port = 77777777777
        assert f.port == 9999
        assert f.url == 'http://www.pumps.com:9999/'

        # The port is inferred from scheme changes, if possible, but
        # only if the port is otherwise unset (self.port is None).
        assert furl.furl('unknown://pump.com').set(scheme='http').port == 80
        assert furl.furl('unknown://pump.com:99').set(scheme='http').port == 99
        assert furl.furl('http://pump.com:99').set(scheme='unknown').port == 99

        # Hostnames are always lowercase.
        f = furl.furl('http://wWw.PuMpS.com:9999')
        assert f.netloc == 'www.pumps.com:9999'
        f.netloc = 'yEp.NoPe:9999'
        assert f.netloc == 'yep.nope:9999'
        f.set(netloc='FeE.fIe.FoE.fUm:9999')
        assert f.netloc == 'fee.fie.foe.fum:9999'

    def test_add(self):
        f = furl.furl('http://pumps.com/')

        assert f is f.add(args={'a': 'a', 'm': 'm&m'}, path='sp ace',
                          fragment_path='1', fragment_args={'f': 'frp'})
        assert self._param(f.url, 'a', 'a')
        assert self._param(f.url, 'm', 'm&m')
        assert str(f.fragment) == '1?f=frp'
        assert str(f.path) == urlsplit(f.url).path == '/sp%20ace'

        assert f is f.add(path='dir', fragment_path='23', args={'b': 'b'},
                          fragment_args={'b': 'bewp'})
        assert self._param(f.url, 'a', 'a')
        assert self._param(f.url, 'm', 'm&m')
        assert self._param(f.url, 'b', 'b')
        assert str(f.path) == '/sp%20ace/dir'
        assert str(f.fragment) == '1/23?f=frp&b=bewp'

        # Supplying both <args> and <query_params> should raise a
        # warning.
        with warnings.catch_warnings(record=True) as w1:
            f.add(args={'a': '1'}, query_params={'a': '2'})
            assert len(w1) == 1 and issubclass(w1[0].category, UserWarning)
            assert self._param(
                f.url, 'a', '1') and self._param(f.url, 'a', '2')
            params = f.args.allitems()
            assert params.index(('a', '1')) < params.index(('a', '2'))

    def test_set(self):
        f = furl.furl('http://pumps.com/sp%20ace/dir')
        assert f is f.set(args={'no': 'nope'}, fragment='sup')
        assert 'a' not in f.args
        assert 'b' not in f.args
        assert f.url == 'http://pumps.com/sp%20ace/dir?no=nope#sup'

        # No conflict warnings between <host>/<port> and <netloc>, or
        # <query> and <params>.
        assert f is f.set(args={'a': 'a a'}, path='path path/dir', port='999',
                          fragment='moresup', scheme='sup', host='host')
        assert str(f.path) == '/path%20path/dir'
        assert f.url == 'sup://host:999/path%20path/dir?a=a+a#moresup'

        # Path as a list of path segments to join.
        assert f is f.set(path=['d1', 'd2'])
        assert f.url == 'sup://host:999/d1/d2?a=a+a#moresup'
        assert f is f.add(path=['/d3/', '/d4/'])
        assert f.url == 'sup://host:999/d1/d2/%2Fd3%2F/%2Fd4%2F?a=a+a#moresup'

        # Set a lot of stuff (but avoid conflicts, which are tested
        # below).
        f.set(
            query_params={'k': 'k'}, fragment_path='no scrubs', scheme='morp',
            host='myhouse', port=69, path='j$j*m#n', fragment_args={'f': 'f'})
        assert f.url == 'morp://myhouse:69/j$j*m%23n?k=k#no%20scrubs?f=f'

        # No side effects.
        oldurl = f.url
        with self.assertRaises(ValueError):
            f.set(args={'a': 'a a'}, path='path path/dir', port='INVALID_PORT',
                  fragment='moresup', scheme='sup', host='host')
        assert f.url == oldurl
        with warnings.catch_warnings(record=True) as w1:
            self.assertRaises(
                ValueError, f.set, netloc='nope.com:99', port='NOPE')
            assert len(w1) == 1 and issubclass(w1[0].category, UserWarning)
        assert f.url == oldurl

        # Separator isn't reset with set().
        f = furl.Fragment()
        f.separator = False
        f.set(path='flush', args={'dad': 'nope'})
        assert str(f) == 'flushdad=nope'

        # Test warnings for potentially overlapping parameters.
        f = furl.furl('http://pumps.com')
        warnings.simplefilter("always")

        # Scheme, origin overlap. Scheme takes precedence.
        with warnings.catch_warnings(record=True) as w1:
            f.set(scheme='hi', origin='bye://sup.sup')
            assert len(w1) == 1 and issubclass(w1[0].category, UserWarning)
            assert f.scheme == 'hi'

        # Netloc, origin, host and/or port. Host and port take precedence.
        with warnings.catch_warnings(record=True) as w1:
            f.set(netloc='dumps.com:99', origin='sup://pumps.com:88')
            assert len(w1) == 1 and issubclass(w1[0].category, UserWarning)
        with warnings.catch_warnings(record=True) as w1:
            f.set(netloc='dumps.com:99', host='ohay.com')
            assert len(w1) == 1 and issubclass(w1[0].category, UserWarning)
            assert f.host == 'ohay.com'
            assert f.port == 99
        with warnings.catch_warnings(record=True) as w2:
            f.set(netloc='dumps.com:99', port=88)
            assert len(w2) == 1 and issubclass(w2[0].category, UserWarning)
            assert f.port == 88
        with warnings.catch_warnings(record=True) as w2:
            f.set(origin='http://dumps.com:99', port=88)
            assert len(w2) == 1 and issubclass(w2[0].category, UserWarning)
            assert f.port == 88
        with warnings.catch_warnings(record=True) as w3:
            f.set(netloc='dumps.com:99', host='ohay.com', port=88)
            assert len(w3) == 1 and issubclass(w3[0].category, UserWarning)

        # Query, args, and query_params overlap - args and query_params
        # take precedence.
        with warnings.catch_warnings(record=True) as w4:
            f.set(query='yosup', args={'a': 'a', 'b': 'b'})
            assert len(w4) == 1 and issubclass(w4[0].category, UserWarning)
            assert self._param(f.url, 'a', 'a')
            assert self._param(f.url, 'b', 'b')
        with warnings.catch_warnings(record=True) as w5:
            f.set(query='yosup', query_params={'a': 'a', 'b': 'b'})
            assert len(w5) == 1 and issubclass(w5[0].category, UserWarning)
            assert self._param(f.url, 'a', 'a')
            assert self._param(f.url, 'b', 'b')
        with warnings.catch_warnings(record=True) as w6:
            f.set(args={'a': 'a', 'b': 'b'}, query_params={'c': 'c', 'd': 'd'})
            assert len(w6) == 1 and issubclass(w6[0].category, UserWarning)
            assert self._param(f.url, 'c', 'c')
            assert self._param(f.url, 'd', 'd')

        # Fragment, fragment_path, fragment_args, and fragment_separator
        # overlap - fragment_separator, fragment_path, and fragment_args
        # take precedence.
        with warnings.catch_warnings(record=True) as w7:
            f.set(fragment='hi', fragment_path='!', fragment_args={'a': 'a'},
                  fragment_separator=False)
            assert len(w7) == 1 and issubclass(w7[0].category, UserWarning)
            assert str(f.fragment) == '!a=a'
        with warnings.catch_warnings(record=True) as w8:
            f.set(fragment='hi', fragment_path='bye')
            assert len(w8) == 1 and issubclass(w8[0].category, UserWarning)
            assert str(f.fragment) == 'bye'
        with warnings.catch_warnings(record=True) as w9:
            f.set(fragment='hi', fragment_args={'a': 'a'})
            assert len(w9) == 1 and issubclass(w9[0].category, UserWarning)
            assert str(f.fragment) == 'hia=a'
        with warnings.catch_warnings(record=True) as w10:
            f.set(fragment='!?a=a', fragment_separator=False)
            assert len(w10) == 1 and issubclass(w10[0].category, UserWarning)
            assert str(f.fragment) == '!a=a'

    def test_remove(self):
        url = ('http://u:p@host:69/a/big/path/?a=a&b=b&s=s+s#a frag?with=args'
               '&a=a')
        f = furl.furl(url)

        # Remove without parameters removes nothing.
        assert f.url == f.remove().url

        # username, password, and port must be True.
        assert f == f.copy().remove(
            username='nope', password='nope', port='nope')

        # Basics.
        assert f is f.remove(fragment=True, args=['a', 'b'], path='path/',
                             username=True, password=True, port=True)
        assert f.url == 'http://host/a/big/?s=s+s'

        # scheme, host, port, netloc, origin.
        f = furl.furl('https://host:999/path')
        assert f.copy().remove(scheme=True).url == '//host:999/path'
        assert f.copy().remove(host=True).url == 'https://:999/path'
        assert f.copy().remove(port=True).url == 'https://host/path'
        assert f.copy().remove(netloc=True).url == 'https:///path'
        assert f.copy().remove(origin=True).url == '/path'

        # No errors are thrown when removing URL components that don't exist.
        f = furl.furl(url)
        assert f is f.remove(fragment_path=['asdf'], fragment_args=['asdf'],
                             args=['asdf'], path=['ppp', 'ump'])
        assert self._param(f.url, 'a', 'a')
        assert self._param(f.url, 'b', 'b')
        assert self._param(f.url, 's', 's s')
        assert str(f.path) == '/a/big/path/'
        assert str(f.fragment.path) == 'a%20frag'
        assert f.fragment.args == {'a': 'a', 'with': 'args'}

        # Path as a list of paths to join before removing.
        assert f is f.remove(fragment_path='a frag', fragment_args=['a'],
                             query_params=['a', 'b'], path=['big', 'path', ''],
                             port=True)
        assert f.url == 'http://u:p@host/a/?s=s+s#with=args'

        assert f is f.remove(
            path=True, query=True, fragment=True, username=True,
            password=True)
        assert f.url == 'http://host'

    def test_join(self):
        empty_tests = ['', '/meat', '/meat/pump?a=a&b=b#fragsup',
                       'sup://www.pumps.org/brg/pap/mrf?a=b&c=d#frag?sup', ]
        run_tests = [
            # Join full URLs.
            ('unknown://pepp.ru', 'unknown://pepp.ru'),
            ('unknown://pepp.ru?one=two&three=four',
             'unknown://pepp.ru?one=two&three=four'),
            ('unknown://pepp.ru/new/url/?one=two#blrp',
             'unknown://pepp.ru/new/url/?one=two#blrp'),

            # Absolute paths ('/foo').
            ('/pump', 'unknown://pepp.ru/pump'),
            ('/pump/2/dump', 'unknown://pepp.ru/pump/2/dump'),
            ('/pump/2/dump/', 'unknown://pepp.ru/pump/2/dump/'),

            # Relative paths ('../foo').
            ('./crit/', 'unknown://pepp.ru/pump/2/dump/crit/'),
            ('.././../././././srp', 'unknown://pepp.ru/pump/2/srp'),
            ('../././../nop', 'unknown://pepp.ru/nop'),

            # Query included.
            ('/erp/?one=two', 'unknown://pepp.ru/erp/?one=two'),
            ('morp?three=four', 'unknown://pepp.ru/erp/morp?three=four'),
            ('/root/pumps?five=six',
             'unknown://pepp.ru/root/pumps?five=six'),

            # Fragment included.
            ('#sup', 'unknown://pepp.ru/root/pumps?five=six#sup'),
            ('/reset?one=two#yepYEP',
             'unknown://pepp.ru/reset?one=two#yepYEP'),
            ('./slurm#uwantpump?', 'unknown://pepp.ru/slurm#uwantpump?'),

            # Unicode.
            ('/?kødpålæg=4', 'unknown://pepp.ru/?k%C3%B8dp%C3%A5l%C3%A6g=4'),
            (u'/?kødpålæg=4', 'unknown://pepp.ru/?k%C3%B8dp%C3%A5l%C3%A6g=4'),
        ]

        for test in empty_tests:
            f = furl.furl().join(test)
            assert f.url == test

        f = furl.furl('')
        for join, result in run_tests:
            assert f is f.join(join) and f.url == result

        # Join other furl object, which serialize to strings with str().
        f = furl.furl('')
        for join, result in run_tests:
            tojoin = furl.furl(join)
            assert f is f.join(tojoin) and f.url == result

        # Join multiple URLs.
        f = furl.furl('')
        f.join('path', 'tcp://blorp.biz', 'http://pepp.ru/', 'a/b/c',
               '#uwantpump?')
        assert f.url == 'http://pepp.ru/a/b/c#uwantpump?'

        # In edge cases (e.g. URLs without an authority/netloc), behave
        # identically to urllib.parse.urljoin(), which changed behavior in
        # Python 3.9.
        f = furl.furl('wss://slrp.com/').join('foo:1')
        if sys.version_info[:2] < (3, 9):
            assert f.url == 'wss://slrp.com/foo:1'
        else:
            assert f.url == 'foo:1'
        f = furl.furl('wss://slrp.com/').join('foo:1:rip')
        assert f.url == 'foo:1:rip'
        f = furl.furl('scheme:path').join('foo:blah')
        assert f.url == 'foo:blah'

    def test_tostr(self):
        f = furl.furl('http://blast.off/?a+b=c+d&two%20tap=cat%20nap%24%21')
        assert f.tostr() == f.url
        assert (f.tostr(query_delimiter=';') ==
                'http://blast.off/?a+b=c+d;two+tap=cat+nap%24%21')
        assert (f.tostr(query_quote_plus=False) ==
                'http://blast.off/?a%20b=c%20d&two%20tap=cat%20nap%24%21')
        assert (f.tostr(query_delimiter=';', query_quote_plus=False) ==
                'http://blast.off/?a%20b=c%20d;two%20tap=cat%20nap%24%21')
        assert (f.tostr(query_quote_plus=False, query_dont_quote=True) ==
                'http://blast.off/?a%20b=c%20d&two%20tap=cat%20nap$!')
        # query_dont_quote ignores invalid query characters, like '$'.
        assert (f.tostr(query_quote_plus=False, query_dont_quote='$') ==
                'http://blast.off/?a%20b=c%20d&two%20tap=cat%20nap$%21')

        url = 'https://klugg.com/?hi=*'
        url_encoded = 'https://klugg.com/?hi=%2A&url='
        f = furl.furl(url).set(args=[('hi', '*'), ('url', url)])
        assert f.tostr() == url_encoded + quote_plus(url)
        assert f.tostr(query_dont_quote=True) == url + '&url=' + url
        assert f.tostr(query_dont_quote='*') == (
            url + '&url=' + quote_plus(url, '*'))

    def test_equality(self):
        assert furl.furl() is not furl.furl() and furl.furl() == furl.furl()

        assert furl.furl() is not None

        url = 'https://www.yahoo.co.uk/one/two/three?a=a&b=b&m=m%26m#fragment'
        assert furl.furl(url) != url  # No furl to string comparisons.
        assert furl.furl(url) == furl.furl(url)
        assert furl.furl(url).remove(path=True) != furl.furl(url)

    def test_urlsplit(self):
        # Without any delimiters like '://' or '/', the input should be
        # treated as a path.
        urls = ['sup', '127.0.0.1', 'www.google.com', '192.168.1.1:8000']
        for url in urls:
            assert isinstance(furl.urlsplit(url), SplitResult)
            assert furl.urlsplit(url).path == urlsplit(url).path

        # No changes to existing urlsplit() behavior for known schemes.
        url = 'http://www.pumps.com/'
        assert isinstance(furl.urlsplit(url), SplitResult)
        assert furl.urlsplit(url) == urlsplit(url)

        url = 'https://www.yahoo.co.uk/one/two/three?a=a&b=b&m=m%26m#fragment'
        assert isinstance(furl.urlsplit(url), SplitResult)
        assert furl.urlsplit(url) == urlsplit(url)

        # Properly split the query from the path for unknown schemes.
        url = 'unknown://www.yahoo.com?one=two&three=four'
        correct = ('unknown', 'www.yahoo.com', '', 'one=two&three=four', '')
        assert isinstance(furl.urlsplit(url), SplitResult)
        assert furl.urlsplit(url) == correct

        url = 'sup://192.168.1.102:8080///one//two////?s=kwl%20string#frag'
        correct = ('sup', '192.168.1.102:8080', '///one//two////',
                   's=kwl%20string', 'frag')
        assert isinstance(furl.urlsplit(url), SplitResult)
        assert furl.urlsplit(url) == correct

        url = 'crazyyy://www.yahoo.co.uk/one/two/three?a=a&b=b&m=m%26m#frag'
        correct = ('crazyyy', 'www.yahoo.co.uk', '/one/two/three',
                   'a=a&b=b&m=m%26m', 'frag')
        assert isinstance(furl.urlsplit(url), SplitResult)
        assert furl.urlsplit(url) == correct

    def test_join_path_segments(self):
        jps = furl.join_path_segments

        # Empty.
        assert jps() == []
        assert jps([]) == []
        assert jps([], [], [], []) == []

        # Null strings.
        #   [''] means nothing, or an empty string, in the final path
        #     segments.
        #   ['', ''] is preserved as a slash in the final path segments.
        assert jps(['']) == []
        assert jps([''], ['']) == []
        assert jps([''], [''], ['']) == []
        assert jps([''], ['', '']) == ['', '']
        assert jps([''], [''], [''], ['']) == []
        assert jps(['', ''], ['', '']) == ['', '', '']
        assert jps(['', '', ''], ['', '']) == ['', '', '', '']
        assert jps(['', '', '', '', '', '']) == ['', '', '', '', '', '']
        assert jps(['', '', '', ''], ['', '']) == ['', '', '', '', '']
        assert jps(['', '', '', ''], ['', ''], ['']) == ['', '', '', '', '']
        assert jps(['', '', '', ''], ['', '', '']) == ['', '', '', '', '', '']

        # Basics.
        assert jps(['a']) == ['a']
        assert jps(['a', 'b']) == ['a', 'b']
        assert jps(['a'], ['b']) == ['a', 'b']
        assert jps(['1', '2', '3'], ['4', '5']) == ['1', '2', '3', '4', '5']

        # A trailing slash is preserved if no new slash is being added.
        #   ex: ['a', ''] + ['b'] == ['a', 'b'], or 'a/' + 'b' == 'a/b'
        assert jps(['a', ''], ['b']) == ['a', 'b']
        assert jps(['a'], [''], ['b']) == ['a', 'b']
        assert jps(['', 'a', ''], ['b']) == ['', 'a', 'b']
        assert jps(['', 'a', ''], ['b', '']) == ['', 'a', 'b', '']

        # A new slash is preserved if no trailing slash exists.
        #   ex: ['a'] + ['', 'b'] == ['a', 'b'], or 'a' + '/b' == 'a/b'
        assert jps(['a'], ['', 'b']) == ['a', 'b']
        assert jps(['a'], [''], ['b']) == ['a', 'b']
        assert jps(['', 'a'], ['', 'b']) == ['', 'a', 'b']
        assert jps(['', 'a', ''], ['b', '']) == ['', 'a', 'b', '']
        assert jps(['', 'a', ''], ['b'], ['']) == ['', 'a', 'b']
        assert jps(['', 'a', ''], ['b'], ['', '']) == ['', 'a', 'b', '']

        # A trailing slash and a new slash means that an extra slash
        # will exist afterwords.
        # ex: ['a', ''] + ['', 'b'] == ['a', '', 'b'], or 'a/' + '/b'
        #   == 'a//b'
        assert jps(['a', ''], ['', 'b']) == ['a', '', 'b']
        assert jps(['a'], [''], [''], ['b']) == ['a', 'b']
        assert jps(['', 'a', ''], ['', 'b']) == ['', 'a', '', 'b']
        assert jps(['', 'a'], [''], ['b', '']) == ['', 'a', 'b', '']
        assert jps(['', 'a'], [''], [''], ['b'], ['']) == ['', 'a', 'b']
        assert jps(['', 'a'], [''], [''], ['b'], ['', '']) == [
            '', 'a', 'b', '']
        assert jps(['', 'a'], ['', ''], ['b'], ['', '']) == ['', 'a', 'b', '']
        assert jps(['', 'a'], ['', '', ''], ['b']) == ['', 'a', '', 'b']
        assert jps(['', 'a', ''], ['', '', ''], ['', 'b']) == [
            '', 'a', '', '', '', 'b']
        assert jps(['a', '', ''], ['', '', ''], ['', 'b']) == [
            'a', '', '', '', '', 'b']

        # Path segments blocks without slashes, are combined as
        # expected.
        assert jps(['a', 'b'], ['c', 'd']) == ['a', 'b', 'c', 'd']
        assert jps(['a'], ['b'], ['c'], ['d']) == ['a', 'b', 'c', 'd']
        assert jps(['a', 'b', 'c', 'd'], ['e']) == ['a', 'b', 'c', 'd', 'e']
        assert jps(['a', 'b', 'c'], ['d'], ['e', 'f']) == [
            'a', 'b', 'c', 'd', 'e', 'f']

        # Putting it all together.
        assert jps(['a', '', 'b'], ['', 'c', 'd']) == ['a', '', 'b', 'c', 'd']
        assert jps(['a', '', 'b', ''], ['c', 'd']) == ['a', '', 'b', 'c', 'd']
        assert jps(['a', '', 'b', ''], ['c', 'd'], ['', 'e']) == [
            'a', '', 'b', 'c', 'd', 'e']
        assert jps(['', 'a', '', 'b', ''], ['', 'c']) == [
            '', 'a', '', 'b', '', 'c']
        assert jps(['', 'a', ''], ['', 'b', ''], ['', 'c']) == [
            '', 'a', '', 'b', '', 'c']

    def test_remove_path_segments(self):
        rps = furl.remove_path_segments

        # [''] represents a slash, equivalent to ['',''].

        # Basics.
        assert rps([], []) == []
        assert rps([''], ['']) == []
        assert rps(['a'], ['a']) == []
        assert rps(['a'], ['', 'a']) == ['a']
        assert rps(['a'], ['a', '']) == ['a']
        assert rps(['a'], ['', 'a', '']) == ['a']

        # Slash manipulation.
        assert rps([''], ['', '']) == []
        assert rps(['', ''], ['']) == []
        assert rps(['', ''], ['', '']) == []
        assert rps(['', 'a', 'b', 'c'], ['b', 'c']) == ['', 'a', '']
        assert rps(['', 'a', 'b', 'c'], ['', 'b', 'c']) == ['', 'a']
        assert rps(['', 'a', '', ''], ['']) == ['', 'a', '']
        assert rps(['', 'a', '', ''], ['', '']) == ['', 'a', '']
        assert rps(['', 'a', '', ''], ['', '', '']) == ['', 'a']

        # Remove a portion of the path from the tail of the original
        # path.
        assert rps(['', 'a', 'b', ''], ['', 'a', 'b', '']) == []
        assert rps(['', 'a', 'b', ''], ['a', 'b', '']) == ['', '']
        assert rps(['', 'a', 'b', ''], ['b', '']) == ['', 'a', '']
        assert rps(['', 'a', 'b', ''], ['', 'b', '']) == ['', 'a']
        assert rps(['', 'a', 'b', ''], ['', '']) == ['', 'a', 'b']
        assert rps(['', 'a', 'b', ''], ['']) == ['', 'a', 'b']
        assert rps(['', 'a', 'b', ''], []) == ['', 'a', 'b', '']

        assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b', 'c']) == []
        assert rps(['', 'a', 'b', 'c'], ['a', 'b', 'c']) == ['', '']
        assert rps(['', 'a', 'b', 'c'], ['b', 'c']) == ['', 'a', '']
        assert rps(['', 'a', 'b', 'c'], ['', 'b', 'c']) == ['', 'a']
        assert rps(['', 'a', 'b', 'c'], ['c']) == ['', 'a', 'b', '']
        assert rps(['', 'a', 'b', 'c'], ['', 'c']) == ['', 'a', 'b']
        assert rps(['', 'a', 'b', 'c'], []) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['']) == ['', 'a', 'b', 'c']

        # Attempt to remove valid subsections, but subsections not from
        # the end of the original path.
        assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b', '']) == [
            '', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['a', 'b']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['a', 'b', '']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', 'a', 'b', '']) == [
            '', 'a', 'b', 'c']

        assert rps(['', 'a', 'b', 'c'], ['a']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', 'a']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['a', '']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', 'a', '']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', 'a', '', '']) == [
            '', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', '', 'a', '', '']) == [
            '', 'a', 'b', 'c']

        assert rps(['', 'a', 'b', 'c'], ['']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['', '']) == ['', 'a', 'b', 'c']
        assert rps(['', 'a', 'b', 'c'], ['c', '']) == ['', 'a', 'b', 'c']

        # Attempt to remove segments longer than the original.
        assert rps([], ['a']) == []
        assert rps([], ['a', 'b']) == []
        assert rps(['a'], ['a', 'b']) == ['a']
        assert rps(['a', 'a'], ['a', 'a', 'a']) == ['a', 'a']

    def test_is_valid_port(self):
        valids = [1, 2, 3, 65535, 119, 2930]
        invalids = [-1, -9999, 0, 'a', [], (0), {1: 1}, 65536, 99999, {}, None]

        for port in valids:
            assert furl.is_valid_port(port)
        for port in invalids:
            assert not furl.is_valid_port(port)

    def test_is_valid_scheme(self):
        valids = ['a', 'ab', 'a-b', 'a.b', 'a+b', 'a----b', 'a123', 'a-b123',
                  'a+b.1-+']
        invalids = ['1', '12', '12+', '-', '.', '+', '1a', '+a', '.b.']

        for valid in valids:
            assert furl.is_valid_scheme(valid)
        for invalid in invalids:
            assert not furl.is_valid_scheme(invalid)

    def test_is_valid_encoded_path_segment(self):
        valids = [('abcdefghijklmnopqrstuvwxyz'
                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                   '0123456789' '-._~' ":@!$&'()*+,;="),
                  '', 'a', 'asdf', 'a%20a', '%3F', ]
        invalids = [' ^`<>[]"#/?', ' ', '%3Z', '/', '?']

        for valid in valids:
            assert furl.is_valid_encoded_path_segment(valid)
        for invalid in invalids:
            assert not furl.is_valid_encoded_path_segment(invalid)

    def test_is_valid_encoded_query_key(self):
        valids = [('abcdefghijklmnopqrstuvwxyz'
                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                   '0123456789' '-._~' ":@!$&'()*+,;" '/?'),
                  '', 'a', 'asdf', 'a%20a', '%3F', 'a+a', '/', '?', ]
        invalids = [' ^`<>[]"#', ' ', '%3Z', '#']

        for valid in valids:
            assert furl.is_valid_encoded_query_key(valid)
        for invalid in invalids:
            assert not furl.is_valid_encoded_query_key(invalid)

    def test_is_valid_encoded_query_value(self):
        valids = [('abcdefghijklmnopqrstuvwxyz'
                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                   '0123456789' '-._~' ":@!$&'()*+,;" '/?='),
                  '', 'a', 'asdf', 'a%20a', '%3F', 'a+a', '/', '?', '=']
        invalids = [' ^`<>[]"#', ' ', '%3Z', '#']

        for valid in valids:
            assert furl.is_valid_encoded_query_value(valid)
        for invalid in invalids:
            assert not furl.is_valid_encoded_query_value(invalid)

    def test_asdict(self):
        path_encoded = '/wiki/%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'

        key_encoded = '%E3%83%AD%E3%83%AA%E3%83%9D%E3%83%83%E3%83%97'
        value_encoded = 'test%C3%A4'
        query_encoded = 'a=1&' + key_encoded + '=' + value_encoded

        host = u'ドメイン.テスト'
        host_encoded = 'xn--eckwd4c7c.xn--zckzah'

        fragment_encoded = path_encoded + '?' + query_encoded
        url = ('https://user:pass@%s%s?%s#%s' % (
            host_encoded, path_encoded, query_encoded, fragment_encoded))

        p = furl.Path(path_encoded)
        q = furl.Query(query_encoded)
        f = furl.Fragment(fragment_encoded)
        u = furl.furl(url)
        d = {
            'url': url,
            'scheme': 'https',
            'username': 'user',
            'password': 'pass',
            'host': host,
            'host_encoded': host_encoded,
            'port': 443,
            'netloc': 'user:pass@xn--eckwd4c7c.xn--zckzah',
            'origin': 'https://xn--eckwd4c7c.xn--zckzah',
            'path': p.asdict(),
            'query': q.asdict(),
            'fragment': f.asdict(),
            }
        assert u.asdict() == d


class TestMetadata(unittest.TestCase):
    def test_metadata_varibles(self):
        def is_non_empty_string(s):
            return isinstance(s, string_types) and s
        assert is_non_empty_string(furl.__title__)
        assert is_non_empty_string(furl.__version__)
        assert is_non_empty_string(furl.__license__)
        assert is_non_empty_string(furl.__author__)
        assert is_non_empty_string(furl.__contact__)
        assert is_non_empty_string(furl.__url__)
        assert is_non_empty_string(furl.__description__)
