# -*- coding: utf-8 -*- """ Tests for hyperlink.hypothesis. """ try: import hypothesis del hypothesis except ImportError: pass else: from string import digits from typing import Sequence, Text try: from unittest.mock import patch except ImportError: from mock import patch # type: ignore[misc] from hypothesis import given, settings from hypothesis.strategies import SearchStrategy, data from idna import IDNAError, check_label, encode as idna_encode from .common import HyperlinkTestCase from .. import DecodedURL, EncodedURL from ..hypothesis import ( DrawCallable, composite, decoded_urls, encoded_urls, hostname_labels, hostnames, idna_text, paths, port_numbers, ) class TestHypothesisStrategies(HyperlinkTestCase): """ Tests for hyperlink.hypothesis. """ @given(idna_text()) def test_idna_text_valid(self, text): # type: (Text) -> None """ idna_text() generates IDNA-encodable text. """ try: idna_encode(text) except IDNAError: # pragma: no cover raise AssertionError("Invalid IDNA text: {!r}".format(text)) @given(data()) def test_idna_text_min_max(self, data): # type: (SearchStrategy) -> None """ idna_text() raises AssertionError if min_size is < 1. """ self.assertRaises(AssertionError, data.draw, idna_text(min_size=0)) self.assertRaises(AssertionError, data.draw, idna_text(max_size=0)) @given(port_numbers()) def test_port_numbers_bounds(self, port): # type: (int) -> None """ port_numbers() generates integers between 1 and 65535, inclusive. """ self.assertGreaterEqual(port, 1) self.assertLessEqual(port, 65535) @given(port_numbers(allow_zero=True)) def test_port_numbers_bounds_allow_zero(self, port): # type: (int) -> None """ port_numbers(allow_zero=True) generates integers between 0 and 65535, inclusive. """ self.assertGreaterEqual(port, 0) self.assertLessEqual(port, 65535) @given(hostname_labels()) def test_hostname_labels_valid_idn(self, label): # type: (Text) -> None """ hostname_labels() generates IDN host name labels. """ try: check_label(label) idna_encode(label) except UnicodeError: # pragma: no cover raise AssertionError("Invalid IDN label: {!r}".format(label)) @given(data()) @settings(max_examples=10) def test_hostname_labels_long_idn_punycode(self, data): # type: (SearchStrategy) -> None """ hostname_labels() handles case where idna_text() generates text that encoded to punycode ends up as longer than allowed. """ @composite def mock_idna_text(draw, min_size, max_size): # type: (DrawCallable, int, int) -> Text # We want a string that does not exceed max_size, but when # encoded to punycode, does exceed max_size. # So use a unicode character that is larger when encoded, # "รก" being a great example, and use it max_size times, which # will be max_size * 3 in size when encoded. return u"\N{LATIN SMALL LETTER A WITH ACUTE}" * max_size with patch("hyperlink.hypothesis.idna_text", mock_idna_text): label = data.draw(hostname_labels()) try: check_label(label) idna_encode(label) except UnicodeError: # pragma: no cover raise AssertionError( "Invalid IDN label: {!r}".format(label) ) @given(hostname_labels(allow_idn=False)) def test_hostname_labels_valid_ascii(self, label): # type: (Text) -> None """ hostname_labels() generates a ASCII host name labels. """ try: check_label(label) label.encode("ascii") except UnicodeError: # pragma: no cover raise AssertionError("Invalid ASCII label: {!r}".format(label)) @given(hostnames()) def test_hostnames_idn(self, hostname): # type: (Text) -> None """ hostnames() generates a IDN host names. """ try: for label in hostname.split(u"."): check_label(label) idna_encode(hostname) except UnicodeError: # pragma: no cover raise AssertionError( "Invalid IDN host name: {!r}".format(hostname) ) @given(hostnames(allow_leading_digit=False)) def test_hostnames_idn_nolead(self, hostname): # type: (Text) -> None """ hostnames(allow_leading_digit=False) generates a IDN host names without leading digits. """ self.assertTrue(hostname == hostname.lstrip(digits)) @given(hostnames(allow_idn=False)) def test_hostnames_ascii(self, hostname): # type: (Text) -> None """ hostnames() generates a ASCII host names. """ try: for label in hostname.split(u"."): check_label(label) hostname.encode("ascii") except UnicodeError: # pragma: no cover raise AssertionError( "Invalid ASCII host name: {!r}".format(hostname) ) @given(hostnames(allow_leading_digit=False, allow_idn=False)) def test_hostnames_ascii_nolead(self, hostname): # type: (Text) -> None """ hostnames(allow_leading_digit=False, allow_idn=False) generates ASCII host names without leading digits. """ self.assertTrue(hostname == hostname.lstrip(digits)) @given(paths()) def test_paths(self, path): # type: (Sequence[Text]) -> None """ paths() generates sequences of URL path components. """ text = u"/".join(path) try: text.encode("utf-8") except UnicodeError: # pragma: no cover raise AssertionError("Invalid URL path: {!r}".format(path)) for segment in path: self.assertNotIn("#/?", segment) @given(encoded_urls()) def test_encoded_urls(self, url): # type: (EncodedURL) -> None """ encoded_urls() generates EncodedURLs. """ self.assertIsInstance(url, EncodedURL) @given(decoded_urls()) def test_decoded_urls(self, url): # type: (DecodedURL) -> None """ decoded_urls() generates DecodedURLs. """ self.assertIsInstance(url, DecodedURL)