mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-26 17:23:50 -04:00
Compare commits
51 Commits
add-second
...
dac09809f5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dac09809f5 | ||
|
|
9f0b6b0ca1 | ||
|
|
7dd1751556 | ||
|
|
3ae7749d0b | ||
|
|
f6cc4afab7 | ||
|
|
6325849855 | ||
|
|
b564a8800e | ||
|
|
43247e81bb | ||
|
|
7f2325ea5e | ||
|
|
bcd18ac936 | ||
|
|
7fc65dd4f5 | ||
|
|
276446c57e | ||
|
|
bc26231164 | ||
|
|
8c80f2a462 | ||
|
|
4ccfc82c8c | ||
|
|
ebd0ed9365 | ||
|
|
02becbd1e5 | ||
|
|
419d6cee69 | ||
|
|
71e8f20b04 | ||
|
|
db90cecd65 | ||
|
|
e93e3f3690 | ||
|
|
d1fab496f4 | ||
|
|
1f4b408d1d | ||
|
|
f15d6128dc | ||
|
|
3ad7a3b2fd | ||
|
|
aea7249124 | ||
|
|
bf3897729c | ||
|
|
8181eda049 | ||
|
|
e3d2e75182 | ||
|
|
22085aa334 | ||
|
|
ffdc0edc4e | ||
|
|
a236df97bf | ||
|
|
2f220a17a5 | ||
|
|
37691f504e | ||
|
|
a1273d7f01 | ||
|
|
0c65b50043 | ||
|
|
9e36418271 | ||
|
|
6f524cc907 | ||
|
|
b37c803edc | ||
|
|
9e7f747675 | ||
|
|
0af9cac583 | ||
|
|
1cc1d9a58d | ||
|
|
9174978e3e | ||
|
|
f8726ca6b7 | ||
|
|
7d54322846 | ||
|
|
7aaa36e630 | ||
|
|
f87a54fffe | ||
|
|
0792ea123f | ||
|
|
28444a460b | ||
|
|
e9ea22ad6d | ||
|
|
16c68e06b9 |
6
.github/workflows/checks.yml
vendored
6
.github/workflows/checks.yml
vendored
@@ -47,8 +47,6 @@ jobs:
|
|||||||
NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }}
|
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_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }}
|
||||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
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
|
- name: Check imports alphabetized
|
||||||
run: poetry run isort --check-only ./app ./tests
|
run: poetry run isort --check-only ./app ./tests
|
||||||
@@ -64,8 +62,6 @@ jobs:
|
|||||||
NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }}
|
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_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }}
|
||||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
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
|
- name: Check coverage threshold
|
||||||
# TODO get this back up to 95
|
# TODO get this back up to 95
|
||||||
run: poetry run coverage report -m --fail-under=93
|
run: poetry run coverage report -m --fail-under=93
|
||||||
@@ -136,8 +132,6 @@ jobs:
|
|||||||
NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD }}
|
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_HTTP_AUTH_USER: ${{ secrets.NOTIFY_E2E_TEST_HTTP_AUTH_USER }}
|
||||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
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
|
- name: Run server
|
||||||
run: make run-flask &
|
run: make run-flask &
|
||||||
env:
|
env:
|
||||||
|
|||||||
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -61,8 +61,6 @@ jobs:
|
|||||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
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"
|
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:
|
with:
|
||||||
@@ -79,8 +77,6 @@ jobs:
|
|||||||
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
--var NEW_RELIC_LICENSE_KEY="$NEW_RELIC_LICENSE_KEY"
|
||||||
--var NOTIFY_E2E_TEST_EMAIL="$NOTIFY_E2E_TEST_EMAIL"
|
--var NOTIFY_E2E_TEST_EMAIL="$NOTIFY_E2E_TEST_EMAIL"
|
||||||
--var NOTIFY_E2E_TEST_PASSWORD="$NOTIFY_E2E_TEST_PASSWORD"
|
--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"
|
--var LOGIN_DOT_GOV_REGISTRATION_URL="$LOGIN_DOT_GOV_REGISTRATION_URL"
|
||||||
--strategy rolling
|
--strategy rolling
|
||||||
|
|
||||||
@@ -93,8 +89,6 @@ jobs:
|
|||||||
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
NEW_RELIC_LICENSE_KEY: ${{ secrets.NEW_RELIC_LICENSE_KEY }}
|
||||||
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
NOTIFY_E2E_TEST_EMAIL: ${{ secrets.NOTIFY_E2E_TEST_EMAIL }}
|
||||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
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"
|
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:
|
with:
|
||||||
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||||
|
|||||||
18
Makefile
18
Makefile
@@ -5,7 +5,8 @@ DATE = $(shell date +%Y-%m-%d:%H:%M:%S)
|
|||||||
APP_VERSION_FILE = app/version.py
|
APP_VERSION_FILE = app/version.py
|
||||||
|
|
||||||
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
|
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
|
||||||
GIT_COMMIT ?= $(shell git rev-parse HEAD)
|
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
|
||||||
|
GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "")
|
||||||
|
|
||||||
## DEVELOPMENT
|
## DEVELOPMENT
|
||||||
|
|
||||||
@@ -23,6 +24,19 @@ bootstrap: ## Set up everything to run the app
|
|||||||
createdb test_notification_api || true
|
createdb test_notification_api || true
|
||||||
(poetry run flask db upgrade) || 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
|
.PHONY: bootstrap-with-docker
|
||||||
bootstrap-with-docker: ## Build the image to run the app in Docker
|
bootstrap-with-docker: ## Build the image to run the app in Docker
|
||||||
docker build -f docker/Dockerfile -t notifications-api .
|
docker build -f docker/Dockerfile -t notifications-api .
|
||||||
@@ -50,7 +64,7 @@ too-complex:
|
|||||||
poetry run radon cc ./app -a -nc
|
poetry run radon cc ./app -a -nc
|
||||||
|
|
||||||
.PHONY: run-flask
|
.PHONY: run-flask
|
||||||
run-flask: ## Run flask
|
run-flask:
|
||||||
poetry run newrelic-admin run-program flask run -p 6011 --host=0.0.0.0
|
poetry run newrelic-admin run-program flask run -p 6011 --host=0.0.0.0
|
||||||
|
|
||||||
.PHONY: run-celery
|
.PHONY: run-celery
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from flask import current_app, g, has_request_context, jsonify, make_response, r
|
|||||||
from flask.ctx import has_app_context
|
from flask.ctx import has_app_context
|
||||||
from flask_marshmallow import Marshmallow
|
from flask_marshmallow import Marshmallow
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
|
from flask_socketio import SocketIO
|
||||||
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
|
||||||
from sqlalchemy import event
|
from sqlalchemy import event
|
||||||
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
|
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
|
||||||
@@ -94,6 +95,14 @@ zendesk_client = ZendeskClient()
|
|||||||
redis_store = RedisClient()
|
redis_store = RedisClient()
|
||||||
document_download_client = DocumentDownloadClient()
|
document_download_client = DocumentDownloadClient()
|
||||||
|
|
||||||
|
socketio = SocketIO(
|
||||||
|
cors_allowed_origins=[
|
||||||
|
config.Config.ADMIN_BASE_URL,
|
||||||
|
],
|
||||||
|
message_queue=config.Config.REDIS_URL,
|
||||||
|
logger=True,
|
||||||
|
engineio_logger=True,
|
||||||
|
)
|
||||||
|
|
||||||
notification_provider_clients = NotificationProviderClients()
|
notification_provider_clients = NotificationProviderClients()
|
||||||
|
|
||||||
@@ -111,6 +120,11 @@ def create_app(application):
|
|||||||
application.config["NOTIFY_APP_NAME"] = application.name
|
application.config["NOTIFY_APP_NAME"] = application.name
|
||||||
init_app(application)
|
init_app(application)
|
||||||
|
|
||||||
|
socketio.init_app(application)
|
||||||
|
|
||||||
|
from app.socket_handlers import register_socket_handlers
|
||||||
|
|
||||||
|
register_socket_handlers(socketio)
|
||||||
request_helper.init_app(application)
|
request_helper.init_app(application)
|
||||||
db.init_app(application)
|
db.init_app(application)
|
||||||
migrate.init_app(application, db=db)
|
migrate.init_app(application, db=db)
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ class AwsSnsClient(SmsClient):
|
|||||||
|
|
||||||
def send_sms(self, to, content, reference, sender=None, international=False):
|
def send_sms(self, to, content, reference, sender=None, international=False):
|
||||||
matched = False
|
matched = False
|
||||||
for match in phonenumbers.PhoneNumberMatcher(to, "US"):
|
if "+" not in to:
|
||||||
|
to = f"+{to}"
|
||||||
|
|
||||||
|
for match in phonenumbers.PhoneNumberMatcher(to, None):
|
||||||
matched = True
|
matched = True
|
||||||
to = phonenumbers.format_number(
|
to = phonenumbers.format_number(
|
||||||
match.number, phonenumbers.PhoneNumberFormat.E164
|
match.number, phonenumbers.PhoneNumberFormat.E164
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ class Config(object):
|
|||||||
S3_RESOURCE = session.resource("s3", config=AWS_CLIENT_CONFIG)
|
S3_RESOURCE = session.resource("s3", config=AWS_CLIENT_CONFIG)
|
||||||
|
|
||||||
CELERY = {
|
CELERY = {
|
||||||
|
"broker_connection_retry_on_startup": True,
|
||||||
"worker_max_tasks_per_child": 500,
|
"worker_max_tasks_per_child": 500,
|
||||||
"task_ignore_result": True,
|
"task_ignore_result": True,
|
||||||
"result_persistent": False,
|
"result_persistent": False,
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ from werkzeug.datastructures import MultiDict
|
|||||||
from app import create_uuid, db
|
from app import create_uuid, db
|
||||||
from app.dao.dao_utils import autocommit
|
from app.dao.dao_utils import autocommit
|
||||||
from app.dao.inbound_sms_dao import Pagination
|
from app.dao.inbound_sms_dao import Pagination
|
||||||
|
from app.dao.jobs_dao import dao_get_job_by_id
|
||||||
from app.enums import KeyType, NotificationStatus, NotificationType
|
from app.enums import KeyType, NotificationStatus, NotificationType
|
||||||
from app.models import FactNotificationStatus, Notification, NotificationHistory
|
from app.models import FactNotificationStatus, Notification, NotificationHistory
|
||||||
from app.utils import (
|
from app.utils import (
|
||||||
|
emit_job_update_summary,
|
||||||
escape_special_characters,
|
escape_special_characters,
|
||||||
get_midnight_in_utc,
|
get_midnight_in_utc,
|
||||||
midnight_n_days_ago,
|
midnight_n_days_ago,
|
||||||
@@ -895,6 +897,19 @@ def dao_update_delivery_receipts(receipts, delivered):
|
|||||||
f"#loadtestperformance batch update query time: \
|
f"#loadtestperformance batch update query time: \
|
||||||
updated {len(receipts)} notification in {elapsed_time} ms"
|
updated {len(receipts)} notification in {elapsed_time} ms"
|
||||||
)
|
)
|
||||||
|
job_ids = (
|
||||||
|
db.session.execute(
|
||||||
|
select(Notification.job_id).where(
|
||||||
|
Notification.message_id.in_(id_to_carrier.keys())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.scalars()
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
for job_id in set(job_ids):
|
||||||
|
job = dao_get_job_by_id(job_id)
|
||||||
|
emit_job_update_summary(job)
|
||||||
|
|
||||||
|
|
||||||
def dao_close_out_delivery_receipts():
|
def dao_close_out_delivery_receipts():
|
||||||
|
|||||||
@@ -105,6 +105,17 @@ def send_sms_to_provider(notification):
|
|||||||
# The future home of the validation is TBD
|
# The future home of the validation is TBD
|
||||||
_experimentally_validate_phone_numbers(recipient)
|
_experimentally_validate_phone_numbers(recipient)
|
||||||
|
|
||||||
|
# TODO current we allow US phone numbers to be uploaded without the country code (1)
|
||||||
|
# This will break certain international phone numbers (Norway, Denmark, East Timor)
|
||||||
|
# When we officially announce support for international numbers, US numbers must contain
|
||||||
|
# their country code.
|
||||||
|
recipient = str(recipient)
|
||||||
|
if len(recipient) == 10:
|
||||||
|
if os.getenv("NOTIFY_ENVIRONMENT") not in [
|
||||||
|
"test"
|
||||||
|
]: # we want to test intl support
|
||||||
|
recipient = f"1{recipient}"
|
||||||
|
|
||||||
sender_numbers = get_sender_numbers(notification)
|
sender_numbers = get_sender_numbers(notification)
|
||||||
if notification.reply_to_text not in sender_numbers:
|
if notification.reply_to_text not in sender_numbers:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
16
app/socket_handlers.py
Normal file
16
app/socket_handlers.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from flask import current_app, request
|
||||||
|
from flask_socketio import join_room, leave_room
|
||||||
|
|
||||||
|
|
||||||
|
def register_socket_handlers(socketio):
|
||||||
|
@socketio.on("join")
|
||||||
|
def on_join(data): # noqa: F401
|
||||||
|
room = data.get("room")
|
||||||
|
join_room(room)
|
||||||
|
current_app.logger.info(f"Socket {request.sid} joined room {room}")
|
||||||
|
|
||||||
|
@socketio.on("leave")
|
||||||
|
def on_leave(data): # noqa: F401
|
||||||
|
room = data.get("room")
|
||||||
|
leave_room(room)
|
||||||
|
current_app.logger.info(f"Socket {request.sid} left room {room}")
|
||||||
15
app/utils.py
15
app/utils.py
@@ -131,3 +131,18 @@ def utc_now():
|
|||||||
def debug_not_production(msg):
|
def debug_not_production(msg):
|
||||||
if os.getenv("NOTIFY_ENVIRONMENT") not in ["production"]:
|
if os.getenv("NOTIFY_ENVIRONMENT") not in ["production"]:
|
||||||
current_app.logger.info(msg)
|
current_app.logger.info(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def emit_job_update_summary(job):
|
||||||
|
from app import socketio
|
||||||
|
|
||||||
|
current_app.logger.info(f"Emitting summary for job {job.id}")
|
||||||
|
socketio.emit(
|
||||||
|
"job_updated",
|
||||||
|
{
|
||||||
|
"job_id": str(job.id),
|
||||||
|
"job_status": job.job_status,
|
||||||
|
"notification_count": job.notification_count,
|
||||||
|
},
|
||||||
|
room=f"job-{job.id}",
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
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()
|
|
||||||
@@ -628,8 +628,6 @@ def validate_phone_number(number, international=False):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = phonenumbers.parse(number, None)
|
parsed = phonenumbers.parse(number, None)
|
||||||
if parsed.country_code != 1:
|
|
||||||
raise InvalidPhoneError("Invalid country code")
|
|
||||||
number = f"{parsed.country_code}{parsed.national_number}"
|
number = f"{parsed.country_code}{parsed.national_number}"
|
||||||
if len(number) < 8:
|
if len(number) < 8:
|
||||||
raise InvalidPhoneError("Not enough digits")
|
raise InvalidPhoneError("Not enough digits")
|
||||||
|
|||||||
120
package-lock.json
generated
Normal file
120
package-lock.json
generated
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"name": "notifications-api",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"socket.io-client": "^4.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@socket.io/component-emitter": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client": {
|
||||||
|
"version": "6.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||||
|
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.17.1",
|
||||||
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-parser": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-client": {
|
||||||
|
"version": "4.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||||
|
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.2",
|
||||||
|
"engine.io-client": "~6.6.1",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"socket.io-client": "^4.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
3900
poetry.lock
generated
3900
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -17,31 +17,31 @@ cachetools = "==5.4.0"
|
|||||||
celery = {version = "==5.4.0", extras = ["redis"]}
|
celery = {version = "==5.4.0", extras = ["redis"]}
|
||||||
certifi = ">=2022.12.7"
|
certifi = ">=2022.12.7"
|
||||||
cffi = "==1.17.1"
|
cffi = "==1.17.1"
|
||||||
charset-normalizer = "^3.1.0"
|
charset-normalizer = "^3.4.1"
|
||||||
click = "==8.1.7"
|
click = "==8.1.8"
|
||||||
click-datetime = "==0.4.0"
|
click-datetime = "==0.4.0"
|
||||||
click-didyoumean = "==0.3.1"
|
click-didyoumean = "==0.3.1"
|
||||||
click-plugins = "==1.1.1"
|
click-plugins = "==1.1.1"
|
||||||
click-repl = "==0.3.0"
|
click-repl = "==0.3.0"
|
||||||
deprecated = "==1.2.14"
|
deprecated = "==1.2.14"
|
||||||
eventlet = "==0.36.1"
|
eventlet = "==0.39.1"
|
||||||
expiringdict = "==1.2.2"
|
expiringdict = "==1.2.2"
|
||||||
flask = "~=3.0"
|
flask = "~=3.0"
|
||||||
flask-bcrypt = "==1.0.1"
|
flask-bcrypt = "==1.0.1"
|
||||||
flask-marshmallow = "==1.2.1"
|
flask-marshmallow = "==1.2.1"
|
||||||
flask-migrate = "==4.0.7"
|
flask-migrate = "==4.1.0"
|
||||||
flask-redis = "==0.4.0"
|
flask-redis = "==0.4.0"
|
||||||
flask-sqlalchemy = "==3.1.1"
|
flask-sqlalchemy = "==3.1.1"
|
||||||
gunicorn = {version = "==23.0.0", extras = ["eventlet"]}
|
gunicorn = {version = "==23.0.0", extras = ["eventlet"]}
|
||||||
iso8601 = "==2.1.0"
|
iso8601 = "==2.1.0"
|
||||||
jsonschema = {version = "==4.23.0", extras = ["format"]}
|
jsonschema = {version = "==4.23.0", extras = ["format"]}
|
||||||
lxml = "==5.3.1"
|
lxml = "==5.3.1"
|
||||||
marshmallow = "==3.26.1"
|
marshmallow = "==4.0.0"
|
||||||
marshmallow-sqlalchemy = "==1.0.0"
|
marshmallow-sqlalchemy = "==1.0.0"
|
||||||
newrelic = "*"
|
newrelic = "*"
|
||||||
notifications-python-client = "==10.0.0"
|
notifications-python-client = "==10.0.0"
|
||||||
oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" }
|
oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" }
|
||||||
packaging = "==24.2"
|
packaging = "==25.0"
|
||||||
poetry-dotenv-plugin = "==0.2.0"
|
poetry-dotenv-plugin = "==0.2.0"
|
||||||
psycopg2-binary = "==2.9.9"
|
psycopg2-binary = "==2.9.9"
|
||||||
pyjwt = "==2.10.1"
|
pyjwt = "==2.10.1"
|
||||||
@@ -52,11 +52,11 @@ faker = "^26.0.0"
|
|||||||
async-timeout = "^5.0.1"
|
async-timeout = "^5.0.1"
|
||||||
bleach = "^6.1.0"
|
bleach = "^6.1.0"
|
||||||
geojson = "^3.2.0"
|
geojson = "^3.2.0"
|
||||||
numpy = "^2.2.3"
|
numpy = "^2.2.5"
|
||||||
ordered-set = "^4.1.0"
|
ordered-set = "^4.1.0"
|
||||||
phonenumbers = "^8.13.42"
|
phonenumbers = "^8.13.42"
|
||||||
python-json-logger = "^2.0.7"
|
python-json-logger = "^3.3.0"
|
||||||
regex = "^2024.7.24"
|
regex = "^2024.11.6"
|
||||||
shapely = "^2.0.5"
|
shapely = "^2.0.5"
|
||||||
smartypants = "^2.0.1"
|
smartypants = "^2.0.1"
|
||||||
mistune = "^3.1.3"
|
mistune = "^3.1.3"
|
||||||
@@ -76,6 +76,7 @@ itsdangerous = "^2.2.0"
|
|||||||
jinja2 = "^3.1.6"
|
jinja2 = "^3.1.6"
|
||||||
redis = "^5.0.8"
|
redis = "^5.0.8"
|
||||||
requests = "^2.32.3"
|
requests = "^2.32.3"
|
||||||
|
flask-socketio = "^5.5.1"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
@@ -84,13 +85,13 @@ bandit = "*"
|
|||||||
black = "^25.1.0"
|
black = "^25.1.0"
|
||||||
cloudfoundry-client = "*"
|
cloudfoundry-client = "*"
|
||||||
exceptiongroup = "==1.2.2"
|
exceptiongroup = "==1.2.2"
|
||||||
flake8 = "^7.1.1"
|
flake8 = "^7.2.0"
|
||||||
flake8-bugbear = "^24.1.17"
|
flake8-bugbear = "^24.12.12"
|
||||||
freezegun = "^1.5.1"
|
freezegun = "^1.5.1"
|
||||||
honcho = "*"
|
honcho = "*"
|
||||||
isort = "^5.13.2"
|
isort = "^5.13.2"
|
||||||
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
|
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
|
||||||
moto = "==5.1.0"
|
moto = "==5.1.4"
|
||||||
pip-audit = "*"
|
pip-audit = "*"
|
||||||
pre-commit = "^4.2.0"
|
pre-commit = "^4.2.0"
|
||||||
pytest = "^8.3.2"
|
pytest = "^8.3.2"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from app import aws_sns_client
|
|||||||
|
|
||||||
def test_send_sms_successful_returns_aws_sns_response(notify_api, mocker):
|
def test_send_sms_successful_returns_aws_sns_response(notify_api, mocker):
|
||||||
boto_mock = mocker.patch.object(aws_sns_client, "_client", create=True)
|
boto_mock = mocker.patch.object(aws_sns_client, "_client", create=True)
|
||||||
to = "6135555555"
|
to = "16135555555"
|
||||||
content = reference = "foo"
|
content = reference = "foo"
|
||||||
with notify_api.app_context():
|
with notify_api.app_context():
|
||||||
aws_sns_client.send_sms(to, content, reference)
|
aws_sns_client.send_sms(to, content, reference)
|
||||||
|
|||||||
@@ -2029,12 +2029,23 @@ def test_update_delivery_receipts(mocker):
|
|||||||
mock_update.where.return_value = mock_where
|
mock_update.where.return_value = mock_where
|
||||||
mock_where.values.return_value = mock_values
|
mock_where.values.return_value = mock_values
|
||||||
|
|
||||||
mock_session.execute.return_value = None
|
FakeJob = type(
|
||||||
|
"FakeJob",
|
||||||
|
(object,),
|
||||||
|
{"id": "job-123", "notification_count": 5, "job_status": "delivered"},
|
||||||
|
)
|
||||||
|
|
||||||
|
fake_result = MagicMock()
|
||||||
|
fake_result.scalars.return_value.all.return_value = ["job-1", "job-2"]
|
||||||
|
fake_result.scalars.return_value.one.return_value = FakeJob()
|
||||||
|
|
||||||
|
mock_session.execute.side_effect = lambda *args, **kwargs: fake_result
|
||||||
with patch("app.dao.notifications_dao.update", return_value=mock_update):
|
with patch("app.dao.notifications_dao.update", return_value=mock_update):
|
||||||
dao_update_delivery_receipts(receipts, delivered)
|
dao_update_delivery_receipts(receipts, delivered)
|
||||||
mock_update.where.assert_called_once()
|
mock_update.where.assert_called_once()
|
||||||
mock_where.values.assert_called_once()
|
mock_where.values.assert_called_once()
|
||||||
mock_session.execute.assert_called_once_with(mock_values)
|
mock_session.execute.assert_any_call(mock_values)
|
||||||
|
assert mock_session.execute.call_count == 4
|
||||||
mock_session.commit.assert_called_once()
|
mock_session.commit.assert_called_once()
|
||||||
|
|
||||||
args, kwargs = mock_where.values.call_args
|
args, kwargs = mock_where.values.call_args
|
||||||
|
|||||||
@@ -628,6 +628,8 @@ def test_bad_or_missing_data(
|
|||||||
assert recipients.has_errors is True
|
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(
|
@pytest.mark.parametrize(
|
||||||
("file_contents", "rows_with_bad_recipients"),
|
("file_contents", "rows_with_bad_recipients"),
|
||||||
[
|
[
|
||||||
@@ -638,7 +640,7 @@ def test_bad_or_missing_data(
|
|||||||
1234
|
1234
|
||||||
+447900123
|
+447900123
|
||||||
""",
|
""",
|
||||||
{0, 1, 2},
|
{0, 1},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"""
|
"""
|
||||||
@@ -647,7 +649,7 @@ def test_bad_or_missing_data(
|
|||||||
+12022340104, USA
|
+12022340104, USA
|
||||||
+23051234567, Mauritius
|
+23051234567, Mauritius
|
||||||
""",
|
""",
|
||||||
{2},
|
set(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,21 +24,16 @@ valid_us_phone_numbers = [
|
|||||||
"(202) 555-0104",
|
"(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 = [
|
valid_international_phone_numbers = [
|
||||||
# "+71234567890", # Russia
|
"+71234567890", # Russia
|
||||||
# "+447123456789", # UK
|
"+447123456789", # UK
|
||||||
# "+4407123456789", # UK
|
"+4407123456789", # UK
|
||||||
# "+4407123 456789", # UK
|
"+4407123 456789", # UK
|
||||||
# "+4407123-456-789", # UK
|
"+4407123-456-789", # UK
|
||||||
# "+23051234567", # Mauritius,
|
"+23051234567", # Mauritius,
|
||||||
# "+682 12345", # Cook islands
|
"+682 12345", # Cook islands
|
||||||
# "+3312345678",
|
"+3312345678",
|
||||||
# "+9-2345-12345-12345", # 15 digits
|
"+9-2345-12345-12345", # 15 digits
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -85,7 +80,7 @@ invalid_us_phone_numbers = sum(
|
|||||||
invalid_phone_numbers = [
|
invalid_phone_numbers = [
|
||||||
("+80233456789", "Not a valid country prefix"),
|
("+80233456789", "Not a valid country prefix"),
|
||||||
("1234567", "Not enough digits"),
|
("1234567", "Not enough digits"),
|
||||||
("+682 1234", "Invalid country code"), # Cook Islands phone numbers can be 5 digits
|
("+682 1234", "Not enough digits"), # Cook Islands phone numbers are 5 digits
|
||||||
("+12345 12345 12345 6", "Too many digits"),
|
("+12345 12345 12345 6", "Too many digits"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -156,46 +151,46 @@ def test_detect_us_phone_numbers(phone_number):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("phone_number", "expected_info"),
|
("phone_number", "expected_info"),
|
||||||
[
|
[
|
||||||
# (
|
(
|
||||||
# "+4407900900123",
|
"+4407900900123",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="44", # UK
|
country_prefix="44", # UK
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "+4407700900123",
|
"+4407700900123",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="44", # Number in TV range
|
country_prefix="44", # Number in TV range
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "+4407700800123",
|
"+4407700800123",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="44", # UK Crown dependency, so prefix same as UK
|
country_prefix="44", # UK Crown dependency, so prefix same as UK
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
# ( #
|
( #
|
||||||
# "+20-12-1234-1234",
|
"+20-12-1234-1234",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="20", # Egypt
|
country_prefix="20", # Egypt
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "+201212341234",
|
"+201212341234",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="20", # Egypt
|
country_prefix="20", # Egypt
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
(
|
(
|
||||||
"+1 664-491-3434",
|
"+1 664-491-3434",
|
||||||
international_phone_info(
|
international_phone_info(
|
||||||
@@ -204,14 +199,14 @@ def test_detect_us_phone_numbers(phone_number):
|
|||||||
billable_units=1,
|
billable_units=1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
# (
|
(
|
||||||
# "+71234567890",
|
"+71234567890",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="7", # Russia
|
country_prefix="7", # Russia
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
(
|
(
|
||||||
"1-202-555-0104",
|
"1-202-555-0104",
|
||||||
international_phone_info(
|
international_phone_info(
|
||||||
@@ -228,14 +223,14 @@ def test_detect_us_phone_numbers(phone_number):
|
|||||||
billable_units=1,
|
billable_units=1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
# (
|
(
|
||||||
# "+23051234567",
|
"+23051234567",
|
||||||
# international_phone_info(
|
international_phone_info(
|
||||||
# international=True,
|
international=True,
|
||||||
# country_prefix="230", # Mauritius
|
country_prefix="230", # Mauritius
|
||||||
# billable_units=1,
|
billable_units=1,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_get_international_info(phone_number, expected_info):
|
def test_get_international_info(phone_number, expected_info):
|
||||||
@@ -288,11 +283,11 @@ def test_valid_us_phone_number_can_be_formatted_consistently(phone_number):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("phone_number", "expected_formatted"),
|
("phone_number", "expected_formatted"),
|
||||||
[
|
[
|
||||||
# ("+44071234567890", "+4471234567890"),
|
("+44071234567890", "+4471234567890"),
|
||||||
("1-202-555-0104", "+12025550104"),
|
("1-202-555-0104", "+12025550104"),
|
||||||
("+12025550104", "+12025550104"),
|
("+12025550104", "+12025550104"),
|
||||||
("12025550104", "+12025550104"),
|
("12025550104", "+12025550104"),
|
||||||
# ("+23051234567", "+23051234567"),
|
("+23051234567", "+23051234567"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_valid_international_phone_number_can_be_formatted_consistently(
|
def test_valid_international_phone_number_can_be_formatted_consistently(
|
||||||
@@ -368,17 +363,17 @@ def test_validates_against_guestlist_of_phone_numbers(phone_number):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
# "recipient_number, allowlist_number",
|
"recipient_number, allowlist_number",
|
||||||
# [
|
[
|
||||||
# ["+4407123-456-789", "+4407123456789"],
|
["+4407123-456-789", "+4407123456789"],
|
||||||
# ["+4407123456789", "+4407123-456-789"],
|
["+4407123456789", "+4407123-456-789"],
|
||||||
# ],
|
],
|
||||||
# )
|
)
|
||||||
# def test_validates_against_guestlist_of_international_phone_numbers(
|
def test_validates_against_guestlist_of_international_phone_numbers(
|
||||||
# recipient_number, allowlist_number
|
recipient_number, allowlist_number
|
||||||
# ):
|
):
|
||||||
# assert allowed_to_send_to(recipient_number, [allowlist_number])
|
assert allowed_to_send_to(recipient_number, [allowlist_number])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("email_address", valid_email_addresses)
|
@pytest.mark.parametrize("email_address", valid_email_addresses)
|
||||||
@@ -388,19 +383,21 @@ 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(
|
@pytest.mark.parametrize(
|
||||||
("phone_number", "expected_formatted"),
|
("phone_number", "expected_formatted"),
|
||||||
[
|
[
|
||||||
# ("+4407900900123", "+44 7900 900123"), # UK
|
("+4407900900123", "+44 7900 900123"), # UK
|
||||||
# ("+44(0)7900900123", "+44 7900 900123"), # UK
|
("+44(0)7900900123", "+44 7900 900123"), # UK
|
||||||
# ("+447900900123", "+44 7900 900123"), # UK
|
("+447900900123", "+44 7900 900123"), # UK
|
||||||
# ("+20-12-1234-1234", "+20 121 234 1234"), # Egypt
|
# ("+20-12-1234-1234", "+20 121 234 1234"), # Egypt
|
||||||
# ("+201212341234", "+20 121 234 1234"), # Egypt
|
# ("+201212341234", "+20 121 234 1234"), # Egypt
|
||||||
("+1 664 491-3434", "+1 664-491-3434"), # Montserrat
|
("+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)
|
("1-202-555-0104", "(202) 555-0104"), # Washington DC (USA)
|
||||||
# ("+23051234567", "+230 5123 4567"), # Mauritius
|
("+23051234567", "+230 5123 4567"), # Mauritius
|
||||||
# ("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France)
|
("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_format_us_and_international_phone_numbers(phone_number, expected_formatted):
|
def test_format_us_and_international_phone_numbers(phone_number, expected_formatted):
|
||||||
@@ -417,7 +414,7 @@ def test_format_us_and_international_phone_numbers(phone_number, expected_format
|
|||||||
(None, ""),
|
(None, ""),
|
||||||
("foo", "foo"),
|
("foo", "foo"),
|
||||||
("TeSt@ExAmPl3.com", "test@exampl3.com"),
|
("TeSt@ExAmPl3.com", "test@exampl3.com"),
|
||||||
# ("+4407900 900 123", "+447900900123"),
|
("+4407900 900 123", "+447900900123"),
|
||||||
("+1 800 555 5555", "+18005555555"),
|
("+1 800 555 5555", "+18005555555"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user