Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validation checks in Jurisdiction constructor #40

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion clarify/jurisdiction.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import lxml.html
from lxml.cssselect import CSSSelector

CLARITY_RESULTS_HOSTNAME = "results.enr.clarityelections.com"
SUPPORTED_LEVELS = ['state', 'county', 'city', 'precinct']


class Jurisdiction(object):

Expand All @@ -20,12 +23,40 @@ def __init__(self, url, level, name=''):
"""
To create an instance, pass a Clarity results URL for the top-level
political jurisdiction (a state, for example), and the corresponding
level in lowercase ("state" or "county").
level in lowercase ("state", "county", "city", or "precinct").
"""

# Preliminary check for any type which is not string-like
if url == None:
raise TypeError('Invalid url parameter')
try:
# Attempt to convert to str
url = str(url)
except TypeError as type_error:
raise type_error
except ValueError as value_error:
raise value_error
except Exception as error:
# If converting to str failed, raise type error
raise TypeError('Invalid url parameter')
if type(url) != str:
raise TypeError('Invalid url parameter')
# if url is an HTTP URL to Clarity Election Results
if len(url) >= 40 and url[0:40] == 'http://' + CLARITY_RESULTS_HOSTNAME + '/':
# Replace HTTP with HTTPS; retain the string after the URL origin
url = 'https://' + CLARITY_RESULTS_HOSTNAME + '/' + url[40]
# if url is not in the allowed list of supported origins
if len(url) < 41 or url[0:41] != 'https://' + CLARITY_RESULTS_HOSTNAME + '/':
raise ValueError('Unsupported url origin')
self.url = url
self.parsed_url = self._parse_url()
self.state = self._get_state_from_url()

if type(level) != str:
raise TypeError('Invalid level parameter')
level = level.lower()
if level not in SUPPORTED_LEVELS:
raise ValueError('Unsupported level')
self.level = level
self.name = name
self.summary_url = self._get_summary_url()
Expand Down
89 changes: 89 additions & 0 deletions tests/test_jurisdiction.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,19 @@
COUNTY_URL_RE = re.compile(r'https://results.enr.clarityelections.com/(?P<state>[A-Z]{2})/(?P<county>[A-Za-z\.]+)/(?P<page_id>\d+)/(?P<page_id_2>\d+)/')


# non-string object which has a type(str(Stringlike)) === str
class Stringlike(object):
def __init__(self, url):
self.url = url
def __str__(self):
return self.url

# non-string object which has a type(str(Stringlike)) !== str
class NotStringlike(object):
def __init__(self, url):
# do something but don't implement __str__ and don't store url
self.noturl = 'noturl'

def mock_county_response_callback(req):
m = COUNTY_REDIRECT_URL_RE.match(req.url)
assert m is not None
Expand All @@ -166,11 +179,87 @@ def mock_subjurisdiction_redirect_page_meta(page_id):


class TestJurisdiction(TestCase):
# Test the constructor
def test_construct(self):
"""
A Jurisdiction with a valid, supported URL and level string should create a class instance and not raise an Exception.
"""
url = 'https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html'
jurisdiction = Jurisdiction(url=url, level='state')
self.assertEqual(jurisdiction.url, url)

def test_construct_valid_county(self):
"""
A Jurisdiction with a valid, supported URL and level string should create a class instance and not raise an Exception.
"""
url = 'https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html'
jurisdiction = Jurisdiction(url=url, level='county')
self.assertEqual(jurisdiction.url, url)

def test_construct_valid_city(self):
"""
A Jurisdiction with a valid, supported URL and level string should create a class instance and not raise an Exception.
"""
url = 'https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html'
jurisdiction = Jurisdiction(url=url, level='city')
self.assertEqual(jurisdiction.url, url)

def test_construct_valid_precinct(self):
"""
A Jurisdiction with a valid, supported URL and level string should create a class instance and not raise an Exception.
"""
url = 'https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html'
jurisdiction = Jurisdiction(url=url, level='precinct')
self.assertEqual(jurisdiction.url, url)

def test_construct_valid_url_stringlike(self):
"""
A Jurisdiction with a valid, supported URL and level string should create a class instance and not raise an Exception.
"""
url = Stringlike('https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html')
jurisdiction = Jurisdiction(url=url, level='state')
self.assertEqual(jurisdiction.url, str(url))

def test_construct_invalid_url_notstringlike(self):
"""
A Jurisdiction with a valid, supported URL and level string should raise a TypeError.
"""
url = NotStringlike('https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html')
with self.assertRaises(ValueError):
Jurisdiction(url=url, level='state')

def test_construct_invalid_url_type(self):
"""
A Jurisdiction with an invalid URL data type should raise an Exception.
"""
with self.assertRaises(TypeError):
Jurisdiction(url=None, level='state')

def test_construct_invalid_hostname(self):
"""
A Jurisdiction with an unsupported hostname in the URL should raise an Exception.
"""
url = 'https://bad.hostname/KY/15261/30235/en/summary.html'
with self.assertRaises(ValueError):
Jurisdiction(url=url, level='state')

def test_construct_no_level(self):
"""
A Jurisdiction with a valid, supported hostname but no level in the URL should raise an Exception.
"""
url = 'https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html'
with self.assertRaises(TypeError):
Jurisdiction(url=url)

def test_construct_invalid_level_str(self):
"""
A Jurisdiction with a valid, supported hostname but an invalid level in the URL should raise an Exception.
"""
url = 'https://results.enr.clarityelections.com/KY/15261/30235/en/summary.html'
with self.assertRaises(ValueError):
Jurisdiction(url=url, level='foo')


@responses.activate
def test_get_subjurisdictions_state(self):
# subjurisdiction url path is in script tag
Expand Down