mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-12 00:02:36 -05:00
436 lines
13 KiB
Python
436 lines
13 KiB
Python
import pytest
|
||
|
||
from notifications_utils.recipients import (
|
||
InvalidEmailError,
|
||
InvalidPhoneError,
|
||
allowed_to_send_to,
|
||
format_phone_number_human_readable,
|
||
format_recipient,
|
||
get_international_phone_info,
|
||
international_phone_info,
|
||
is_us_phone_number,
|
||
show_mangled_number_clues,
|
||
try_validate_and_format_phone_number,
|
||
validate_and_format_phone_number,
|
||
validate_email_address,
|
||
validate_phone_number,
|
||
)
|
||
|
||
valid_us_phone_numbers = [
|
||
"1-202-555-0104",
|
||
"+12025550104",
|
||
"12025550104",
|
||
"2025550104",
|
||
"(202) 555-0104",
|
||
]
|
||
|
||
# TODO
|
||
# International phone number tests are commented out as a result of issue #943 in notifications-admin. We are
|
||
# deliberately eliminating the ability to send to numbers outside of country code 1. These tests should
|
||
# be removed at some point when we are sure we are never going to support international numbers
|
||
|
||
valid_international_phone_numbers = [
|
||
# "+71234567890", # Russia
|
||
# "+447123456789", # UK
|
||
# "+4407123456789", # UK
|
||
# "+4407123 456789", # UK
|
||
# "+4407123-456-789", # UK
|
||
# "+23051234567", # Mauritius,
|
||
# "+682 12345", # Cook islands
|
||
# "+3312345678",
|
||
# "+9-2345-12345-12345", # 15 digits
|
||
]
|
||
|
||
|
||
valid_phone_numbers = valid_us_phone_numbers + valid_international_phone_numbers
|
||
|
||
|
||
invalid_us_phone_numbers = sum(
|
||
[
|
||
[(phone_number, error) for phone_number in group]
|
||
for error, group in [
|
||
(
|
||
"Too many digits",
|
||
(
|
||
"55512345678",
|
||
"+155512345678",
|
||
"(555) 1234-5678",
|
||
),
|
||
),
|
||
(
|
||
"Not enough digits",
|
||
(
|
||
"555123123",
|
||
"(555) 123-123",
|
||
"7890x32109",
|
||
"07123 ☟☜⬇⬆☞☝",
|
||
"07123☟☜⬇⬆☞☝",
|
||
),
|
||
),
|
||
("Phone number range is not in use", ("1555123123",)),
|
||
("Phone number is not possible", ("07123 456789...",)),
|
||
(
|
||
"The string supplied did not seem to be a phone number.",
|
||
(
|
||
'07";DROP TABLE;"',
|
||
"ALPHANUM3R1C",
|
||
),
|
||
),
|
||
]
|
||
],
|
||
[],
|
||
)
|
||
|
||
# TODO what is wrong with cook islands
|
||
invalid_phone_numbers = [
|
||
("+80233456789", "Not a valid country prefix"),
|
||
("1234567", "Not enough digits"),
|
||
# ("+682 1234", "Invalid country code"), # Cook Islands phone numbers can be 5 digits
|
||
("+12345 12345 12345 6", "Too many digits"),
|
||
]
|
||
|
||
|
||
valid_email_addresses = (
|
||
"email@domain.com",
|
||
"email@domain.COM",
|
||
"firstname.lastname@domain.com",
|
||
"firstname.o'lastname@domain.com",
|
||
"email@subdomain.domain.com",
|
||
"firstname+lastname@domain.com",
|
||
"1234567890@domain.com",
|
||
"email@domain-one.com",
|
||
"_______@domain.com",
|
||
"email@domain.name",
|
||
"email@domain.superlongtld",
|
||
"email@domain.co.jp",
|
||
"firstname-lastname@domain.com",
|
||
"info@german-financial-services.vermögensberatung",
|
||
"info@german-financial-services.reallylongarbitrarytldthatiswaytoohugejustincase",
|
||
"japanese-info@例え.テスト",
|
||
"email@double--hyphen.com",
|
||
)
|
||
invalid_email_addresses = (
|
||
"email@123.123.123.123",
|
||
"email@[123.123.123.123]",
|
||
"plainaddress",
|
||
"@no-local-part.com",
|
||
"Outlook Contact <outlook-contact@domain.com>",
|
||
"no-at.domain.com",
|
||
"no-tld@domain",
|
||
";beginning-semicolon@domain.co.uk",
|
||
"middle-semicolon@domain.co;uk",
|
||
"trailing-semicolon@domain.com;",
|
||
'"email+leading-quotes@domain.com',
|
||
'email+middle"-quotes@domain.com',
|
||
'"quoted-local-part"@domain.com',
|
||
'"quoted@domain.com"',
|
||
"lots-of-dots@domain..gov..uk",
|
||
"two-dots..in-local@domain.com",
|
||
"multiple@domains@domain.com",
|
||
"spaces in local@domain.com",
|
||
"spaces-in-domain@dom ain.com",
|
||
"underscores-in-domain@dom_ain.com",
|
||
"pipe-in-domain@example.com|gov.uk",
|
||
"comma,in-local@gov.uk",
|
||
"comma-in-domain@domain,gov.uk",
|
||
"pound-sign-in-local£@domain.com",
|
||
"local-with-’-apostrophe@domain.com",
|
||
"local-with-”-quotes@domain.com",
|
||
"domain-starts-with-a-dot@.domain.com",
|
||
"brackets(in)local@domain.com",
|
||
"email-too-long-{}@example.com".format("a" * 320),
|
||
"incorrect-punycode@xn---something.com",
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize("phone_number", valid_international_phone_numbers)
|
||
def test_detect_international_phone_numbers(phone_number):
|
||
assert is_us_phone_number(phone_number) is False
|
||
|
||
|
||
@pytest.mark.parametrize("phone_number", valid_us_phone_numbers)
|
||
def test_detect_us_phone_numbers(phone_number):
|
||
assert is_us_phone_number(phone_number) is True
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("phone_number", "expected_info"),
|
||
[
|
||
(
|
||
"+4407900900123",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="44", # UK
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"+4407700900123",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="44", # Number in TV range
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"+4407700800123",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="44", # UK Crown dependency, so prefix same as UK
|
||
billable_units=1,
|
||
),
|
||
),
|
||
( #
|
||
"+20-12-1234-1234",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="20", # Egypt
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"+201212341234",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="20", # Egypt
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"+1 664-491-3434",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="1664", # Montserrat
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"+71234567890",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="7", # Russia
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"1-202-555-0104",
|
||
international_phone_info(
|
||
international=False,
|
||
country_prefix="1", # USA
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"202-555-0104",
|
||
international_phone_info(
|
||
international=False,
|
||
country_prefix="1", # USA
|
||
billable_units=1,
|
||
),
|
||
),
|
||
(
|
||
"+23051234567",
|
||
international_phone_info(
|
||
international=True,
|
||
country_prefix="230", # Mauritius
|
||
billable_units=1,
|
||
),
|
||
),
|
||
],
|
||
)
|
||
def test_get_international_info(phone_number, expected_info):
|
||
assert get_international_phone_info(phone_number) == expected_info
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"phone_number",
|
||
[
|
||
"+21 4321 0987",
|
||
"+00997 1234 7890",
|
||
"+801234-7890",
|
||
"+8-0-1234-78901",
|
||
],
|
||
)
|
||
def test_get_international_info_raises(phone_number):
|
||
with pytest.raises(InvalidPhoneError) as error:
|
||
get_international_phone_info(phone_number)
|
||
assert str(error.value) == "Not a valid country prefix"
|
||
|
||
|
||
@pytest.mark.parametrize("phone_number", valid_us_phone_numbers)
|
||
@pytest.mark.parametrize(
|
||
"extra_args",
|
||
[
|
||
{},
|
||
{"international": False},
|
||
],
|
||
)
|
||
def test_phone_number_accepts_valid_values(extra_args, phone_number):
|
||
try:
|
||
validate_phone_number(phone_number, **extra_args)
|
||
except InvalidPhoneError:
|
||
pytest.fail("Unexpected InvalidPhoneError")
|
||
|
||
|
||
@pytest.mark.parametrize("phone_number", valid_phone_numbers)
|
||
def test_phone_number_accepts_valid_international_values(phone_number):
|
||
try:
|
||
validate_phone_number(phone_number, international=True)
|
||
except InvalidPhoneError:
|
||
pytest.fail("Unexpected InvalidPhoneError")
|
||
|
||
|
||
@pytest.mark.parametrize("phone_number", valid_us_phone_numbers)
|
||
def test_valid_us_phone_number_can_be_formatted_consistently(phone_number):
|
||
assert validate_and_format_phone_number(phone_number) == "+12025550104"
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("phone_number", "expected_formatted"),
|
||
[
|
||
("+44071234567890", "+4471234567890"),
|
||
("1-202-555-0104", "+12025550104"),
|
||
("+12025550104", "+12025550104"),
|
||
("12025550104", "+12025550104"),
|
||
("+23051234567", "+23051234567"),
|
||
],
|
||
)
|
||
def test_valid_international_phone_number_can_be_formatted_consistently(
|
||
phone_number, expected_formatted
|
||
):
|
||
assert (
|
||
validate_and_format_phone_number(phone_number, international=True)
|
||
== expected_formatted
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize(("phone_number", "error_message"), invalid_us_phone_numbers)
|
||
@pytest.mark.parametrize(
|
||
"extra_args",
|
||
[
|
||
{},
|
||
{"international": False},
|
||
],
|
||
)
|
||
def test_phone_number_rejects_invalid_values(extra_args, phone_number, error_message):
|
||
with pytest.raises(InvalidPhoneError) as e:
|
||
validate_phone_number(phone_number, **extra_args)
|
||
assert error_message == str(e.value)
|
||
|
||
|
||
@pytest.mark.parametrize(("phone_number", "error_message"), invalid_phone_numbers)
|
||
def test_phone_number_rejects_invalid_international_values(phone_number, error_message):
|
||
with pytest.raises(InvalidPhoneError) as e:
|
||
validate_phone_number(phone_number, international=True)
|
||
assert error_message == str(e.value)
|
||
|
||
|
||
def test_show_mangled_number_clues():
|
||
x = show_mangled_number_clues("848!!-202?-2020$$")
|
||
assert x == "XXX!!-XXX?-XXXX$$"
|
||
|
||
|
||
@pytest.mark.parametrize("email_address", valid_email_addresses)
|
||
def test_validate_email_address_accepts_valid(email_address):
|
||
try:
|
||
assert validate_email_address(email_address) == email_address
|
||
except InvalidEmailError:
|
||
pytest.fail("Unexpected InvalidEmailError")
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"email",
|
||
[
|
||
" email@domain.com ",
|
||
"\temail@domain.com",
|
||
"\temail@domain.com\n",
|
||
"\u200Bemail@domain.com\u200B",
|
||
],
|
||
)
|
||
def test_validate_email_address_strips_whitespace(email):
|
||
assert validate_email_address(email) == "email@domain.com"
|
||
|
||
|
||
@pytest.mark.parametrize("email_address", invalid_email_addresses)
|
||
def test_validate_email_address_raises_for_invalid(email_address):
|
||
with pytest.raises(InvalidEmailError) as e:
|
||
validate_email_address(email_address)
|
||
assert str(e.value) == "Not a valid email address"
|
||
|
||
|
||
@pytest.mark.parametrize("phone_number", valid_us_phone_numbers)
|
||
def test_validates_against_guestlist_of_phone_numbers(phone_number):
|
||
assert allowed_to_send_to(
|
||
phone_number, ["2025550104", "2025550105", "test@example.com"]
|
||
)
|
||
assert not allowed_to_send_to(
|
||
phone_number, ["2025550105", "2028675309", "test@example.com"]
|
||
)
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"recipient_number, allowlist_number",
|
||
[
|
||
["+4407123-456-789", "+4407123456789"],
|
||
["+4407123456789", "+4407123-456-789"],
|
||
],
|
||
)
|
||
def test_validates_against_guestlist_of_international_phone_numbers(
|
||
recipient_number, allowlist_number
|
||
):
|
||
assert allowed_to_send_to(recipient_number, [allowlist_number])
|
||
|
||
|
||
@pytest.mark.parametrize("email_address", valid_email_addresses)
|
||
def test_validates_against_guestlist_of_email_addresses(email_address):
|
||
assert not allowed_to_send_to(
|
||
email_address, ["very_special_and_unique@example.com"]
|
||
)
|
||
|
||
|
||
# TODO something wrong with formatting Egyptian numbers, doesn't seem
|
||
# like this would affect sendability need to confirm with AWS simulated numbers.
|
||
@pytest.mark.parametrize(
|
||
("phone_number", "expected_formatted"),
|
||
[
|
||
("+4407900900123", "+44 7900 900123"), # UK
|
||
("+44(0)7900900123", "+44 7900 900123"), # UK
|
||
("+447900900123", "+44 7900 900123"), # UK
|
||
# ("+20-12-1234-1234", "+20 121 234 1234"), # Egypt
|
||
# ("+201212341234", "+20 121 234 1234"), # Egypt
|
||
("+1 664 491-3434", "+1 664-491-3434"), # Montserrat
|
||
("+7 499 1231212", "+7 499 123-12-12"), # Moscow (Russia)
|
||
("1-202-555-0104", "(202) 555-0104"), # Washington DC (USA)
|
||
("+23051234567", "+230 5123 4567"), # Mauritius
|
||
("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France)
|
||
],
|
||
)
|
||
def test_format_us_and_international_phone_numbers(phone_number, expected_formatted):
|
||
assert format_phone_number_human_readable(phone_number) == expected_formatted
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("recipient", "expected_formatted"),
|
||
[
|
||
(True, ""),
|
||
(False, ""),
|
||
(0, ""),
|
||
(0.1, ""),
|
||
(None, ""),
|
||
("foo", "foo"),
|
||
("TeSt@ExAmPl3.com", "test@exampl3.com"),
|
||
("+4407900 900 123", "+447900900123"),
|
||
("+1 800 555 5555", "+18005555555"),
|
||
],
|
||
)
|
||
def test_format_recipient(recipient, expected_formatted):
|
||
assert format_recipient(recipient) == expected_formatted
|
||
|
||
|
||
def test_try_format_recipient_doesnt_throw():
|
||
assert try_validate_and_format_phone_number("ALPHANUM3R1C") == "ALPHANUM3R1C"
|
||
|
||
|
||
def test_format_phone_number_human_readable_doenst_throw():
|
||
assert format_phone_number_human_readable("ALPHANUM3R1C") == "ALPHANUM3R1C"
|