Compare commits

..

51 Commits

Author SHA1 Message Date
dependabot[bot]
dac09809f5 Bump marshmallow from 3.26.1 to 4.0.0
Bumps [marshmallow](https://github.com/marshmallow-code/marshmallow) from 3.26.1 to 4.0.0.
- [Changelog](https://github.com/marshmallow-code/marshmallow/blob/dev/CHANGELOG.rst)
- [Commits](https://github.com/marshmallow-code/marshmallow/compare/3.26.1...4.0.0)

---
updated-dependencies:
- dependency-name: marshmallow
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-24 15:48:13 +00:00
Andrew Shumway
9f0b6b0ca1 Merge pull request #1664 from GSA/dependabot/pip/packaging-25.0
Bump packaging from 24.2 to 25.0
2025-04-24 09:46:03 -06:00
dependabot[bot]
7dd1751556 Bump packaging from 24.2 to 25.0
Bumps [packaging](https://github.com/pypa/packaging) from 24.2 to 25.0.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/24.2...25.0)

---
updated-dependencies:
- dependency-name: packaging
  dependency-version: '25.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-24 15:25:37 +00:00
Andrew Shumway
3ae7749d0b Merge pull request #1663 from GSA/dependabot/pip/python-json-logger-3.3.0
Bump python-json-logger from 2.0.7 to 3.3.0
2025-04-24 09:23:24 -06:00
dependabot[bot]
f6cc4afab7 Bump python-json-logger from 2.0.7 to 3.3.0
Bumps [python-json-logger](https://github.com/nhairs/python-json-logger) from 2.0.7 to 3.3.0.
- [Release notes](https://github.com/nhairs/python-json-logger/releases)
- [Changelog](https://github.com/nhairs/python-json-logger/blob/main/docs/changelog.md)
- [Commits](https://github.com/nhairs/python-json-logger/compare/v2.0.7...v3.3.0)

---
updated-dependencies:
- dependency-name: python-json-logger
  dependency-version: 3.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-24 15:11:34 +00:00
Andrew Shumway
6325849855 Merge pull request #1662 from GSA/dependabot/pip/flask-migrate-4.1.0
Bump flask-migrate from 4.0.7 to 4.1.0
2025-04-24 09:09:24 -06:00
dependabot[bot]
b564a8800e Bump flask-migrate from 4.0.7 to 4.1.0
Bumps [flask-migrate](https://github.com/miguelgrinberg/flask-migrate) from 4.0.7 to 4.1.0.
- [Release notes](https://github.com/miguelgrinberg/flask-migrate/releases)
- [Changelog](https://github.com/miguelgrinberg/Flask-Migrate/blob/main/CHANGES.md)
- [Commits](https://github.com/miguelgrinberg/flask-migrate/compare/v4.0.7...v4.1.0)

---
updated-dependencies:
- dependency-name: flask-migrate
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 22:08:21 +00:00
Andrew Shumway
43247e81bb Merge pull request #1657 from GSA/dependabot/pip/click-8.1.8
Bump click from 8.1.7 to 8.1.8
2025-04-23 16:06:10 -06:00
dependabot[bot]
7f2325ea5e Bump click from 8.1.7 to 8.1.8
Bumps [click](https://github.com/pallets/click) from 8.1.7 to 8.1.8.
- [Release notes](https://github.com/pallets/click/releases)
- [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/click/compare/8.1.7...8.1.8)

---
updated-dependencies:
- dependency-name: click
  dependency-version: 8.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 21:55:43 +00:00
Andrew Shumway
bcd18ac936 Merge pull request #1658 from GSA/dependabot/pip/moto-5.1.4
Bump moto from 5.1.0 to 5.1.4
2025-04-23 15:53:36 -06:00
dependabot[bot]
7fc65dd4f5 Bump moto from 5.1.0 to 5.1.4
Bumps [moto](https://github.com/getmoto/moto) from 5.1.0 to 5.1.4.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getmoto/moto/compare/5.1.0...5.1.4)

---
updated-dependencies:
- dependency-name: moto
  dependency-version: 5.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 21:39:23 +00:00
Andrew Shumway
276446c57e Merge pull request #1659 from GSA/dependabot/pip/numpy-2.2.5
Bump numpy from 2.2.4 to 2.2.5
2025-04-23 15:37:08 -06:00
dependabot[bot]
bc26231164 Bump numpy from 2.2.4 to 2.2.5
Bumps [numpy](https://github.com/numpy/numpy) from 2.2.4 to 2.2.5.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v2.2.4...v2.2.5)

---
updated-dependencies:
- dependency-name: numpy
  dependency-version: 2.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 21:29:37 +00:00
Andrew Shumway
8c80f2a462 Merge pull request #1660 from GSA/dependabot/pip/eventlet-0.39.1
Bump eventlet from 0.36.1 to 0.39.1
2025-04-23 15:27:25 -06:00
dependabot[bot]
4ccfc82c8c Bump eventlet from 0.36.1 to 0.39.1
Bumps [eventlet](https://github.com/eventlet/eventlet) from 0.36.1 to 0.39.1.
- [Changelog](https://github.com/eventlet/eventlet/blob/master/NEWS)
- [Commits](https://github.com/eventlet/eventlet/compare/0.36.1...0.39.1)

---
updated-dependencies:
- dependency-name: eventlet
  dependency-version: 0.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-22 21:28:21 +00:00
Cliff Hill
ebd0ed9365 Merge pull request #1634 from GSA/2401-add-in-flask-socketio-for-api-calls-rather-than-using-ajax
2401 add in flask socketio for api calls rather than using ajax
2025-04-21 16:33:47 -04:00
Beverly Nguyen
02becbd1e5 removing run.py and unnecessary logs 2025-04-18 11:48:26 -07:00
Carlo Costino
419d6cee69 Update the flask-socketio config to play more nicely:
* Reverts run commands to what they previously were
* Addresses some outstanding linting/formatting
* Accounts for proper config initialization (CORS, Redis)
* Updates dependencies and pulls in latest changes from main

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-04-16 17:43:10 -04:00
Carlo Costino
71e8f20b04 Merge branch 'main' into 2401-add-in-flask-socketio-for-api-calls-rather-than-using-ajax 2025-04-16 10:55:16 -04:00
Kenneth Kehl
db90cecd65 Merge pull request #1655 from GSA/intl_nums
fix default case for US numbers
2025-04-15 11:44:44 -07:00
Kenneth Kehl
e93e3f3690 cleanup 2025-04-15 11:36:09 -07:00
Kenneth Kehl
d1fab496f4 pull from main 2025-04-15 11:33:25 -07:00
Kenneth Kehl
1f4b408d1d fix phone number check 2025-04-15 11:13:26 -07:00
Kenneth Kehl
f15d6128dc fix default case for US numbers 2025-04-15 10:44:30 -07:00
Beverly Nguyen
3ad7a3b2fd Merge pull request #1648 from GSA/update-makefile
Update Makefile to handle pre-existing git hooks
2025-04-14 13:29:01 -07:00
ccostino
aea7249124 Merge pull request #1644 from GSA/dependabot/pip/numpy-2.2.4
Bump numpy from 2.2.3 to 2.2.4
2025-04-14 16:20:07 -04:00
Carlo Costino
bf3897729c Create new command instead of trying to be fancy
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-04-14 16:16:56 -04:00
Carlo Costino
8181eda049 Update Makefile to handle pre-existing git hooks
This changeset adds a bit of extra support to the bootstrap command to make sure that pre-existing git hooks do not interfere with the installation of the pre-commit git hooks.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2025-04-14 15:45:03 -04:00
dependabot[bot]
e3d2e75182 Bump numpy from 2.2.3 to 2.2.4
Bumps [numpy](https://github.com/numpy/numpy) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v2.2.3...v2.2.4)

---
updated-dependencies:
- dependency-name: numpy
  dependency-version: 2.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 18:56:13 +00:00
ccostino
22085aa334 Merge pull request #1645 from GSA/dependabot/pip/regex-2024.11.6
Bump regex from 2024.9.11 to 2024.11.6
2025-04-14 14:53:50 -04:00
Kenneth Kehl
ffdc0edc4e Merge pull request #1629 from GSA/intl_nums
restore international numbers
2025-04-14 10:24:49 -07:00
dependabot[bot]
a236df97bf Bump regex from 2024.9.11 to 2024.11.6
Bumps [regex](https://github.com/mrabarnett/mrab-regex) from 2024.9.11 to 2024.11.6.
- [Changelog](https://github.com/mrabarnett/mrab-regex/blob/hg/changelog.txt)
- [Commits](https://github.com/mrabarnett/mrab-regex/compare/2024.9.11...2024.11.6)

---
updated-dependencies:
- dependency-name: regex
  dependency-version: 2024.11.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 15:30:44 +00:00
Andrew Shumway
2f220a17a5 Merge pull request #1641 from GSA/dependabot/pip/charset-normalizer-3.4.1
Bump charset-normalizer from 3.4.0 to 3.4.1
2025-04-14 09:28:13 -06:00
dependabot[bot]
37691f504e Bump charset-normalizer from 3.4.0 to 3.4.1
Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/jawah/charset_normalizer/releases)
- [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.0...3.4.1)

---
updated-dependencies:
- dependency-name: charset-normalizer
  dependency-version: 3.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 15:20:35 +00:00
Andrew Shumway
a1273d7f01 Merge pull request #1640 from GSA/dependabot/pip/flake8-bugbear-24.12.12
Bump flake8-bugbear from 24.8.19 to 24.12.12
2025-04-14 09:18:21 -06:00
Kenneth Kehl
0c65b50043 update project 2025-04-14 07:53:35 -07:00
Beverly Nguyen
9e36418271 poetry 2025-04-10 17:47:38 -07:00
Beverly Nguyen
6f524cc907 Merge branch 'main' into 2401-add-in-flask-socketio-for-api-calls-rather-than-using-ajax 2025-04-10 17:44:09 -07:00
Beverly Nguyen
b37c803edc fix testing 2025-04-10 17:36:56 -07:00
dependabot[bot]
9e7f747675 Bump flake8-bugbear from 24.8.19 to 24.12.12
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 24.8.19 to 24.12.12.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/24.8.19...24.12.12)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-version: 24.12.12
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 15:55:56 +00:00
Andrew Shumway
0af9cac583 Merge pull request #1642 from GSA/dependabot/pip/flake8-7.2.0
Bump flake8 from 7.1.1 to 7.2.0
2025-04-10 09:53:41 -06:00
Beverly Nguyen
1cc1d9a58d poetry add flask-socketio 2025-04-09 17:08:33 -07:00
Beverly Nguyen
9174978e3e merge main 2025-04-09 17:01:34 -07:00
Beverly Nguyen
f8726ca6b7 socket io setup 2025-04-09 16:56:42 -07:00
dependabot[bot]
7d54322846 Bump flake8 from 7.1.1 to 7.2.0
Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.1 to 7.2.0.
- [Commits](https://github.com/pycqa/flake8/compare/7.1.1...7.2.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-version: 7.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 14:55:34 +00:00
Kenneth Kehl
7aaa36e630 merge from main 2025-04-08 10:05:30 -07:00
Kenneth Kehl
f87a54fffe fix test 2025-04-08 10:05:06 -07:00
Beverly Nguyen
0792ea123f Merge branch 'main' into 2401-add-in-flask-socketio-for-api-calls-rather-than-using-ajax 2025-04-03 15:30:54 -07:00
Kenneth Kehl
28444a460b fix test 2025-04-03 08:16:33 -07:00
Kenneth Kehl
e9ea22ad6d restore international numbers 2025-04-03 08:02:08 -07:00
Beverly Nguyen
16c68e06b9 initial test socket io setup 2025-03-27 09:51:22 -07:00
20 changed files with 2570 additions and 1854 deletions

View File

@@ -47,8 +47,6 @@ 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
@@ -64,8 +62,6 @@ 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
@@ -136,8 +132,6 @@ 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:

View File

@@ -61,8 +61,6 @@ 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:
@@ -79,8 +77,6 @@ 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
@@ -93,8 +89,6 @@ 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 }}

View File

@@ -5,7 +5,8 @@ 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)
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "")
## DEVELOPMENT
@@ -23,6 +24,19 @@ 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 .
@@ -50,7 +64,7 @@ too-complex:
poetry run radon cc ./app -a -nc
.PHONY: run-flask
run-flask: ## Run flask
run-flask:
poetry run newrelic-admin run-program flask run -p 6011 --host=0.0.0.0
.PHONY: run-celery

View File

@@ -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_marshmallow import Marshmallow
from flask_migrate import Migrate
from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy
from sqlalchemy import event
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
@@ -94,6 +95,14 @@ zendesk_client = ZendeskClient()
redis_store = RedisClient()
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()
@@ -111,6 +120,11 @@ def create_app(application):
application.config["NOTIFY_APP_NAME"] = application.name
init_app(application)
socketio.init_app(application)
from app.socket_handlers import register_socket_handlers
register_socket_handlers(socketio)
request_helper.init_app(application)
db.init_app(application)
migrate.init_app(application, db=db)

View File

@@ -48,7 +48,10 @@ class AwsSnsClient(SmsClient):
def send_sms(self, to, content, reference, sender=None, international=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
to = phonenumbers.format_number(
match.number, phonenumbers.PhoneNumberFormat.E164

View File

@@ -179,6 +179,7 @@ class Config(object):
S3_RESOURCE = session.resource("s3", config=AWS_CLIENT_CONFIG)
CELERY = {
"broker_connection_retry_on_startup": True,
"worker_max_tasks_per_child": 500,
"task_ignore_result": True,
"result_persistent": False,

View File

@@ -26,9 +26,11 @@ from werkzeug.datastructures import MultiDict
from app import create_uuid, db
from app.dao.dao_utils import autocommit
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.models import FactNotificationStatus, Notification, NotificationHistory
from app.utils import (
emit_job_update_summary,
escape_special_characters,
get_midnight_in_utc,
midnight_n_days_ago,
@@ -895,6 +897,19 @@ def dao_update_delivery_receipts(receipts, delivered):
f"#loadtestperformance batch update query time: \
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():

View File

@@ -105,6 +105,17 @@ def send_sms_to_provider(notification):
# The future home of the validation is TBD
_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)
if notification.reply_to_text not in sender_numbers:
raise ValueError(

16
app/socket_handlers.py Normal file
View 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}")

View File

@@ -131,3 +131,18 @@ def utc_now():
def debug_not_production(msg):
if os.getenv("NOTIFY_ENVIRONMENT") not in ["production"]:
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}",
)

View File

@@ -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()

View File

@@ -628,8 +628,6 @@ 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")

120
package-lock.json generated Normal file
View 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
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"socket.io-client": "^4.8.1"
}
}

3900
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,31 +17,31 @@ cachetools = "==5.4.0"
celery = {version = "==5.4.0", extras = ["redis"]}
certifi = ">=2022.12.7"
cffi = "==1.17.1"
charset-normalizer = "^3.1.0"
click = "==8.1.7"
charset-normalizer = "^3.4.1"
click = "==8.1.8"
click-datetime = "==0.4.0"
click-didyoumean = "==0.3.1"
click-plugins = "==1.1.1"
click-repl = "==0.3.0"
deprecated = "==1.2.14"
eventlet = "==0.36.1"
eventlet = "==0.39.1"
expiringdict = "==1.2.2"
flask = "~=3.0"
flask-bcrypt = "==1.0.1"
flask-marshmallow = "==1.2.1"
flask-migrate = "==4.0.7"
flask-migrate = "==4.1.0"
flask-redis = "==0.4.0"
flask-sqlalchemy = "==3.1.1"
gunicorn = {version = "==23.0.0", extras = ["eventlet"]}
iso8601 = "==2.1.0"
jsonschema = {version = "==4.23.0", extras = ["format"]}
lxml = "==5.3.1"
marshmallow = "==3.26.1"
marshmallow = "==4.0.0"
marshmallow-sqlalchemy = "==1.0.0"
newrelic = "*"
notifications-python-client = "==10.0.0"
oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" }
packaging = "==24.2"
packaging = "==25.0"
poetry-dotenv-plugin = "==0.2.0"
psycopg2-binary = "==2.9.9"
pyjwt = "==2.10.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.3"
numpy = "^2.2.5"
ordered-set = "^4.1.0"
phonenumbers = "^8.13.42"
python-json-logger = "^2.0.7"
regex = "^2024.7.24"
python-json-logger = "^3.3.0"
regex = "^2024.11.6"
shapely = "^2.0.5"
smartypants = "^2.0.1"
mistune = "^3.1.3"
@@ -76,6 +76,7 @@ itsdangerous = "^2.2.0"
jinja2 = "^3.1.6"
redis = "^5.0.8"
requests = "^2.32.3"
flask-socketio = "^5.5.1"
[tool.poetry.group.dev.dependencies]
@@ -84,13 +85,13 @@ bandit = "*"
black = "^25.1.0"
cloudfoundry-client = "*"
exceptiongroup = "==1.2.2"
flake8 = "^7.1.1"
flake8-bugbear = "^24.1.17"
flake8 = "^7.2.0"
flake8-bugbear = "^24.12.12"
freezegun = "^1.5.1"
honcho = "*"
isort = "^5.13.2"
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
moto = "==5.1.0"
moto = "==5.1.4"
pip-audit = "*"
pre-commit = "^4.2.0"
pytest = "^8.3.2"

View File

@@ -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 = "6135555555"
to = "16135555555"
content = reference = "foo"
with notify_api.app_context():
aws_sns_client.send_sms(to, content, reference)

View File

@@ -2029,12 +2029,23 @@ def test_update_delivery_receipts(mocker):
mock_update.where.return_value = mock_where
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):
dao_update_delivery_receipts(receipts, delivered)
mock_update.where.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()
args, kwargs = mock_where.values.call_args

View File

@@ -628,6 +628,8 @@ 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"),
[
@@ -638,7 +640,7 @@ def test_bad_or_missing_data(
1234
+447900123
""",
{0, 1, 2},
{0, 1},
),
(
"""
@@ -647,7 +649,7 @@ def test_bad_or_missing_data(
+12022340104, USA
+23051234567, Mauritius
""",
{2},
set(),
),
],
)

View File

@@ -24,21 +24,16 @@ 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
]
@@ -85,7 +80,7 @@ invalid_us_phone_numbers = sum(
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
("+682 1234", "Not enough digits"), # Cook Islands phone numbers are 5 digits
("+12345 12345 12345 6", "Too many digits"),
]
@@ -156,46 +151,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(
@@ -204,14 +199,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(
@@ -228,14 +223,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):
@@ -288,11 +283,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(
@@ -368,17 +363,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)
@@ -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(
("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):
@@ -417,7 +414,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"),
],
)