diff --git a/app/utils.py b/app/utils.py index 2a70ab3fb..de4ac69d8 100644 --- a/app/utils.py +++ b/app/utils.py @@ -13,15 +13,7 @@ import ago import dateutil import pyexcel import yaml -from flask import ( - Markup, - abort, - current_app, - redirect, - request, - session, - url_for, -) +from flask import abort, current_app, redirect, request, session, url_for from flask_login import current_user from notifications_utils.field import Field from notifications_utils.formatters import make_quotes_smart @@ -409,143 +401,23 @@ def set_status_filters(filter_args): _dir_path = os.path.dirname(os.path.realpath(__file__)) -class AgreementInfo: +class NotGovernmentEmailDomain(Exception): + pass - with open('{}/domains.yml'.format(_dir_path)) as domains: - domains = yaml.safe_load(domains) - domain_names = sorted(domains.keys(), key=len, reverse=True) + +class GovernmentEmailDomain(): + + with open('{}/email_domains.yml'.format(_dir_path)) as email_domains: + domain_names = yaml.safe_load(email_domains) def __init__(self, email_address_or_domain): - - self._match = next(filter( - self.get_matching_function(email_address_or_domain), - self.domain_names, - ), None) - - self._domain = email_address_or_domain.split('@')[-1] - - ( - self.owner, - self.crown_status, - self.agreement_signed, - self.canonical_domain, - ) = self._get_info() - - @classmethod - def from_user(cls, user): - return cls(user.email_address if user.is_authenticated else '') - - @classmethod - def from_current_user(cls): - return cls.from_user(current_user) - - @property - def as_human_readable(self): - if self.canonical_domain and 'dwp' in self.canonical_domain: - return 'DWP - Requires OED approval' - if self.agreement_signed: - return 'Yes, on behalf of {}'.format(self.owner) - elif self.owner: - return '{} (organisation is {}, {})'.format( - { - False: 'No', - None: 'Can’t tell', - }.get(self.agreement_signed), - self.owner, - { - True: 'a crown body', - False: 'a non-crown body', - None: 'crown status unknown', - }.get(self.crown_status), - ) - else: - return 'Can’t tell (domain is {})'.format(self._domain) - - @property - def as_info_for_branding_request(self): - return self.owner or 'Can’t tell (domain is {})'.format(self._domain) - - @property - def as_jinja_template(self): - if self.crown_status is None: - return 'agreement-choose' - if self.agreement_signed: - return 'agreement-signed' - return 'agreement' - - def as_terms_of_use_paragraph(self, **kwargs): - return Markup(self._as_terms_of_use_paragraph(**kwargs)) - - def _as_terms_of_use_paragraph(self, terms_link, download_link, support_link, signed_in): - - if not signed_in: - return (( - '{} Sign in to download a copy ' - 'or find out if one is already in place.' - ).format(self._acceptance_required, terms_link)) - - if self.agreement_signed is None: - return (( - '{} Download the agreement or ' - 'contact us to find out if we already ' - 'have one in place with your organisation.' - ).format(self._acceptance_required, download_link, support_link)) - - if self.agreement_signed is False: - return (( - '{} Download a copy.' - ).format(self._acceptance_required, download_link)) - - return ( - 'Your organisation ({}) has already accepted the ' - 'GOV.UK Notify data sharing and financial ' - 'agreement.'.format(self.owner) - ) - - def as_pricing_paragraph(self, **kwargs): - return Markup(self._as_pricing_paragraph(**kwargs)) - - def _as_pricing_paragraph(self, pricing_link, download_link, support_link, signed_in): - - if not signed_in: - return (( - 'Sign in to download a copy or find ' - 'out if one is already in place with your organisation.' - ).format(pricing_link)) - - if self.agreement_signed is None: - return (( - 'Download the agreement or ' - 'contact us to find out if we already ' - 'have one in place with your organisation.' - ).format(download_link, support_link)) - - return ( - 'Download the agreement ' - '({} {}).'.format( - download_link, - self.owner, - { - True: 'has already accepted it', - False: 'hasn’t accepted it yet' - }.get(self.agreement_signed) - ) - ) - - @property - def _acceptance_required(self): - return ( - 'Your organisation {} must also accept our data sharing ' - 'and financial agreement.'.format( - '({})'.format(self.owner) if self.owner else '', - ) - ) - - @property - def crown_status_or_404(self): - if self.crown_status is None: - abort(404) - return self.crown_status + try: + self._match = next(filter( + self.get_matching_function(email_address_or_domain), + self.domain_names, + )) + except StopIteration: + raise NotGovernmentEmailDomain() @staticmethod def get_matching_function(email_address_or_domain): @@ -564,45 +436,6 @@ class AgreementInfo: return fn - def _get_info(self): - - details = self.domains.get(self._match, {}) - - if details is None: - raise TypeError('Domain must have details ({})'.format(self._domain)) - - if isinstance(details, str): - self.is_canonical = False - return AgreementInfo(details)._get_info() - - elif isinstance(details, dict): - self.is_canonical = bool(details) - return( - details.get("owner"), - details.get("crown"), - details.get("agreement_signed"), - self._match, - ) - - -class NotGovernmentEmailDomain(Exception): - pass - - -class GovernmentEmailDomain(AgreementInfo): - - with open('{}/email_domains.yml'.format(_dir_path)) as email_domains: - domain_names = yaml.safe_load(email_domains) - - def __init__(self, email_address_or_domain): - try: - self._match = next(filter( - self.get_matching_function(email_address_or_domain), - self.domain_names, - )) - except StopIteration: - raise NotGovernmentEmailDomain() - def unicode_truncate(s, length): encoded = s.encode('utf-8')[:length] diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index c7fb087a8..cb5a85b1c 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -1,16 +1,13 @@ -from collections import Counter, OrderedDict +from collections import OrderedDict from csv import DictReader from io import StringIO from pathlib import Path import pytest from freezegun import freeze_time -from notifications_utils.recipients import validate_email_address from app import format_datetime_relative from app.utils import ( - AgreementInfo, - GovernmentEmailDomain, Spreadsheet, email_safe, generate_next_dict, @@ -292,159 +289,6 @@ def test_get_cdn_domain_on_non_localhost(client, mocker): assert domain == 'static-logos.admintest.com' -@pytest.mark.parametrize("domain_or_email_address", ( - "test@dclgdatamart.co.uk", "test@communities.gsi.gov.uk", "test@communities.gov.uk", -)) -def test_get_valid_agreement_info_known_details(domain_or_email_address): - agreement_info = AgreementInfo(domain_or_email_address) - assert agreement_info.crown_status is None - assert agreement_info.owner == "Ministry of Housing, Communities & Local Government" - assert agreement_info.agreement_signed is True - assert agreement_info.as_human_readable == ( - 'Yes, on behalf of Ministry of Housing, Communities & Local Government' - ) - - -@pytest.mark.parametrize("domain_or_email_address", ( - "test@dwp.gov.uk", "test@dwp.gsi.gov.uk", -)) -def test_dwp_go_live_requests_are_flagged(domain_or_email_address): - agreement_info = AgreementInfo(domain_or_email_address) - assert agreement_info.owner == "Department for Work and Pensions" - assert agreement_info.agreement_signed is True - assert agreement_info.as_human_readable == ( - 'DWP - Requires OED approval' - ) - - -@pytest.mark.parametrize("domain_or_email_address, is_canonical", ( - ("test@dclgdatamart.co.uk", False), - ("test@communities.gsi.gov.uk", False), - ("test@communities.gov.uk", True), -)) -def test_get_canonical_domain(domain_or_email_address, is_canonical): - assert AgreementInfo(domain_or_email_address).canonical_domain == 'communities.gov.uk' - assert AgreementInfo(domain_or_email_address).is_canonical == is_canonical - - -def test_get_canonical_domain_passes_through_unknown_domain(): - assert AgreementInfo('example.com').canonical_domain is None - assert AgreementInfo('example.com').is_canonical is False - - -@pytest.mark.parametrize("domain_or_email_address", ( - "test@police.gov.uk", "police.gov.uk", -)) -def test_get_valid_agreement_info_unknown_details(domain_or_email_address): - government_domain = AgreementInfo(domain_or_email_address) - assert government_domain.crown_status is None - assert government_domain.owner is None - assert government_domain.agreement_signed is None - assert government_domain.as_human_readable == 'Can’t tell (domain is police.gov.uk)' - - -def test_get_valid_agreement_info_only_org_known(): - agreement_info = AgreementInfo('nhs.net') - # Some parts of the NHS are Crown, some aren’t - assert agreement_info.crown_status is None - assert agreement_info.owner == 'NHS' - assert agreement_info.agreement_signed is None - assert agreement_info.as_human_readable == 'Can’t tell (organisation is NHS, crown status unknown)' - - -def test_get_valid_agreement_info_some_known_details(): - agreement_info = AgreementInfo("marinemanagement.org.uk") - assert agreement_info.crown_status is None - assert agreement_info.owner == "Marine Management Organisation" - assert agreement_info.agreement_signed is True - assert agreement_info.as_human_readable == ( - 'Yes, on behalf of Marine Management Organisation' - ) - - -def test_get_valid_local_agreement_info_some_known_details(): - # This example may need to be updated to use a different council if - # Babergh every sign the agreement - agreement_info = AgreementInfo("babergh.gov.uk") - assert agreement_info.crown_status is False - assert agreement_info.owner == "Babergh District Council" - assert agreement_info.agreement_signed is False - assert agreement_info.as_human_readable == ( - 'No (organisation is Babergh District Council, a non-crown body)' - ) - - -def test_get_valid_government_domain_gets_most_specific_first(): - - generic = AgreementInfo("gov.uk") - assert generic.crown_status is None - assert generic.owner is None - assert generic.agreement_signed is None - assert generic.as_human_readable == ( - 'Can’t tell (domain is gov.uk)' - ) - - specific = AgreementInfo("dacorum.gov.uk") - assert specific.crown_status is False - assert specific.owner == 'Dacorum Borough Council' - assert specific.agreement_signed is True - assert specific.as_human_readable == ( - 'Yes, on behalf of Dacorum Borough Council' - ) - - -def test_get_domain_info_for_branding_request(): - - assert AgreementInfo("gov.uk").as_info_for_branding_request == ( - 'Can’t tell (domain is gov.uk)' - ) - assert AgreementInfo("dacorum.gov.uk").as_info_for_branding_request == ( - 'Dacorum Borough Council' - ) - - -def test_domains_are_lowercased(): - for domain in AgreementInfo.domains.keys(): - assert domain == domain.lower() - - -def test_validate_government_domain_data(): - - for domain in AgreementInfo.domains.keys(): - - validate_email_address('test@{}'.format(domain)) - - agreement_info = AgreementInfo(domain) - - assert agreement_info.crown_status in { - True, False, None - } - - assert isinstance(agreement_info.owner, str) and agreement_info.owner.strip() - - assert agreement_info.agreement_signed in { - True, False, None - } - - -def test_domain_data_is_canonicalized(): - for owner, count in Counter( - AgreementInfo(domain).owner - for domain in AgreementInfo.domains.keys() - if AgreementInfo(domain).is_canonical - ).most_common(): - if count > 1: - raise ValueError( - '{} entries in domains.yml for {}'.format(count, owner) - ) - - -def test_validate_email_domain_data(): - - for domain in GovernmentEmailDomain.domains.keys(): - validate_email_address('test@{}'.format(domain)) - - @pytest.mark.parametrize('time, human_readable_datetime', [ ('2018-03-14 09:00', '14 March at 9:00am'), ('2018-03-14 15:00', '14 March at 3:00pm'),