mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-27 01:33:42 -04:00
merge from main
This commit is contained in:
@@ -249,7 +249,7 @@
|
||||
"filename": "tests/app/dao/test_users_dao.py",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 54,
|
||||
"line_number": 69,
|
||||
"is_secret": false
|
||||
},
|
||||
{
|
||||
@@ -257,7 +257,7 @@
|
||||
"filename": "tests/app/dao/test_users_dao.py",
|
||||
"hashed_secret": "f2c57870308dc87f432e5912d4de6f8e322721ba",
|
||||
"is_verified": false,
|
||||
"line_number": 184,
|
||||
"line_number": 199,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -384,5 +384,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2024-10-11T14:53:42Z"
|
||||
"generated_at": "2024-10-28T20:26:27Z"
|
||||
}
|
||||
|
||||
16
.github/actions/deploy-proxy/action.yml
vendored
16
.github/actions/deploy-proxy/action.yml
vendored
@@ -1,6 +1,9 @@
|
||||
name: Deploy egress proxy
|
||||
description: Set egress space security groups and deploy proxy
|
||||
inputs:
|
||||
cf_org:
|
||||
description: The org the target app exists in.
|
||||
required: true
|
||||
cf_space:
|
||||
description: The space the target app exists in.
|
||||
required: true
|
||||
@@ -16,6 +19,19 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install cf-cli
|
||||
shell: bash
|
||||
run: |
|
||||
curl -A "cg-deploy-action" -v -L -o cf-cli_amd64.deb 'https://packages.cloudfoundry.org/stable?release=debian64&version=v8&source=github'
|
||||
sudo dpkg -i cf-cli_amd64.deb
|
||||
- name: Login to cf-cli
|
||||
shell: bash
|
||||
run: |
|
||||
cf api api.fr.cloud.gov
|
||||
cf auth
|
||||
- name: Target org and space
|
||||
shell: bash
|
||||
run: cf target -o ${{ inputs.cf_org }} -s ${{ inputs.cf_space }}
|
||||
- name: Set restricted space egress
|
||||
shell: bash
|
||||
run: ./terraform/set_space_egress.sh -t -s ${{ inputs.cf_space }}
|
||||
|
||||
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
NOTIFY_E2E_TEST_PASSWORD: ${{ secrets.NOTIFY_E2E_TEST_PASSWORD }}
|
||||
- name: Check coverage threshold
|
||||
# TODO get this back up to 95
|
||||
run: poetry run coverage report -m --fail-under=91
|
||||
run: poetry run coverage report -m --fail-under=93
|
||||
|
||||
validate-new-relic-config:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
10
.github/workflows/deploy-demo.yml
vendored
10
.github/workflows/deploy-demo.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||
|
||||
- name: Deploy to cloud.gov
|
||||
uses: 18f/cg-deploy-action@main
|
||||
uses: cloud-gov/cg-cli-tools@main
|
||||
env:
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
@@ -64,7 +64,8 @@ jobs:
|
||||
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-demo
|
||||
push_arguments: >-
|
||||
cf_command: >-
|
||||
push -f manifest.yml
|
||||
--vars-file deploy-config/demo.yml
|
||||
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
||||
--var SECRET_KEY="$SECRET_KEY"
|
||||
@@ -73,6 +74,7 @@ jobs:
|
||||
--var NOTIFY_E2E_TEST_EMAIL="$NOTIFY_E2E_TEST_EMAIL"
|
||||
--var NOTIFY_E2E_TEST_PASSWORD="$NOTIFY_E2E_TEST_PASSWORD"
|
||||
--var LOGIN_DOT_GOV_REGISTRATION_URL="$LOGIN_DOT_GOV_REGISTRATION_URL"
|
||||
--strategy rolling
|
||||
|
||||
- name: Check for changes to templates.json
|
||||
id: changed-templates
|
||||
@@ -95,6 +97,10 @@ jobs:
|
||||
- name: Deploy egress proxy
|
||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/deploy-proxy
|
||||
env:
|
||||
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
with:
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-demo
|
||||
app: notify-api-demo
|
||||
|
||||
10
.github/workflows/deploy-prod.yml
vendored
10
.github/workflows/deploy-prod.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||
|
||||
- name: Deploy to cloud.gov
|
||||
uses: 18f/cg-deploy-action@main
|
||||
uses: cloud-gov/cg-cli-tools@main
|
||||
env:
|
||||
DANGEROUS_SALT: ${{ secrets.DANGEROUS_SALT }}
|
||||
SECRET_KEY: ${{ secrets.SECRET_KEY }}
|
||||
@@ -68,7 +68,8 @@ jobs:
|
||||
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-production
|
||||
push_arguments: >-
|
||||
cf_command: >-
|
||||
push -f manifest.yml
|
||||
--vars-file deploy-config/production.yml
|
||||
--var DANGEROUS_SALT="$DANGEROUS_SALT"
|
||||
--var SECRET_KEY="$SECRET_KEY"
|
||||
@@ -77,6 +78,7 @@ jobs:
|
||||
--var NOTIFY_E2E_TEST_EMAIL="$NOTIFY_E2E_TEST_EMAIL"
|
||||
--var NOTIFY_E2E_TEST_PASSWORD="$NOTIFY_E2E_TEST_PASSWORD"
|
||||
--var LOGIN_DOT_GOV_REGISTRATION_URL="$LOGIN_DOT_GOV_REGISTRATION_URL"
|
||||
--strategy rolling
|
||||
|
||||
- name: Check for changes to templates.json
|
||||
id: changed-templates
|
||||
@@ -99,6 +101,10 @@ jobs:
|
||||
- name: Deploy egress proxy
|
||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/deploy-proxy
|
||||
env:
|
||||
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
with:
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-production
|
||||
app: notify-api-production
|
||||
|
||||
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@@ -103,7 +103,11 @@ jobs:
|
||||
- name: Deploy egress proxy
|
||||
if: steps.changed-egress-config.outputs.any_changed == 'true'
|
||||
uses: ./.github/actions/deploy-proxy
|
||||
env:
|
||||
CF_USERNAME: ${{ secrets.CLOUDGOV_USERNAME }}
|
||||
CF_PASSWORD: ${{ secrets.CLOUDGOV_PASSWORD }}
|
||||
with:
|
||||
cf_org: gsa-tts-benefits-studio
|
||||
cf_space: notify-staging
|
||||
app: notify-api-staging
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -84,7 +84,7 @@ test: ## Run tests and create coverage report
|
||||
poetry run coverage run --omit=*/migrations/*,*/tests/* -m pytest --maxfail=10
|
||||
|
||||
## TODO set this back to 95 asap
|
||||
poetry run coverage report -m --fail-under=91
|
||||
poetry run coverage report -m --fail-under=93
|
||||
poetry run coverage html -d .coverage_cache
|
||||
|
||||
.PHONY: py-lock
|
||||
|
||||
@@ -295,7 +295,11 @@ def get_old_job_location(service_id, job_id):
|
||||
|
||||
|
||||
def get_job_and_metadata_from_s3(service_id, job_id):
|
||||
obj = get_s3_object(*get_job_location(service_id, job_id))
|
||||
try:
|
||||
obj = get_s3_object(*get_job_location(service_id, job_id))
|
||||
except botocore.exceptions.ClientError:
|
||||
obj = get_s3_object(*get_old_job_location(service_id, job_id))
|
||||
|
||||
return obj.get()["Body"].read().decode("utf-8"), obj.get()["Metadata"]
|
||||
|
||||
|
||||
@@ -472,23 +476,7 @@ def get_personalisation_from_s3(service_id, job_id, job_row_number):
|
||||
|
||||
set_job_cache(job_cache, f"{job_id}_personalisation", extract_personalisation(job))
|
||||
|
||||
# If we can find the quick dictionary, use it
|
||||
if job_cache.get(f"{job_id}_personalisation") is not None:
|
||||
personalisation_to_return = job_cache.get(f"{job_id}_personalisation")[0].get(
|
||||
job_row_number
|
||||
)
|
||||
if personalisation_to_return:
|
||||
return personalisation_to_return
|
||||
else:
|
||||
current_app.logger.warning(
|
||||
f"Was unable to retrieve personalisation from lookup dictionary for job {job_id}"
|
||||
)
|
||||
return {}
|
||||
else:
|
||||
current_app.logger.error(
|
||||
f"Was unable to construct lookup dictionary for job {job_id}"
|
||||
)
|
||||
return {}
|
||||
return job_cache.get(f"{job_id}_personalisation")[0].get(job_row_number)
|
||||
|
||||
|
||||
def get_job_metadata_from_s3(service_id, job_id):
|
||||
|
||||
@@ -24,12 +24,6 @@ from app.dao.annual_billing_dao import (
|
||||
dao_create_or_update_annual_billing_for_year,
|
||||
set_default_free_allowance_for_service,
|
||||
)
|
||||
from app.dao.fact_billing_dao import (
|
||||
delete_billing_data_for_service_for_day,
|
||||
fetch_billing_data_for_day,
|
||||
get_service_ids_that_need_billing_populated,
|
||||
update_fact_billing,
|
||||
)
|
||||
from app.dao.jobs_dao import dao_get_job_by_id
|
||||
from app.dao.organization_dao import (
|
||||
dao_add_service_to_organization,
|
||||
@@ -63,7 +57,7 @@ from app.models import (
|
||||
TemplateHistory,
|
||||
User,
|
||||
)
|
||||
from app.utils import get_midnight_in_utc, utc_now
|
||||
from app.utils import utc_now
|
||||
from notifications_utils.recipients import RecipientCSV
|
||||
from notifications_utils.template import SMSMessageTemplate
|
||||
from tests.app.db import (
|
||||
@@ -167,6 +161,7 @@ def purge_functional_test_data(user_email_prefix):
|
||||
delete_model_user(usr)
|
||||
|
||||
|
||||
# TODO maintainability what is the purpose of this command? Who would use it and why?
|
||||
@notify_command(name="insert-inbound-numbers")
|
||||
@click.option(
|
||||
"-f",
|
||||
@@ -175,7 +170,6 @@ def purge_functional_test_data(user_email_prefix):
|
||||
help="""Full path of the file to upload, file is a contains inbound numbers, one number per line.""",
|
||||
)
|
||||
def insert_inbound_numbers_from_file(file_name):
|
||||
# TODO maintainability what is the purpose of this command? Who would use it and why?
|
||||
|
||||
current_app.logger.info(f"Inserting inbound numbers from {file_name}")
|
||||
with open(file_name) as file:
|
||||
@@ -195,50 +189,6 @@ def setup_commands(application):
|
||||
application.cli.add_command(command_group)
|
||||
|
||||
|
||||
@notify_command(name="rebuild-ft-billing-for-day")
|
||||
@click.option("-s", "--service_id", required=False, type=click.UUID)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--day",
|
||||
help="The date to recalculate, as YYYY-MM-DD",
|
||||
required=True,
|
||||
type=click_dt(format="%Y-%m-%d"),
|
||||
)
|
||||
def rebuild_ft_billing_for_day(service_id, day):
|
||||
# TODO maintainability what is the purpose of this command? Who would use it and why?
|
||||
|
||||
"""
|
||||
Rebuild the data in ft_billing for the given service_id and date
|
||||
"""
|
||||
|
||||
def rebuild_ft_data(process_day, service):
|
||||
deleted_rows = delete_billing_data_for_service_for_day(process_day, service)
|
||||
current_app.logger.info(
|
||||
f"deleted {deleted_rows} existing billing rows for {service} on {process_day}"
|
||||
)
|
||||
transit_data = fetch_billing_data_for_day(
|
||||
process_day=process_day, service_id=service
|
||||
)
|
||||
# transit_data = every row that should exist
|
||||
for data in transit_data:
|
||||
# upsert existing rows
|
||||
update_fact_billing(data, process_day)
|
||||
current_app.logger.info(
|
||||
f"added/updated {len(transit_data)} billing rows for {service} on {process_day}"
|
||||
)
|
||||
|
||||
if service_id:
|
||||
# confirm the service exists
|
||||
dao_fetch_service_by_id(service_id)
|
||||
rebuild_ft_data(day, service_id)
|
||||
else:
|
||||
services = get_service_ids_that_need_billing_populated(
|
||||
get_midnight_in_utc(day), get_midnight_in_utc(day + timedelta(days=1))
|
||||
)
|
||||
for row in services:
|
||||
rebuild_ft_data(day, row.service_id)
|
||||
|
||||
|
||||
@notify_command(name="bulk-invite-user-to-service")
|
||||
@click.option(
|
||||
"-f",
|
||||
@@ -472,31 +422,6 @@ def associate_services_to_organizations():
|
||||
current_app.logger.info("finished associating services to organizations")
|
||||
|
||||
|
||||
@notify_command(name="populate-service-volume-intentions")
|
||||
@click.option(
|
||||
"-f",
|
||||
"--file_name",
|
||||
required=True,
|
||||
help="Pipe delimited file containing service_id, SMS, email",
|
||||
)
|
||||
def populate_service_volume_intentions(file_name):
|
||||
# [0] service_id
|
||||
# [1] SMS:: volume intentions for service
|
||||
# [2] Email:: volume intentions for service
|
||||
|
||||
# TODO maintainability what is the purpose of this command? Who would use it and why?
|
||||
|
||||
with open(file_name, "r") as f:
|
||||
for line in itertools.islice(f, 1, None):
|
||||
columns = line.split(",")
|
||||
current_app.logger.info(columns)
|
||||
service = dao_fetch_service_by_id(columns[0])
|
||||
service.volume_sms = columns[1]
|
||||
service.volume_email = columns[2]
|
||||
dao_update_service(service)
|
||||
current_app.logger.info("populate-service-volume-intentions complete")
|
||||
|
||||
|
||||
@notify_command(name="populate-go-live")
|
||||
@click.option(
|
||||
"-f", "--file_name", required=True, help="CSV file containing live service data"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from flask import current_app
|
||||
from sqlalchemy import asc, desc, or_, select, text, union
|
||||
from sqlalchemy import asc, delete, desc, func, or_, select, text, union, update
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
from sqlalchemy.sql import functions
|
||||
@@ -109,11 +109,12 @@ def _update_notification_status(
|
||||
def update_notification_status_by_id(
|
||||
notification_id, status, sent_by=None, provider_response=None, carrier=None
|
||||
):
|
||||
notification = (
|
||||
Notification.query.with_for_update()
|
||||
stmt = (
|
||||
select(Notification)
|
||||
.with_for_update()
|
||||
.filter(Notification.id == notification_id)
|
||||
.first()
|
||||
)
|
||||
notification = db.session.execute(stmt).scalars().first()
|
||||
|
||||
if not notification:
|
||||
current_app.logger.info(
|
||||
@@ -156,9 +157,8 @@ def update_notification_status_by_id(
|
||||
@autocommit
|
||||
def update_notification_status_by_reference(reference, status):
|
||||
# this is used to update emails
|
||||
notification = Notification.query.filter(
|
||||
Notification.reference == reference
|
||||
).first()
|
||||
stmt = select(Notification).filter(Notification.reference == reference)
|
||||
notification = db.session.execute(stmt).scalars().first()
|
||||
|
||||
if not notification:
|
||||
current_app.logger.error(
|
||||
@@ -200,19 +200,20 @@ def get_notifications_for_job(
|
||||
|
||||
|
||||
def dao_get_notification_count_for_job_id(*, job_id):
|
||||
return Notification.query.filter_by(job_id=job_id).count()
|
||||
stmt = select(func.count(Notification.id)).filter_by(job_id=job_id)
|
||||
return db.session.execute(stmt).scalar()
|
||||
|
||||
|
||||
def dao_get_notification_count_for_service(*, service_id):
|
||||
notification_count = Notification.query.filter_by(service_id=service_id).count()
|
||||
return notification_count
|
||||
stmt = select(func.count(Notification.id)).filter_by(service_id=service_id)
|
||||
return db.session.execute(stmt).scalar()
|
||||
|
||||
|
||||
def dao_get_failed_notification_count():
|
||||
failed_count = Notification.query.filter_by(
|
||||
stmt = select(func.count(Notification.id)).filter_by(
|
||||
status=NotificationStatus.FAILED
|
||||
).count()
|
||||
return failed_count
|
||||
)
|
||||
return db.session.execute(stmt).scalar()
|
||||
|
||||
|
||||
def get_notification_with_personalisation(service_id, notification_id, key_type):
|
||||
@@ -220,11 +221,12 @@ def get_notification_with_personalisation(service_id, notification_id, key_type)
|
||||
if key_type:
|
||||
filter_dict["key_type"] = key_type
|
||||
|
||||
return (
|
||||
Notification.query.filter_by(**filter_dict)
|
||||
stmt = (
|
||||
select(Notification)
|
||||
.filter_by(**filter_dict)
|
||||
.options(joinedload(Notification.template))
|
||||
.one()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def get_notification_by_id(notification_id, service_id=None, _raise=False):
|
||||
@@ -233,9 +235,13 @@ def get_notification_by_id(notification_id, service_id=None, _raise=False):
|
||||
if service_id:
|
||||
filters.append(Notification.service_id == service_id)
|
||||
|
||||
query = Notification.query.filter(*filters)
|
||||
stmt = select(Notification).filter(*filters)
|
||||
|
||||
return query.one() if _raise else query.first()
|
||||
return (
|
||||
db.session.execute(stmt).scalars().one()
|
||||
if _raise
|
||||
else db.session.execute(stmt).scalars().first()
|
||||
)
|
||||
|
||||
|
||||
def get_notifications_for_service(
|
||||
@@ -415,12 +421,13 @@ def move_notifications_to_notification_history(
|
||||
deleted += delete_count_per_call
|
||||
|
||||
# Deleting test Notifications, test notifications are not persisted to NotificationHistory
|
||||
Notification.query.filter(
|
||||
stmt = delete(Notification).filter(
|
||||
Notification.notification_type == notification_type,
|
||||
Notification.service_id == service_id,
|
||||
Notification.created_at < timestamp_to_delete_backwards_from,
|
||||
Notification.key_type == KeyType.TEST,
|
||||
).delete(synchronize_session=False)
|
||||
)
|
||||
db.session.execute(stmt)
|
||||
db.session.commit()
|
||||
|
||||
return deleted
|
||||
@@ -442,8 +449,9 @@ def dao_timeout_notifications(cutoff_time, limit=100000):
|
||||
current_statuses = [NotificationStatus.SENDING, NotificationStatus.PENDING]
|
||||
new_status = NotificationStatus.TEMPORARY_FAILURE
|
||||
|
||||
notifications = (
|
||||
Notification.query.filter(
|
||||
stmt = (
|
||||
select(Notification)
|
||||
.filter(
|
||||
Notification.created_at < cutoff_time,
|
||||
Notification.status.in_(current_statuses),
|
||||
Notification.notification_type.in_(
|
||||
@@ -451,14 +459,15 @@ def dao_timeout_notifications(cutoff_time, limit=100000):
|
||||
),
|
||||
)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
notifications = db.session.execute(stmt).scalars().all()
|
||||
|
||||
Notification.query.filter(
|
||||
Notification.id.in_([n.id for n in notifications]),
|
||||
).update(
|
||||
{"status": new_status, "updated_at": updated_at}, synchronize_session=False
|
||||
stmt = (
|
||||
update(Notification)
|
||||
.filter(Notification.id.in_([n.id for n in notifications]))
|
||||
.values({"status": new_status, "updated_at": updated_at})
|
||||
)
|
||||
db.session.execute(stmt)
|
||||
|
||||
db.session.commit()
|
||||
return notifications
|
||||
@@ -466,15 +475,23 @@ def dao_timeout_notifications(cutoff_time, limit=100000):
|
||||
|
||||
@autocommit
|
||||
def dao_update_notifications_by_reference(references, update_dict):
|
||||
updated_count = Notification.query.filter(
|
||||
Notification.reference.in_(references)
|
||||
).update(update_dict, synchronize_session=False)
|
||||
stmt = (
|
||||
update(Notification)
|
||||
.filter(Notification.reference.in_(references))
|
||||
.values(update_dict)
|
||||
)
|
||||
result = db.session.execute(stmt)
|
||||
updated_count = result.rowcount
|
||||
|
||||
updated_history_count = 0
|
||||
if updated_count != len(references):
|
||||
updated_history_count = NotificationHistory.query.filter(
|
||||
NotificationHistory.reference.in_(references)
|
||||
).update(update_dict, synchronize_session=False)
|
||||
stmt = (
|
||||
update(NotificationHistory)
|
||||
.filter(NotificationHistory.reference.in_(references))
|
||||
.values(update_dict)
|
||||
)
|
||||
result = db.session.execute(stmt)
|
||||
updated_history_count = result.rowcount
|
||||
|
||||
return updated_count, updated_history_count
|
||||
|
||||
@@ -541,18 +558,21 @@ def dao_get_notifications_by_recipient_or_reference(
|
||||
|
||||
|
||||
def dao_get_notification_by_reference(reference):
|
||||
return Notification.query.filter(Notification.reference == reference).one()
|
||||
stmt = select(Notification).filter(Notification.reference == reference)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def dao_get_notification_history_by_reference(reference):
|
||||
try:
|
||||
# This try except is necessary because in test keys and research mode does not create notification history.
|
||||
# Otherwise we could just search for the NotificationHistory object
|
||||
return Notification.query.filter(Notification.reference == reference).one()
|
||||
stmt = select(Notification).filter(Notification.reference == reference)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
except NoResultFound:
|
||||
return NotificationHistory.query.filter(
|
||||
stmt = select(NotificationHistory).filter(
|
||||
NotificationHistory.reference == reference
|
||||
).one()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def dao_get_notifications_processing_time_stats(start_date, end_date):
|
||||
@@ -590,11 +610,12 @@ def dao_get_notifications_processing_time_stats(start_date, end_date):
|
||||
|
||||
|
||||
def dao_get_last_notification_added_for_job_id(job_id):
|
||||
last_notification_added = (
|
||||
Notification.query.filter(Notification.job_id == job_id)
|
||||
stmt = (
|
||||
select(Notification)
|
||||
.filter(Notification.job_id == job_id)
|
||||
.order_by(Notification.job_row_number.desc())
|
||||
.first()
|
||||
)
|
||||
last_notification_added = db.session.execute(stmt).scalars().first()
|
||||
|
||||
return last_notification_added
|
||||
|
||||
@@ -602,11 +623,12 @@ def dao_get_last_notification_added_for_job_id(job_id):
|
||||
def notifications_not_yet_sent(should_be_sending_after_seconds, notification_type):
|
||||
older_than_date = utc_now() - timedelta(seconds=should_be_sending_after_seconds)
|
||||
|
||||
notifications = Notification.query.filter(
|
||||
stmt = select(Notification).filter(
|
||||
Notification.created_at <= older_than_date,
|
||||
Notification.notification_type == notification_type,
|
||||
Notification.status == NotificationStatus.CREATED,
|
||||
).all()
|
||||
)
|
||||
notifications = db.session.execute(stmt).scalars().all()
|
||||
return notifications
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from sqlalchemy import delete, select, update
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
from app import db
|
||||
@@ -6,55 +7,57 @@ from app.models import Domain, Organization, Service, User
|
||||
|
||||
|
||||
def dao_get_organizations():
|
||||
return Organization.query.order_by(
|
||||
stmt = select(Organization).order_by(
|
||||
Organization.active.desc(), Organization.name.asc()
|
||||
).all()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
def dao_count_organizations_with_live_services():
|
||||
return (
|
||||
db.session.query(Organization.id)
|
||||
stmt = (
|
||||
select(func.count(func.distinct(Organization.id)))
|
||||
.join(Organization.services)
|
||||
.filter(
|
||||
Service.active.is_(True),
|
||||
Service.restricted.is_(False),
|
||||
Service.count_as_live.is_(True),
|
||||
)
|
||||
.distinct()
|
||||
.count()
|
||||
)
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
def dao_get_organization_services(organization_id):
|
||||
return Organization.query.filter_by(id=organization_id).one().services
|
||||
stmt = select(Organization).filter_by(id=organization_id)
|
||||
return db.session.execute(stmt).scalars().one().services
|
||||
|
||||
|
||||
def dao_get_organization_live_services(organization_id):
|
||||
return Service.query.filter_by(
|
||||
organization_id=organization_id, restricted=False
|
||||
).all()
|
||||
stmt = select(Service).filter_by(organization_id=organization_id, restricted=False)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
def dao_get_organization_by_id(organization_id):
|
||||
return Organization.query.filter_by(id=organization_id).one()
|
||||
stmt = select(Organization).filter_by(id=organization_id)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def dao_get_organization_by_email_address(email_address):
|
||||
email_address = email_address.lower().replace(".gsi.gov.uk", ".gov.uk")
|
||||
|
||||
for domain in Domain.query.order_by(func.char_length(Domain.domain).desc()).all():
|
||||
stmt = select(Domain).order_by(func.char_length(Domain.domain).desc())
|
||||
domains = db.session.execute(stmt).scalars().all()
|
||||
for domain in domains:
|
||||
if email_address.endswith(
|
||||
"@{}".format(domain.domain)
|
||||
) or email_address.endswith(".{}".format(domain.domain)):
|
||||
return Organization.query.filter_by(id=domain.organization_id).one()
|
||||
stmt = select(Organization).filter_by(id=domain.organization_id)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def dao_get_organization_by_service_id(service_id):
|
||||
return (
|
||||
Organization.query.join(Organization.services).filter_by(id=service_id).first()
|
||||
)
|
||||
stmt = select(Organization).join(Organization.services).filter_by(id=service_id)
|
||||
return db.session.execute(stmt).scalars().first()
|
||||
|
||||
|
||||
@autocommit
|
||||
@@ -65,10 +68,14 @@ def dao_create_organization(organization):
|
||||
@autocommit
|
||||
def dao_update_organization(organization_id, **kwargs):
|
||||
domains = kwargs.pop("domains", None)
|
||||
num_updated = Organization.query.filter_by(id=organization_id).update(kwargs)
|
||||
stmt = (
|
||||
update(Organization).where(Organization.id == organization_id).values(**kwargs)
|
||||
)
|
||||
num_updated = db.session.execute(stmt).rowcount
|
||||
|
||||
if isinstance(domains, list):
|
||||
Domain.query.filter_by(organization_id=organization_id).delete()
|
||||
stmt = delete(Domain).filter_by(organization_id=organization_id)
|
||||
db.session.execute(stmt)
|
||||
db.session.bulk_save_objects(
|
||||
[
|
||||
Domain(domain=domain.lower(), organization_id=organization_id)
|
||||
@@ -76,7 +83,7 @@ def dao_update_organization(organization_id, **kwargs):
|
||||
]
|
||||
)
|
||||
|
||||
organization = Organization.query.get(organization_id)
|
||||
organization = db.session.get(Organization, organization_id)
|
||||
if "organization_type" in kwargs:
|
||||
_update_organization_services(
|
||||
organization, "organization_type", only_where_none=False
|
||||
@@ -101,7 +108,8 @@ def _update_organization_services(organization, attribute, only_where_none=True)
|
||||
@autocommit
|
||||
@version_class(Service)
|
||||
def dao_add_service_to_organization(service, organization_id):
|
||||
organization = Organization.query.filter_by(id=organization_id).one()
|
||||
stmt = select(Organization).filter_by(id=organization_id)
|
||||
organization = db.session.execute(stmt).scalars().one()
|
||||
|
||||
service.organization_id = organization_id
|
||||
service.organization_type = organization.organization_type
|
||||
@@ -122,7 +130,8 @@ def dao_get_users_for_organization(organization_id):
|
||||
@autocommit
|
||||
def dao_add_user_to_organization(organization_id, user_id):
|
||||
organization = dao_get_organization_by_id(organization_id)
|
||||
user = User.query.filter_by(id=user_id).one()
|
||||
stmt = select(User).filter_by(id=user_id)
|
||||
user = db.session.execute(stmt).scalars().one()
|
||||
user.organizations.append(organization)
|
||||
db.session.add(organization)
|
||||
return user
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
from sqlalchemy import select
|
||||
|
||||
from app import db
|
||||
from app.dao.dao_utils import autocommit
|
||||
from app.models import TemplateFolder
|
||||
|
||||
|
||||
def dao_get_template_folder_by_id_and_service_id(template_folder_id, service_id):
|
||||
return TemplateFolder.query.filter(
|
||||
stmt = select(TemplateFolder).filter(
|
||||
TemplateFolder.id == template_folder_id, TemplateFolder.service_id == service_id
|
||||
).one()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def dao_get_valid_template_folders_by_id(folder_ids):
|
||||
return TemplateFolder.query.filter(TemplateFolder.id.in_(folder_ids)).all()
|
||||
stmt = select(TemplateFolder).filter(TemplateFolder.id.in_(folder_ids))
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
@autocommit
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import asc, desc
|
||||
from sqlalchemy import asc, desc, select
|
||||
|
||||
from app import db
|
||||
from app.dao.dao_utils import VersionOptions, autocommit, version_class
|
||||
@@ -46,24 +46,29 @@ def dao_redact_template(template, user_id):
|
||||
|
||||
def dao_get_template_by_id_and_service_id(template_id, service_id, version=None):
|
||||
if version is not None:
|
||||
return TemplateHistory.query.filter_by(
|
||||
stmt = select(TemplateHistory).filter_by(
|
||||
id=template_id, hidden=False, service_id=service_id, version=version
|
||||
).one()
|
||||
return Template.query.filter_by(
|
||||
)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
stmt = select(Template).filter_by(
|
||||
id=template_id, hidden=False, service_id=service_id
|
||||
).one()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def dao_get_template_by_id(template_id, version=None):
|
||||
if version is not None:
|
||||
return TemplateHistory.query.filter_by(id=template_id, version=version).one()
|
||||
return Template.query.filter_by(id=template_id).one()
|
||||
stmt = select(TemplateHistory).filter_by(id=template_id, version=version)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
stmt = select(Template).filter_by(id=template_id)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def dao_get_all_templates_for_service(service_id, template_type=None):
|
||||
if template_type is not None:
|
||||
return (
|
||||
Template.query.filter_by(
|
||||
stmt = (
|
||||
select(Template)
|
||||
.filter_by(
|
||||
service_id=service_id,
|
||||
template_type=template_type,
|
||||
hidden=False,
|
||||
@@ -73,26 +78,27 @@ def dao_get_all_templates_for_service(service_id, template_type=None):
|
||||
asc(Template.name),
|
||||
asc(Template.template_type),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
return (
|
||||
Template.query.filter_by(service_id=service_id, hidden=False, archived=False)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
stmt = (
|
||||
select(Template)
|
||||
.filter_by(service_id=service_id, hidden=False, archived=False)
|
||||
.order_by(
|
||||
asc(Template.name),
|
||||
asc(Template.template_type),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
def dao_get_template_versions(service_id, template_id):
|
||||
return (
|
||||
TemplateHistory.query.filter_by(
|
||||
stmt = (
|
||||
select(TemplateHistory)
|
||||
.filter_by(
|
||||
service_id=service_id,
|
||||
id=template_id,
|
||||
hidden=False,
|
||||
)
|
||||
.order_by(desc(TemplateHistory.version))
|
||||
.all()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
@@ -4,7 +4,7 @@ from secrets import randbelow
|
||||
|
||||
import sqlalchemy
|
||||
from flask import current_app
|
||||
from sqlalchemy import func, text
|
||||
from sqlalchemy import delete, func, select, text
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app import db
|
||||
@@ -37,8 +37,8 @@ def get_login_gov_user(login_uuid, email_address):
|
||||
login.gov uuids are. Eventually the code that checks by email address
|
||||
should be removed.
|
||||
"""
|
||||
|
||||
user = User.query.filter_by(login_uuid=login_uuid).first()
|
||||
stmt = select(User).filter_by(login_uuid=login_uuid)
|
||||
user = db.session.execute(stmt).scalars().first()
|
||||
if user:
|
||||
if user.email_address != email_address:
|
||||
try:
|
||||
@@ -54,7 +54,8 @@ def get_login_gov_user(login_uuid, email_address):
|
||||
|
||||
return user
|
||||
# Remove this 1 July 2025, all users should have login.gov uuids by now
|
||||
user = User.query.filter(User.email_address.ilike(email_address)).first()
|
||||
stmt = select(User).filter(User.email_address.ilike(email_address))
|
||||
user = db.session.execute(stmt).scalars().first()
|
||||
|
||||
if user:
|
||||
save_user_attribute(user, {"login_uuid": login_uuid})
|
||||
@@ -102,24 +103,27 @@ def create_user_code(user, code, code_type):
|
||||
def get_user_code(user, code, code_type):
|
||||
# Get the most recent codes to try and reduce the
|
||||
# time searching for the correct code.
|
||||
codes = VerifyCode.query.filter_by(user=user, code_type=code_type).order_by(
|
||||
VerifyCode.created_at.desc()
|
||||
stmt = (
|
||||
select(VerifyCode)
|
||||
.filter_by(user=user, code_type=code_type)
|
||||
.order_by(VerifyCode.created_at.desc())
|
||||
)
|
||||
codes = db.session.execute(stmt).scalars().all()
|
||||
return next((x for x in codes if x.check_code(code)), None)
|
||||
|
||||
|
||||
def delete_codes_older_created_more_than_a_day_ago():
|
||||
deleted = (
|
||||
db.session.query(VerifyCode)
|
||||
.filter(VerifyCode.created_at < utc_now() - timedelta(hours=24))
|
||||
.delete()
|
||||
stmt = delete(VerifyCode).filter(
|
||||
VerifyCode.created_at < utc_now() - timedelta(hours=24)
|
||||
)
|
||||
|
||||
deleted = db.session.execute(stmt)
|
||||
db.session.commit()
|
||||
return deleted
|
||||
|
||||
|
||||
def use_user_code(id):
|
||||
verify_code = VerifyCode.query.get(id)
|
||||
verify_code = db.session.get(VerifyCode, id)
|
||||
verify_code.code_used = True
|
||||
db.session.add(verify_code)
|
||||
db.session.commit()
|
||||
@@ -131,36 +135,42 @@ def delete_model_user(user):
|
||||
|
||||
|
||||
def delete_user_verify_codes(user):
|
||||
VerifyCode.query.filter_by(user=user).delete()
|
||||
stmt = delete(VerifyCode).filter_by(user=user)
|
||||
db.session.execute(stmt)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def count_user_verify_codes(user):
|
||||
query = VerifyCode.query.filter(
|
||||
stmt = select(func.count(VerifyCode.id)).filter(
|
||||
VerifyCode.user == user,
|
||||
VerifyCode.expiry_datetime > utc_now(),
|
||||
VerifyCode.code_used.is_(False),
|
||||
)
|
||||
return query.count()
|
||||
result = db.session.execute(stmt).scalar()
|
||||
return result or 0
|
||||
|
||||
|
||||
def get_user_by_id(user_id=None):
|
||||
if user_id:
|
||||
return User.query.filter_by(id=user_id).one()
|
||||
return User.query.filter_by().all()
|
||||
stmt = select(User).filter_by(id=user_id)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
return get_users()
|
||||
|
||||
|
||||
def get_users():
|
||||
return User.query.all()
|
||||
stmt = select(User)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
def get_user_by_email(email):
|
||||
return User.query.filter(func.lower(User.email_address) == func.lower(email)).one()
|
||||
stmt = select(User).filter(func.lower(User.email_address) == func.lower(email))
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def get_users_by_partial_email(email):
|
||||
email = escape_special_characters(email)
|
||||
return User.query.filter(User.email_address.ilike("%{}%".format(email))).all()
|
||||
stmt = select(User).filter(User.email_address.ilike("%{}%".format(email)))
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
def increment_failed_login_count(user):
|
||||
@@ -188,16 +198,17 @@ def get_user_and_accounts(user_id):
|
||||
# TODO: With sqlalchemy 2.0 change as below because of the breaking change
|
||||
# at User.organizations.services, we need to verify that the below subqueryload
|
||||
# that we have put is functionally doing the same thing as before
|
||||
return (
|
||||
User.query.filter(User.id == user_id)
|
||||
stmt = (
|
||||
select(User)
|
||||
.filter(User.id == user_id)
|
||||
.options(
|
||||
# eagerly load the user's services and organizations, and also the service's org and vice versa
|
||||
# (so we can see if the user knows about it)
|
||||
joinedload(User.services).joinedload(Service.organization),
|
||||
joinedload(User.organizations).subqueryload(Organization.services),
|
||||
)
|
||||
.one()
|
||||
)
|
||||
return db.session.execute(stmt).scalars().unique().one()
|
||||
|
||||
|
||||
@autocommit
|
||||
|
||||
@@ -98,17 +98,7 @@ def send_sms_to_provider(notification):
|
||||
|
||||
# TODO This is temporary to test the capability of validating phone numbers
|
||||
# The future home of the validation is TBD
|
||||
if "+" not in recipient:
|
||||
recipient_lookup = f"+{recipient}"
|
||||
else:
|
||||
recipient_lookup = recipient
|
||||
if recipient_lookup in current_app.config[
|
||||
"SIMULATED_SMS_NUMBERS"
|
||||
] and os.getenv("NOTIFY_ENVIRONMENT") in ["development", "test"]:
|
||||
current_app.logger.info(hilite("#validate-phone-number fired"))
|
||||
aws_pinpoint_client.validate_phone_number("01", recipient)
|
||||
else:
|
||||
current_app.logger.info(hilite("#validate-phone-number not fired"))
|
||||
_experimentally_validate_phone_numbers(recipient)
|
||||
|
||||
sender_numbers = get_sender_numbers(notification)
|
||||
if notification.reply_to_text not in sender_numbers:
|
||||
@@ -145,6 +135,18 @@ def send_sms_to_provider(notification):
|
||||
return message_id
|
||||
|
||||
|
||||
def _experimentally_validate_phone_numbers(recipient):
|
||||
if "+" not in recipient:
|
||||
recipient_lookup = f"+{recipient}"
|
||||
else:
|
||||
recipient_lookup = recipient
|
||||
if recipient_lookup in current_app.config["SIMULATED_SMS_NUMBERS"] and os.getenv(
|
||||
"NOTIFY_ENVIRONMENT"
|
||||
) in ["development", "test"]:
|
||||
current_app.logger.info(hilite("#validate-phone-number fired"))
|
||||
aws_pinpoint_client.validate_phone_number("01", recipient)
|
||||
|
||||
|
||||
def _get_verify_code(notification):
|
||||
key = f"2facode-{notification.id}".replace(" ", "")
|
||||
recipient = redis_store.get(key)
|
||||
|
||||
@@ -453,16 +453,6 @@ def get_all_notifications_for_service(service_id):
|
||||
data = notifications_filter_schema.load(MultiDict(request.get_json()))
|
||||
current_app.logger.debug(f"use POST, request {request.get_json()} data {data}")
|
||||
|
||||
if data.get("to"):
|
||||
notification_type = (
|
||||
data.get("template_type")[0] if data.get("template_type") else None
|
||||
)
|
||||
return search_for_notification_by_to_field(
|
||||
service_id=service_id,
|
||||
search_term=data["to"],
|
||||
statuses=data.get("status"),
|
||||
notification_type=notification_type,
|
||||
)
|
||||
page = data["page"] if "page" in data else 1
|
||||
page_size = (
|
||||
data["page_size"]
|
||||
@@ -583,53 +573,6 @@ def get_notification_for_service(service_id, notification_id):
|
||||
)
|
||||
|
||||
|
||||
def search_for_notification_by_to_field(
|
||||
service_id, search_term, statuses, notification_type
|
||||
):
|
||||
results = notifications_dao.dao_get_notifications_by_recipient_or_reference(
|
||||
service_id=service_id,
|
||||
search_term=search_term,
|
||||
statuses=statuses,
|
||||
notification_type=notification_type,
|
||||
page=1,
|
||||
page_size=current_app.config["PAGE_SIZE"],
|
||||
)
|
||||
|
||||
# We try and get the next page of results to work out if we need provide a pagination link to the next page
|
||||
# in our response. Note, this was previously be done by having
|
||||
# notifications_dao.dao_get_notifications_by_recipient_or_reference use count=False when calling
|
||||
# Flask-Sqlalchemys `paginate'. But instead we now use this way because it is much more performant for
|
||||
# services with many results (unlike using Flask SqlAlchemy `paginate` with `count=True`, this approach
|
||||
# doesn't do an additional query to count all the results of which there could be millions but instead only
|
||||
# asks for a single extra page of results).
|
||||
next_page_of_pagination = notifications_dao.dao_get_notifications_by_recipient_or_reference(
|
||||
service_id=service_id,
|
||||
search_term=search_term,
|
||||
statuses=statuses,
|
||||
notification_type=notification_type,
|
||||
page=2,
|
||||
page_size=current_app.config["PAGE_SIZE"],
|
||||
error_out=False, # False so that if there are no results, it doesn't end in aborting with a 404
|
||||
)
|
||||
|
||||
return (
|
||||
jsonify(
|
||||
notifications=notification_with_template_schema.dump(
|
||||
results.items, many=True
|
||||
),
|
||||
links=get_prev_next_pagination_links(
|
||||
1,
|
||||
len(next_page_of_pagination.items),
|
||||
".get_all_notifications_for_service",
|
||||
statuses=statuses,
|
||||
notification_type=notification_type,
|
||||
service_id=service_id,
|
||||
),
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
|
||||
@service_blueprint.route("/<uuid:service_id>/notifications/monthly", methods=["GET"])
|
||||
def get_monthly_notification_stats(service_id):
|
||||
# check service_id validity
|
||||
|
||||
@@ -32,7 +32,7 @@ service_invite = Blueprint("service_invite", __name__)
|
||||
register_errors(service_invite)
|
||||
|
||||
|
||||
def _create_service_invite(invited_user, invite_link_host):
|
||||
def _create_service_invite(invited_user, nonce):
|
||||
|
||||
template_id = current_app.config["INVITATION_EMAIL_TEMPLATE_ID"]
|
||||
|
||||
@@ -40,12 +40,6 @@ def _create_service_invite(invited_user, invite_link_host):
|
||||
|
||||
service = Service.query.get(current_app.config["NOTIFY_SERVICE_ID"])
|
||||
|
||||
token = generate_token(
|
||||
str(invited_user.email_address),
|
||||
current_app.config["SECRET_KEY"],
|
||||
current_app.config["DANGEROUS_SALT"],
|
||||
)
|
||||
|
||||
# The raw permissions are in the form "a,b,c,d"
|
||||
# but need to be in the form ["a", "b", "c", "d"]
|
||||
data = {}
|
||||
@@ -59,7 +53,8 @@ def _create_service_invite(invited_user, invite_link_host):
|
||||
data["invited_user_email"] = invited_user.email_address
|
||||
|
||||
url = os.environ["LOGIN_DOT_GOV_REGISTRATION_URL"]
|
||||
url = url.replace("NONCE", token)
|
||||
|
||||
url = url.replace("NONCE", nonce) # handed from data sent from admin.
|
||||
|
||||
user_data_url_safe = get_user_data_url_safe(data)
|
||||
|
||||
@@ -94,10 +89,16 @@ def _create_service_invite(invited_user, invite_link_host):
|
||||
@service_invite.route("/service/<service_id>/invite", methods=["POST"])
|
||||
def create_invited_user(service_id):
|
||||
request_json = request.get_json()
|
||||
try:
|
||||
nonce = request_json.pop("nonce")
|
||||
except KeyError:
|
||||
current_app.logger.exception("nonce not found in submitted data.")
|
||||
raise
|
||||
|
||||
invited_user = invited_user_schema.load(request_json)
|
||||
save_invited_user(invited_user)
|
||||
|
||||
_create_service_invite(invited_user, request_json.get("invite_link_host"))
|
||||
_create_service_invite(invited_user, nonce)
|
||||
|
||||
return jsonify(data=invited_user_schema.dump(invited_user)), 201
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ def upgrade():
|
||||
#
|
||||
# go_live = datetime.datetime.strptime('2016-05-18', '%Y-%m-%d')
|
||||
# notifications_history_start_date = datetime.datetime.strptime('2016-06-26 23:21:55', '%Y-%m-%d %H:%M:%S')
|
||||
# jobs = session.query(Job).join(Template).filter(Job.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091',
|
||||
# stmt = select(Job).join(Template).filter(Job.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091',
|
||||
# Job.created_at >= go_live,
|
||||
# Job.created_at < notifications_history_start_date).all()
|
||||
#
|
||||
# jobs = db.session.execute(stmt).scalars().all()
|
||||
# for job in jobs:
|
||||
# for i in range(0, job.notifications_delivered):
|
||||
# notification = NotificationHistory(id=uuid.uuid4(),
|
||||
@@ -76,12 +76,11 @@ def downgrade():
|
||||
#
|
||||
# go_live = datetime.datetime.strptime('2016-05-18', '%Y-%m-%d')
|
||||
# notifications_history_start_date = datetime.datetime.strptime('2016-06-26 23:21:55', '%Y-%m-%d %H:%M:%S')
|
||||
#
|
||||
# session.query(NotificationHistory).filter(
|
||||
# stmt = delete(NotificationHistory).where(
|
||||
# NotificationHistory.created_at >= go_live,
|
||||
# NotificationHistory.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091',
|
||||
# NotificationHistory.created_at < notifications_history_start_date).delete()
|
||||
#
|
||||
# NotificationHistory.created_at < notifications_history_start_date)
|
||||
# session.execute(stmt)
|
||||
# session.commit()
|
||||
# ### end Alembic commands ###
|
||||
pass
|
||||
|
||||
@@ -122,19 +122,15 @@ class SanitiseText:
|
||||
def is_punjabi(cls, value):
|
||||
# Gukmukhi script or Shahmukhi script
|
||||
|
||||
if regex.search(r"[\u0A00-\u0A7F]+", value):
|
||||
return True
|
||||
elif regex.search(r"[\u0600-\u06FF]+", value):
|
||||
return True
|
||||
elif regex.search(r"[\u0750-\u077F]+", value):
|
||||
return True
|
||||
elif regex.search(r"[\u08A0-\u08FF]+", value):
|
||||
return True
|
||||
elif regex.search(r"[\uFB50-\uFDFF]+", value):
|
||||
return True
|
||||
elif regex.search(r"[\uFE70-\uFEFF]+", value):
|
||||
return True
|
||||
elif regex.search(r"[\u0900-\u097F]+", value):
|
||||
if (
|
||||
regex.search(r"[\u0A00-\u0A7F]+", value)
|
||||
or regex.search(r"[\u0600-\u06FF]+", value)
|
||||
or regex.search(r"[\u0750-\u077F]+", value)
|
||||
or regex.search(r"[\u08A0-\u08FF]+", value)
|
||||
or regex.search(r"[\uFB50-\uFDFF]+", value)
|
||||
or regex.search(r"[\uFE70-\uFEFF]+", value)
|
||||
or regex.search(r"[\u0900-\u097F]+", value)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -156,33 +152,27 @@ class SanitiseText:
|
||||
|
||||
@classmethod
|
||||
def _is_extended_language_group_two(cls, value):
|
||||
if regex.search(r"\p{IsBuhid}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsCanadian_Aboriginal}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsCherokee}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsDevanagari}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsEthiopic}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsGeorgian}", value):
|
||||
if (
|
||||
regex.search(r"\p{IsBuhid}", value)
|
||||
or regex.search(r"\p{IsCanadian_Aboriginal}", value)
|
||||
or regex.search(r"\p{IsCherokee}", value)
|
||||
or regex.search(r"\p{IsDevanagari}", value)
|
||||
or regex.search(r"\p{IsEthiopic}", value)
|
||||
or regex.search(r"\p{IsGeorgian}", value)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _is_extended_language_group_three(cls, value):
|
||||
if regex.search(r"\p{IsGreek}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsGujarati}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsHanunoo}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsHebrew}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsLimbu}", value):
|
||||
return True
|
||||
if regex.search(r"\p{IsKannada}", value):
|
||||
if (
|
||||
regex.search(r"\p{IsGreek}", value)
|
||||
or regex.search(r"\p{IsGujarati}", value)
|
||||
or regex.search(r"\p{IsHanunoo}", value)
|
||||
or regex.search(r"\p{IsHebrew}", value)
|
||||
or regex.search(r"\p{IsLimbu}", value)
|
||||
or regex.search(r"\p{IsKannada}", value)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
8
poetry.lock
generated
8
poetry.lock
generated
@@ -4519,13 +4519,13 @@ test = ["websockets"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.0.3"
|
||||
version = "3.0.6"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"},
|
||||
{file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"},
|
||||
{file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"},
|
||||
{file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -4803,4 +4803,4 @@ multidict = ">=4.0"
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12.2"
|
||||
content-hash = "42172a923e16c5b0965ab06f717d41e8491ee35f7be674091b38014c48b7a89e"
|
||||
content-hash = "cf18ae74630e47eec18cc6c5fea9e554476809d20589d82c54a8d761bb2c3de0"
|
||||
|
||||
@@ -47,7 +47,7 @@ psycopg2-binary = "==2.9.9"
|
||||
pyjwt = "==2.8.0"
|
||||
python-dotenv = "==1.0.1"
|
||||
sqlalchemy = "==2.0.31"
|
||||
werkzeug = "^3.0.3"
|
||||
werkzeug = "^3.0.6"
|
||||
faker = "^26.0.0"
|
||||
async-timeout = "^4.0.3"
|
||||
bleach = "^6.1.0"
|
||||
|
||||
@@ -4,9 +4,11 @@ from functools import partial
|
||||
|
||||
import pytest
|
||||
from freezegun import freeze_time
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from app import db
|
||||
from app.dao.notifications_dao import (
|
||||
dao_create_notification,
|
||||
dao_delete_notifications_by_id,
|
||||
@@ -55,7 +57,10 @@ def test_should_by_able_to_update_status_by_reference(
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.SENDING
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.SENDING
|
||||
)
|
||||
notification.reference = "reference"
|
||||
dao_update_notification(notification)
|
||||
|
||||
@@ -64,7 +69,8 @@ def test_should_by_able_to_update_status_by_reference(
|
||||
)
|
||||
assert updated.status == NotificationStatus.DELIVERED
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
|
||||
|
||||
@@ -81,7 +87,10 @@ def test_should_by_able_to_update_status_by_id(
|
||||
dao_create_notification(notification)
|
||||
assert notification.status == NotificationStatus.SENDING
|
||||
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.SENDING
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.SENDING
|
||||
)
|
||||
|
||||
with freeze_time("2000-01-02 12:00:00"):
|
||||
updated = update_notification_status_by_id(
|
||||
@@ -92,7 +101,8 @@ def test_should_by_able_to_update_status_by_id(
|
||||
assert updated.status == NotificationStatus.DELIVERED
|
||||
assert updated.updated_at == datetime(2000, 1, 2, 12, 0, 0)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
assert notification.updated_at == datetime(2000, 1, 2, 12, 0, 0)
|
||||
assert notification.status == NotificationStatus.DELIVERED
|
||||
@@ -107,15 +117,17 @@ def test_should_not_update_status_by_id_if_not_sending_and_does_not_update_job(
|
||||
job=sample_job,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
assert not update_notification_status_by_id(
|
||||
notification.id, NotificationStatus.FAILED
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
assert sample_job == Job.query.get(notification.job_id)
|
||||
assert sample_job == db.session.get(Job, notification.job_id)
|
||||
|
||||
|
||||
def test_should_not_update_status_by_reference_if_not_sending_and_does_not_update_job(
|
||||
@@ -128,20 +140,22 @@ def test_should_not_update_status_by_reference_if_not_sending_and_does_not_updat
|
||||
job=sample_job,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
assert not update_notification_status_by_reference(
|
||||
"reference", NotificationStatus.FAILED
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
assert sample_job == Job.query.get(notification.job_id)
|
||||
assert sample_job == db.session.get(Job, notification.job_id)
|
||||
|
||||
|
||||
def test_should_update_status_by_id_if_created(sample_template, sample_notification):
|
||||
assert (
|
||||
Notification.query.get(sample_notification.id).status
|
||||
db.session.get(Notification, sample_notification.id).status
|
||||
== NotificationStatus.CREATED
|
||||
)
|
||||
updated = update_notification_status_by_id(
|
||||
@@ -149,7 +163,7 @@ def test_should_update_status_by_id_if_created(sample_template, sample_notificat
|
||||
NotificationStatus.FAILED,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(sample_notification.id).status
|
||||
db.session.get(Notification, sample_notification.id).status
|
||||
== NotificationStatus.FAILED
|
||||
)
|
||||
assert updated.status == NotificationStatus.FAILED
|
||||
@@ -244,11 +258,17 @@ def test_should_not_update_status_by_reference_if_not_sending(sample_template):
|
||||
status=NotificationStatus.CREATED,
|
||||
reference="reference",
|
||||
)
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.CREATED
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.CREATED
|
||||
)
|
||||
updated = update_notification_status_by_reference(
|
||||
"reference", NotificationStatus.FAILED
|
||||
)
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.CREATED
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.CREATED
|
||||
)
|
||||
assert not updated
|
||||
|
||||
|
||||
@@ -264,14 +284,18 @@ def test_should_by_able_to_update_status_by_id_from_pending_to_delivered(
|
||||
assert update_notification_status_by_id(
|
||||
notification_id=notification.id, status=NotificationStatus.PENDING
|
||||
)
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.PENDING
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.PENDING
|
||||
)
|
||||
|
||||
assert update_notification_status_by_id(
|
||||
notification.id,
|
||||
NotificationStatus.DELIVERED,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
|
||||
|
||||
@@ -289,7 +313,10 @@ def test_should_by_able_to_update_status_by_id_from_pending_to_temporary_failure
|
||||
notification_id=notification.id,
|
||||
status=NotificationStatus.PENDING,
|
||||
)
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.PENDING
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.PENDING
|
||||
)
|
||||
|
||||
assert update_notification_status_by_id(
|
||||
notification.id,
|
||||
@@ -297,7 +324,7 @@ def test_should_by_able_to_update_status_by_id_from_pending_to_temporary_failure
|
||||
)
|
||||
|
||||
assert (
|
||||
Notification.query.get(notification.id).status
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.TEMPORARY_FAILURE
|
||||
)
|
||||
|
||||
@@ -312,14 +339,17 @@ def test_should_by_able_to_update_status_by_id_from_sending_to_permanent_failure
|
||||
)
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.SENDING
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.SENDING
|
||||
)
|
||||
|
||||
assert update_notification_status_by_id(
|
||||
notification.id,
|
||||
status=NotificationStatus.PERMANENT_FAILURE,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.PERMANENT_FAILURE
|
||||
)
|
||||
|
||||
@@ -331,7 +361,10 @@ def test_should_not_update_status_once_notification_status_is_delivered(
|
||||
template=sample_email_template,
|
||||
status=NotificationStatus.SENDING,
|
||||
)
|
||||
assert Notification.query.get(notification.id).status == NotificationStatus.SENDING
|
||||
assert (
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.SENDING
|
||||
)
|
||||
|
||||
notification.reference = "reference"
|
||||
dao_update_notification(notification)
|
||||
@@ -340,7 +373,8 @@ def test_should_not_update_status_once_notification_status_is_delivered(
|
||||
NotificationStatus.DELIVERED,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
|
||||
update_notification_status_by_reference(
|
||||
@@ -348,7 +382,8 @@ def test_should_not_update_status_once_notification_status_is_delivered(
|
||||
NotificationStatus.FAILED,
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(notification.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
|
||||
|
||||
@@ -370,7 +405,7 @@ def test_create_notification_creates_notification_with_personalisation(
|
||||
sample_template_with_placeholders,
|
||||
sample_job,
|
||||
):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
|
||||
data = create_notification(
|
||||
template=sample_template_with_placeholders,
|
||||
@@ -379,8 +414,8 @@ def test_create_notification_creates_notification_with_personalisation(
|
||||
status=NotificationStatus.CREATED,
|
||||
)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert data.to == notification_from_db.to
|
||||
assert data.job_id == notification_from_db.job_id
|
||||
@@ -393,15 +428,15 @@ def test_create_notification_creates_notification_with_personalisation(
|
||||
|
||||
|
||||
def test_save_notification_creates_sms(sample_template, sample_job):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
|
||||
data = _notification_json(sample_template, job_id=sample_job.id)
|
||||
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert "1" == notification_from_db.to
|
||||
assert data["job_id"] == notification_from_db.job_id
|
||||
@@ -412,16 +447,36 @@ def test_save_notification_creates_sms(sample_template, sample_job):
|
||||
assert notification_from_db.status == NotificationStatus.CREATED
|
||||
|
||||
|
||||
def _get_notification_query_all():
|
||||
stmt = select(Notification)
|
||||
return db.session.execute(stmt).scalars().all()
|
||||
|
||||
|
||||
def _get_notification_query_one():
|
||||
stmt = select(Notification)
|
||||
return db.session.execute(stmt).scalars().one()
|
||||
|
||||
|
||||
def _get_notification_query_count():
|
||||
stmt = select(func.count(Notification.id))
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
def _get_notification_history_query_count():
|
||||
stmt = select(func.count(NotificationHistory.id))
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
def test_save_notification_and_create_email(sample_email_template, sample_job):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
|
||||
data = _notification_json(sample_email_template, job_id=sample_job.id)
|
||||
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert "1" == notification_from_db.to
|
||||
assert data["job_id"] == notification_from_db.job_id
|
||||
@@ -433,29 +488,29 @@ def test_save_notification_and_create_email(sample_email_template, sample_job):
|
||||
|
||||
|
||||
def test_save_notification(sample_email_template, sample_job):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_email_template, job_id=sample_job.id)
|
||||
|
||||
notification_1 = Notification(**data)
|
||||
notification_2 = Notification(**data)
|
||||
dao_create_notification(notification_1)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert _get_notification_query_count() == 1
|
||||
|
||||
dao_create_notification(notification_2)
|
||||
|
||||
assert Notification.query.count() == 2
|
||||
assert _get_notification_query_count() == 2
|
||||
|
||||
|
||||
def test_save_notification_does_not_creates_history(sample_email_template, sample_job):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_email_template, job_id=sample_job.id)
|
||||
|
||||
notification_1 = Notification(**data)
|
||||
dao_create_notification(notification_1)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert NotificationHistory.query.count() == 0
|
||||
assert _get_notification_query_count() == 1
|
||||
assert _get_notification_history_query_count() == 0
|
||||
|
||||
|
||||
def test_update_notification_with_research_mode_service_does_not_create_or_update_history(
|
||||
@@ -464,14 +519,14 @@ def test_update_notification_with_research_mode_service_does_not_create_or_updat
|
||||
sample_template.service.research_mode = True
|
||||
notification = create_notification(template=sample_template)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert NotificationHistory.query.count() == 0
|
||||
assert _get_notification_query_count() == 1
|
||||
assert _get_notification_history_query_count() == 0
|
||||
|
||||
notification.status = NotificationStatus.DELIVERED
|
||||
dao_update_notification(notification)
|
||||
|
||||
assert Notification.query.one().status == NotificationStatus.DELIVERED
|
||||
assert NotificationHistory.query.count() == 0
|
||||
assert _get_notification_query_one().status == NotificationStatus.DELIVERED
|
||||
assert _get_notification_history_query_count() == 0
|
||||
|
||||
|
||||
def test_not_save_notification_and_not_create_stats_on_commit_error(
|
||||
@@ -479,26 +534,26 @@ def test_not_save_notification_and_not_create_stats_on_commit_error(
|
||||
):
|
||||
random_id = str(uuid.uuid4())
|
||||
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_template, job_id=random_id)
|
||||
|
||||
notification = Notification(**data)
|
||||
with pytest.raises(SQLAlchemyError):
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 0
|
||||
assert Job.query.get(sample_job.id).notifications_sent == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
assert db.session.get(Job, sample_job.id).notifications_sent == 0
|
||||
|
||||
|
||||
def test_save_notification_and_increment_job(sample_template, sample_job, sns_provider):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_template, job_id=sample_job.id)
|
||||
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert "1" == notification_from_db.to
|
||||
assert data["job_id"] == notification_from_db.job_id
|
||||
@@ -510,21 +565,21 @@ def test_save_notification_and_increment_job(sample_template, sample_job, sns_pr
|
||||
|
||||
notification_2 = Notification(**data)
|
||||
dao_create_notification(notification_2)
|
||||
assert Notification.query.count() == 2
|
||||
assert _get_notification_query_count() == 2
|
||||
|
||||
|
||||
def test_save_notification_and_increment_correct_job(sample_template, sns_provider):
|
||||
job_1 = create_job(sample_template)
|
||||
job_2 = create_job(sample_template)
|
||||
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_template, job_id=job_1.id)
|
||||
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert "1" == notification_from_db.to
|
||||
assert data["job_id"] == notification_from_db.job_id
|
||||
@@ -537,14 +592,14 @@ def test_save_notification_and_increment_correct_job(sample_template, sns_provid
|
||||
|
||||
|
||||
def test_save_notification_with_no_job(sample_template, sns_provider):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_template)
|
||||
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert "1" == notification_from_db.to
|
||||
assert data["service"] == notification_from_db.service
|
||||
@@ -592,7 +647,7 @@ def test_get_notification_by_id_when_notification_exists_for_different_service(
|
||||
|
||||
def test_get_notifications_by_reference(sample_template):
|
||||
client_reference = "some-client-ref"
|
||||
assert len(Notification.query.all()) == 0
|
||||
assert len(_get_notification_query_all()) == 0
|
||||
create_notification(sample_template, client_reference=client_reference)
|
||||
create_notification(sample_template, client_reference=client_reference)
|
||||
create_notification(sample_template, client_reference="other-ref")
|
||||
@@ -603,14 +658,14 @@ def test_get_notifications_by_reference(sample_template):
|
||||
|
||||
|
||||
def test_save_notification_no_job_id(sample_template):
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
data = _notification_json(sample_template)
|
||||
|
||||
notification = Notification(**data)
|
||||
dao_create_notification(notification)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
notification_from_db = Notification.query.all()[0]
|
||||
assert _get_notification_query_count() == 1
|
||||
notification_from_db = _get_notification_query_all()[0]
|
||||
assert notification_from_db.id
|
||||
assert "1" == notification_from_db.to
|
||||
assert data["service"] == notification_from_db.service
|
||||
@@ -687,13 +742,13 @@ def test_update_notification_sets_status(sample_notification):
|
||||
assert sample_notification.status == NotificationStatus.CREATED
|
||||
sample_notification.status = NotificationStatus.FAILED
|
||||
dao_update_notification(sample_notification)
|
||||
notification_from_db = Notification.query.get(sample_notification.id)
|
||||
notification_from_db = db.session.get(Notification, sample_notification.id)
|
||||
assert notification_from_db.status == NotificationStatus.FAILED
|
||||
|
||||
|
||||
@freeze_time("2016-01-10")
|
||||
def test_should_limit_notifications_return_by_day_limit_plus_one(sample_template):
|
||||
assert len(Notification.query.all()) == 0
|
||||
assert len(_get_notification_query_all()) == 0
|
||||
|
||||
# create one notification a day between 1st and 9th,
|
||||
# with assumption that the local timezone is EST
|
||||
@@ -706,7 +761,7 @@ def test_should_limit_notifications_return_by_day_limit_plus_one(sample_template
|
||||
status=NotificationStatus.FAILED,
|
||||
)
|
||||
|
||||
all_notifications = Notification.query.all()
|
||||
all_notifications = _get_notification_query_all()
|
||||
assert len(all_notifications) == 10
|
||||
|
||||
all_notifications = get_notifications_for_service(
|
||||
@@ -722,19 +777,19 @@ def test_should_limit_notifications_return_by_day_limit_plus_one(sample_template
|
||||
|
||||
def test_creating_notification_does_not_add_notification_history(sample_template):
|
||||
create_notification(template=sample_template)
|
||||
assert Notification.query.count() == 1
|
||||
assert NotificationHistory.query.count() == 0
|
||||
assert _get_notification_query_count() == 1
|
||||
assert _get_notification_history_query_count() == 0
|
||||
|
||||
|
||||
def test_should_delete_notification_for_id(sample_template):
|
||||
notification = create_notification(template=sample_template)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert NotificationHistory.query.count() == 0
|
||||
assert _get_notification_query_count() == 1
|
||||
assert _get_notification_history_query_count() == 0
|
||||
|
||||
dao_delete_notifications_by_id(notification.id)
|
||||
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
|
||||
|
||||
def test_should_delete_notification_and_ignore_history_for_research_mode(
|
||||
@@ -744,31 +799,32 @@ def test_should_delete_notification_and_ignore_history_for_research_mode(
|
||||
|
||||
notification = create_notification(template=sample_template)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert _get_notification_query_count() == 1
|
||||
|
||||
dao_delete_notifications_by_id(notification.id)
|
||||
|
||||
assert Notification.query.count() == 0
|
||||
assert _get_notification_query_count() == 0
|
||||
|
||||
|
||||
def test_should_delete_only_notification_with_id(sample_template):
|
||||
notification_1 = create_notification(template=sample_template)
|
||||
notification_2 = create_notification(template=sample_template)
|
||||
assert Notification.query.count() == 2
|
||||
assert _get_notification_query_count() == 2
|
||||
|
||||
dao_delete_notifications_by_id(notification_1.id)
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert Notification.query.first().id == notification_2.id
|
||||
assert _get_notification_query_count() == 1
|
||||
stmt = select(Notification)
|
||||
assert db.session.execute(stmt).scalars().first().id == notification_2.id
|
||||
|
||||
|
||||
def test_should_delete_no_notifications_if_no_matching_ids(sample_template):
|
||||
create_notification(template=sample_template)
|
||||
assert Notification.query.count() == 1
|
||||
assert _get_notification_query_count() == 1
|
||||
|
||||
dao_delete_notifications_by_id(uuid.uuid4())
|
||||
|
||||
assert Notification.query.count() == 1
|
||||
assert _get_notification_query_count() == 1
|
||||
|
||||
|
||||
def _notification_json(sample_template, job_id=None, id=None, status=None):
|
||||
@@ -814,16 +870,19 @@ def test_dao_timeout_notifications(sample_template):
|
||||
temporary_failure_notifications = dao_timeout_notifications(utc_now())
|
||||
|
||||
assert len(temporary_failure_notifications) == 2
|
||||
assert Notification.query.get(created.id).status == NotificationStatus.CREATED
|
||||
assert db.session.get(Notification, created.id).status == NotificationStatus.CREATED
|
||||
assert (
|
||||
Notification.query.get(sending.id).status
|
||||
db.session.get(Notification, sending.id).status
|
||||
== NotificationStatus.TEMPORARY_FAILURE
|
||||
)
|
||||
assert (
|
||||
Notification.query.get(pending.id).status
|
||||
db.session.get(Notification, pending.id).status
|
||||
== NotificationStatus.TEMPORARY_FAILURE
|
||||
)
|
||||
assert Notification.query.get(delivered.id).status == NotificationStatus.DELIVERED
|
||||
assert (
|
||||
db.session.get(Notification, delivered.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
|
||||
|
||||
def test_dao_timeout_notifications_only_updates_for_older_notifications(
|
||||
@@ -842,8 +901,8 @@ def test_dao_timeout_notifications_only_updates_for_older_notifications(
|
||||
temporary_failure_notifications = dao_timeout_notifications(utc_now())
|
||||
|
||||
assert len(temporary_failure_notifications) == 0
|
||||
assert Notification.query.get(sending.id).status == NotificationStatus.SENDING
|
||||
assert Notification.query.get(pending.id).status == NotificationStatus.PENDING
|
||||
assert db.session.get(Notification, sending.id).status == NotificationStatus.SENDING
|
||||
assert db.session.get(Notification, pending.id).status == NotificationStatus.PENDING
|
||||
|
||||
|
||||
def test_should_return_notifications_excluding_jobs_by_default(
|
||||
@@ -935,7 +994,7 @@ def test_get_notifications_created_by_api_or_csv_are_returned_correctly_excludin
|
||||
key_type=sample_test_api_key.key_type,
|
||||
)
|
||||
|
||||
all_notifications = Notification.query.all()
|
||||
all_notifications = _get_notification_query_all()
|
||||
assert len(all_notifications) == 4
|
||||
|
||||
# returns all real API derived notifications
|
||||
@@ -982,7 +1041,7 @@ def test_get_notifications_with_a_live_api_key_type(
|
||||
key_type=sample_test_api_key.key_type,
|
||||
)
|
||||
|
||||
all_notifications = Notification.query.all()
|
||||
all_notifications = _get_notification_query_all()
|
||||
assert len(all_notifications) == 4
|
||||
|
||||
# only those created with normal API key, no jobs
|
||||
@@ -1114,7 +1173,7 @@ def test_should_exclude_test_key_notifications_by_default(
|
||||
key_type=sample_test_api_key.key_type,
|
||||
)
|
||||
|
||||
all_notifications = Notification.query.all()
|
||||
all_notifications = _get_notification_query_all()
|
||||
assert len(all_notifications) == 4
|
||||
|
||||
all_notifications = get_notifications_for_service(
|
||||
@@ -1757,10 +1816,10 @@ def test_dao_update_notifications_by_reference_updated_notifications(sample_temp
|
||||
update_dict={"status": NotificationStatus.DELIVERED, "billable_units": 2},
|
||||
)
|
||||
assert updated_count == 2
|
||||
updated_1 = Notification.query.get(notification_1.id)
|
||||
updated_1 = db.session.get(Notification, notification_1.id)
|
||||
assert updated_1.billable_units == 2
|
||||
assert updated_1.status == NotificationStatus.DELIVERED
|
||||
updated_2 = Notification.query.get(notification_2.id)
|
||||
updated_2 = db.session.get(Notification, notification_2.id)
|
||||
assert updated_2.billable_units == 2
|
||||
assert updated_2.status == NotificationStatus.DELIVERED
|
||||
|
||||
@@ -1823,10 +1882,11 @@ def test_dao_update_notifications_by_reference_updates_history_when_one_of_two_n
|
||||
assert updated_count == 1
|
||||
assert updated_history_count == 1
|
||||
assert (
|
||||
Notification.query.get(notification2.id).status == NotificationStatus.DELIVERED
|
||||
db.session.get(Notification, notification2.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
assert (
|
||||
NotificationHistory.query.get(notification1.id).status
|
||||
db.session.get(NotificationHistory, notification1.id).status
|
||||
== NotificationStatus.DELIVERED
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||
|
||||
from app import db
|
||||
@@ -57,7 +58,8 @@ def test_get_organization_by_id_gets_correct_organization(notify_db_session):
|
||||
def test_update_organization(notify_db_session):
|
||||
create_organization()
|
||||
|
||||
organization = Organization.query.one()
|
||||
stmt = select(Organization)
|
||||
organization = db.session.execute(stmt).scalars().one()
|
||||
user = create_user()
|
||||
email_branding = create_email_branding()
|
||||
|
||||
@@ -78,7 +80,8 @@ def test_update_organization(notify_db_session):
|
||||
|
||||
dao_update_organization(organization.id, **data)
|
||||
|
||||
organization = Organization.query.one()
|
||||
stmt = select(Organization)
|
||||
organization = db.session.execute(stmt).scalars().one()
|
||||
|
||||
for attribute, value in data.items():
|
||||
assert getattr(organization, attribute) == value
|
||||
@@ -102,7 +105,8 @@ def test_update_organization_domains_lowercases(
|
||||
):
|
||||
create_organization()
|
||||
|
||||
organization = Organization.query.one()
|
||||
stmt = select(Organization)
|
||||
organization = db.session.execute(stmt).scalars().one()
|
||||
|
||||
# Seed some domains
|
||||
dao_update_organization(organization.id, domains=["123", "456"])
|
||||
@@ -121,7 +125,8 @@ def test_update_organization_domains_lowercases_integrity_error(
|
||||
):
|
||||
create_organization()
|
||||
|
||||
organization = Organization.query.one()
|
||||
stmt = select(Organization)
|
||||
organization = db.session.execute(stmt).scalars().one()
|
||||
|
||||
# Seed some domains
|
||||
dao_update_organization(organization.id, domains=["123", "456"])
|
||||
@@ -175,11 +180,11 @@ def test_update_organization_updates_the_service_org_type_if_org_type_is_provide
|
||||
|
||||
assert sample_organization.organization_type == OrganizationType.FEDERAL
|
||||
assert sample_service.organization_type == OrganizationType.FEDERAL
|
||||
stmt = select(Service.get_history_model()).filter_by(
|
||||
id=sample_service.id, version=2
|
||||
)
|
||||
assert (
|
||||
Service.get_history_model()
|
||||
.query.filter_by(id=sample_service.id, version=2)
|
||||
.one()
|
||||
.organization_type
|
||||
db.session.execute(stmt).scalars().one().organization_type
|
||||
== OrganizationType.FEDERAL
|
||||
)
|
||||
|
||||
@@ -229,11 +234,11 @@ def test_add_service_to_organization(sample_service, sample_organization):
|
||||
assert sample_organization.services[0].id == sample_service.id
|
||||
|
||||
assert sample_service.organization_type == sample_organization.organization_type
|
||||
stmt = select(Service.get_history_model()).filter_by(
|
||||
id=sample_service.id, version=2
|
||||
)
|
||||
assert (
|
||||
Service.get_history_model()
|
||||
.query.filter_by(id=sample_service.id, version=2)
|
||||
.one()
|
||||
.organization_type
|
||||
db.session.execute(stmt).scalars().one().organization_type
|
||||
== sample_organization.organization_type
|
||||
)
|
||||
assert sample_service.organization_id == sample_organization.id
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from sqlalchemy import select
|
||||
|
||||
from app import db
|
||||
from app.dao.service_user_dao import dao_get_service_user
|
||||
from app.dao.template_folder_dao import (
|
||||
@@ -17,5 +19,5 @@ def test_dao_delete_template_folder_deletes_user_folder_permissions(
|
||||
dao_update_template_folder(folder)
|
||||
|
||||
dao_delete_template_folder(folder)
|
||||
|
||||
assert db.session.query(user_folder_permissions).all() == []
|
||||
stmt = select(user_folder_permissions)
|
||||
assert db.session.execute(stmt).scalars().all() == []
|
||||
|
||||
@@ -2,8 +2,10 @@ from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from freezegun import freeze_time
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from app import db
|
||||
from app.dao.templates_dao import (
|
||||
dao_create_template,
|
||||
dao_get_all_templates_for_service,
|
||||
@@ -17,6 +19,16 @@ from app.models import Template, TemplateHistory, TemplateRedacted
|
||||
from tests.app.db import create_template
|
||||
|
||||
|
||||
def template_query_count():
|
||||
stmt = select(func.count()).select_from(Template)
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
def template_history_query_count():
|
||||
stmt = select(func.count()).select_from(TemplateHistory)
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"template_type, subject",
|
||||
[
|
||||
@@ -37,7 +49,7 @@ def test_create_template(sample_service, sample_user, template_type, subject):
|
||||
template = Template(**data)
|
||||
dao_create_template(template)
|
||||
|
||||
assert Template.query.count() == 1
|
||||
assert template_query_count() == 1
|
||||
assert len(dao_get_all_templates_for_service(sample_service.id)) == 1
|
||||
assert (
|
||||
dao_get_all_templates_for_service(sample_service.id)[0].name
|
||||
@@ -50,11 +62,13 @@ def test_create_template(sample_service, sample_user, template_type, subject):
|
||||
|
||||
|
||||
def test_create_template_creates_redact_entry(sample_service):
|
||||
assert TemplateRedacted.query.count() == 0
|
||||
stmt = select(func.count()).select_from(TemplateRedacted)
|
||||
assert db.session.execute(stmt).scalar() == 0
|
||||
|
||||
template = create_template(sample_service)
|
||||
|
||||
redacted = TemplateRedacted.query.one()
|
||||
stmt = select(TemplateRedacted)
|
||||
redacted = db.session.execute(stmt).scalars().one()
|
||||
assert redacted.template_id == template.id
|
||||
assert redacted.redact_personalisation is False
|
||||
assert redacted.updated_by_id == sample_service.created_by_id
|
||||
@@ -79,7 +93,8 @@ def test_update_template(sample_service, sample_user):
|
||||
|
||||
|
||||
def test_redact_template(sample_template):
|
||||
redacted = TemplateRedacted.query.one()
|
||||
stmt = select(TemplateRedacted)
|
||||
redacted = db.session.execute(stmt).scalars().one()
|
||||
assert redacted.template_id == sample_template.id
|
||||
assert redacted.redact_personalisation is False
|
||||
|
||||
@@ -96,7 +111,7 @@ def test_get_all_templates_for_service(service_factory):
|
||||
service_1 = service_factory.get("service 1", email_from="service.1")
|
||||
service_2 = service_factory.get("service 2", email_from="service.2")
|
||||
|
||||
assert Template.query.count() == 2
|
||||
assert template_query_count() == 2
|
||||
assert len(dao_get_all_templates_for_service(service_1.id)) == 1
|
||||
assert len(dao_get_all_templates_for_service(service_2.id)) == 1
|
||||
|
||||
@@ -119,7 +134,7 @@ def test_get_all_templates_for_service(service_factory):
|
||||
content="Template content",
|
||||
)
|
||||
|
||||
assert Template.query.count() == 5
|
||||
assert template_query_count() == 5
|
||||
assert len(dao_get_all_templates_for_service(service_1.id)) == 3
|
||||
assert len(dao_get_all_templates_for_service(service_2.id)) == 2
|
||||
|
||||
@@ -144,7 +159,7 @@ def test_get_all_templates_for_service_is_alphabetised(sample_service):
|
||||
service=sample_service,
|
||||
)
|
||||
|
||||
assert Template.query.count() == 3
|
||||
assert template_query_count() == 3
|
||||
assert (
|
||||
dao_get_all_templates_for_service(sample_service.id)[0].name
|
||||
== "Sample Template 1"
|
||||
@@ -171,7 +186,7 @@ def test_get_all_templates_for_service_is_alphabetised(sample_service):
|
||||
|
||||
|
||||
def test_get_all_returns_empty_list_if_no_templates(sample_service):
|
||||
assert Template.query.count() == 0
|
||||
assert template_query_count() == 0
|
||||
assert len(dao_get_all_templates_for_service(sample_service.id)) == 0
|
||||
|
||||
|
||||
@@ -257,8 +272,8 @@ def test_get_template_by_id_and_service_returns_none_if_no_template(
|
||||
def test_create_template_creates_a_history_record_with_current_data(
|
||||
sample_service, sample_user
|
||||
):
|
||||
assert Template.query.count() == 0
|
||||
assert TemplateHistory.query.count() == 0
|
||||
assert template_query_count() == 0
|
||||
assert template_history_query_count() == 0
|
||||
data = {
|
||||
"name": "Sample Template",
|
||||
"template_type": TemplateType.EMAIL,
|
||||
@@ -270,10 +285,12 @@ def test_create_template_creates_a_history_record_with_current_data(
|
||||
template = Template(**data)
|
||||
dao_create_template(template)
|
||||
|
||||
assert Template.query.count() == 1
|
||||
assert template_query_count() == 1
|
||||
|
||||
template_from_db = Template.query.first()
|
||||
template_history = TemplateHistory.query.first()
|
||||
stmt = select(Template)
|
||||
template_from_db = db.session.execute(stmt).scalars().first()
|
||||
stmt = select(TemplateHistory)
|
||||
template_history = db.session.execute(stmt).scalars().first()
|
||||
|
||||
assert template_from_db.id == template_history.id
|
||||
assert template_from_db.name == template_history.name
|
||||
@@ -286,8 +303,8 @@ def test_create_template_creates_a_history_record_with_current_data(
|
||||
def test_update_template_creates_a_history_record_with_current_data(
|
||||
sample_service, sample_user
|
||||
):
|
||||
assert Template.query.count() == 0
|
||||
assert TemplateHistory.query.count() == 0
|
||||
assert template_query_count() == 0
|
||||
assert template_history_query_count() == 0
|
||||
data = {
|
||||
"name": "Sample Template",
|
||||
"template_type": TemplateType.EMAIL,
|
||||
@@ -301,22 +318,26 @@ def test_update_template_creates_a_history_record_with_current_data(
|
||||
|
||||
created = dao_get_all_templates_for_service(sample_service.id)[0]
|
||||
assert created.name == "Sample Template"
|
||||
assert Template.query.count() == 1
|
||||
assert Template.query.first().version == 1
|
||||
assert TemplateHistory.query.count() == 1
|
||||
assert template_query_count() == 1
|
||||
stmt = select(Template)
|
||||
assert db.session.execute(stmt).scalars().first().version == 1
|
||||
assert template_history_query_count() == 1
|
||||
|
||||
created.name = "new name"
|
||||
dao_update_template(created)
|
||||
|
||||
assert Template.query.count() == 1
|
||||
assert TemplateHistory.query.count() == 2
|
||||
assert template_query_count() == 1
|
||||
assert template_history_query_count() == 2
|
||||
|
||||
template_from_db = Template.query.first()
|
||||
stmt = select(Template)
|
||||
template_from_db = db.session.execute(stmt).scalars().first()
|
||||
|
||||
assert template_from_db.version == 2
|
||||
|
||||
assert TemplateHistory.query.filter_by(name="Sample Template").one().version == 1
|
||||
assert TemplateHistory.query.filter_by(name="new name").one().version == 2
|
||||
stmt = select(TemplateHistory).filter_by(name="Sample Template")
|
||||
assert db.session.execute(stmt).scalars().one().version == 1
|
||||
stmt = select(TemplateHistory).filter_by(name="new name")
|
||||
assert db.session.execute(stmt).scalars().one().version == 2
|
||||
|
||||
|
||||
def test_get_template_history_version(sample_user, sample_service, sample_template):
|
||||
|
||||
@@ -38,6 +38,21 @@ from tests.app.db import (
|
||||
)
|
||||
|
||||
|
||||
def _get_user_query_count():
|
||||
stmt = select(func.count(User.id))
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
def _get_user_query_first():
|
||||
stmt = select(User)
|
||||
return db.session.execute(stmt).scalars().first()
|
||||
|
||||
|
||||
def _get_verify_code_query_count():
|
||||
stmt = select(func.count(VerifyCode.id))
|
||||
return db.session.execute(stmt).scalar() or 0
|
||||
|
||||
|
||||
@freeze_time("2020-01-28T12:00:00")
|
||||
@pytest.mark.parametrize(
|
||||
"phone_number, expected_phone_number",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1959,6 +1959,84 @@ def test_get_all_notifications_for_service_including_ones_made_by_jobs(
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_get_monthly_notification_stats_by_user(
|
||||
client,
|
||||
sample_service,
|
||||
sample_user,
|
||||
mocker,
|
||||
):
|
||||
mock_s3 = mocker.patch("app.service.rest.get_phone_number_from_s3")
|
||||
mock_s3.return_value = ""
|
||||
|
||||
mock_s3 = mocker.patch("app.service.rest.get_personalisation_from_s3")
|
||||
mock_s3.return_value = {}
|
||||
|
||||
auth_header = create_admin_authorization_header()
|
||||
|
||||
response = client.get(
|
||||
path=(
|
||||
f"/service/{sample_service.id}/notifications/{sample_user.id}/monthly?year=2024"
|
||||
),
|
||||
headers=[auth_header],
|
||||
)
|
||||
|
||||
resp = json.loads(response.get_data(as_text=True))
|
||||
print(f"RESP is {resp}")
|
||||
# TODO This test could be a little more complete
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_get_single_month_notification_stats_by_user(
|
||||
client,
|
||||
sample_service,
|
||||
sample_user,
|
||||
mocker,
|
||||
):
|
||||
mock_s3 = mocker.patch("app.service.rest.get_phone_number_from_s3")
|
||||
mock_s3.return_value = ""
|
||||
|
||||
mock_s3 = mocker.patch("app.service.rest.get_personalisation_from_s3")
|
||||
mock_s3.return_value = {}
|
||||
|
||||
auth_header = create_admin_authorization_header()
|
||||
|
||||
response = client.get(
|
||||
path=(
|
||||
f"/service/{sample_service.id}/notifications/{sample_user.id}/month?year=2024&month=07"
|
||||
),
|
||||
headers=[auth_header],
|
||||
)
|
||||
|
||||
resp = json.loads(response.get_data(as_text=True))
|
||||
print(f"RESP is {resp}")
|
||||
# TODO This test could be a little more complete
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_get_single_month_notification_stats_for_service(
|
||||
client,
|
||||
sample_service,
|
||||
mocker,
|
||||
):
|
||||
mock_s3 = mocker.patch("app.service.rest.get_phone_number_from_s3")
|
||||
mock_s3.return_value = ""
|
||||
|
||||
mock_s3 = mocker.patch("app.service.rest.get_personalisation_from_s3")
|
||||
mock_s3.return_value = {}
|
||||
|
||||
auth_header = create_admin_authorization_header()
|
||||
|
||||
response = client.get(
|
||||
path=(f"/service/{sample_service.id}/notifications/month?year=2024&month=07"),
|
||||
headers=[auth_header],
|
||||
)
|
||||
|
||||
resp = json.loads(response.get_data(as_text=True))
|
||||
print(f"RESP is {resp}")
|
||||
# TODO This test could be a little more complete
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_get_only_api_created_notifications_for_service(
|
||||
admin_request,
|
||||
sample_job,
|
||||
|
||||
@@ -45,6 +45,7 @@ def test_create_invited_user(
|
||||
permissions="send_messages,manage_service,manage_api_keys",
|
||||
auth_type=AuthType.EMAIL,
|
||||
folder_permissions=["folder_1", "folder_2", "folder_3"],
|
||||
nonce="FakeNonce",
|
||||
**extra_args,
|
||||
)
|
||||
|
||||
@@ -108,6 +109,7 @@ def test_create_invited_user_without_auth_type(
|
||||
"from_user": str(invite_from.id),
|
||||
"permissions": "send_messages,manage_service,manage_api_keys",
|
||||
"folder_permissions": [],
|
||||
"nonce": "FakeNonce",
|
||||
}
|
||||
|
||||
json_resp = admin_request.post(
|
||||
@@ -131,6 +133,7 @@ def test_create_invited_user_invalid_email(client, sample_service, mocker, fake_
|
||||
"from_user": str(invite_from.id),
|
||||
"permissions": "send_messages,manage_service,manage_api_keys",
|
||||
"folder_permissions": [fake_uuid, fake_uuid],
|
||||
"nonce": "FakeNonce",
|
||||
}
|
||||
|
||||
data = json.dumps(data)
|
||||
|
||||
Reference in New Issue
Block a user