diff --git a/rfc3986/parseresult.py b/rfc3986/parseresult.py index 73eedc9..baad3f8 100644 --- a/rfc3986/parseresult.py +++ b/rfc3986/parseresult.py @@ -83,12 +83,7 @@ class ParseResult(namedtuple('ParseResult', PARSED_COMPONENTS)): @property def authority(self): """Normalized authority generated from the subauthority parts.""" - _authority = getattr(self, '_authority', None) - if _authority is None: - _authority = self._authority = normalizers.normalize_authority( - (self.userinfo, self.host, self.port) - ) - return _authority + return self.reference.authority def _generate_authority(self, attributes): # I swear I did not align the comparisons below. That's just how they @@ -98,6 +93,8 @@ class ParseResult(namedtuple('ParseResult', PARSED_COMPONENTS)): if (self.userinfo != userinfo or self.host != host or self.port != port): + if port: + port = '{0}'.format(port) return normalizers.normalize_authority((userinfo, host, port)) return self.authority @@ -165,6 +162,7 @@ def split_authority(authority): # Handle IPv6 host addresses if rest.startswith(u'['): host, rest = rest.split(u']', 1) + host += u']' if ':' in rest: extra_host, port = rest.split(u':', 1) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 0000000..9c67b70 --- /dev/null +++ b/tests/base.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015 Ian Cordasco +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class BaseTestParsesURIs: + test_class = None + + """Tests for self.test_class handling of URIs.""" + def test_handles_basic_uri(self, basic_uri): + """Test that self.test_class can handle a simple URI.""" + uri = self.test_class.from_string(basic_uri) + assert uri.scheme == 'http' + assert uri.authority == basic_uri[7:] # len('http://') + assert uri.host == uri.authority + assert uri.path is None + assert uri.query is None + assert uri.fragment is None + assert uri.port is None + assert uri.userinfo is None + + def test_handles_basic_uri_with_port(self, basic_uri_with_port): + """Test that self.test_class can handle a simple URI with a port.""" + uri = self.test_class.from_string(basic_uri_with_port) + assert uri.scheme == 'ftp' + assert uri.authority == basic_uri_with_port[6:] + assert uri.host != uri.authority + assert str(uri.port) == '21' + assert uri.path is None + assert uri.query is None + assert uri.fragment is None + assert uri.userinfo is None + + def test_handles_uri_with_port_and_userinfo( + self, uri_with_port_and_userinfo): + """ + Test that self.test_class can handle a URI with a port and userinfo. + """ + uri = self.test_class.from_string(uri_with_port_and_userinfo) + assert uri.scheme == 'ssh' + # 6 == len('ftp://') + assert uri.authority == uri_with_port_and_userinfo[6:] + assert uri.host != uri.authority + assert str(uri.port) == '22' + assert uri.path is None + assert uri.query is None + assert uri.fragment is None + assert uri.userinfo == 'user:pass' + + def test_handles_basic_uri_with_path(self, basic_uri_with_path): + """Test that self.test_class can handle a URI with a path.""" + uri = self.test_class.from_string(basic_uri_with_path) + assert uri.scheme == 'http' + assert basic_uri_with_path == (uri.scheme + '://' + uri.authority + + uri.path) + assert uri.host == uri.authority + assert uri.path == '/path/to/resource' + assert uri.query is None + assert uri.fragment is None + assert uri.userinfo is None + assert uri.port is None + + def test_handles_uri_with_path_and_query(self, uri_with_path_and_query): + """ + Test that self.test_class can handle a URI with a path and query. + """ + uri = self.test_class.from_string(uri_with_path_and_query) + assert uri.scheme == 'http' + assert uri.host == uri.authority + assert uri.path == '/path/to/resource' + assert uri.query == 'key=value' + assert uri.fragment is None + assert uri.userinfo is None + assert uri.port is None + + def test_handles_uri_with_everything(self, uri_with_everything): + """ + Test that self.test_class can handle and with everything in it. + """ + uri = self.test_class.from_string(uri_with_everything) + assert uri.scheme == 'https' + assert uri.path == '/path/to/resource' + assert uri.query == 'key=value' + assert uri.fragment == 'fragment' + assert uri.userinfo == 'user:pass' + assert str(uri.port) == '443' + + def test_handles_relative_uri(self, relative_uri): + """Test that self.test_class can handle a relative URI.""" + uri = self.test_class.from_string(relative_uri) + assert uri.scheme is None + assert uri.authority == relative_uri[2:] + + +class BaseTestUnsplits: + test_class = None + + def test_basic_uri_unsplits(self, basic_uri): + uri = self.test_class.from_string(basic_uri) + assert uri.unsplit() == basic_uri + + def test_basic_uri_with_port_unsplits(self, basic_uri_with_port): + uri = self.test_class.from_string(basic_uri_with_port) + assert uri.unsplit() == basic_uri_with_port + + def test_uri_with_port_and_userinfo_unsplits(self, + uri_with_port_and_userinfo): + uri = self.test_class.from_string(uri_with_port_and_userinfo) + assert uri.unsplit() == uri_with_port_and_userinfo + + def test_basic_uri_with_path_unsplits(self, basic_uri_with_path): + uri = self.test_class.from_string(basic_uri_with_path) + assert uri.unsplit() == basic_uri_with_path + + def test_uri_with_path_and_query_unsplits(self, uri_with_path_and_query): + uri = self.test_class.from_string(uri_with_path_and_query) + assert uri.unsplit() == uri_with_path_and_query + + def test_uri_with_everything_unsplits(self, uri_with_everything): + uri = self.test_class.from_string(uri_with_everything) + assert uri.unsplit() == uri_with_everything + + def test_relative_uri_unsplits(self, relative_uri): + uri = self.test_class.from_string(relative_uri) + assert uri.unsplit() == relative_uri + + def test_absolute_path_uri_unsplits(self, absolute_path_uri): + uri = self.test_class.from_string(absolute_path_uri) + assert uri.unsplit() == absolute_path_uri diff --git a/tests/conftest.py b/tests/conftest.py index ba44dad..a09192f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,70 @@ # -*- coding: utf-8 -*- import sys +import pytest + +SNOWMAN = b'\xe2\x98\x83' + +valid_hosts = [ + '[21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A]', '[::1]', + '[21DA:D3:0:2F3B:2AA:FF:FE28:9C5A]', '[FE80::2AA:FF:FE9A:4CA2]', + '[FF02::2]', '[FF02:3::5]', '[FF02:0:0:0:0:0:0:2]', + '[FF02:30:0:0:0:0:0:5]', '127.0.0.1', 'www.example.com', 'localhost', + 'http-bin.org', + ] + +invalid_hosts = [ + '[FF02::3::5]', # IPv6 can only have one :: + '[FADF:01]', # Not properly compacted (missing a :) + 'localhost:80:80:80', # Too many ports + '256.256.256.256', # Invalid IPv4 Address + SNOWMAN.decode('utf-8') + ] + + +@pytest.fixture(params=valid_hosts) +def basic_uri(request): + return 'http://%s' % request.param + + +@pytest.fixture(params=valid_hosts) +def basic_uri_with_port(request): + return 'ftp://%s:21' % request.param + + +@pytest.fixture(params=valid_hosts) +def uri_with_port_and_userinfo(request): + return 'ssh://user:pass@%s:22' % request.param + + +@pytest.fixture(params=valid_hosts) +def basic_uri_with_path(request): + return 'http://%s/path/to/resource' % request.param + + +@pytest.fixture(params=valid_hosts) +def uri_with_path_and_query(request): + return 'http://%s/path/to/resource?key=value' % request.param + + +@pytest.fixture(params=valid_hosts) +def uri_with_everything(request): + return 'https://user:pass@%s:443/path/to/resource?key=value#fragment' % ( + request.param) + + +@pytest.fixture(params=valid_hosts) +def relative_uri(request): + return '//%s' % request.param + + +@pytest.fixture +def absolute_path_uri(): + return '/path/to/file' + + +@pytest.fixture(params=invalid_hosts) +def invalid_uri(request): + return 'https://%s' % request.param + sys.path.insert(0, '.') diff --git a/tests/test_parseresult.py b/tests/test_parseresult.py new file mode 100644 index 0000000..38df8d3 --- /dev/null +++ b/tests/test_parseresult.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015 Ian Cordasco +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from rfc3986 import parseresult as pr + +from . import base + + +class TestParseResultParsesURIs(base.BaseTestParsesURIs): + test_class = pr.ParseResult + + +class TestParseResultUnsplits(base.BaseTestUnsplits): + test_class = pr.ParseResult + + +class TestStdlibShims: + def test_uri_with_everything(self, uri_with_everything): + uri = pr.ParseResult.from_string(uri_with_everything) + assert uri.host == uri.hostname + assert uri.netloc == uri.authority + assert uri.query == uri.params + assert uri.geturl() == uri.unsplit() + + +def test_creates_a_copy_with_a_new_path(uri_with_everything): + uri = pr.ParseResult.from_string(uri_with_everything) + new_uri = uri.copy_with(path='/parse/result/tests/are/fun') + assert new_uri.path == '/parse/result/tests/are/fun' + + +def test_creates_a_copy_with_a_new_port(basic_uri): + uri = pr.ParseResult.from_string(basic_uri) + new_uri = uri.copy_with(port=443) + assert new_uri.port == 443 diff --git a/tests/test_unicode_support.py b/tests/test_unicode_support.py index 19b98de..a756c66 100644 --- a/tests/test_unicode_support.py +++ b/tests/test_unicode_support.py @@ -45,6 +45,20 @@ def test_urlparse_a_unicode_hostname(): assert parsed.host == unicode_url[7:] +def test_urlparse_a_unicode_hostname_with_auth(): + url = b'http://userinfo@' + SNOWMAN + b'.com' + parsed = urlparse(url) + assert parsed.userinfo == 'userinfo' + + +def test_urlparse_an_invalid_authority_parses_port(): + url = 'http://foo:b@r@[::1]:80/get' + parsed = urlparse(url) + assert parsed.port == 80 + assert parsed.userinfo == 'foo:b@r' + assert parsed.hostname == '[::1]' + + def test_unsplit_idna_a_unicode_hostname(): parsed = urlparse(SNOWMAN_HOST) assert parsed.unsplit(use_idna=True) == SNOWMAN_IDNA_HOST diff --git a/tests/test_uri.py b/tests/test_uri.py index d169b39..6cd7da3 100644 --- a/tests/test_uri.py +++ b/tests/test_uri.py @@ -5,67 +5,7 @@ from rfc3986.exceptions import InvalidAuthority, ResolutionError from rfc3986.misc import URI_MATCHER from rfc3986.uri import URIReference - -valid_hosts = [ - '[21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A]', '[::1]', - '[21DA:D3:0:2F3B:2AA:FF:FE28:9C5A]', '[FE80::2AA:FF:FE9A:4CA2]', - '[FF02::2]', '[FF02:3::5]', '[FF02:0:0:0:0:0:0:2]', - '[FF02:30:0:0:0:0:0:5]', '127.0.0.1', 'www.example.com', 'localhost', - 'http-bin.org', - ] - -invalid_hosts = [ - '[FF02::3::5]', # IPv6 can only have one :: - '[FADF:01]', # Not properly compacted (missing a :) - 'localhost:80:80:80', # Too many ports - '256.256.256.256' # Invalid IPv4 Address - ] - - -@pytest.fixture(params=valid_hosts) -def basic_uri(request): - return 'http://%s' % request.param - - -@pytest.fixture(params=valid_hosts) -def basic_uri_with_port(request): - return 'ftp://%s:21' % request.param - - -@pytest.fixture(params=valid_hosts) -def uri_with_port_and_userinfo(request): - return 'ssh://user:pass@%s:22' % request.param - - -@pytest.fixture(params=valid_hosts) -def basic_uri_with_path(request): - return 'http://%s/path/to/resource' % request.param - - -@pytest.fixture(params=valid_hosts) -def uri_with_path_and_query(request): - return 'http://%s/path/to/resource?key=value' % request.param - - -@pytest.fixture(params=valid_hosts) -def uri_with_everything(request): - return 'https://user:pass@%s:443/path/to/resource?key=value#fragment' % ( - request.param) - - -@pytest.fixture(params=valid_hosts) -def relative_uri(request): - return '//%s' % request.param - - -@pytest.fixture -def absolute_path_uri(): - return '/path/to/file' - - -@pytest.fixture(params=invalid_hosts) -def invalid_uri(request): - return 'https://%s' % request.param +from . import base @pytest.fixture @@ -73,85 +13,9 @@ def scheme_and_path_uri(): return 'mailto:user@example.com' -class TestURIReferenceParsesURIs: +class TestURIReferenceParsesURIs(base.BaseTestParsesURIs): """Tests for URIReference handling of URIs.""" - def test_handles_basic_uri(self, basic_uri): - """Test that URIReference can handle a simple URI.""" - uri = URIReference.from_string(basic_uri) - assert uri.scheme == 'http' - assert uri.authority == basic_uri[7:] # len('http://') - assert uri.host == uri.authority - assert uri.path is None - assert uri.query is None - assert uri.fragment is None - assert uri.port is None - assert uri.userinfo is None - - def test_handles_basic_uri_with_port(self, basic_uri_with_port): - """Test that URIReference can handle a simple URI with a port.""" - uri = URIReference.from_string(basic_uri_with_port) - assert uri.scheme == 'ftp' - assert uri.authority == basic_uri_with_port[6:] # len('ftp://') - assert uri.host != uri.authority - assert uri.port == '21' - assert uri.path is None - assert uri.query is None - assert uri.fragment is None - assert uri.userinfo is None - - def test_handles_uri_with_port_and_userinfo( - self, uri_with_port_and_userinfo): - """ - Test that URIReference can handle a URI with a port and userinfo. - """ - uri = URIReference.from_string(uri_with_port_and_userinfo) - assert uri.scheme == 'ssh' - # 6 == len('ftp://') - assert uri.authority == uri_with_port_and_userinfo[6:] - assert uri.host != uri.authority - assert uri.port == '22' - assert uri.path is None - assert uri.query is None - assert uri.fragment is None - assert uri.userinfo == 'user:pass' - - def test_handles_basic_uri_with_path(self, basic_uri_with_path): - """Test that URIReference can handle a URI with a path.""" - uri = URIReference.from_string(basic_uri_with_path) - assert uri.scheme == 'http' - assert basic_uri_with_path == (uri.scheme + '://' + uri.authority - + uri.path) - assert uri.host == uri.authority - assert uri.path == '/path/to/resource' - assert uri.query is None - assert uri.fragment is None - assert uri.userinfo is None - assert uri.port is None - - def test_handles_uri_with_path_and_query(self, uri_with_path_and_query): - """ - Test that URIReference can handle a URI with a path and query. - """ - uri = URIReference.from_string(uri_with_path_and_query) - assert uri.scheme == 'http' - assert uri.host == uri.authority - assert uri.path == '/path/to/resource' - assert uri.query == 'key=value' - assert uri.fragment is None - assert uri.userinfo is None - assert uri.port is None - - def test_handles_uri_with_everything(self, uri_with_everything): - """ - Test that URIReference can handle and with everything in it. - """ - uri = URIReference.from_string(uri_with_everything) - assert uri.scheme == 'https' - assert uri.path == '/path/to/resource' - assert uri.query == 'key=value' - assert uri.fragment == 'fragment' - assert uri.userinfo == 'user:pass' - assert uri.port == '443' + test_class = URIReference def test_authority_info_raises_InvalidAuthority(self, invalid_uri): """Test that an invalid IPv6 is caught by authority_info().""" @@ -166,12 +30,6 @@ class TestURIReferenceParsesURIs: assert uri.userinfo is None assert uri.port is None - def test_handles_relative_uri(self, relative_uri): - """Test that URIReference can handle a relative URI.""" - uri = URIReference.from_string(relative_uri) - assert uri.scheme is None - assert uri.authority == relative_uri[2:] - def test_handles_absolute_path_uri(self, absolute_path_uri): """Test that URIReference can handle a path-only URI.""" uri = URIReference.from_string(absolute_path_uri) @@ -182,9 +40,13 @@ class TestURIReferenceParsesURIs: 'port': None, } + def test_scheme_and_path_uri_is_valid(self, scheme_and_path_uri): + uri = self.test_class.from_string(scheme_and_path_uri) + assert uri.is_valid() is True + def test_handles_scheme_and_path_uri(self, scheme_and_path_uri): - """Test that URIReference can handle a `scheme:path` URI.""" - uri = URIReference.from_string(scheme_and_path_uri) + """Test that self.test_class can handle a `scheme:path` URI.""" + uri = self.test_class.from_string(scheme_and_path_uri) assert uri.path == 'user@example.com' assert uri.scheme == 'mailto' assert uri.query is None @@ -276,42 +138,11 @@ class TestURIValidation: assert uri.is_valid() is False -class TestURIReferenceUnsplits: - def test_basic_uri_unsplits(self, basic_uri): - uri = URIReference.from_string(basic_uri) - assert uri.unsplit() == basic_uri - - def test_basic_uri_with_port_unsplits(self, basic_uri_with_port): - uri = URIReference.from_string(basic_uri_with_port) - assert uri.unsplit() == basic_uri_with_port - - def test_uri_with_port_and_userinfo_unsplits(self, - uri_with_port_and_userinfo): - uri = URIReference.from_string(uri_with_port_and_userinfo) - assert uri.unsplit() == uri_with_port_and_userinfo - - def test_basic_uri_with_path_unsplits(self, basic_uri_with_path): - uri = URIReference.from_string(basic_uri_with_path) - assert uri.unsplit() == basic_uri_with_path - - def test_uri_with_path_and_query_unsplits(self, uri_with_path_and_query): - uri = URIReference.from_string(uri_with_path_and_query) - assert uri.unsplit() == uri_with_path_and_query - - def test_uri_with_everything_unsplits(self, uri_with_everything): - uri = URIReference.from_string(uri_with_everything) - assert uri.unsplit() == uri_with_everything - - def test_relative_uri_unsplits(self, relative_uri): - uri = URIReference.from_string(relative_uri) - assert uri.unsplit() == relative_uri - - def test_absolute_path_uri_unsplits(self, absolute_path_uri): - uri = URIReference.from_string(absolute_path_uri) - assert uri.unsplit() == absolute_path_uri +class TestURIReferenceUnsplits(base.BaseTestUnsplits): + test_class = URIReference def test_scheme_and_path_uri_unsplits(self, scheme_and_path_uri): - uri = URIReference.from_string(scheme_and_path_uri) + uri = self.test_class.from_string(scheme_and_path_uri) assert uri.unsplit() == scheme_and_path_uri diff --git a/tox.ini b/tox.ini index 09bc07d..8ad68bc 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,12 @@ envlist = py26,py27,py32,py33,py34,pypy,{py27,py34}-flake8 pip_pre = False deps = -rdev-requirements.txt +commands = + py.test {posargs:--cov rfc3986 tests/} + coverage report --fail-under 100 -m + +[testenv:pypy] +deps = {[testenv]deps} commands = py.test {posargs} [testenv:py27-flake8]