mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-21 14:59:26 -04:00
Compare commits
10 Commits
04-15-2025
...
add-second
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b742aec2b7 | ||
|
|
2457c13ef7 | ||
|
|
225d6fc090 | ||
|
|
f9e7885032 | ||
|
|
ef6b741dcb | ||
|
|
7bbb9c4552 | ||
|
|
1f56a37d56 | ||
|
|
02b7d662b0 | ||
|
|
a00b727c40 | ||
|
|
a2bce3c470 |
6
.github/workflows/checks.yml
vendored
6
.github/workflows/checks.yml
vendored
@@ -47,6 +47,8 @@ jobs:
|
||||
NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_EMAIL_TWO: ${{ secrets.NOTIFY_E2E_TEST_EMAIL_TWO }}
|
||||
NOTIFY_E2E_TEST_PASSWORD_TWO: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD_TWO }}
|
||||
|
||||
- name: Check imports alphabetized
|
||||
run: poetry run isort --check-only ./app ./tests
|
||||
@@ -62,6 +64,8 @@ jobs:
|
||||
NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_EMAIL_TWO: ${{ secrets.NOTIFY_E2E_TEST_EMAIL_TWO }}
|
||||
NOTIFY_E2E_TEST_PASSWORD_TWO: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD_TWO }}
|
||||
- name: Check coverage threshold
|
||||
# TODO get this back up to 95
|
||||
run: poetry run coverage report -m --fail-under=93
|
||||
@@ -132,6 +136,8 @@ jobs:
|
||||
NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_EMAIL_TWO: ${{ secrets.NOTIFY_E2E_TEST_EMAIL_TWO }}
|
||||
NOTIFY_E2E_TEST_PASSWORD_TWO: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD_TWO }}
|
||||
- name: Run server
|
||||
run: make run-flask &
|
||||
env:
|
||||
|
||||
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -61,6 +61,8 @@ jobs:
|
||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_EMAIL_TWO: ${{ secrets.NOTIFY_E2E_TEST_EMAIL_TWO }}
|
||||
NOTIFY_E2E_TEST_PASSWORD_TWO: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD_TWO }}
|
||||
LOGIN_DOT_GOV_REGISTRATION_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/set-up-your-profile&response_type=code&scope=openid+email&state=STATE"
|
||||
|
||||
with:
|
||||
@@ -77,6 +79,8 @@ jobs:
|
||||
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
||||
--var NOTIFY_E2E_TEST_EMAIL="$NOTIFY_E2E_TEST_EMAIL"
|
||||
--var NOTIFY_E2E_TEST_PASSWORD="$NOTIFY_E2E_TEST_PASSWORD"
|
||||
--var NOTIFY_E2E_TEST_EMAIL_TWO: "$NOTIFY_E2E_TEST_EMAIL_TWO"
|
||||
--var NOTIFY_E2E_TEST_PASSWORD_TWO: "$NOTIFY_E2E_TEST_PASSWORD_TWO"
|
||||
--var LOGIN_DOT_GOV_REGISTRATION_URL="$LOGIN_DOT_GOV_REGISTRATION_URL"
|
||||
--strategy rolling
|
||||
|
||||
@@ -89,6 +93,8 @@ jobs:
|
||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
NOTIFY_E2E_TEST_EMAIL_TWO: ${{ secrets.NOTIFY_E2E_TEST_EMAIL_TWO }}
|
||||
NOTIFY_E2E_TEST_PASSWORD_TWO: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD_TWO }}
|
||||
LOGIN_DOT_GOV_REGISTRATION_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/set-up-your-profile&response_type=code&scope=openid+email&state=STATE"
|
||||
with:
|
||||
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
|
||||
16
Makefile
16
Makefile
@@ -5,8 +5,7 @@ DATE = $(shell date +%Y-%m-%d:%H:%M:%S)
|
||||
APP_VERSION_FILE = app/version.py
|
||||
|
||||
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
|
||||
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
|
||||
GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "")
|
||||
GIT_COMMIT ?= $(shell git rev-parse HEAD)
|
||||
|
||||
## DEVELOPMENT
|
||||
|
||||
@@ -24,19 +23,6 @@ bootstrap: ## Set up everything to run the app
|
||||
createdb test_notification_api || true
|
||||
(poetry run flask db upgrade) || true
|
||||
|
||||
.PHONY: bootstrap-with-git-hooks
|
||||
bootstrap-with-git-hooks: ## Sets everything up and accounts for pre-existing git hooks
|
||||
make generate-version-file
|
||||
poetry self add poetry-dotenv-plugin
|
||||
poetry lock --no-update
|
||||
poetry install --sync --no-root
|
||||
git config --global --unset-all core.hooksPath
|
||||
poetry run pre-commit install
|
||||
git config --global core.hookspath "${GIT_HOOKS_PATH}"
|
||||
createdb notification_api || true
|
||||
createdb test_notification_api || true
|
||||
(poetry run flask db upgrade) || true
|
||||
|
||||
.PHONY: bootstrap-with-docker
|
||||
bootstrap-with-docker: ## Build the image to run the app in Docker
|
||||
docker build -f docker/Dockerfile -t notifications-api .
|
||||
|
||||
@@ -33,7 +33,7 @@ class DocumentDownloadClient:
|
||||
"document": file_contents,
|
||||
"is_csv": is_csv or False,
|
||||
},
|
||||
timeout=30
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
@@ -29,7 +29,7 @@ class PerformancePlatformClient:
|
||||
self.performance_platform_url + payload["dataType"],
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=30
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if resp.status_code == 200:
|
||||
|
||||
@@ -48,10 +48,7 @@ class AwsSnsClient(SmsClient):
|
||||
|
||||
def send_sms(self, to, content, reference, sender=None, international=False):
|
||||
matched = False
|
||||
if "+" not in to:
|
||||
to = f"+{to}"
|
||||
|
||||
for match in phonenumbers.PhoneNumberMatcher(to, None):
|
||||
for match in phonenumbers.PhoneNumberMatcher(to, "US"):
|
||||
matched = True
|
||||
to = phonenumbers.format_number(
|
||||
match.number, phonenumbers.PhoneNumberFormat.E164
|
||||
|
||||
@@ -35,7 +35,7 @@ def cronitor(task_name):
|
||||
params={
|
||||
"host": current_app.config["API_HOST_NAME"],
|
||||
},
|
||||
timeout=30
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Revision ID: 0216_remove_colours
|
||||
Revises: 0215_email_brand_type
|
||||
Create Date: 2018-08-24 13:36:49.346156
|
||||
"""
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Revision ID: 0219_default_email_branding
|
||||
Revises: 0216_remove_colours
|
||||
Create Date: 2018-08-24 13:36:49.346156
|
||||
"""
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy import text
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Revision ID: 0220_email_brand_type_non_null
|
||||
Revises: 0219_default_email_branding
|
||||
Create Date: 2018-08-24 13:36:49.346156
|
||||
"""
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Revision ID: 0221_nullable_service_branding
|
||||
Revises: 0220_email_brand_type_non_null
|
||||
Create Date: 2018-08-24 13:36:49.346156
|
||||
"""
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Revision ID: 0222_drop_service_branding
|
||||
Revises: 0221_nullable_service_branding
|
||||
Create Date: 2018-08-24 13:36:49.346156
|
||||
"""
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Revision ID: 0223_add_domain_constraint
|
||||
Revises: 0222_drop_service_branding
|
||||
Create Date: 2018-08-24 13:36:49.346156
|
||||
"""
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
|
||||
63
migrations/versions/0417_add_second_e2e_test_user.py
Normal file
63
migrations/versions/0417_add_second_e2e_test_user.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import datetime
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
from app import db
|
||||
from app.dao.users_dao import get_user_by_email
|
||||
from app.models import User
|
||||
from app.utils import utc_now
|
||||
from app.enums import AuthType
|
||||
|
||||
revision = "0417_add_second_e2e_test_user"
|
||||
down_revision = "0416_readd_e2e_test_user"
|
||||
|
||||
|
||||
def upgrade():
|
||||
email_address = os.getenv("NOTIFY_E2E_TEST_EMAIL_TWO")
|
||||
password = os.getenv("NOTIFY_E2E_TEST_PASSWORD_TWO")
|
||||
if not email_address or not password:
|
||||
raise ValueError(
|
||||
"Required variables [NOTIFY_E2E_TEST_EMAIL_TWO] and [NOTIFY_E2E_TEST_PASSWORD_TWO] missing!"
|
||||
)
|
||||
name = f"e2e_test_user_{uuid.uuid4()}"
|
||||
data = {
|
||||
"id": uuid.uuid4(),
|
||||
"name": name,
|
||||
"email_address": email_address,
|
||||
"password": password,
|
||||
"mobile_number": "+12025555555",
|
||||
"state": "active",
|
||||
"created_at": utc_now(),
|
||||
"password_changed_at": utc_now(),
|
||||
"failed_login_count": 0,
|
||||
"platform_admin": "f",
|
||||
"email_access_validated_at": utc_now(),
|
||||
"auth_type": AuthType.SMS,
|
||||
}
|
||||
conn = op.get_bind()
|
||||
|
||||
# delete the old user because
|
||||
delete_sql = f"""
|
||||
delete from users where email_address='{email_address}'
|
||||
"""
|
||||
|
||||
insert_sql = """
|
||||
insert into users (id, name, email_address, _password, mobile_number, state, created_at, password_changed_at, failed_login_count, platform_admin, email_access_validated_at, auth_type)
|
||||
values (:id, :name, :email_address, :password, :mobile_number, :state, :created_at, :password_changed_at, :failed_login_count, :platform_admin, :email_access_validated_at, :auth_type)
|
||||
"""
|
||||
|
||||
conn.execute(sa.text(delete_sql))
|
||||
|
||||
conn.execute(sa.text(insert_sql), data)
|
||||
|
||||
|
||||
def downgrade():
|
||||
email_address = os.getenv("NOTIFY_E2E_TEST_EMAIL_TWO")
|
||||
user_to_delete = get_user_by_email(email_address)
|
||||
if not user_to_delete:
|
||||
return
|
||||
db.session.remove(user_to_delete)
|
||||
db.session.commit()
|
||||
@@ -12,15 +12,15 @@ from notifications_utils.sanitise_text import SanitiseSMS
|
||||
from . import email_with_smart_quotes_regex
|
||||
|
||||
OBSCURE_ZERO_WIDTH_WHITESPACE = (
|
||||
"\u180E" # Mongolian vowel separator
|
||||
"\u200B" # zero width space
|
||||
"\u200C" # zero width non-joiner
|
||||
"\u200D" # zero width joiner
|
||||
"\u180e" # Mongolian vowel separator
|
||||
"\u200b" # zero width space
|
||||
"\u200c" # zero width non-joiner
|
||||
"\u200d" # zero width joiner
|
||||
"\u2060" # word joiner
|
||||
"\uFEFF" # zero width non-breaking space
|
||||
"\ufeff" # zero width non-breaking space
|
||||
)
|
||||
|
||||
OBSCURE_FULL_WIDTH_WHITESPACE = "\u00A0" # non breaking space
|
||||
OBSCURE_FULL_WIDTH_WHITESPACE = "\u00a0" # non breaking space
|
||||
|
||||
ALL_WHITESPACE = (
|
||||
string.whitespace + OBSCURE_ZERO_WIDTH_WHITESPACE + OBSCURE_FULL_WIDTH_WHITESPACE
|
||||
@@ -61,7 +61,7 @@ more_than_two_newlines_in_a_row = re.compile(r"\n{3,}")
|
||||
def unlink_govuk_escaped(message):
|
||||
return re.sub(
|
||||
govuk_not_a_link,
|
||||
r"\1\2\3" + ".\u200B" + r"\4", # Unicode zero-width space
|
||||
r"\1\2\3" + ".\u200b" + r"\4", # Unicode zero-width space
|
||||
message,
|
||||
)
|
||||
|
||||
|
||||
@@ -628,6 +628,8 @@ def validate_phone_number(number, international=False):
|
||||
|
||||
try:
|
||||
parsed = phonenumbers.parse(number, None)
|
||||
if parsed.country_code != 1:
|
||||
raise InvalidPhoneError("Invalid country code")
|
||||
number = f"{parsed.country_code}{parsed.national_number}"
|
||||
if len(number) < 8:
|
||||
raise InvalidPhoneError("Not enough digits")
|
||||
|
||||
@@ -15,13 +15,13 @@ class SanitiseText:
|
||||
"’": "'", # RIGHT SINGLE QUOTATION MARK (U+2019)
|
||||
"“": '"', # LEFT DOUBLE QUOTATION MARK (U+201C)
|
||||
"”": '"', # RIGHT DOUBLE QUOTATION MARK (U+201D)
|
||||
"\u180E": "", # Mongolian vowel separator
|
||||
"\u200B": "", # zero width space
|
||||
"\u200C": "", # zero width non-joiner
|
||||
"\u200D": "", # zero width joiner
|
||||
"\u180e": "", # Mongolian vowel separator
|
||||
"\u200b": "", # zero width space
|
||||
"\u200c": "", # zero width non-joiner
|
||||
"\u200d": "", # zero width joiner
|
||||
"\u2060": "", # word joiner
|
||||
"\uFEFF": "", # zero width non-breaking space
|
||||
"\u00A0": " ", # NON BREAKING WHITE SPACE (U+200B)
|
||||
"\ufeff": "", # zero width non-breaking space
|
||||
"\u00a0": " ", # NON BREAKING WHITE SPACE (U+200B)
|
||||
"\t": " ", # TAB
|
||||
}
|
||||
|
||||
|
||||
829
poetry.lock
generated
829
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ cachetools = "==5.4.0"
|
||||
celery = {version = "==5.4.0", extras = ["redis"]}
|
||||
certifi = ">=2022.12.7"
|
||||
cffi = "==1.17.1"
|
||||
charset-normalizer = "^3.4.1"
|
||||
charset-normalizer = "^3.1.0"
|
||||
click = "==8.1.7"
|
||||
click-datetime = "==0.4.0"
|
||||
click-didyoumean = "==0.3.1"
|
||||
@@ -52,11 +52,11 @@ faker = "^26.0.0"
|
||||
async-timeout = "^5.0.1"
|
||||
bleach = "^6.1.0"
|
||||
geojson = "^3.2.0"
|
||||
numpy = "^2.2.4"
|
||||
numpy = "^2.2.3"
|
||||
ordered-set = "^4.1.0"
|
||||
phonenumbers = "^8.13.42"
|
||||
python-json-logger = "^2.0.7"
|
||||
regex = "^2024.11.6"
|
||||
regex = "^2024.7.24"
|
||||
shapely = "^2.0.5"
|
||||
smartypants = "^2.0.1"
|
||||
mistune = "^3.1.3"
|
||||
@@ -84,8 +84,8 @@ bandit = "*"
|
||||
black = "^25.1.0"
|
||||
cloudfoundry-client = "*"
|
||||
exceptiongroup = "==1.2.2"
|
||||
flake8 = "^7.2.0"
|
||||
flake8-bugbear = "^24.12.12"
|
||||
flake8 = "^7.1.1"
|
||||
flake8-bugbear = "^24.1.17"
|
||||
freezegun = "^1.5.1"
|
||||
honcho = "*"
|
||||
isort = "^5.13.2"
|
||||
|
||||
@@ -5,7 +5,7 @@ from app import aws_sns_client
|
||||
|
||||
def test_send_sms_successful_returns_aws_sns_response(notify_api, mocker):
|
||||
boto_mock = mocker.patch.object(aws_sns_client, "_client", create=True)
|
||||
to = "16135555555"
|
||||
to = "6135555555"
|
||||
content = reference = "foo"
|
||||
with notify_api.app_context():
|
||||
aws_sns_client.send_sms(to, content, reference)
|
||||
|
||||
@@ -278,7 +278,7 @@ def test_send_notification_to_queue_throws_exception_deletes_notification(
|
||||
with pytest.raises(Boto3Error):
|
||||
send_notification_to_queue(sample_notification, False)
|
||||
mocked.assert_called_once_with(
|
||||
[(str(sample_notification.id))], queue="send-sms-tasks", countdown=60
|
||||
[str(sample_notification.id)], queue="send-sms-tasks", countdown=60
|
||||
)
|
||||
|
||||
assert _get_notification_query_count() == 0
|
||||
|
||||
@@ -75,7 +75,7 @@ def test_create_invited_org_user(
|
||||
# assert len(notification.personalisation["url"]) > len(expected_start_of_invite_url)
|
||||
|
||||
mocked.assert_called_once_with(
|
||||
[(str(notification.id))], queue="notify-internal-tasks", countdown=60
|
||||
[str(notification.id)], queue="notify-internal-tasks", countdown=60
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ def test_create_invited_user(
|
||||
)
|
||||
|
||||
mocked.assert_called_once_with(
|
||||
[(str(notification.id))], queue="notify-internal-tasks", countdown=60
|
||||
[str(notification.id)], queue="notify-internal-tasks", countdown=60
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ def test_what_will_trigger_conditional_placeholder(value):
|
||||
"list: ",
|
||||
),
|
||||
(
|
||||
{"placeholder": [" ", " \t ", "\u180E"]},
|
||||
{"placeholder": [" ", " \t ", "\u180e"]},
|
||||
"list: ",
|
||||
"list: ",
|
||||
),
|
||||
|
||||
@@ -66,7 +66,7 @@ def test_HTML_template_has_URLs_replaced_with_links():
|
||||
|
||||
def test_escaping_govuk_in_email_templates():
|
||||
template_content = "GOV.UK"
|
||||
expected = "GOV.\u200BUK"
|
||||
expected = "GOV.\u200bUK"
|
||||
assert unlink_govuk_escaped(template_content) == expected
|
||||
template_json = {
|
||||
"content": template_content,
|
||||
@@ -81,23 +81,23 @@ def test_escaping_govuk_in_email_templates():
|
||||
("template_content", "expected"),
|
||||
[
|
||||
# Cases that we add the breaking space
|
||||
("GOV.UK", "GOV.\u200BUK"),
|
||||
("gov.uk", "gov.\u200Buk"),
|
||||
("GOV.UK", "GOV.\u200bUK"),
|
||||
("gov.uk", "gov.\u200buk"),
|
||||
(
|
||||
"content with space infront GOV.UK",
|
||||
"content with space infront GOV.\u200BUK",
|
||||
"content with space infront GOV.\u200bUK",
|
||||
),
|
||||
("content with tab infront\tGOV.UK", "content with tab infront\tGOV.\u200BUK"),
|
||||
("content with tab infront\tGOV.UK", "content with tab infront\tGOV.\u200bUK"),
|
||||
(
|
||||
"content with newline infront\nGOV.UK",
|
||||
"content with newline infront\nGOV.\u200BUK",
|
||||
"content with newline infront\nGOV.\u200bUK",
|
||||
),
|
||||
("*GOV.UK", "*GOV.\u200BUK"),
|
||||
("#GOV.UK", "#GOV.\u200BUK"),
|
||||
("^GOV.UK", "^GOV.\u200BUK"),
|
||||
(" #GOV.UK", " #GOV.\u200BUK"),
|
||||
("GOV.UK with CONTENT after", "GOV.\u200BUK with CONTENT after"),
|
||||
("#GOV.UK with CONTENT after", "#GOV.\u200BUK with CONTENT after"),
|
||||
("*GOV.UK", "*GOV.\u200bUK"),
|
||||
("#GOV.UK", "#GOV.\u200bUK"),
|
||||
("^GOV.UK", "^GOV.\u200bUK"),
|
||||
(" #GOV.UK", " #GOV.\u200bUK"),
|
||||
("GOV.UK with CONTENT after", "GOV.\u200bUK with CONTENT after"),
|
||||
("#GOV.UK with CONTENT after", "#GOV.\u200bUK with CONTENT after"),
|
||||
# Cases that we don't add the breaking space
|
||||
("https://gov.uk", "https://gov.uk"),
|
||||
("https://www.gov.uk", "https://www.gov.uk"),
|
||||
@@ -366,7 +366,7 @@ def test_unicode_dash_lookup():
|
||||
"""
|
||||
\t bar
|
||||
""",
|
||||
" \u180E\u200B \u200C bar \u200D \u2060\uFEFF ",
|
||||
" \u180e\u200b \u200c bar \u200d \u2060\ufeff ",
|
||||
],
|
||||
)
|
||||
def test_strip_all_whitespace(value):
|
||||
@@ -378,7 +378,7 @@ def test_strip_all_whitespace(value):
|
||||
[
|
||||
"notifications-email",
|
||||
" \tnotifications-email \x0c ",
|
||||
"\rn\u200Coti\u200Dfi\u200Bcati\u2060ons-\u180Eemai\uFEFFl\uFEFF",
|
||||
"\rn\u200coti\u200dfi\u200bcati\u2060ons-\u180eemai\ufeffl\ufeff",
|
||||
],
|
||||
)
|
||||
def test_strip_and_remove_obscure_whitespace(value):
|
||||
@@ -421,12 +421,12 @@ def test_strip_unsupported_characters():
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
"\u200C Your tax is\ndue\n\n",
|
||||
"\u200c Your tax is\ndue\n\n",
|
||||
" Your tax is due ",
|
||||
# Non breaking spaces replaced by single spaces
|
||||
"\u00A0Your\u00A0tax\u00A0 is\u00A0\u00A0due\u00A0",
|
||||
"\u00a0Your\u00a0tax\u00a0 is\u00a0\u00a0due\u00a0",
|
||||
# zero width spaces are removed
|
||||
"\u180EYour \u200Btax\u200C is \u200D\u2060due \uFEFF",
|
||||
"\u180eYour \u200btax\u200c is \u200d\u2060due \ufeff",
|
||||
# tabs are replaced by single spaces
|
||||
"\tYour tax\tis due ",
|
||||
],
|
||||
|
||||
@@ -559,7 +559,7 @@ def test_normalise_postcode(postcode, normalised_postcode):
|
||||
("N5 1AA", True),
|
||||
("SO14 6WB", True),
|
||||
("so14 6wb", True),
|
||||
("so14\u00A06wb", True),
|
||||
("so14\u00a06wb", True),
|
||||
# invalida / incomplete postcodes
|
||||
("N5", False),
|
||||
("SO144 6WB", False),
|
||||
@@ -600,7 +600,7 @@ def test_if_postcode_is_a_real_uk_postcode_normalises_before_checking_postcode(m
|
||||
("N5 3EF", "N5 3EF"),
|
||||
("N53EF ", "N5 3EF"),
|
||||
("n53Ef", "N5 3EF"),
|
||||
("n5 \u00A0 \t 3Ef", "N5 3EF"),
|
||||
("n5 \u00a0 \t 3Ef", "N5 3EF"),
|
||||
("SO146WB", "SO14 6WB"),
|
||||
("BFPO2", "BFPO 2"),
|
||||
("BFPO232", "BFPO 232"),
|
||||
|
||||
@@ -628,8 +628,6 @@ def test_bad_or_missing_data(
|
||||
assert recipients.has_errors is True
|
||||
|
||||
|
||||
# TODO, original test for number one had {0, 1, 2}, but it has morphed to {0, 1}
|
||||
# Is +447900123 legit or not? What changed?
|
||||
@pytest.mark.parametrize(
|
||||
("file_contents", "rows_with_bad_recipients"),
|
||||
[
|
||||
@@ -640,7 +638,7 @@ def test_bad_or_missing_data(
|
||||
1234
|
||||
+447900123
|
||||
""",
|
||||
{0, 1},
|
||||
{0, 1, 2},
|
||||
),
|
||||
(
|
||||
"""
|
||||
@@ -649,7 +647,7 @@ def test_bad_or_missing_data(
|
||||
+12022340104, USA
|
||||
+23051234567, Mauritius
|
||||
""",
|
||||
set(),
|
||||
{2},
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -879,14 +877,14 @@ def test_ignores_spaces_and_case_in_placeholders(key, expected):
|
||||
("\n", None), # newline
|
||||
("\r", None), # carriage return
|
||||
("\t", None), # tab
|
||||
("\u180E", "MONGOLIAN VOWEL SEPARATOR"),
|
||||
("\u200B", "ZERO WIDTH SPACE"),
|
||||
("\u200C", "ZERO WIDTH NON-JOINER"),
|
||||
("\u200D", "ZERO WIDTH JOINER"),
|
||||
("\u180e", "MONGOLIAN VOWEL SEPARATOR"),
|
||||
("\u200b", "ZERO WIDTH SPACE"),
|
||||
("\u200c", "ZERO WIDTH NON-JOINER"),
|
||||
("\u200d", "ZERO WIDTH JOINER"),
|
||||
("\u2060", "WORD JOINER"),
|
||||
("\uFEFF", "ZERO WIDTH NO-BREAK SPACE"),
|
||||
("\ufeff", "ZERO WIDTH NO-BREAK SPACE"),
|
||||
# all the things
|
||||
(" \n\r\t\u000A\u000D\u180E\u200B\u200C\u200D\u2060\uFEFF", None),
|
||||
(" \n\r\t\u000a\u000d\u180e\u200b\u200c\u200d\u2060\ufeff", None),
|
||||
],
|
||||
)
|
||||
def test_ignores_leading_whitespace_in_file(character, name):
|
||||
|
||||
@@ -24,16 +24,21 @@ valid_us_phone_numbers = [
|
||||
"(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
|
||||
# "+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
|
||||
]
|
||||
|
||||
|
||||
@@ -80,7 +85,7 @@ invalid_us_phone_numbers = sum(
|
||||
invalid_phone_numbers = [
|
||||
("+80233456789", "Not a valid country prefix"),
|
||||
("1234567", "Not enough digits"),
|
||||
("+682 1234", "Not enough digits"), # Cook Islands phone numbers are 5 digits
|
||||
("+682 1234", "Invalid country code"), # Cook Islands phone numbers can be 5 digits
|
||||
("+12345 12345 12345 6", "Too many digits"),
|
||||
]
|
||||
|
||||
@@ -151,46 +156,46 @@ def test_detect_us_phone_numbers(phone_number):
|
||||
@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,
|
||||
),
|
||||
),
|
||||
# (
|
||||
# "+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(
|
||||
@@ -199,14 +204,14 @@ def test_detect_us_phone_numbers(phone_number):
|
||||
billable_units=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"+71234567890",
|
||||
international_phone_info(
|
||||
international=True,
|
||||
country_prefix="7", # Russia
|
||||
billable_units=1,
|
||||
),
|
||||
),
|
||||
# (
|
||||
# "+71234567890",
|
||||
# international_phone_info(
|
||||
# international=True,
|
||||
# country_prefix="7", # Russia
|
||||
# billable_units=1,
|
||||
# ),
|
||||
# ),
|
||||
(
|
||||
"1-202-555-0104",
|
||||
international_phone_info(
|
||||
@@ -223,14 +228,14 @@ def test_detect_us_phone_numbers(phone_number):
|
||||
billable_units=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"+23051234567",
|
||||
international_phone_info(
|
||||
international=True,
|
||||
country_prefix="230", # Mauritius
|
||||
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):
|
||||
@@ -283,11 +288,11 @@ def test_valid_us_phone_number_can_be_formatted_consistently(phone_number):
|
||||
@pytest.mark.parametrize(
|
||||
("phone_number", "expected_formatted"),
|
||||
[
|
||||
("+44071234567890", "+4471234567890"),
|
||||
# ("+44071234567890", "+4471234567890"),
|
||||
("1-202-555-0104", "+12025550104"),
|
||||
("+12025550104", "+12025550104"),
|
||||
("12025550104", "+12025550104"),
|
||||
("+23051234567", "+23051234567"),
|
||||
# ("+23051234567", "+23051234567"),
|
||||
],
|
||||
)
|
||||
def test_valid_international_phone_number_can_be_formatted_consistently(
|
||||
@@ -339,7 +344,7 @@ def test_validate_email_address_accepts_valid(email_address):
|
||||
" email@domain.com ",
|
||||
"\temail@domain.com",
|
||||
"\temail@domain.com\n",
|
||||
"\u200Bemail@domain.com\u200B",
|
||||
"\u200bemail@domain.com\u200b",
|
||||
],
|
||||
)
|
||||
def test_validate_email_address_strips_whitespace(email):
|
||||
@@ -363,17 +368,17 @@ def test_validates_against_guestlist_of_phone_numbers(phone_number):
|
||||
)
|
||||
|
||||
|
||||
@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(
|
||||
# "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)
|
||||
@@ -383,21 +388,19 @@ def test_validates_against_guestlist_of_email_addresses(email_address):
|
||||
)
|
||||
|
||||
|
||||
# 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
|
||||
# ("+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)
|
||||
# ("+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)
|
||||
# ("+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):
|
||||
@@ -414,7 +417,7 @@ def test_format_us_and_international_phone_numbers(phone_number, expected_format
|
||||
(None, ""),
|
||||
("foo", "foo"),
|
||||
("TeSt@ExAmPl3.com", "test@exampl3.com"),
|
||||
("+4407900 900 123", "+447900900123"),
|
||||
# ("+4407900 900 123", "+447900900123"),
|
||||
("+1 800 555 5555", "+18005555555"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ params, ids = zip(
|
||||
("…", "..."),
|
||||
"compatibility transform unicode char (HORIZONTAL ELLIPSIS (U+2026)",
|
||||
),
|
||||
(("\u200B", ""), "compatibility transform unicode char (ZERO WIDTH SPACE (U+200B)"),
|
||||
(("\u200b", ""), "compatibility transform unicode char (ZERO WIDTH SPACE (U+200B)"),
|
||||
(
|
||||
("‘", "'"),
|
||||
"compatibility transform unicode char (LEFT SINGLE QUOTATION MARK (U+2018)",
|
||||
|
||||
Reference in New Issue
Block a user