From 752a13fbd2b367c3f3aa47bcc76329fea6f46660 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 23 May 2024 10:18:02 -0700 Subject: [PATCH 1/3] replace utcnow with timezone naive utc call --- app/utils.py | 14 ++++++++++++- .../versions/0336_broadcast_msg_content_2.py | 3 ++- notifications_utils/recipients.py | 4 +--- notifications_utils/template.py | 5 +---- tests/app/user/test_rest_verify.py | 21 ++++++++++--------- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/app/utils.py b/app/utils.py index 22f9a034c..847ec3dbc 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from flask import url_for from sqlalchemy import func @@ -132,3 +132,15 @@ def hilite(message): ansi_green = "\033[32m" ansi_reset = "\033[0m" return f"{ansi_green}{message}{ansi_reset}" + + +def aware_utcnow(): + return datetime.now(timezone.utc) + + +def naive_utcnow(): + return aware_utcnow().replace(tzinfo=None) + + +def utc_now(): + return naive_utcnow() diff --git a/migrations/versions/0336_broadcast_msg_content_2.py b/migrations/versions/0336_broadcast_msg_content_2.py index a42cbc24e..014059d00 100644 --- a/migrations/versions/0336_broadcast_msg_content_2.py +++ b/migrations/versions/0336_broadcast_msg_content_2.py @@ -8,10 +8,11 @@ Create Date: 2020-12-04 15:06:22.544803 import sqlalchemy as sa from alembic import op -from notifications_utils.template import BroadcastMessageTemplate from sqlalchemy.dialects import postgresql from sqlalchemy.orm.session import Session +from notifications_utils.template import BroadcastMessageTemplate + revision = "0336_broadcast_msg_content_2" down_revision = "0335_broadcast_msg_content" diff --git a/notifications_utils/recipients.py b/notifications_utils/recipients.py index 0d8536c33..68e2cb101 100644 --- a/notifications_utils/recipients.py +++ b/notifications_utils/recipients.py @@ -17,9 +17,7 @@ from notifications_utils.formatters import ( strip_and_remove_obscure_whitespace, ) from notifications_utils.insensitive_dict import InsensitiveDict -from notifications_utils.international_billing_rates import ( - INTERNATIONAL_BILLING_RATES, -) +from notifications_utils.international_billing_rates import INTERNATIONAL_BILLING_RATES from notifications_utils.postal_address import ( address_line_7_key, address_lines_1_to_6_and_postcode_keys, diff --git a/notifications_utils/template.py b/notifications_utils/template.py index 302fd3899..ec112173f 100644 --- a/notifications_utils/template.py +++ b/notifications_utils/template.py @@ -43,10 +43,7 @@ from notifications_utils.markdown import ( notify_letter_preview_markdown, notify_plain_text_email_markdown, ) -from notifications_utils.postal_address import ( - PostalAddress, - address_lines_1_to_7_keys, -) +from notifications_utils.postal_address import PostalAddress, address_lines_1_to_7_keys from notifications_utils.sanitise_text import SanitiseSMS from notifications_utils.take import Take from notifications_utils.template_change import TemplateChange diff --git a/tests/app/user/test_rest_verify.py b/tests/app/user/test_rest_verify.py index 26eb085a4..c1855787b 100644 --- a/tests/app/user/test_rest_verify.py +++ b/tests/app/user/test_rest_verify.py @@ -12,12 +12,13 @@ from app.dao.services_dao import dao_fetch_service_by_id from app.dao.users_dao import create_user_code from app.enums import AuthType, CodeType from app.models import Notification, User, VerifyCode +from app.utils import utc_now from tests import create_admin_authorization_header @freeze_time("2016-01-01T12:00:00") def test_user_verify_sms_code(client, sample_sms_code): - sample_sms_code.user.logged_in_at = datetime.utcnow() - timedelta(days=1) + sample_sms_code.user.logged_in_at = utc_now() - timedelta(days=1) assert not VerifyCode.query.first().code_used assert sample_sms_code.user.current_session_id is None data = json.dumps( @@ -31,8 +32,8 @@ def test_user_verify_sms_code(client, sample_sms_code): ) assert resp.status_code == 204 assert VerifyCode.query.first().code_used - assert sample_sms_code.user.logged_in_at == datetime.utcnow() - assert sample_sms_code.user.email_access_validated_at != datetime.utcnow() + assert sample_sms_code.user.logged_in_at == utc_now() + assert sample_sms_code.user.email_access_validated_at != utc_now() assert sample_sms_code.user.current_session_id is not None @@ -122,7 +123,7 @@ def test_user_verify_code_expired_code_and_increments_failed_login_count( @freeze_time("2016-01-01 10:00:00.000000") def test_user_verify_password(client, sample_user): - yesterday = datetime.utcnow() - timedelta(days=1) + yesterday = utc_now() - timedelta(days=1) sample_user.logged_in_at = yesterday data = json.dumps({"password": "password"}) auth_header = create_admin_authorization_header() @@ -288,8 +289,8 @@ def test_send_sms_code_returns_204_when_too_many_codes_already_created( verify_code = VerifyCode( code_type=CodeType.SMS, _code=12345, - created_at=datetime.utcnow() - timedelta(minutes=10), - expiry_datetime=datetime.utcnow() + timedelta(minutes=40), + created_at=utc_now() - timedelta(minutes=10), + expiry_datetime=utc_now() + timedelta(minutes=40), user=sample_user, ) db.session.add(verify_code) @@ -537,8 +538,8 @@ def test_send_email_code_returns_404_for_bad_input_data(admin_request): # we send iAuthType.SMS and AuthType.WEBAUTHN users email code to validate their email access @pytest.mark.parametrize("auth_type", AuthType) def test_user_verify_email_code(admin_request, sample_user, auth_type): - sample_user.logged_in_at = datetime.utcnow() - timedelta(days=1) - sample_user.email_access_validated_at = datetime.utcnow() - timedelta(days=1) + sample_user.logged_in_at = utc_now() - timedelta(days=1) + sample_user.email_access_validated_at = utc_now() - timedelta(days=1) sample_user.auth_type = auth_type magic_code = str(uuid.uuid4()) verify_code = create_user_code(sample_user, magic_code, CodeType.EMAIL) @@ -553,8 +554,8 @@ def test_user_verify_email_code(admin_request, sample_user, auth_type): ) assert verify_code.code_used - assert sample_user.logged_in_at == datetime.utcnow() - assert sample_user.email_access_validated_at == datetime.utcnow() + assert sample_user.logged_in_at == utc_now() + assert sample_user.email_access_validated_at == utc_now() assert sample_user.current_session_id is not None From 905df17f652e9126cd0f38a9b87f3cde5f0fec16 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 23 May 2024 13:59:51 -0700 Subject: [PATCH 2/3] remove datetime.utcnow() --- app/celery/nightly_tasks.py | 20 ++-- app/celery/process_ses_receipts_tasks.py | 5 +- app/celery/provider_tasks.py | 7 +- app/celery/reporting_tasks.py | 13 +-- app/celery/scheduled_tasks.py | 23 ++-- app/celery/tasks.py | 15 ++- app/clients/cloudwatch/aws_cloudwatch.py | 7 +- app/commands.py | 4 +- app/complaint/complaint_rest.py | 4 +- app/dao/api_key_dao.py | 7 +- app/dao/date_util.py | 4 +- app/dao/fact_billing_dao.py | 10 +- app/dao/fact_notification_status_dao.py | 17 +-- app/dao/fact_processing_time_dao.py | 5 +- app/dao/invited_org_user_dao.py | 7 +- app/dao/invited_user_dao.py | 5 +- app/dao/jobs_dao.py | 14 +-- app/dao/notifications_dao.py | 18 ++-- app/dao/provider_details_dao.py | 7 +- app/dao/service_callback_api_dao.py | 7 +- app/dao/service_data_retention_dao.py | 5 +- app/dao/service_inbound_api_dao.py | 7 +- app/dao/services_dao.py | 11 +- app/dao/templates_dao.py | 4 +- app/dao/uploads_dao.py | 5 +- app/dao/users_dao.py | 16 +-- app/delivery/send_to_providers.py | 4 +- app/history_meta.py | 8 +- app/models.py | 100 +++++++++--------- app/notifications/process_notifications.py | 4 +- app/performance_dashboard/rest.py | 3 +- app/platform_stats/rest.py | 4 +- app/schema_validation/__init__.py | 7 +- app/schemas.py | 8 +- app/service/rest.py | 8 +- app/service_invite/rest.py | 5 +- app/user/rest.py | 13 ++- app/utils.py | 4 +- app/v2/notifications/post_notifications.py | 5 +- notifications_utils/__init__.py | 13 +++ notifications_utils/clients/redis/__init__.py | 4 +- notifications_utils/letter_timings.py | 15 ++- notifications_utils/template.py | 4 +- tests/app/aws/test_s3.py | 4 +- tests/app/celery/test_nightly_tasks.py | 17 +-- .../celery/test_process_ses_receipts_tasks.py | 22 ++-- tests/app/celery/test_reporting_tasks.py | 9 +- tests/app/celery/test_scheduled_tasks.py | 93 ++++++++-------- .../app/celery/test_service_callback_tasks.py | 4 +- tests/app/celery/test_tasks.py | 64 +++++------ tests/app/clients/test_aws_cloudwatch.py | 8 +- tests/app/complaint/test_complaint_rest.py | 5 +- tests/app/conftest.py | 17 +-- .../notification_dao/test_notification_dao.py | 81 +++++++------- ...t_notification_dao_delete_notifications.py | 53 +++++----- .../test_notification_dao_template_usage.py | 9 +- tests/app/dao/test_api_key_dao.py | 9 +- tests/app/dao/test_complaint_dao.py | 11 +- tests/app/dao/test_fact_billing_dao.py | 63 ++++++----- .../dao/test_fact_notification_status_dao.py | 13 ++- tests/app/dao/test_invited_user_dao.py | 5 +- tests/app/dao/test_jobs_dao.py | 33 +++--- tests/app/dao/test_organization_dao.py | 4 +- tests/app/dao/test_provider_details_dao.py | 3 +- .../dao/test_service_data_retention_dao.py | 8 +- .../dao/test_service_email_reply_to_dao.py | 5 +- tests/app/dao/test_service_sms_sender_dao.py | 9 +- tests/app/dao/test_services_dao.py | 9 +- tests/app/dao/test_uploads_dao.py | 13 +-- tests/app/dao/test_users_dao.py | 15 +-- tests/app/db.py | 21 ++-- tests/app/delivery/test_send_to_providers.py | 6 +- tests/app/inbound_sms/test_rest.py | 5 +- tests/app/job/test_rest.py | 17 +-- tests/app/organization/test_rest.py | 6 +- tests/app/service/test_rest.py | 25 ++--- tests/app/service/test_statistics_rest.py | 5 +- tests/app/template/test_rest_history.py | 3 +- tests/app/template_statistics/test_rest.py | 8 +- tests/app/test_commands.py | 3 +- tests/app/test_model.py | 11 +- .../clients/redis/test_redis_client.py | 6 +- .../test_letter_timings.py | 3 +- 83 files changed, 591 insertions(+), 570 deletions(-) diff --git a/app/celery/nightly_tasks.py b/app/celery/nightly_tasks.py index 1c208104b..dd063be64 100644 --- a/app/celery/nightly_tasks.py +++ b/app/celery/nightly_tasks.py @@ -27,7 +27,7 @@ from app.dao.service_data_retention_dao import ( ) from app.enums import NotificationType from app.models import FactProcessingTime -from app.utils import get_midnight_in_utc +from app.utils import get_midnight_in_utc, utc_now @notify_celery.task(name="remove_sms_email_jobs") @@ -46,7 +46,7 @@ def _remove_csv_files(job_types): @notify_celery.task(name="cleanup-unfinished-jobs") def cleanup_unfinished_jobs(): - now = datetime.utcnow() + now = utc_now() jobs = dao_get_unfinished_jobs() for job in jobs: # The query already checks that the processing_finished time is null, so here we are saying @@ -88,7 +88,7 @@ def _delete_notifications_older_than_retention_by_type(notification_type): for f in flexible_data_retention: day_to_delete_backwards_from = get_midnight_in_utc( - datetime.utcnow() + utc_now() ).date() - timedelta(days=f.days_of_retention) delete_notifications_for_service_and_type.apply_async( @@ -100,7 +100,7 @@ def _delete_notifications_older_than_retention_by_type(notification_type): }, ) - seven_days_ago = get_midnight_in_utc(datetime.utcnow()).date() - timedelta(days=7) + seven_days_ago = get_midnight_in_utc(utc_now()).date() - timedelta(days=7) service_ids_with_data_retention = {x.service_id for x in flexible_data_retention} @@ -136,14 +136,14 @@ def _delete_notifications_older_than_retention_by_type(notification_type): def delete_notifications_for_service_and_type( service_id, notification_type, datetime_to_delete_before ): - start = datetime.utcnow() + start = utc_now() num_deleted = move_notifications_to_notification_history( notification_type, service_id, datetime_to_delete_before, ) if num_deleted: - end = datetime.utcnow() + end = utc_now() current_app.logger.info( f"delete-notifications-for-service-and-type: " f"service: {service_id}, " @@ -158,7 +158,7 @@ def delete_notifications_for_service_and_type( def timeout_notifications(): notifications = ["dummy value so len() > 0"] - cutoff_time = datetime.utcnow() - timedelta( + cutoff_time = utc_now() - timedelta( seconds=current_app.config.get("SENDING_NOTIFICATIONS_TIMEOUT_PERIOD") ) @@ -179,11 +179,11 @@ def timeout_notifications(): @cronitor("delete-inbound-sms") def delete_inbound_sms(): try: - start = datetime.utcnow() + start = utc_now() deleted = delete_inbound_sms_older_than_retention() current_app.logger.info( "Delete inbound sms job started {} finished {} deleted {} inbound sms notifications".format( - start, datetime.utcnow(), deleted + start, utc_now(), deleted ) ) except SQLAlchemyError: @@ -197,7 +197,7 @@ def save_daily_notification_processing_time(local_date=None): # local_date is a string in the format of "YYYY-MM-DD" if local_date is None: # if a date is not provided, we run against yesterdays data - local_date = (datetime.utcnow() - timedelta(days=1)).date() + local_date = (utc_now() - timedelta(days=1)).date() else: local_date = datetime.strptime(local_date, "%Y-%m-%d").date() diff --git a/app/celery/process_ses_receipts_tasks.py b/app/celery/process_ses_receipts_tasks.py index 73aa6a4d6..b44d18cc7 100644 --- a/app/celery/process_ses_receipts_tasks.py +++ b/app/celery/process_ses_receipts_tasks.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta import iso8601 from celery.exceptions import Retry @@ -22,6 +22,7 @@ from app.dao.service_callback_api_dao import ( ) from app.enums import CallbackType, NotificationStatus from app.models import Complaint +from app.utils import utc_now @notify_celery.task( @@ -57,7 +58,7 @@ def process_ses_results(self, response): message_time = iso8601.parse_date(ses_message["mail"]["timestamp"]).replace( tzinfo=None ) - if datetime.utcnow() - message_time < timedelta(minutes=5): + if utc_now() - message_time < timedelta(minutes=5): current_app.logger.info( f"Notification not found for reference: {reference}" f"(while attempting update to {notification_status}). " diff --git a/app/celery/provider_tasks.py b/app/celery/provider_tasks.py index 5d8d05ca2..7d32c9326 100644 --- a/app/celery/provider_tasks.py +++ b/app/celery/provider_tasks.py @@ -1,6 +1,6 @@ import json import os -from datetime import datetime, timedelta +from datetime import timedelta from flask import current_app from sqlalchemy.orm.exc import NoResultFound @@ -18,6 +18,7 @@ from app.dao.notifications_dao import ( from app.delivery import send_to_providers from app.enums import NotificationStatus from app.exceptions import NotificationTechnicalFailureException +from app.utils import utc_now # This is the amount of time to wait after sending an sms message before we check the aws logs and look for delivery # receipts @@ -113,9 +114,7 @@ def deliver_sms(self, notification_id): message_id = send_to_providers.send_sms_to_provider(notification) # We have to put it in UTC. For other timezones, the delay # will be ignored and it will fire immediately (although this probably only affects developer testing) - my_eta = datetime.utcnow() + timedelta( - seconds=DELIVERY_RECEIPT_DELAY_IN_SECONDS - ) + my_eta = utc_now() + timedelta(seconds=DELIVERY_RECEIPT_DELAY_IN_SECONDS) check_sms_delivery_receipt.apply_async( [message_id, notification_id, notification.created_at], eta=my_eta, diff --git a/app/celery/reporting_tasks.py b/app/celery/reporting_tasks.py index aa8f6fece..87c3269be 100644 --- a/app/celery/reporting_tasks.py +++ b/app/celery/reporting_tasks.py @@ -9,6 +9,7 @@ from app.dao.fact_billing_dao import fetch_billing_data_for_day, update_fact_bil from app.dao.fact_notification_status_dao import update_fact_notification_status from app.dao.notifications_dao import get_service_ids_with_notifications_on_date from app.enums import NotificationType +from app.utils import utc_now @notify_celery.task(name="create-nightly-billing") @@ -17,7 +18,7 @@ def create_nightly_billing(day_start=None): # day_start is a datetime.date() object. e.g. # up to 4 days of data counting back from day_start is consolidated if day_start is None: - day_start = datetime.utcnow().date() - timedelta(days=1) + day_start = utc_now().date() - timedelta(days=1) else: # When calling the task its a string in the format of "YYYY-MM-DD" day_start = datetime.strptime(day_start, "%Y-%m-%d").date() @@ -39,9 +40,9 @@ def create_nightly_billing_for_day(process_day): f"create-nightly-billing-for-day task for {process_day}: started" ) - start = datetime.utcnow() + start = utc_now() transit_data = fetch_billing_data_for_day(process_day=process_day) - end = datetime.utcnow() + end = utc_now() current_app.logger.info( f"create-nightly-billing-for-day task for {process_day}: data fetched in {(end - start).seconds} seconds" @@ -78,7 +79,7 @@ def create_nightly_notification_status(): mean the aggregated results are temporarily incorrect. """ - yesterday = datetime.utcnow().date() - timedelta(days=1) + yesterday = utc_now().date() - timedelta(days=1) for notification_type in (NotificationType.SMS, NotificationType.EMAIL): days = 4 @@ -107,14 +108,14 @@ def create_nightly_notification_status_for_service_and_day( ): process_day = datetime.strptime(process_day, "%Y-%m-%d").date() - start = datetime.utcnow() + start = utc_now() update_fact_notification_status( process_day=process_day, notification_type=notification_type, service_id=service_id, ) - end = datetime.utcnow() + end = utc_now() current_app.logger.info( f"create-nightly-notification-status-for-service-and-day task update " f"for {service_id}, {notification_type} for {process_day}: " diff --git a/app/celery/scheduled_tasks.py b/app/celery/scheduled_tasks.py index 7da92a2a3..3597bdbb7 100644 --- a/app/celery/scheduled_tasks.py +++ b/app/celery/scheduled_tasks.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta from flask import current_app from sqlalchemy import between @@ -31,6 +31,7 @@ from app.dao.users_dao import delete_codes_older_created_more_than_a_day_ago from app.enums import JobStatus, NotificationType from app.models import Job from app.notifications.process_notifications import send_notification_to_queue +from app.utils import utc_now from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket MAX_NOTIFICATION_FAILS = 10000 @@ -52,11 +53,11 @@ def run_scheduled_jobs(): @notify_celery.task(name="delete-verify-codes") def delete_verify_codes(): try: - start = datetime.utcnow() + start = utc_now() deleted = delete_codes_older_created_more_than_a_day_ago() current_app.logger.info( "Delete job started {} finished {} deleted {} verify codes".format( - start, datetime.utcnow(), deleted + start, utc_now(), deleted ) ) except SQLAlchemyError: @@ -67,20 +68,20 @@ def delete_verify_codes(): @notify_celery.task(name="expire-or-delete-invitations") def expire_or_delete_invitations(): try: - start = datetime.utcnow() + start = utc_now() expired_invites = expire_invitations_created_more_than_two_days_ago() current_app.logger.info( - f"Expire job started {start} finished {datetime.utcnow()} expired {expired_invites} invitations" + f"Expire job started {start} finished {utc_now()} expired {expired_invites} invitations" ) except SQLAlchemyError: current_app.logger.exception("Failed to expire invitations") raise try: - start = datetime.utcnow() + start = utc_now() deleted_invites = delete_org_invitations_created_more_than_two_days_ago() current_app.logger.info( - f"Delete job started {start} finished {datetime.utcnow()} deleted {deleted_invites} invitations" + f"Delete job started {start} finished {utc_now()} deleted {deleted_invites} invitations" ) except SQLAlchemyError: current_app.logger.exception("Failed to delete invitations") @@ -101,8 +102,8 @@ def check_job_status(): update the job_status to 'error' process the rows in the csv that are missing (in another task) just do the check here. """ - thirty_minutes_ago = datetime.utcnow() - timedelta(minutes=30) - thirty_five_minutes_ago = datetime.utcnow() - timedelta(minutes=35) + thirty_minutes_ago = utc_now() - timedelta(minutes=30) + thirty_five_minutes_ago = utc_now() - timedelta(minutes=35) incomplete_in_progress_jobs = Job.query.filter( Job.job_status == JobStatus.IN_PROGRESS, @@ -179,8 +180,8 @@ def check_for_missing_rows_in_completed_jobs(): name="check-for-services-with-high-failure-rates-or-sending-to-tv-numbers" ) def check_for_services_with_high_failure_rates_or_sending_to_tv_numbers(): - start_date = datetime.utcnow() - timedelta(days=1) - end_date = datetime.utcnow() + start_date = utc_now() - timedelta(days=1) + end_date = utc_now() message = "" services_with_failures = dao_find_services_with_high_failure_rates( diff --git a/app/celery/tasks.py b/app/celery/tasks.py index c94b93789..e0428152a 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -1,5 +1,4 @@ import json -from datetime import datetime from flask import current_app from requests import HTTPError, RequestException, request @@ -24,7 +23,7 @@ from app.notifications.process_notifications import persist_notification from app.notifications.validators import check_service_over_total_message_limit from app.serialised_models import SerialisedService, SerialisedTemplate from app.service.utils import service_allowed_to_send_to -from app.utils import DATETIME_FORMAT +from app.utils import DATETIME_FORMAT, utc_now from app.v2.errors import TotalRequestsError from notifications_utils.recipients import RecipientCSV @@ -32,7 +31,7 @@ from notifications_utils.recipients import RecipientCSV @notify_celery.task(name="process-job") def process_job(job_id, sender_id=None): """Update job status, get csv data from s3, and begin processing csv rows.""" - start = datetime.utcnow() + start = utc_now() job = dao_get_job_by_id(job_id) current_app.logger.info( "Starting process-job task for job id {} with status: {}".format( @@ -82,7 +81,7 @@ def process_job(job_id, sender_id=None): def job_complete(job, resumed=False, start=None): job.job_status = JobStatus.FINISHED - finished = datetime.utcnow() + finished = utc_now() job.processing_finished = finished dao_update_job(job) @@ -157,7 +156,7 @@ def __total_sending_limits_for_job_exceeded(service, job, job_id): return False except TotalRequestsError: job.job_status = "sending limits exceeded" - job.processing_finished = datetime.utcnow() + job.processing_finished = utc_now() dao_update_job(job) current_app.logger.error( "Job {} size {} error. Total sending limits {} exceeded".format( @@ -211,7 +210,7 @@ def save_sms(self, service_id, notification_id, encrypted_notification, sender_i notification_type=NotificationType.SMS, api_key_id=None, key_type=KeyType.NORMAL, - created_at=datetime.utcnow(), + created_at=utc_now(), created_by_id=created_by_id, job_id=notification.get("job", None), job_row_number=notification.get("row_number", None), @@ -272,7 +271,7 @@ def save_email( notification_type=NotificationType.EMAIL, api_key_id=None, key_type=KeyType.NORMAL, - created_at=datetime.utcnow(), + created_at=utc_now(), job_id=notification.get("job", None), job_row_number=notification.get("row_number", None), notification_id=notification_id, @@ -438,7 +437,7 @@ def process_incomplete_jobs(job_ids): # reset the processing start time so that the check_job_status scheduled task doesn't pick this job up again for job in jobs: job.job_status = JobStatus.IN_PROGRESS - job.processing_started = datetime.utcnow() + job.processing_started = utc_now() dao_update_job(job) current_app.logger.info("Resuming Job(s) {}".format(job_ids)) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 0deac089e..d010957ac 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -1,7 +1,7 @@ import json import os import re -from datetime import datetime, timedelta +from datetime import timedelta from boto3 import client from flask import current_app @@ -9,6 +9,7 @@ from flask import current_app from app.clients import AWS_CLIENT_CONFIG, Client from app.cloudfoundry_config import cloud_config from app.exceptions import NotificationTechnicalFailureException +from app.utils import utc_now class AwsCloudwatchClient(Client): @@ -50,7 +51,7 @@ class AwsCloudwatchClient(Client): def _get_log(self, my_filter, log_group_name, sent_at): # Check all cloudwatch logs from the time the notification was sent (currently 5 minutes previously) until now - now = datetime.utcnow() + now = utc_now() beginning = sent_at next_token = None all_log_events = [] @@ -112,7 +113,7 @@ class AwsCloudwatchClient(Client): # TODO this clumsy approach to getting the account number will be fixed as part of notify-api #258 account_number = self._extract_account_number(cloud_config.ses_domain_arn) - time_now = datetime.utcnow() + time_now = utc_now() log_group_name = f"sns/{region}/{account_number[4]}/DirectPublishToPhoneNumber" filter_pattern = '{$.notification.messageId="XXXXX"}' filter_pattern = filter_pattern.replace("XXXXX", message_id) diff --git a/app/commands.py b/app/commands.py index 5637832cb..826c2013b 100644 --- a/app/commands.py +++ b/app/commands.py @@ -61,7 +61,7 @@ from app.models import ( TemplateHistory, User, ) -from app.utils import get_midnight_in_utc +from app.utils import get_midnight_in_utc, utc_now from notifications_utils.recipients import RecipientCSV from notifications_utils.template import SMSMessageTemplate from tests.app.db import ( @@ -327,7 +327,7 @@ def update_jobs_archived_flag(start_date, end_date): total_updated = 0 while process_date < end_date: - start_time = datetime.utcnow() + start_time = utc_now() sql = """update jobs set archived = true where diff --git a/app/complaint/complaint_rest.py b/app/complaint/complaint_rest.py index 122534c36..ff558a013 100644 --- a/app/complaint/complaint_rest.py +++ b/app/complaint/complaint_rest.py @@ -6,7 +6,7 @@ from app.complaint.complaint_schema import complaint_count_request from app.dao.complaint_dao import fetch_count_of_complaints, fetch_paginated_complaints from app.errors import register_errors from app.schema_validation import validate -from app.utils import pagination_links +from app.utils import pagination_links, utc_now complaint_blueprint = Blueprint("complaint", __name__, url_prefix="/complaint") @@ -35,7 +35,7 @@ def get_complaint_count(): validate(request.args, complaint_count_request) # If start and end date are not set, we are expecting today's stats. - today = str(datetime.utcnow().date()) + today = str(utc_now().date()) start_date = datetime.strptime( request.args.get("start_date", today), "%Y-%m-%d" diff --git a/app/dao/api_key_dao.py b/app/dao/api_key_dao.py index cd5dd3f2b..06266ab18 100644 --- a/app/dao/api_key_dao.py +++ b/app/dao/api_key_dao.py @@ -1,11 +1,12 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta from sqlalchemy import func, or_ from app import db from app.dao.dao_utils import autocommit, version_class from app.models import ApiKey +from app.utils import utc_now @autocommit @@ -23,7 +24,7 @@ def save_model_api_key(api_key): @version_class(ApiKey) def expire_api_key(service_id, api_key_id): api_key = ApiKey.query.filter_by(id=api_key_id, service_id=service_id).one() - api_key.expiry_date = datetime.utcnow() + api_key.expiry_date = utc_now() db.session.add(api_key) @@ -32,7 +33,7 @@ def get_model_api_keys(service_id, id=None): return ApiKey.query.filter_by( id=id, service_id=service_id, expiry_date=None ).one() - seven_days_ago = datetime.utcnow() - timedelta(days=7) + seven_days_ago = utc_now() - timedelta(days=7) return ApiKey.query.filter( or_( ApiKey.expiry_date == None, # noqa diff --git a/app/dao/date_util.py b/app/dao/date_util.py index 7aafd711f..98c0372c5 100644 --- a/app/dao/date_util.py +++ b/app/dao/date_util.py @@ -1,5 +1,7 @@ from datetime import date, datetime, time, timedelta +from app.utils import utc_now + def get_months_for_financial_year(year): return [ @@ -22,7 +24,7 @@ def get_calendar_year_dates(year): def get_current_calendar_year(): - now = datetime.utcnow() + now = utc_now() current_year = int(now.strftime("%Y")) year = current_year return get_calendar_year(year) diff --git a/app/dao/fact_billing_dao.py b/app/dao/fact_billing_dao.py index 97f346cf5..14d82835b 100644 --- a/app/dao/fact_billing_dao.py +++ b/app/dao/fact_billing_dao.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, timedelta +from datetime import date, timedelta from flask import current_app from sqlalchemy import Date, Integer, and_, desc, func, union @@ -18,7 +18,7 @@ from app.models import ( Rate, Service, ) -from app.utils import get_midnight_in_utc +from app.utils import get_midnight_in_utc, utc_now def fetch_sms_free_allowance_remainder_until_date(end_date): @@ -198,7 +198,7 @@ def fetch_monthly_billing_for_year(service_id, year): we also update the table on-the-fly if we need accurate data for this year. """ _, year_end = get_calendar_year_dates(year) - today = datetime.utcnow().date() + today = utc_now().date() # if year end date is less than today, we are calculating for data in the past and have no need for deltas. if year_end >= today: @@ -535,7 +535,7 @@ def update_fact_billing(data, process_day): set_={ "notifications_sent": stmt.excluded.notifications_sent, "billable_units": stmt.excluded.billable_units, - "updated_at": datetime.utcnow(), + "updated_at": utc_now(), }, ) db.session.connection().execute(stmt) @@ -699,7 +699,7 @@ def query_organization_sms_usage_for_year(organization_id, year): def fetch_usage_year_for_organization(organization_id, year): year_start, year_end = get_calendar_year_dates(year) - today = datetime.utcnow().date() + today = utc_now().date() services = dao_get_organization_live_services(organization_id) # if year end date is less than today, we are calculating for data in the past and have no need for deltas. diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index 5eee7d8c2..22c87fe83 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta from sqlalchemy import Date, case, func from sqlalchemy.dialects.postgresql import insert @@ -19,6 +19,7 @@ from app.utils import ( get_midnight_in_utc, get_month_from_utc_column, midnight_n_days_ago, + utc_now, ) @@ -128,7 +129,7 @@ def fetch_notification_status_for_service_for_today_and_7_previous_days( service_id, by_template=False, limit_days=7 ): start_date = midnight_n_days_ago(limit_days) - now = datetime.utcnow() + now = utc_now() stats_for_7_days = db.session.query( FactNotificationStatus.notification_type.cast(db.Text).label( "notification_type" @@ -212,8 +213,8 @@ def fetch_notification_status_totals_for_all_services(start_date, end_date): FactNotificationStatus.key_type, ) ) - today = get_midnight_in_utc(datetime.utcnow()) - if start_date <= datetime.utcnow().date() <= end_date: + today = get_midnight_in_utc(utc_now()) + if start_date <= utc_now().date() <= end_date: stats_for_today = ( db.session.query( Notification.notification_type.cast(db.Text).label("notification_type"), @@ -299,8 +300,8 @@ def fetch_stats_for_all_services_by_date_range( if not include_from_test_key: stats = stats.filter(FactNotificationStatus.key_type != KeyType.TEST) - if start_date <= datetime.utcnow().date() <= end_date: - today = get_midnight_in_utc(datetime.utcnow()) + if start_date <= utc_now().date() <= end_date: + today = get_midnight_in_utc(utc_now()) subquery = ( db.session.query( Notification.notification_type.label("notification_type"), @@ -395,8 +396,8 @@ def fetch_monthly_template_usage_for_service(start_date, end_date, service_id): ) ) - if start_date <= datetime.utcnow() <= end_date: - today = get_midnight_in_utc(datetime.utcnow()) + if start_date <= utc_now() <= end_date: + today = get_midnight_in_utc(utc_now()) month = get_month_from_utc_column(Notification.created_at) stats_for_today = ( diff --git a/app/dao/fact_processing_time_dao.py b/app/dao/fact_processing_time_dao.py index 350de270a..af8efcf10 100644 --- a/app/dao/fact_processing_time_dao.py +++ b/app/dao/fact_processing_time_dao.py @@ -1,11 +1,10 @@ -from datetime import datetime - from sqlalchemy.dialects.postgresql import insert from sqlalchemy.sql.expression import case from app import db from app.dao.dao_utils import autocommit from app.models import FactProcessingTime +from app.utils import utc_now @autocommit @@ -27,7 +26,7 @@ def insert_update_processing_time(processing_time): set_={ "messages_total": stmt.excluded.messages_total, "messages_within_10_secs": stmt.excluded.messages_within_10_secs, - "updated_at": datetime.utcnow(), + "updated_at": utc_now(), }, ) db.session.connection().execute(stmt) diff --git a/app/dao/invited_org_user_dao.py b/app/dao/invited_org_user_dao.py index 3ed122371..2bcf36a05 100644 --- a/app/dao/invited_org_user_dao.py +++ b/app/dao/invited_org_user_dao.py @@ -1,7 +1,8 @@ -from datetime import datetime, timedelta +from datetime import timedelta from app import db from app.models import InvitedOrganizationUser +from app.utils import utc_now def save_invited_org_user(invited_org_user): @@ -28,9 +29,7 @@ def get_invited_org_users_for_organization(organization_id): def delete_org_invitations_created_more_than_two_days_ago(): deleted = ( db.session.query(InvitedOrganizationUser) - .filter( - InvitedOrganizationUser.created_at <= datetime.utcnow() - timedelta(days=2) - ) + .filter(InvitedOrganizationUser.created_at <= utc_now() - timedelta(days=2)) .delete() ) db.session.commit() diff --git a/app/dao/invited_user_dao.py b/app/dao/invited_user_dao.py index ab83c2534..a342f504d 100644 --- a/app/dao/invited_user_dao.py +++ b/app/dao/invited_user_dao.py @@ -1,8 +1,9 @@ -from datetime import datetime, timedelta +from datetime import timedelta from app import db from app.enums import InvitedUserStatus from app.models import InvitedUser +from app.utils import utc_now def save_invited_user(invited_user): @@ -41,7 +42,7 @@ def expire_invitations_created_more_than_two_days_ago(): expired = ( db.session.query(InvitedUser) .filter( - InvitedUser.created_at <= datetime.utcnow() - timedelta(days=2), + InvitedUser.created_at <= utc_now() - timedelta(days=2), InvitedUser.status.in_((InvitedUserStatus.PENDING,)), ) .update({InvitedUser.status: InvitedUserStatus.EXPIRED}) diff --git a/app/dao/jobs_dao.py b/app/dao/jobs_dao.py index 209fe76d6..a278bb7fe 100644 --- a/app/dao/jobs_dao.py +++ b/app/dao/jobs_dao.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta from flask import current_app from sqlalchemy import and_, asc, desc, func @@ -13,7 +13,7 @@ from app.models import ( ServiceDataRetention, Template, ) -from app.utils import midnight_n_days_ago +from app.utils import midnight_n_days_ago, utc_now def dao_get_notification_outcomes_for_job(service_id, job_id): @@ -110,7 +110,7 @@ def dao_set_scheduled_jobs_to_pending(): jobs = ( Job.query.filter( Job.job_status == JobStatus.SCHEDULED, - Job.scheduled_for < datetime.utcnow(), + Job.scheduled_for < utc_now(), ) .order_by(asc(Job.scheduled_for)) .with_for_update() @@ -131,7 +131,7 @@ def dao_get_future_scheduled_job_by_id_and_service_id(job_id, service_id): Job.service_id == service_id, Job.id == job_id, Job.job_status == JobStatus.SCHEDULED, - Job.scheduled_for > datetime.utcnow(), + Job.scheduled_for > utc_now(), ).one() @@ -152,7 +152,7 @@ def dao_get_jobs_older_than_data_retention(notification_types): ServiceDataRetention.notification_type.in_(notification_types) ).all() jobs = [] - today = datetime.utcnow().date() + today = utc_now().date() for f in flexible_data_retention: end_date = today - timedelta(days=f.days_of_retention) @@ -193,8 +193,8 @@ def dao_get_jobs_older_than_data_retention(notification_types): def find_jobs_with_missing_rows(): # Jobs can be a maximum of 100,000 rows. It typically takes 10 minutes to create all those notifications. # Using 20 minutes as a condition seems reasonable. - ten_minutes_ago = datetime.utcnow() - timedelta(minutes=20) - yesterday = datetime.utcnow() - timedelta(days=1) + ten_minutes_ago = utc_now() - timedelta(minutes=20) + yesterday = utc_now() - timedelta(days=1) jobs_with_rows_missing = ( db.session.query(Job) .filter( diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index f00ae4a9b..320bff36c 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta from flask import current_app from sqlalchemy import asc, desc, or_, select, text, union @@ -16,6 +16,7 @@ from app.utils import ( escape_special_characters, get_midnight_in_utc, midnight_n_days_ago, + utc_now, ) from notifications_utils.international_billing_rates import INTERNATIONAL_BILLING_RATES from notifications_utils.recipients import ( @@ -95,7 +96,7 @@ def _update_notification_status( current_status=notification.status, status=status ) notification.status = status - notification.sent_at = datetime.utcnow() + notification.sent_at = utc_now() if provider_response: notification.provider_response = provider_response if carrier: @@ -179,7 +180,7 @@ def update_notification_status_by_reference(reference, status): @autocommit def dao_update_notification(notification): - notification.updated_at = datetime.utcnow() + notification.updated_at = utc_now() # notify-api-742 remove phone numbers from db notification.to = "1" notification.normalised_to = "1" @@ -327,7 +328,7 @@ def sanitize_successful_notification_by_id(notification_id, carrier, provider_re "notification_id": notification_id, "carrier": carrier, "response": provider_response, - "sent_at": datetime.utcnow(), + "sent_at": utc_now(), } db.session.execute(update_query, input_params) @@ -437,7 +438,7 @@ def dao_timeout_notifications(cutoff_time, limit=100000): Set email and SMS notifications (only) to "temporary-failure" status if they're still sending from before the specified cutoff_time. """ - updated_at = datetime.utcnow() + updated_at = utc_now() current_statuses = [NotificationStatus.SENDING, NotificationStatus.PENDING] new_status = NotificationStatus.TEMPORARY_FAILURE @@ -599,9 +600,7 @@ 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 = datetime.utcnow() - timedelta( - seconds=should_be_sending_after_seconds - ) + older_than_date = utc_now() - timedelta(seconds=should_be_sending_after_seconds) notifications = Notification.query.filter( Notification.created_at <= older_than_date, @@ -622,8 +621,7 @@ def _duplicate_update_warning(notification, status): id=notification.id, old_status=notification.status, new_status=status, - time_diff=datetime.utcnow() - - (notification.updated_at or notification.created_at), + time_diff=utc_now() - (notification.updated_at or notification.created_at), type=notification.notification_type, sent_by=notification.sent_by, service_id=notification.service_id, diff --git a/app/dao/provider_details_dao.py b/app/dao/provider_details_dao.py index 0cb22adcd..73132a44e 100644 --- a/app/dao/provider_details_dao.py +++ b/app/dao/provider_details_dao.py @@ -7,6 +7,7 @@ from app import db from app.dao.dao_utils import autocommit from app.enums import NotificationType from app.models import FactBilling, ProviderDetails, ProviderDetailsHistory, User +from app.utils import utc_now def get_provider_details_by_id(provider_details_id): @@ -66,7 +67,7 @@ def _get_sms_providers_for_update(time_threshold): # if something updated recently, don't update again. If the updated_at is null, treat it as min time if any( - (provider.updated_at or datetime.min) > datetime.utcnow() - time_threshold + (provider.updated_at or datetime.min) > utc_now() - time_threshold for provider in q ): current_app.logger.info( @@ -102,7 +103,7 @@ def _update_provider_details_without_commit(provider_details): Doesn't commit, for when you need to control the database transaction manually """ provider_details.version += 1 - provider_details.updated_at = datetime.utcnow() + provider_details.updated_at = utc_now() history = ProviderDetailsHistory.from_original(provider_details) db.session.add(provider_details) db.session.add(history) @@ -111,7 +112,7 @@ def _update_provider_details_without_commit(provider_details): def dao_get_provider_stats(): # this query does not include the current day since the task to populate ft_billing runs overnight - current_datetime = datetime.utcnow() + current_datetime = utc_now() first_day_of_the_month = current_datetime.date().replace(day=1) subquery = ( diff --git a/app/dao/service_callback_api_dao.py b/app/dao/service_callback_api_dao.py index 75ea69f09..a1a39d982 100644 --- a/app/dao/service_callback_api_dao.py +++ b/app/dao/service_callback_api_dao.py @@ -1,16 +1,15 @@ -from datetime import datetime - from app import create_uuid, db from app.dao.dao_utils import autocommit, version_class from app.enums import CallbackType from app.models import ServiceCallbackApi +from app.utils import utc_now @autocommit @version_class(ServiceCallbackApi) def save_service_callback_api(service_callback_api): service_callback_api.id = create_uuid() - service_callback_api.created_at = datetime.utcnow() + service_callback_api.created_at = utc_now() db.session.add(service_callback_api) @@ -24,7 +23,7 @@ def reset_service_callback_api( if bearer_token: service_callback_api.bearer_token = bearer_token service_callback_api.updated_by_id = updated_by_id - service_callback_api.updated_at = datetime.utcnow() + service_callback_api.updated_at = utc_now() db.session.add(service_callback_api) diff --git a/app/dao/service_data_retention_dao.py b/app/dao/service_data_retention_dao.py index 1e14127d7..b95ca5720 100644 --- a/app/dao/service_data_retention_dao.py +++ b/app/dao/service_data_retention_dao.py @@ -1,8 +1,7 @@ -from datetime import datetime - from app import db from app.dao.dao_utils import autocommit from app.models import ServiceDataRetention +from app.utils import utc_now def fetch_service_data_retention_by_id(service_id, data_retention_id): @@ -50,7 +49,7 @@ def update_service_data_retention( updated_count = ServiceDataRetention.query.filter( ServiceDataRetention.id == service_data_retention_id, ServiceDataRetention.service_id == service_id, - ).update({"days_of_retention": days_of_retention, "updated_at": datetime.utcnow()}) + ).update({"days_of_retention": days_of_retention, "updated_at": utc_now()}) return updated_count diff --git a/app/dao/service_inbound_api_dao.py b/app/dao/service_inbound_api_dao.py index 11634d3ee..a04affe9e 100644 --- a/app/dao/service_inbound_api_dao.py +++ b/app/dao/service_inbound_api_dao.py @@ -1,15 +1,14 @@ -from datetime import datetime - from app import create_uuid, db from app.dao.dao_utils import autocommit, version_class from app.models import ServiceInboundApi +from app.utils import utc_now @autocommit @version_class(ServiceInboundApi) def save_service_inbound_api(service_inbound_api): service_inbound_api.id = create_uuid() - service_inbound_api.created_at = datetime.utcnow() + service_inbound_api.created_at = utc_now() db.session.add(service_inbound_api) @@ -23,7 +22,7 @@ def reset_service_inbound_api( if bearer_token: service_inbound_api.bearer_token = bearer_token service_inbound_api.updated_by_id = updated_by_id - service_inbound_api.updated_at = datetime.utcnow() + service_inbound_api.updated_at = utc_now() db.session.add(service_inbound_api) diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index df8e59287..7ddc456e7 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta from flask import current_app from sqlalchemy import Float, cast, select @@ -44,6 +44,7 @@ from app.utils import ( escape_special_characters, get_archived_db_column_value, get_midnight_in_utc, + utc_now, ) @@ -252,7 +253,7 @@ def dao_archive_service(service_id): for api_key in service.api_keys: if not api_key.expiry_date: - api_key.expiry_date = datetime.utcnow() + api_key.expiry_date = utc_now() def dao_fetch_service_by_id_and_user(service_id, user_id): @@ -404,7 +405,7 @@ def delete_service_and_all_associated_db_objects(service): def dao_fetch_todays_stats_for_service(service_id): - today = datetime.utcnow().date() + today = utc_now().date() start_date = get_midnight_in_utc(today) return ( db.session.query( @@ -428,7 +429,7 @@ def dao_fetch_todays_stats_for_service(service_id): def dao_fetch_todays_stats_for_all_services( include_from_test_key=True, only_active=True ): - today = datetime.utcnow().date() + today = utc_now().date() start_date = get_midnight_in_utc(today) end_date = get_midnight_in_utc(today + timedelta(days=1)) @@ -491,7 +492,7 @@ def dao_suspend_service(service_id): for api_key in service.api_keys: if not api_key.expiry_date: - api_key.expiry_date = datetime.utcnow() + api_key.expiry_date = utc_now() service.active = False diff --git a/app/dao/templates_dao.py b/app/dao/templates_dao.py index 26cdc2497..55d4363d6 100644 --- a/app/dao/templates_dao.py +++ b/app/dao/templates_dao.py @@ -1,11 +1,11 @@ import uuid -from datetime import datetime from sqlalchemy import asc, desc from app import db from app.dao.dao_utils import VersionOptions, autocommit, version_class from app.models import Template, TemplateHistory, TemplateRedacted +from app.utils import utc_now @autocommit @@ -39,7 +39,7 @@ def dao_update_template(template): @autocommit def dao_redact_template(template, user_id): template.template_redacted.redact_personalisation = True - template.template_redacted.updated_at = datetime.utcnow() + template.template_redacted.updated_at = utc_now() template.template_redacted.updated_by_id = user_id db.session.add(template.template_redacted) diff --git a/app/dao/uploads_dao.py b/app/dao/uploads_dao.py index cdbe9d247..1f7b7021c 100644 --- a/app/dao/uploads_dao.py +++ b/app/dao/uploads_dao.py @@ -1,4 +1,3 @@ -from datetime import datetime from os import getenv from flask import current_app @@ -7,7 +6,7 @@ from sqlalchemy import String, and_, desc, func, literal, text from app import db from app.enums import JobStatus, NotificationStatus, NotificationType from app.models import Job, Notification, ServiceDataRetention, Template -from app.utils import midnight_n_days_ago +from app.utils import midnight_n_days_ago, utc_now def _get_printing_day(created_at): @@ -40,7 +39,7 @@ def _naive_gmt_to_utc(column): def dao_get_uploads_by_service_id(service_id, limit_days=None, page=1, page_size=50): # Hardcoded filter to exclude cancelled or scheduled jobs # for the moment, but we may want to change this method take 'statuses' as a argument in the future - today = datetime.utcnow().date() + today = utc_now().date() jobs_query_filter = [ Job.service_id == service_id, Job.original_file_name != current_app.config["TEST_MESSAGE_FILENAME"], diff --git a/app/dao/users_dao.py b/app/dao/users_dao.py index 58b660aba..d7291b35c 100644 --- a/app/dao/users_dao.py +++ b/app/dao/users_dao.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta from secrets import randbelow import sqlalchemy @@ -14,7 +14,7 @@ from app.dao.service_user_dao import dao_get_service_users_by_user_id from app.enums import AuthType, PermissionType from app.errors import InvalidRequest from app.models import Organization, Service, User, VerifyCode -from app.utils import escape_special_characters, get_archived_db_column_value +from app.utils import escape_special_characters, get_archived_db_column_value, utc_now def _remove_values_for_keys_if_present(dict, keys): @@ -76,9 +76,9 @@ def save_model_user( ): if password: user.password = password - user.password_changed_at = datetime.utcnow() + user.password_changed_at = utc_now() if validated_email_access: - user.email_access_validated_at = datetime.utcnow() + user.email_access_validated_at = utc_now() if update_dict: _remove_values_for_keys_if_present(update_dict, ["id", "password_changed_at"]) db.session.query(User).filter_by(id=user.id).update(update_dict or {}) @@ -90,7 +90,7 @@ def save_model_user( def create_user_code(user, code, code_type): verify_code = VerifyCode( code_type=code_type, - expiry_datetime=datetime.utcnow() + timedelta(minutes=30), + expiry_datetime=utc_now() + timedelta(minutes=30), user=user, ) verify_code.code = code @@ -111,7 +111,7 @@ def get_user_code(user, code, code_type): def delete_codes_older_created_more_than_a_day_ago(): deleted = ( db.session.query(VerifyCode) - .filter(VerifyCode.created_at < datetime.utcnow() - timedelta(hours=24)) + .filter(VerifyCode.created_at < utc_now() - timedelta(hours=24)) .delete() ) db.session.commit() @@ -138,7 +138,7 @@ def delete_user_verify_codes(user): def count_user_verify_codes(user): query = VerifyCode.query.filter( VerifyCode.user == user, - VerifyCode.expiry_datetime > datetime.utcnow(), + VerifyCode.expiry_datetime > utc_now(), VerifyCode.code_used.is_(False), ) return query.count() @@ -179,7 +179,7 @@ def reset_failed_login_count(user): def update_user_password(user, password): # reset failed login count - they've just reset their password so should be fine user.password = password - user.password_changed_at = datetime.utcnow() + user.password_changed_at = utc_now() db.session.add(user) db.session.commit() diff --git a/app/delivery/send_to_providers.py b/app/delivery/send_to_providers.py index 8a06d820a..a9b90d368 100644 --- a/app/delivery/send_to_providers.py +++ b/app/delivery/send_to_providers.py @@ -1,5 +1,4 @@ import json -from datetime import datetime from urllib import parse from cachetools import TTLCache, cached @@ -14,6 +13,7 @@ from app.dao.provider_details_dao import get_provider_details_by_notification_ty from app.enums import BrandType, KeyType, NotificationStatus, NotificationType from app.exceptions import NotificationTechnicalFailureException from app.serialised_models import SerialisedService, SerialisedTemplate +from app.utils import utc_now from notifications_utils.template import ( HTMLEmailTemplate, PlainTextEmailTemplate, @@ -177,7 +177,7 @@ def send_email_to_provider(notification): def update_notification_to_sending(notification, provider): - notification.sent_at = datetime.utcnow() + notification.sent_at = utc_now() notification.sent_by = provider.name if notification.status not in NotificationStatus.completed_types(): notification.status = NotificationStatus.SENDING diff --git a/app/history_meta.py b/app/history_meta.py index f7bef7076..1ef19f198 100644 --- a/app/history_meta.py +++ b/app/history_meta.py @@ -15,12 +15,12 @@ session events. """ -import datetime - from sqlalchemy import Column, ForeignKeyConstraint, Integer, Table, util from sqlalchemy.orm import attributes, object_mapper, registry from sqlalchemy.orm.properties import ColumnProperty, RelationshipProperty +from app.utils import utc_now + def col_references_table(col, table): for fk in col.foreign_keys: @@ -236,10 +236,10 @@ def create_history(obj, history_cls=None): if not obj.version: obj.version = 1 - obj.created_at = datetime.datetime.utcnow() + obj.created_at = utc_now() else: obj.version += 1 - now = datetime.datetime.utcnow() + now = utc_now() obj.updated_at = now data["updated_at"] = now diff --git a/app/models.py b/app/models.py index 71eea3295..e6c3c66ae 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,3 @@ -import datetime import itertools import uuid @@ -36,6 +35,7 @@ from app.utils import ( DATETIME_FORMAT, DATETIME_FORMAT_NO_TIMEZONE, get_dt_string_or_none, + utc_now, ) from notifications_utils.clients.encryption.encryption_client import EncryptionError from notifications_utils.recipients import ( @@ -115,14 +115,14 @@ class User(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, index=False, unique=False, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) _password = db.Column(db.String, index=False, unique=False, nullable=False) mobile_number = db.Column(db.String, index=False, unique=False, nullable=True) @@ -131,7 +131,7 @@ class User(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) logged_in_at = db.Column(db.DateTime, nullable=True) failed_login_count = db.Column(db.Integer, nullable=False, default=0) @@ -144,7 +144,7 @@ class User(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) preferred_timezone = db.Column( db.Text, @@ -368,12 +368,12 @@ class Organization(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) agreement_signed = db.Column(db.Boolean, nullable=True) agreement_signed_at = db.Column(db.DateTime, nullable=True) @@ -488,14 +488,14 @@ class Service(db.Model, Versioned): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, index=False, unique=False, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) active = db.Column( db.Boolean, @@ -626,12 +626,12 @@ class AnnualBilling(db.Model): updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) UniqueConstraint( "financial_year_start", @@ -698,13 +698,13 @@ class InboundNumber(db.Model): ) created_at = db.Column( db.DateTime, - default=datetime.datetime.utcnow, + default=utc_now(), nullable=False, ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) def serialize(self): @@ -753,13 +753,13 @@ class ServiceSmsSender(db.Model): ) created_at = db.Column( db.DateTime, - default=datetime.datetime.utcnow, + default=utc_now(), nullable=False, ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) def get_reply_to_text(self): @@ -798,7 +798,7 @@ class ServicePermission(db.Model): ) created_at = db.Column( db.DateTime, - default=datetime.datetime.utcnow, + default=utc_now(), nullable=False, ) @@ -826,7 +826,7 @@ class ServiceGuestList(db.Model): service = db.relationship("Service", backref="guest_list") recipient_type = enum_column(RecipientType, nullable=False) recipient = db.Column(db.String(255), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow) + created_at = db.Column(db.DateTime, default=utc_now()) @classmethod def from_string(cls, service_id, recipient_type, recipient): @@ -867,7 +867,7 @@ class ServiceInboundApi(db.Model, Versioned): _bearer_token = db.Column("bearer_token", db.String(), nullable=False) created_at = db.Column( db.DateTime, - default=datetime.datetime.utcnow, + default=utc_now(), nullable=False, ) updated_at = db.Column(db.DateTime, nullable=True) @@ -914,7 +914,7 @@ class ServiceCallbackApi(db.Model, Versioned): _bearer_token = db.Column("bearer_token", db.String(), nullable=False) created_at = db.Column( db.DateTime, - default=datetime.datetime.utcnow, + default=utc_now(), nullable=False, ) updated_at = db.Column(db.DateTime, nullable=True) @@ -974,14 +974,14 @@ class ApiKey(db.Model, Versioned): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, index=False, unique=False, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) created_by = db.relationship("User") created_by_id = db.Column( @@ -1101,9 +1101,9 @@ class TemplateBase(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) - updated_at = db.Column(db.DateTime, onupdate=datetime.datetime.utcnow) + updated_at = db.Column(db.DateTime, onupdate=utc_now()) content = db.Column(db.Text, nullable=False) archived = db.Column(db.Boolean, nullable=False, default=False) hidden = db.Column(db.Boolean, nullable=False, default=False) @@ -1250,7 +1250,7 @@ class TemplateRedacted(db.Model): updated_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_by_id = db.Column( UUID(as_uuid=True), @@ -1304,7 +1304,7 @@ class ProviderDetails(db.Model): updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) created_by_id = db.Column( UUID(as_uuid=True), @@ -1326,9 +1326,7 @@ class ProviderDetailsHistory(db.Model, HistoryModel): notification_type = enum_column(NotificationType, nullable=False) active = db.Column(db.Boolean, nullable=False) version = db.Column(db.Integer, primary_key=True, nullable=False) - updated_at = db.Column( - db.DateTime, nullable=True, onupdate=datetime.datetime.utcnow - ) + updated_at = db.Column(db.DateTime, nullable=True, onupdate=utc_now()) created_by_id = db.Column( UUID(as_uuid=True), db.ForeignKey("users.id"), index=True, nullable=True ) @@ -1359,14 +1357,14 @@ class Job(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, index=False, unique=False, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) notification_count = db.Column(db.Integer, nullable=False) notifications_sent = db.Column(db.Integer, nullable=False, default=0) @@ -1410,7 +1408,7 @@ class VerifyCode(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) @property @@ -1508,7 +1506,7 @@ class Notification(db.Model): index=False, unique=False, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) status = enum_column( NotificationStatus, @@ -1785,7 +1783,7 @@ class NotificationHistory(db.Model, HistoryModel): index=False, unique=False, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) status = enum_column( NotificationStatus, @@ -1854,7 +1852,7 @@ class InvitedUser(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) status = enum_column( InvitedUserStatus, @@ -1893,7 +1891,7 @@ class InvitedOrganizationUser(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) status = enum_column( @@ -1939,7 +1937,7 @@ class Permission(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) __table_args__ = ( @@ -1962,7 +1960,7 @@ class Event(db.Model): index=False, unique=False, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) data = db.Column(JSON, nullable=False) @@ -1986,7 +1984,7 @@ class InboundSms(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) service_id = db.Column( UUID(as_uuid=True), @@ -2066,12 +2064,12 @@ class ServiceEmailReplyTo(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) def serialize(self): @@ -2112,12 +2110,12 @@ class FactBilling(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) @@ -2149,12 +2147,12 @@ class FactNotificationStatus(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) @@ -2167,12 +2165,12 @@ class FactProcessingTime(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) @@ -2195,7 +2193,7 @@ class Complaint(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) def serialize(self): @@ -2234,12 +2232,12 @@ class ServiceDataRetention(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) __table_args__ = ( @@ -2288,12 +2286,12 @@ class WebauthnCredential(db.Model): created_at = db.Column( db.DateTime, nullable=False, - default=datetime.datetime.utcnow, + default=utc_now(), ) updated_at = db.Column( db.DateTime, nullable=True, - onupdate=datetime.datetime.utcnow, + onupdate=utc_now(), ) def serialize(self): diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 8f542d31a..2a423767d 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -1,5 +1,4 @@ import uuid -from datetime import datetime from flask import current_app @@ -12,6 +11,7 @@ from app.dao.notifications_dao import ( ) from app.enums import KeyType, NotificationStatus, NotificationType from app.models import Notification +from app.utils import utc_now from app.v2.errors import BadRequestError from notifications_utils.recipients import ( format_email_address, @@ -77,7 +77,7 @@ def persist_notification( document_download_count=None, updated_at=None, ): - notification_created_at = created_at or datetime.utcnow() + notification_created_at = created_at or utc_now() if not notification_id: notification_id = uuid.uuid4() diff --git a/app/performance_dashboard/rest.py b/app/performance_dashboard/rest.py index 4810dc17b..52267a353 100644 --- a/app/performance_dashboard/rest.py +++ b/app/performance_dashboard/rest.py @@ -12,6 +12,7 @@ from app.performance_dashboard.performance_dashboard_schema import ( performance_dashboard_request, ) from app.schema_validation import validate +from app.utils import utc_now performance_dashboard_blueprint = Blueprint( "performance_dashboard", __name__, url_prefix="/performance-dashboard" @@ -29,7 +30,7 @@ def get_performance_dashboard(): validate(request.args, performance_dashboard_request) # If start and end date are not set, we are expecting today's stats. - today = str(datetime.utcnow().date()) + today = str(utc_now().date()) start_date = datetime.strptime( request.args.get("start_date", today), diff --git a/app/platform_stats/rest.py b/app/platform_stats/rest.py index 447e924d1..17f2faabb 100644 --- a/app/platform_stats/rest.py +++ b/app/platform_stats/rest.py @@ -17,7 +17,7 @@ from app.errors import InvalidRequest, register_errors from app.platform_stats.platform_stats_schema import platform_stats_request from app.schema_validation import validate from app.service.statistics import format_admin_stats -from app.utils import get_midnight_in_utc +from app.utils import get_midnight_in_utc, utc_now platform_stats_blueprint = Blueprint("platform_stats", __name__) @@ -30,7 +30,7 @@ def get_platform_stats(): validate(request.args, platform_stats_request) # If start and end date are not set, we are expecting today's stats. - today = str(datetime.utcnow().date()) + today = str(utc_now().date()) start_date = datetime.strptime( request.args.get("start_date", today), "%Y-%m-%d" diff --git a/app/schema_validation/__init__.py b/app/schema_validation/__init__.py index c4f8f6486..04be11d1e 100644 --- a/app/schema_validation/__init__.py +++ b/app/schema_validation/__init__.py @@ -1,10 +1,11 @@ import json -from datetime import datetime, timedelta +from datetime import timedelta from uuid import UUID from iso8601 import ParseError, iso8601 from jsonschema import Draft7Validator, FormatChecker, ValidationError +from app.utils import utc_now from notifications_utils.recipients import ( InvalidEmailError, InvalidPhoneError, @@ -41,9 +42,9 @@ def validate_schema_date_with_hour(instance): if isinstance(instance, str): try: dt = iso8601.parse_date(instance).replace(tzinfo=None) - if dt < datetime.utcnow(): + if dt < utc_now(): raise ValidationError("datetime can not be in the past") - if dt > datetime.utcnow() + timedelta(hours=24): + if dt > utc_now() + timedelta(hours=24): raise ValidationError("datetime can only be 24 hours in the future") except ParseError: raise ValidationError( diff --git a/app/schemas.py b/app/schemas.py index 7b47da593..8657eeafe 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta from uuid import UUID from dateutil.parser import parse @@ -19,7 +19,7 @@ from app import ma, models from app.dao.permissions_dao import permission_dao from app.enums import ServicePermissionType, TemplateType from app.models import ServicePermission -from app.utils import DATETIME_FORMAT_NO_TIMEZONE, get_template_instance +from app.utils import DATETIME_FORMAT_NO_TIMEZONE, get_template_instance, utc_now from notifications_utils.recipients import ( InvalidEmailError, InvalidPhoneError, @@ -41,12 +41,12 @@ def _validate_positive_number(value, msg="Not a positive integer"): def _validate_datetime_not_more_than_96_hours_in_future( dte, msg="Date cannot be more than 96hrs in the future" ): - if dte > datetime.utcnow() + timedelta(hours=96): + if dte > utc_now() + timedelta(hours=96): raise ValidationError(msg) def _validate_datetime_not_in_past(dte, msg="Date cannot be in the past"): - if dte < datetime.utcnow(): + if dte < utc_now(): raise ValidationError(msg) diff --git a/app/service/rest.py b/app/service/rest.py index ce5083073..d9629f879 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -102,7 +102,7 @@ from app.service.service_senders_schema import ( ) from app.service.utils import get_guest_list_objects from app.user.users_schema import post_set_permissions_schema -from app.utils import get_prev_next_pagination_links, hilite +from app.utils import get_prev_next_pagination_links, hilite, utc_now service_blueprint = Blueprint("service", __name__) @@ -144,7 +144,7 @@ def get_services(): include_from_test_key = request.args.get("include_from_test_key", "True") != "False" # If start and end date are not set, we are expecting today's stats. - today = str(datetime.utcnow().date()) + today = str(utc_now().date()) start_date = datetime.strptime( request.args.get("start_date", today), "%Y-%m-%d" @@ -583,7 +583,7 @@ def get_monthly_notification_stats(service_id): ) statistics.add_monthly_notification_status_stats(data, stats) - now = datetime.utcnow() + now = utc_now() if end_date > now: todays_deltas = fetch_notification_status_for_service_for_day( now, service_id=service_id @@ -615,7 +615,7 @@ def get_service_statistics(service_id, today_only, limit_days=7): def get_detailed_services( start_date, end_date, only_active=False, include_from_test_key=True ): - if start_date == datetime.utcnow().date(): + if start_date == utc_now().date(): stats = dao_fetch_todays_stats_for_all_services( include_from_test_key=include_from_test_key, only_active=only_active ) diff --git a/app/service_invite/rest.py b/app/service_invite/rest.py index 02899d3e9..4f57c9d7f 100644 --- a/app/service_invite/rest.py +++ b/app/service_invite/rest.py @@ -1,7 +1,6 @@ import base64 import json import os -from datetime import datetime from flask import Blueprint, current_app, jsonify, request from itsdangerous import BadData, SignatureExpired @@ -25,7 +24,7 @@ from app.notifications.process_notifications import ( send_notification_to_queue, ) from app.schemas import invited_user_schema -from app.utils import hilite +from app.utils import hilite, utc_now from notifications_utils.url_safe_token import check_token, generate_token service_invite = Blueprint("service_invite", __name__) @@ -156,7 +155,7 @@ def resend_service_invite(service_id, invited_user_id): invited_user_id=invited_user_id, ) - fetched.created_at = datetime.utcnow() + fetched.created_at = utc_now() fetched.status = InvitedUserStatus.PENDING current_data = {k: v for k, v in invited_user_schema.dump(fetched).items()} diff --git a/app/user/rest.py b/app/user/rest.py index ea2da8eee..760334841 100644 --- a/app/user/rest.py +++ b/app/user/rest.py @@ -1,6 +1,5 @@ import json import uuid -from datetime import datetime from urllib.parse import urlencode from flask import Blueprint, abort, current_app, jsonify, request @@ -55,7 +54,7 @@ from app.user.users_schema import ( post_verify_code_schema, post_verify_webauthn_schema, ) -from app.utils import url_with_token +from app.utils import url_with_token, utc_now from notifications_utils.recipients import is_us_phone_number, use_numeric_sender user_blueprint = Blueprint("user", __name__) @@ -222,15 +221,15 @@ def verify_user_code(user_id): # only relevant from sms increment_failed_login_count(user_to_verify) raise InvalidRequest("Code not found", status_code=404) - if datetime.utcnow() > code.expiry_datetime or code.code_used: + if utc_now() > code.expiry_datetime or code.code_used: # sms and email increment_failed_login_count(user_to_verify) raise InvalidRequest("Code has expired", status_code=400) user_to_verify.current_session_id = str(uuid.uuid4()) - user_to_verify.logged_in_at = datetime.utcnow() + user_to_verify.logged_in_at = utc_now() if data["code_type"] == CodeType.EMAIL: - user_to_verify.email_access_validated_at = datetime.utcnow() + user_to_verify.email_access_validated_at = utc_now() user_to_verify.failed_login_count = 0 save_model_user(user_to_verify) @@ -263,7 +262,7 @@ def complete_login_after_webauthn_authentication_attempt(user_id): if successful: user.current_session_id = str(uuid.uuid4()) - user.logged_in_at = datetime.utcnow() + user.logged_in_at = utc_now() user.failed_login_count = 0 save_model_user(user) else: @@ -676,7 +675,7 @@ def get_organizations_and_services_for_user(user_id): def _create_reset_password_url(email, next_redirect, base_url=None): - data = json.dumps({"email": email, "created_at": str(datetime.utcnow())}) + data = json.dumps({"email": email, "created_at": str(utc_now())}) static_url_part = "/new-password/" full_url = url_with_token( data, static_url_part, current_app.config, base_url=base_url diff --git a/app/utils.py b/app/utils.py index 847ec3dbc..e286d6539 100644 --- a/app/utils.py +++ b/app/utils.py @@ -94,7 +94,7 @@ def midnight_n_days_ago(number_of_days): """ Returns midnight a number of days ago. Takes care of daylight savings etc. """ - return get_midnight_in_utc(datetime.utcnow() - timedelta(days=number_of_days)) + return get_midnight_in_utc(utc_now() - timedelta(days=number_of_days)) def escape_special_characters(string): @@ -104,7 +104,7 @@ def escape_special_characters(string): def get_archived_db_column_value(column): - date = datetime.utcnow().strftime("%Y-%m-%d") + date = utc_now().strftime("%Y-%m-%d") return f"_archived_{date}_{column}" diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py index 3c8fa1fdb..856179f85 100644 --- a/app/v2/notifications/post_notifications.py +++ b/app/v2/notifications/post_notifications.py @@ -1,6 +1,5 @@ import functools import uuid -from datetime import datetime import botocore from flask import abort, current_app, jsonify, request @@ -27,7 +26,7 @@ from app.notifications.validators import ( validate_template, ) from app.schema_validation import validate -from app.utils import DATETIME_FORMAT +from app.utils import DATETIME_FORMAT, utc_now from app.v2.errors import BadRequestError from app.v2.notifications import v2_notification_blueprint from app.v2.notifications.create_response import ( @@ -226,7 +225,7 @@ def save_email_or_sms_to_queue( "reply_to_text": reply_to_text, "document_download_count": document_download_count, "status": NotificationStatus.CREATED, - "created_at": datetime.utcnow().strftime(DATETIME_FORMAT), + "created_at": utc_now().strftime(DATETIME_FORMAT), } encrypted = encryption.encrypt(data) diff --git a/notifications_utils/__init__.py b/notifications_utils/__init__.py index 84a55d644..1a94f3b94 100644 --- a/notifications_utils/__init__.py +++ b/notifications_utils/__init__.py @@ -1,4 +1,5 @@ import re +from datetime import datetime, timezone SMS_CHAR_COUNT_LIMIT = 918 # 153 * 6, no network issues but check with providers before upping this further LETTER_MAX_PAGE_COUNT = 10 @@ -23,3 +24,15 @@ email_with_smart_quotes_regex = re.compile( # and then later remove when performing tricky formatting operations MAGIC_SEQUENCE = "🇬🇧🐦✉️" magic_sequence_regex = re.compile(MAGIC_SEQUENCE) + + +def aware_utcnow(): + return datetime.now(timezone.utc) + + +def naive_utcnow(): + return aware_utcnow().replace(tzinfo=None) + + +def utc_now(): + return naive_utcnow() diff --git a/notifications_utils/clients/redis/__init__.py b/notifications_utils/clients/redis/__init__.py index 93a77d561..d201b3332 100644 --- a/notifications_utils/clients/redis/__init__.py +++ b/notifications_utils/clients/redis/__init__.py @@ -1,11 +1,11 @@ -from datetime import datetime +from app.utils import utc_now from .request_cache import RequestCache # noqa: F401 (unused import) def total_limit_cache_key(service_id): return "{}-{}-{}".format( - str(service_id), datetime.utcnow().strftime("%Y-%m-%d"), "total-count" + str(service_id), utc_now().strftime("%Y-%m-%d"), "total-count" ) diff --git a/notifications_utils/letter_timings.py b/notifications_utils/letter_timings.py index 62abf2c21..b171d6c3b 100644 --- a/notifications_utils/letter_timings.py +++ b/notifications_utils/letter_timings.py @@ -1,9 +1,10 @@ from collections import namedtuple -from datetime import datetime, time, timedelta +from datetime import time, timedelta import pytz from govuk_bank_holidays.bank_holidays import BankHolidays +from app.utils import utc_now from notifications_utils.countries.data import Postage from notifications_utils.timezones import utc_string_to_aware_gmt_datetime @@ -101,11 +102,7 @@ def get_letter_timings(upload_time, postage): # print deadline is 3pm BST printed_by = set_gmt_hour(print_day, hour=15) - now = ( - datetime.utcnow() - .replace(tzinfo=pytz.utc) - .astimezone(pytz.timezone("Europe/London")) - ) + now = utc_now().replace(tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/London")) return LetterTimings( printed_by=printed_by, @@ -135,7 +132,7 @@ def too_late_to_cancel_letter(notification_created_at): time_created_at = notification_created_at day_created_on = time_created_at.date() - current_time = datetime.utcnow() + current_time = utc_now() current_day = current_time.date() if ( _after_letter_processing_deadline() @@ -152,14 +149,14 @@ def too_late_to_cancel_letter(notification_created_at): def _after_letter_processing_deadline(): - current_utc_datetime = datetime.utcnow() + current_utc_datetime = utc_now() bst_time = current_utc_datetime.time() return bst_time >= LETTER_PROCESSING_DEADLINE def _notification_created_before_today_deadline(notification_created_at): - current_bst_datetime = datetime.utcnow() + current_bst_datetime = utc_now() todays_deadline = current_bst_datetime.replace( hour=LETTER_PROCESSING_DEADLINE.hour, minute=LETTER_PROCESSING_DEADLINE.minute, diff --git a/notifications_utils/template.py b/notifications_utils/template.py index ec112173f..62dce8a55 100644 --- a/notifications_utils/template.py +++ b/notifications_utils/template.py @@ -1,7 +1,6 @@ import math import re from abc import ABC, abstractmethod -from datetime import datetime from functools import lru_cache from html import unescape from os import path @@ -13,6 +12,7 @@ from notifications_utils import ( LETTER_MAX_PAGE_COUNT, MAGIC_SEQUENCE, SMS_CHAR_COUNT_LIMIT, + utc_now, ) from notifications_utils.countries.data import Postage from notifications_utils.field import Field, PlainTextField @@ -739,7 +739,7 @@ class BaseLetterTemplate(SubjectMixin, Template): ) self.admin_base_url = admin_base_url self.logo_file_name = logo_file_name - self.date = date or datetime.utcnow() + self.date = date or utc_now() @property def subject(self): diff --git a/tests/app/aws/test_s3.py b/tests/app/aws/test_s3.py index 8b903daa6..c009c369c 100644 --- a/tests/app/aws/test_s3.py +++ b/tests/app/aws/test_s3.py @@ -1,5 +1,4 @@ import os -from datetime import datetime from os import getenv import pytest @@ -13,6 +12,7 @@ from app.aws.s3 import ( remove_csv_object, remove_s3_object, ) +from app.utils import utc_now default_access_key = getenv("CSV_AWS_ACCESS_KEY_ID") default_secret_key = getenv("CSV_AWS_SECRET_ACCESS_KEY") @@ -23,7 +23,7 @@ def single_s3_object_stub(key="foo", last_modified=None): return { "ETag": '"d41d8cd98f00b204e9800998ecf8427e"', "Key": key, - "LastModified": last_modified or datetime.utcnow(), + "LastModified": last_modified or utc_now(), } diff --git a/tests/app/celery/test_nightly_tasks.py b/tests/app/celery/test_nightly_tasks.py index e6d3be737..3a0526622 100644 --- a/tests/app/celery/test_nightly_tasks.py +++ b/tests/app/celery/test_nightly_tasks.py @@ -19,6 +19,7 @@ from app.celery.nightly_tasks import ( ) from app.enums import NotificationType, TemplateType from app.models import FactProcessingTime, Job +from app.utils import utc_now from tests.app.db import ( create_job, create_notification, @@ -62,7 +63,7 @@ def test_will_remove_csv_files_for_jobs_older_than_seven_days( """ mocker.patch("app.celery.nightly_tasks.s3.remove_job_from_s3") - seven_days_ago = datetime.utcnow() - timedelta(days=7) + seven_days_ago = utc_now() - timedelta(days=7) just_under_seven_days = seven_days_ago + timedelta(seconds=1) eight_days_ago = seven_days_ago - timedelta(days=1) nine_days_ago = eight_days_ago - timedelta(days=1) @@ -115,9 +116,9 @@ def test_will_remove_csv_files_for_jobs_older_than_retention_period( template_type=TemplateType.EMAIL, ) - four_days_ago = datetime.utcnow() - timedelta(days=4) - eight_days_ago = datetime.utcnow() - timedelta(days=8) - thirty_one_days_ago = datetime.utcnow() - timedelta(days=31) + four_days_ago = utc_now() - timedelta(days=4) + eight_days_ago = utc_now() - timedelta(days=8) + thirty_one_days_ago = utc_now() - timedelta(days=31) job1_to_delete = create_job(sms_template_service_1, created_at=four_days_ago) job2_to_delete = create_job(email_template_service_1, created_at=eight_days_ago) @@ -369,21 +370,21 @@ def test_delete_notifications_task_calls_task_for_services_that_have_sent_notifi # will be deleted as service has no custom retention, but past our default 7 days create_notification( service_will_delete_1.templates[0], - created_at=datetime.utcnow() - timedelta(days=8), + created_at=utc_now() - timedelta(days=8), ) create_notification( service_will_delete_2.templates[0], - created_at=datetime.utcnow() - timedelta(days=8), + created_at=utc_now() - timedelta(days=8), ) # will be kept as it's recent, and we won't run delete_notifications_for_service_and_type create_notification( - nothing_to_delete_sms_template, created_at=datetime.utcnow() - timedelta(days=2) + nothing_to_delete_sms_template, created_at=utc_now() - timedelta(days=2) ) # this is an old notification, but for email not sms, so we won't run delete_notifications_for_service_and_type create_notification( nothing_to_delete_email_template, - created_at=datetime.utcnow() - timedelta(days=8), + created_at=utc_now() - timedelta(days=8), ) mock_subtask = mocker.patch( diff --git a/tests/app/celery/test_process_ses_receipts_tasks.py b/tests/app/celery/test_process_ses_receipts_tasks.py index 0b9a45b23..226394eeb 100644 --- a/tests/app/celery/test_process_ses_receipts_tasks.py +++ b/tests/app/celery/test_process_ses_receipts_tasks.py @@ -1,5 +1,4 @@ import json -from datetime import datetime from unittest.mock import ANY from freezegun import freeze_time @@ -18,6 +17,7 @@ from app.celery.test_key_tasks import ( from app.dao.notifications_dao import get_notification_by_id from app.enums import CallbackType, NotificationStatus from app.models import Complaint +from app.utils import utc_now from tests.app.conftest import create_sample_notification from tests.app.db import ( create_notification, @@ -136,7 +136,7 @@ def test_process_ses_results(sample_email_template): create_notification( sample_email_template, reference="ref1", - sent_at=datetime.utcnow(), + sent_at=utc_now(), status=NotificationStatus.SENDING, ) @@ -147,7 +147,7 @@ def test_process_ses_results_retry_called(sample_email_template, mocker): create_notification( sample_email_template, reference="ref1", - sent_at=datetime.utcnow(), + sent_at=utc_now(), status=NotificationStatus.SENDING, ) mocker.patch( @@ -198,7 +198,7 @@ def test_ses_callback_should_update_notification_status( template=sample_email_template, reference="ref", status=NotificationStatus.SENDING, - sent_at=datetime.utcnow(), + sent_at=utc_now(), ) create_service_callback_api( service=sample_email_template.service, url="https://original_url.com" @@ -294,7 +294,7 @@ def test_ses_callback_does_not_call_send_delivery_status_if_no_db_entry( template=sample_email_template, reference="ref", status=NotificationStatus.SENDING, - sent_at=datetime.utcnow(), + sent_at=utc_now(), ) assert ( get_notification_by_id(notification.id).status == NotificationStatus.SENDING @@ -318,7 +318,7 @@ def test_ses_callback_should_update_multiple_notification_status_sent( notify_db_session, template=sample_email_template, reference="ref1", - sent_at=datetime.utcnow(), + sent_at=utc_now(), status=NotificationStatus.SENDING, ) create_sample_notification( @@ -326,7 +326,7 @@ def test_ses_callback_should_update_multiple_notification_status_sent( notify_db_session, template=sample_email_template, reference="ref2", - sent_at=datetime.utcnow(), + sent_at=utc_now(), status=NotificationStatus.SENDING, ) create_sample_notification( @@ -334,7 +334,7 @@ def test_ses_callback_should_update_multiple_notification_status_sent( notify_db_session, template=sample_email_template, reference="ref3", - sent_at=datetime.utcnow(), + sent_at=utc_now(), status=NotificationStatus.SENDING, ) create_service_callback_api( @@ -358,7 +358,7 @@ def test_ses_callback_should_set_status_to_temporary_failure( template=sample_email_template, reference="ref", status=NotificationStatus.SENDING, - sent_at=datetime.utcnow(), + sent_at=utc_now(), ) create_service_callback_api( service=notification.service, url="https://original_url.com" @@ -384,7 +384,7 @@ def test_ses_callback_should_set_status_to_permanent_failure( template=sample_email_template, reference="ref", status=NotificationStatus.SENDING, - sent_at=datetime.utcnow(), + sent_at=utc_now(), ) create_service_callback_api( service=sample_email_template.service, url="https://original_url.com" @@ -412,7 +412,7 @@ def test_ses_callback_should_send_on_complaint_to_user_callback_api( notification = create_notification( template=sample_email_template, reference="ref1", - sent_at=datetime.utcnow(), + sent_at=utc_now(), status=NotificationStatus.SENDING, ) response = ses_complaint_callback() diff --git a/tests/app/celery/test_reporting_tasks.py b/tests/app/celery/test_reporting_tasks.py index 031f4e9b0..a32f68fc3 100644 --- a/tests/app/celery/test_reporting_tasks.py +++ b/tests/app/celery/test_reporting_tasks.py @@ -15,6 +15,7 @@ from app.config import QueueNames from app.dao.fact_billing_dao import get_rate from app.enums import KeyType, NotificationStatus, NotificationType, TemplateType from app.models import FactBilling, FactNotificationStatus, Notification +from app.utils import utc_now from tests.app.db import ( create_notification, create_notification_history, @@ -420,7 +421,7 @@ def test_create_nightly_notification_status_for_service_and_day(notify_db_sessio template_type=TemplateType.EMAIL, ) - process_day = datetime.utcnow().date() - timedelta(days=5) + process_day = utc_now().date() - timedelta(days=5) with freeze_time(datetime.combine(process_day, time.max)): create_notification( template=first_template, @@ -449,9 +450,7 @@ def test_create_nightly_notification_status_for_service_and_day(notify_db_sessio ) # these created notifications from a different day get ignored - with freeze_time( - datetime.combine(datetime.utcnow().date() - timedelta(days=4), time.max) - ): + with freeze_time(datetime.combine(utc_now().date() - timedelta(days=4), time.max)): create_notification(template=first_template) create_notification_history(template=second_template) @@ -519,7 +518,7 @@ def test_create_nightly_notification_status_for_service_and_day_overwrites_old_d ): first_service = create_service(service_name="First Service") first_template = create_template(service=first_service) - process_day = datetime.utcnow().date() + process_day = utc_now().date() # first run: one notification, expect one row (just one status) notification = create_notification( diff --git a/tests/app/celery/test_scheduled_tasks.py b/tests/app/celery/test_scheduled_tasks.py index 73b6b6074..90a29f5ed 100644 --- a/tests/app/celery/test_scheduled_tasks.py +++ b/tests/app/celery/test_scheduled_tasks.py @@ -1,5 +1,5 @@ from collections import namedtuple -from datetime import datetime, timedelta +from datetime import timedelta from unittest import mock from unittest.mock import ANY, call @@ -18,6 +18,7 @@ from app.celery.scheduled_tasks import ( from app.config import QueueNames, Test from app.dao.jobs_dao import dao_get_job_by_id from app.enums import JobStatus, NotificationStatus, TemplateType +from app.utils import utc_now from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket from tests.app import load_example_csv from tests.app.db import create_job, create_notification, create_template @@ -51,7 +52,7 @@ def test_should_call_expire_or_delete_invotations_on_expire_or_delete_invitation def test_should_update_scheduled_jobs_and_put_on_queue(mocker, sample_template): mocked = mocker.patch("app.celery.tasks.process_job.apply_async") - one_minute_in_the_past = datetime.utcnow() - timedelta(minutes=1) + one_minute_in_the_past = utc_now() - timedelta(minutes=1) job = create_job( sample_template, job_status=JobStatus.SCHEDULED, @@ -68,9 +69,9 @@ def test_should_update_scheduled_jobs_and_put_on_queue(mocker, sample_template): def test_should_update_all_scheduled_jobs_and_put_on_queue(sample_template, mocker): mocked = mocker.patch("app.celery.tasks.process_job.apply_async") - one_minute_in_the_past = datetime.utcnow() - timedelta(minutes=1) - ten_minutes_in_the_past = datetime.utcnow() - timedelta(minutes=10) - twenty_minutes_in_the_past = datetime.utcnow() - timedelta(minutes=20) + one_minute_in_the_past = utc_now() - timedelta(minutes=1) + ten_minutes_in_the_past = utc_now() - timedelta(minutes=10) + twenty_minutes_in_the_past = utc_now() - timedelta(minutes=20) job_1 = create_job( sample_template, job_status=JobStatus.SCHEDULED, @@ -107,8 +108,8 @@ def test_check_job_status_task_calls_process_incomplete_jobs(mocker, sample_temp job = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) create_notification(template=sample_template, job=job) @@ -124,9 +125,9 @@ def test_check_job_status_task_calls_process_incomplete_jobs_when_scheduled_job_ job = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) check_job_status() @@ -141,8 +142,8 @@ def test_check_job_status_task_calls_process_incomplete_jobs_for_pending_schedul job = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), job_status=JobStatus.PENDING, ) @@ -159,7 +160,7 @@ def test_check_job_status_task_does_not_call_process_incomplete_jobs_for_non_sch create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), + created_at=utc_now() - timedelta(hours=2), job_status=JobStatus.PENDING, ) check_job_status() @@ -174,17 +175,17 @@ def test_check_job_status_task_calls_process_incomplete_jobs_for_multiple_jobs( job = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) job_2 = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) check_job_status() @@ -199,23 +200,23 @@ def test_check_job_status_task_only_sends_old_tasks(mocker, sample_template): job = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=29), + created_at=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=29), job_status=JobStatus.IN_PROGRESS, ) create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(minutes=50), - scheduled_for=datetime.utcnow() - timedelta(minutes=29), + created_at=utc_now() - timedelta(minutes=50), + scheduled_for=utc_now() - timedelta(minutes=29), job_status=JobStatus.PENDING, ) check_job_status() @@ -229,16 +230,16 @@ def test_check_job_status_task_sets_jobs_to_error(mocker, sample_template): job = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) job_2 = create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=29), + created_at=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=29), job_status=JobStatus.IN_PROGRESS, ) check_job_status() @@ -267,33 +268,33 @@ def test_replay_created_notifications(notify_db_session, sample_service, mocker) # notifications expected to be resent old_sms = create_notification( template=sms_template, - created_at=datetime.utcnow() - timedelta(seconds=older_than), + created_at=utc_now() - timedelta(seconds=older_than), status=NotificationStatus.CREATED, ) old_email = create_notification( template=email_template, - created_at=datetime.utcnow() - timedelta(seconds=older_than), + created_at=utc_now() - timedelta(seconds=older_than), status=NotificationStatus.CREATED, ) # notifications that are not to be resent create_notification( template=sms_template, - created_at=datetime.utcnow() - timedelta(seconds=older_than), + created_at=utc_now() - timedelta(seconds=older_than), status=NotificationStatus.SENDING, ) create_notification( template=email_template, - created_at=datetime.utcnow() - timedelta(seconds=older_than), + created_at=utc_now() - timedelta(seconds=older_than), status=NotificationStatus.DELIVERED, ) create_notification( template=sms_template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.CREATED, ) create_notification( template=email_template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.CREATED, ) @@ -310,16 +311,16 @@ def test_check_job_status_task_does_not_raise_error(sample_template): create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.FINISHED, ) create_job( template=sample_template, notification_count=3, - created_at=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.FINISHED, ) @@ -351,7 +352,7 @@ def test_check_for_missing_rows_in_completed_jobs_ignores_old_and_new_jobs( template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - offset, + processing_finished=utc_now() - offset, ) for i in range(0, 4): create_notification(job=job, job_row_number=i) @@ -373,7 +374,7 @@ def test_check_for_missing_rows_in_completed_jobs(mocker, sample_email_template) template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=20), + processing_finished=utc_now() - timedelta(minutes=20), ) for i in range(0, 4): create_notification(job=job, job_row_number=i) @@ -400,7 +401,7 @@ def test_check_for_missing_rows_in_completed_jobs_calls_save_email( template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=20), + processing_finished=utc_now() - timedelta(minutes=20), ) for i in range(0, 4): create_notification(job=job, job_row_number=i) @@ -430,7 +431,7 @@ def test_check_for_missing_rows_in_completed_jobs_uses_sender_id( template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=20), + processing_finished=utc_now() - timedelta(minutes=20), ) for i in range(0, 4): create_notification(job=job, job_row_number=i) diff --git a/tests/app/celery/test_service_callback_tasks.py b/tests/app/celery/test_service_callback_tasks.py index 51761320b..2c0df1374 100644 --- a/tests/app/celery/test_service_callback_tasks.py +++ b/tests/app/celery/test_service_callback_tasks.py @@ -11,7 +11,7 @@ from app.celery.service_callback_tasks import ( send_delivery_status_to_service, ) from app.enums import CallbackType, NotificationStatus, NotificationType -from app.utils import DATETIME_FORMAT +from app.utils import DATETIME_FORMAT, utc_now from tests.app.db import ( create_complaint, create_notification, @@ -101,7 +101,7 @@ def test_send_complaint_to_service_posts_https_request_to_service_with_encrypted "complaint_id": str(complaint.id), "reference": notification.client_reference, "to": notification.to, - "complaint_date": datetime.utcnow().strftime(DATETIME_FORMAT), + "complaint_date": utc_now().strftime(DATETIME_FORMAT), } assert request_mock.call_count == 1 diff --git a/tests/app/celery/test_tasks.py b/tests/app/celery/test_tasks.py index 8c5b264de..593926c18 100644 --- a/tests/app/celery/test_tasks.py +++ b/tests/app/celery/test_tasks.py @@ -36,7 +36,7 @@ from app.enums import ( ) from app.models import Job, Notification from app.serialised_models import SerialisedService, SerialisedTemplate -from app.utils import DATETIME_FORMAT +from app.utils import DATETIME_FORMAT, utc_now from notifications_utils.recipients import Row from notifications_utils.template import PlainTextEmailTemplate, SMSMessageTemplate from tests.app import load_example_csv @@ -419,7 +419,7 @@ def test_should_send_template_to_correct_sms_task_and_persist( == sample_template_with_placeholders.version ) assert persisted_notification.status == NotificationStatus.CREATED - assert persisted_notification.created_at <= datetime.utcnow() + assert persisted_notification.created_at <= utc_now() assert not persisted_notification.sent_at assert not persisted_notification.sent_by assert not persisted_notification.job_id @@ -455,7 +455,7 @@ def test_should_save_sms_if_restricted_service_and_valid_number( assert persisted_notification.template_id == template.id assert persisted_notification.template_version == template.version assert persisted_notification.status == NotificationStatus.CREATED - assert persisted_notification.created_at <= datetime.utcnow() + assert persisted_notification.created_at <= utc_now() assert not persisted_notification.sent_at assert not persisted_notification.sent_by assert not persisted_notification.job_id @@ -565,7 +565,7 @@ def test_should_save_sms_template_to_and_persist_with_job_id(sample_job, mocker) mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") notification_id = uuid.uuid4() - now = datetime.utcnow() + now = utc_now() save_sms( sample_job.service.id, notification_id, @@ -676,7 +676,7 @@ def test_save_email_should_use_template_version_from_job_not_latest( dao_update_template(sample_email_template) t = dao_get_template_by_id(sample_email_template.id) assert t.version > version_on_notification - now = datetime.utcnow() + now = utc_now() save_email( sample_email_template.service_id, uuid.uuid4(), @@ -706,7 +706,7 @@ def test_should_use_email_template_subject_placeholders( mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") notification_id = uuid.uuid4() - now = datetime.utcnow() + now = utc_now() save_email( sample_email_template_with_placeholders.service_id, notification_id, @@ -791,7 +791,7 @@ def test_should_use_email_template_and_persist_without_personalisation( notification_id = uuid.uuid4() - now = datetime.utcnow() + now = utc_now() save_email( sample_email_template.service_id, notification_id, @@ -1157,9 +1157,9 @@ def test_process_incomplete_job_sms(mocker, sample_template): job = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) @@ -1189,9 +1189,9 @@ def test_process_incomplete_job_with_notifications_all_sent(mocker, sample_templ job = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) @@ -1229,9 +1229,9 @@ def test_process_incomplete_jobs_sms(mocker, sample_template): job = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) create_notification(sample_template, job, 0) @@ -1243,9 +1243,9 @@ def test_process_incomplete_jobs_sms(mocker, sample_template): job2 = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) @@ -1282,9 +1282,9 @@ def test_process_incomplete_jobs_no_notifications_added(mocker, sample_template) job = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) @@ -1339,9 +1339,9 @@ def test_process_incomplete_job_email(mocker, sample_email_template): job = create_job( template=sample_email_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) @@ -1371,22 +1371,22 @@ def test_process_incomplete_jobs_sets_status_to_in_progress_and_resets_processin job1 = create_job( sample_template, - processing_started=datetime.utcnow() - timedelta(minutes=30), + processing_started=utc_now() - timedelta(minutes=30), job_status=JobStatus.ERROR, ) job2 = create_job( sample_template, - processing_started=datetime.utcnow() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.ERROR, ) process_incomplete_jobs([str(job1.id), str(job2.id)]) assert job1.job_status == JobStatus.IN_PROGRESS - assert job1.processing_started == datetime.utcnow() + assert job1.processing_started == utc_now() assert job2.job_status == JobStatus.IN_PROGRESS - assert job2.processing_started == datetime.utcnow() + assert job2.processing_started == utc_now() assert mock_process_incomplete_job.mock_calls == [ call(str(job1.id)), @@ -1422,7 +1422,7 @@ def test_save_api_email_or_sms(mocker, sample_service, notification_type): "reply_to_text": None, "document_download_count": 0, "status": NotificationStatus.CREATED, - "created_at": datetime.utcnow().strftime(DATETIME_FORMAT), + "created_at": utc_now().strftime(DATETIME_FORMAT), } if notification_type == NotificationType.EMAIL: @@ -1476,7 +1476,7 @@ def test_save_api_email_dont_retry_if_notification_already_exists( "reply_to_text": "our.email@gov.uk", "document_download_count": 0, "status": NotificationStatus.CREATED, - "created_at": datetime.utcnow().strftime(DATETIME_FORMAT), + "created_at": utc_now().strftime(DATETIME_FORMAT), } if notification_type == NotificationType.EMAIL: @@ -1621,7 +1621,7 @@ def test_save_api_tasks_use_cache( "reply_to_text": "our.email@gov.uk", "document_download_count": 0, "status": NotificationStatus.CREATED, - "created_at": datetime.utcnow().strftime(DATETIME_FORMAT), + "created_at": utc_now().strftime(DATETIME_FORMAT), } ) diff --git a/tests/app/clients/test_aws_cloudwatch.py b/tests/app/clients/test_aws_cloudwatch.py index 2eb70c94b..b9529037b 100644 --- a/tests/app/clients/test_aws_cloudwatch.py +++ b/tests/app/clients/test_aws_cloudwatch.py @@ -1,10 +1,8 @@ -# import pytest -from datetime import datetime - import pytest from flask import current_app from app import aws_cloudwatch_client +from app.utils import utc_now def test_check_sms_no_event_error_condition(notify_api, mocker): @@ -87,7 +85,7 @@ def test_check_sms_success(notify_api, mocker): message_id = "succeed" notification_id = "ccc" - created_at = datetime.utcnow() + created_at = utc_now() with notify_api.app_context(): aws_cloudwatch_client.check_sms(message_id, notification_id, created_at) @@ -109,7 +107,7 @@ def test_check_sms_failure(notify_api, mocker): ) message_id = "fail" notification_id = "bbb" - created_at = datetime.utcnow() + created_at = utc_now() with notify_api.app_context(): aws_cloudwatch_client.check_sms(message_id, notification_id, created_at) diff --git a/tests/app/complaint/test_complaint_rest.py b/tests/app/complaint/test_complaint_rest.py index 305b72837..7a881fab7 100644 --- a/tests/app/complaint/test_complaint_rest.py +++ b/tests/app/complaint/test_complaint_rest.py @@ -1,9 +1,10 @@ import json -from datetime import date, datetime +from datetime import date from flask import url_for from freezegun import freeze_time +from app.utils import utc_now from tests import create_admin_authorization_header from tests.app.db import ( create_complaint, @@ -93,7 +94,7 @@ def test_get_complaint_sets_start_and_end_date_to_today_if_not_specified( ) dao_mock.assert_called_once_with( - start_date=datetime.utcnow().date(), end_date=datetime.utcnow().date() + start_date=utc_now().date(), end_date=utc_now().date() ) assert response.status_code == 200 assert json.loads(response.get_data(as_text=True)) == 5 diff --git a/tests/app/conftest.py b/tests/app/conftest.py index e96ed1069..25e9f3f08 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -46,6 +46,7 @@ from app.models import ( Template, TemplateHistory, ) +from app.utils import utc_now from tests import create_admin_authorization_header from tests.app.db import ( create_api_key, @@ -91,7 +92,7 @@ def create_sample_notification( normalised_to=None, ): if created_at is None: - created_at = datetime.utcnow() + created_at = utc_now() if service is None: service = create_service(check_if_service_exists=True) if template is None: @@ -383,7 +384,7 @@ def sample_job(notify_db_session): "template_version": template.version, "original_file_name": "some.csv", "notification_count": 1, - "created_at": datetime.utcnow(), + "created_at": utc_now(), "created_by": service.created_by, "job_status": JobStatus.PENDING, "scheduled_for": None, @@ -410,7 +411,7 @@ def sample_scheduled_job(sample_template_with_placeholders): return create_job( sample_template_with_placeholders, job_status=JobStatus.SCHEDULED, - scheduled_for=(datetime.utcnow() + timedelta(minutes=60)).isoformat(), + scheduled_for=(utc_now() + timedelta(minutes=60)).isoformat(), ) @@ -437,7 +438,7 @@ def sample_notification_with_job(notify_db_session): @pytest.fixture(scope="function") def sample_notification(notify_db_session): - created_at = datetime.utcnow() + created_at = utc_now() service = create_service(check_if_service_exists=True) template = create_template(service=service) @@ -484,7 +485,7 @@ def sample_notification(notify_db_session): @pytest.fixture(scope="function") def sample_email_notification(notify_db_session): - created_at = datetime.utcnow() + created_at = utc_now() service = create_service(check_if_service_exists=True) template = create_template(service, template_type=TemplateType.EMAIL) job = create_job(template) @@ -519,8 +520,8 @@ def sample_email_notification(notify_db_session): @pytest.fixture(scope="function") def sample_notification_history(notify_db_session, sample_template): - created_at = datetime.utcnow() - sent_at = datetime.utcnow() + created_at = utc_now() + sent_at = utc_now() notification_type = sample_template.template_type api_key = create_api_key(sample_template.service, key_type=KeyType.NORMAL) @@ -575,7 +576,7 @@ def sample_expired_user(notify_db_session): "from_user": from_user, "permissions": "send_messages,manage_service,manage_api_keys", "folder_permissions": ["folder_1_id", "folder_2_id"], - "created_at": datetime.utcnow() - timedelta(days=3), + "created_at": utc_now() - timedelta(days=3), "status": InvitedUserStatus.EXPIRED, } expired_user = InvitedUser(**data) diff --git a/tests/app/dao/notification_dao/test_notification_dao.py b/tests/app/dao/notification_dao/test_notification_dao.py index e0ca6cd47..4bc1ce5ba 100644 --- a/tests/app/dao/notification_dao/test_notification_dao.py +++ b/tests/app/dao/notification_dao/test_notification_dao.py @@ -36,6 +36,7 @@ from app.enums import ( TemplateType, ) from app.models import Job, Notification, NotificationHistory +from app.utils import utc_now from tests.app.db import ( create_ft_notification_status, create_job, @@ -701,7 +702,7 @@ def test_should_limit_notifications_return_by_day_limit_plus_one(sample_template with freeze_time(past_date): create_notification( sample_template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.FAILED, ) @@ -777,7 +778,7 @@ def _notification_json(sample_template, job_id=None, id=None, status=None): "service_id": sample_template.service.id, "template_id": sample_template.id, "template_version": sample_template.version, - "created_at": datetime.utcnow(), + "created_at": utc_now(), "billable_units": 1, "notification_type": sample_template.template_type, "key_type": KeyType.NORMAL, @@ -792,7 +793,7 @@ def _notification_json(sample_template, job_id=None, id=None, status=None): def test_dao_timeout_notifications(sample_template): - with freeze_time(datetime.utcnow() - timedelta(minutes=2)): + with freeze_time(utc_now() - timedelta(minutes=2)): created = create_notification( sample_template, status=NotificationStatus.CREATED, @@ -810,7 +811,7 @@ def test_dao_timeout_notifications(sample_template): status=NotificationStatus.DELIVERED, ) - temporary_failure_notifications = dao_timeout_notifications(datetime.utcnow()) + temporary_failure_notifications = dao_timeout_notifications(utc_now()) assert len(temporary_failure_notifications) == 2 assert Notification.query.get(created.id).status == NotificationStatus.CREATED @@ -828,7 +829,7 @@ def test_dao_timeout_notifications(sample_template): def test_dao_timeout_notifications_only_updates_for_older_notifications( sample_template, ): - with freeze_time(datetime.utcnow() + timedelta(minutes=10)): + with freeze_time(utc_now() + timedelta(minutes=10)): sending = create_notification( sample_template, status=NotificationStatus.SENDING, @@ -838,7 +839,7 @@ def test_dao_timeout_notifications_only_updates_for_older_notifications( status=NotificationStatus.PENDING, ) - temporary_failure_notifications = dao_timeout_notifications(datetime.utcnow()) + temporary_failure_notifications = dao_timeout_notifications(utc_now()) assert len(temporary_failure_notifications) == 0 assert Notification.query.get(sending.id).status == NotificationStatus.SENDING @@ -913,23 +914,23 @@ def test_get_notifications_created_by_api_or_csv_are_returned_correctly_excludin sample_test_api_key, ): create_notification( - template=sample_job.template, created_at=datetime.utcnow(), job=sample_job + template=sample_job.template, created_at=utc_now(), job=sample_job ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_api_key, key_type=sample_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_team_api_key, key_type=sample_team_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_test_api_key, key_type=sample_test_api_key.key_type, ) @@ -959,24 +960,24 @@ def test_get_notifications_with_a_live_api_key_type( ): create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), job=sample_job, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_api_key, key_type=sample_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_team_api_key, key_type=sample_team_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_test_api_key, key_type=sample_test_api_key.key_type, ) @@ -1001,23 +1002,23 @@ def test_get_notifications_with_a_test_api_key_type( sample_job, sample_api_key, sample_team_api_key, sample_test_api_key ): create_notification( - template=sample_job.template, created_at=datetime.utcnow(), job=sample_job + template=sample_job.template, created_at=utc_now(), job=sample_job ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_api_key, key_type=sample_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_team_api_key, key_type=sample_team_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_test_api_key, key_type=sample_test_api_key.key_type, ) @@ -1045,24 +1046,24 @@ def test_get_notifications_with_a_team_api_key_type( ): create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), job=sample_job, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_api_key, key_type=sample_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_team_api_key, key_type=sample_team_api_key.key_type, ) create_notification( sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_test_api_key, key_type=sample_test_api_key.key_type, ) @@ -1090,25 +1091,25 @@ def test_should_exclude_test_key_notifications_by_default( ): create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), job=sample_job, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_api_key, key_type=sample_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_team_api_key, key_type=sample_team_api_key.key_type, ) create_notification( template=sample_job.template, - created_at=datetime.utcnow(), + created_at=utc_now(), api_key=sample_test_api_key, key_type=sample_test_api_key.key_type, ) @@ -1699,9 +1700,9 @@ def test_dao_get_notifications_by_to_field_orders_by_created_at_desc(sample_temp ) notification_a_minute_ago = notification( - created_at=datetime.utcnow() - timedelta(minutes=1) + created_at=utc_now() - timedelta(minutes=1) ) - notification = notification(created_at=datetime.utcnow()) + notification = notification(created_at=utc_now()) notifications = dao_get_notifications_by_recipient_or_reference( sample_template.service_id, @@ -1718,9 +1719,9 @@ def test_dao_get_last_notification_added_for_job_id_valid_job_id(sample_template job = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) create_notification(sample_template, job, 0) @@ -1734,9 +1735,9 @@ def test_dao_get_last_notification_added_for_job_id_no_notifications(sample_temp job = create_job( template=sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(hours=2), - scheduled_for=datetime.utcnow() - timedelta(minutes=31), - processing_started=datetime.utcnow() - timedelta(minutes=31), + created_at=utc_now() - timedelta(hours=2), + scheduled_for=utc_now() - timedelta(minutes=31), + processing_started=utc_now() - timedelta(minutes=31), job_status=JobStatus.IN_PROGRESS, ) @@ -1890,17 +1891,17 @@ def test_notifications_not_yet_sent(sample_service, notification_type): template = create_template(service=sample_service, template_type=notification_type) old_notification = create_notification( template=template, - created_at=datetime.utcnow() - timedelta(seconds=older_than), + created_at=utc_now() - timedelta(seconds=older_than), status=NotificationStatus.CREATED, ) create_notification( template=template, - created_at=datetime.utcnow() - timedelta(seconds=older_than), + created_at=utc_now() - timedelta(seconds=older_than), status=NotificationStatus.SENDING, ) create_notification( template=template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.CREATED, ) @@ -1917,17 +1918,17 @@ def test_notifications_not_yet_sent_return_no_rows(sample_service, notification_ template = create_template(service=sample_service, template_type=notification_type) create_notification( template=template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.CREATED, ) create_notification( template=template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.SENDING, ) create_notification( template=template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.DELIVERED, ) diff --git a/tests/app/dao/notification_dao/test_notification_dao_delete_notifications.py b/tests/app/dao/notification_dao/test_notification_dao_delete_notifications.py index 086f3c9e9..e22721216 100644 --- a/tests/app/dao/notification_dao/test_notification_dao_delete_notifications.py +++ b/tests/app/dao/notification_dao/test_notification_dao_delete_notifications.py @@ -9,6 +9,7 @@ from app.dao.notifications_dao import ( ) from app.enums import KeyType, NotificationStatus, NotificationType, TemplateType from app.models import Notification, NotificationHistory +from app.utils import utc_now from tests.app.db import ( create_notification, create_notification_history, @@ -22,20 +23,20 @@ def test_move_notifications_does_nothing_if_notification_history_row_already_exi ): notification = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=8), + created_at=utc_now() - timedelta(days=8), status=NotificationStatus.TEMPORARY_FAILURE, ) create_notification_history( id=notification.id, template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=8), + created_at=utc_now() - timedelta(days=8), status=NotificationStatus.DELIVERED, ) move_notifications_to_notification_history( NotificationType.EMAIL, sample_email_template.service_id, - datetime.utcnow(), + utc_now(), 1, ) @@ -184,58 +185,58 @@ def test_insert_notification_history_delete_notifications(sample_email_template) # should be deleted n1 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, minutes=4), + created_at=utc_now() - timedelta(days=1, minutes=4), status=NotificationStatus.DELIVERED, ) n2 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, minutes=20), + created_at=utc_now() - timedelta(days=1, minutes=20), status=NotificationStatus.PERMANENT_FAILURE, ) n3 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, minutes=30), + created_at=utc_now() - timedelta(days=1, minutes=30), status=NotificationStatus.TEMPORARY_FAILURE, ) n4 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, minutes=59), + created_at=utc_now() - timedelta(days=1, minutes=59), status=NotificationStatus.TEMPORARY_FAILURE, ) n5 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, hours=1), + created_at=utc_now() - timedelta(days=1, hours=1), status=NotificationStatus.SENDING, ) n6 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, minutes=61), + created_at=utc_now() - timedelta(days=1, minutes=61), status=NotificationStatus.PENDING, ) n7 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, hours=1, seconds=1), + created_at=utc_now() - timedelta(days=1, hours=1, seconds=1), status=NotificationStatus.VALIDATION_FAILED, ) n8 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(days=1, minutes=20), + created_at=utc_now() - timedelta(days=1, minutes=20), status=NotificationStatus.CREATED, ) # should NOT be deleted - wrong status n9 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(hours=1), + created_at=utc_now() - timedelta(hours=1), status=NotificationStatus.DELIVERED, ) n10 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(hours=1), + created_at=utc_now() - timedelta(hours=1), status=NotificationStatus.TECHNICAL_FAILURE, ) n11 = create_notification( template=sample_email_template, - created_at=datetime.utcnow() - timedelta(hours=23, minutes=59), + created_at=utc_now() - timedelta(hours=23, minutes=59), status=NotificationStatus.CREATED, ) @@ -244,7 +245,7 @@ def test_insert_notification_history_delete_notifications(sample_email_template) del_count = insert_notification_history_delete_notifications( notification_type=sample_email_template.template_type, service_id=sample_email_template.service_id, - timestamp_to_delete_backwards_from=datetime.utcnow() - timedelta(days=1), + timestamp_to_delete_backwards_from=utc_now() - timedelta(days=1), ) assert del_count == 8 notifications = Notification.query.all() @@ -260,24 +261,24 @@ def test_insert_notification_history_delete_notifications_more_notifications_tha ): create_notification( template=sample_template, - created_at=datetime.utcnow() + timedelta(minutes=4), + created_at=utc_now() + timedelta(minutes=4), status=NotificationStatus.DELIVERED, ) create_notification( template=sample_template, - created_at=datetime.utcnow() + timedelta(minutes=20), + created_at=utc_now() + timedelta(minutes=20), status=NotificationStatus.PERMANENT_FAILURE, ) create_notification( template=sample_template, - created_at=datetime.utcnow() + timedelta(minutes=30), + created_at=utc_now() + timedelta(minutes=30), status=NotificationStatus.TEMPORARY_FAILURE, ) del_count = insert_notification_history_delete_notifications( notification_type=sample_template.template_type, service_id=sample_template.service_id, - timestamp_to_delete_backwards_from=datetime.utcnow() + timedelta(hours=1), + timestamp_to_delete_backwards_from=utc_now() + timedelta(hours=1), qry_limit=1, ) @@ -293,7 +294,7 @@ def test_insert_notification_history_delete_notifications_only_insert_delete_for ): notification_to_move = create_notification( template=sample_email_template, - created_at=datetime.utcnow() + timedelta(minutes=4), + created_at=utc_now() + timedelta(minutes=4), status=NotificationStatus.DELIVERED, ) another_service = create_service(service_name="Another service") @@ -302,14 +303,14 @@ def test_insert_notification_history_delete_notifications_only_insert_delete_for ) notification_to_stay = create_notification( template=another_template, - created_at=datetime.utcnow() + timedelta(minutes=4), + created_at=utc_now() + timedelta(minutes=4), status=NotificationStatus.DELIVERED, ) del_count = insert_notification_history_delete_notifications( notification_type=sample_email_template.template_type, service_id=sample_email_template.service_id, - timestamp_to_delete_backwards_from=datetime.utcnow() + timedelta(hours=1), + timestamp_to_delete_backwards_from=utc_now() + timedelta(hours=1), ) assert del_count == 1 @@ -326,19 +327,19 @@ def test_insert_notification_history_delete_notifications_insert_for_key_type( ): create_notification( template=sample_template, - created_at=datetime.utcnow() - timedelta(hours=4), + created_at=utc_now() - timedelta(hours=4), status=NotificationStatus.DELIVERED, key_type=KeyType.NORMAL, ) create_notification( template=sample_template, - created_at=datetime.utcnow() - timedelta(hours=4), + created_at=utc_now() - timedelta(hours=4), status=NotificationStatus.DELIVERED, key_type=KeyType.TEAM, ) with_test_key = create_notification( template=sample_template, - created_at=datetime.utcnow() - timedelta(hours=4), + created_at=utc_now() - timedelta(hours=4), status=NotificationStatus.DELIVERED, key_type=KeyType.TEST, ) @@ -346,7 +347,7 @@ def test_insert_notification_history_delete_notifications_insert_for_key_type( del_count = insert_notification_history_delete_notifications( notification_type=sample_template.template_type, service_id=sample_template.service_id, - timestamp_to_delete_backwards_from=datetime.utcnow(), + timestamp_to_delete_backwards_from=utc_now(), ) assert del_count == 2 diff --git a/tests/app/dao/notification_dao/test_notification_dao_template_usage.py b/tests/app/dao/notification_dao/test_notification_dao_template_usage.py index c22482aca..9c55fe903 100644 --- a/tests/app/dao/notification_dao/test_notification_dao_template_usage.py +++ b/tests/app/dao/notification_dao/test_notification_dao_template_usage.py @@ -1,13 +1,14 @@ -from datetime import datetime, timedelta +from datetime import timedelta from app.dao.notifications_dao import dao_get_last_date_template_was_used +from app.utils import utc_now from tests.app.db import create_ft_notification_status, create_notification def test_dao_get_last_date_template_was_used_returns_local_date_from_stats_table( sample_template, ): - last_status_date = (datetime.utcnow() - timedelta(days=2)).date() + last_status_date = (utc_now() - timedelta(days=2)).date() create_ft_notification_status(local_date=last_status_date, template=sample_template) last_used_date = dao_get_last_date_template_was_used( @@ -19,10 +20,10 @@ def test_dao_get_last_date_template_was_used_returns_local_date_from_stats_table def test_dao_get_last_date_template_was_used_returns_created_at_from_notifications( sample_template, ): - last_notification_date = datetime.utcnow() - timedelta(hours=2) + last_notification_date = utc_now() - timedelta(hours=2) create_notification(template=sample_template, created_at=last_notification_date) - last_status_date = (datetime.utcnow() - timedelta(days=2)).date() + last_status_date = (utc_now() - timedelta(days=2)).date() create_ft_notification_status(local_date=last_status_date, template=sample_template) last_used_date = dao_get_last_date_template_was_used( template_id=sample_template.id, service_id=sample_template.service_id diff --git a/tests/app/dao/test_api_key_dao.py b/tests/app/dao/test_api_key_dao.py index 3bbe758e3..f63391143 100644 --- a/tests/app/dao/test_api_key_dao.py +++ b/tests/app/dao/test_api_key_dao.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta import pytest from sqlalchemy.exc import IntegrityError @@ -13,6 +13,7 @@ from app.dao.api_key_dao import ( ) from app.enums import KeyType from app.models import ApiKey +from app.utils import utc_now def test_save_api_key_should_create_new_api_key_and_history(sample_service): @@ -43,7 +44,7 @@ def test_expire_api_key_should_update_the_api_key_and_create_history_record( expire_api_key(service_id=sample_api_key.service_id, api_key_id=sample_api_key.id) all_api_keys = get_model_api_keys(service_id=sample_api_key.service_id) assert len(all_api_keys) == 1 - assert all_api_keys[0].expiry_date <= datetime.utcnow() + assert all_api_keys[0].expiry_date <= utc_now() assert all_api_keys[0].secret == sample_api_key.secret assert all_api_keys[0].id == sample_api_key.id assert all_api_keys[0].service_id == sample_api_key.service_id @@ -107,7 +108,7 @@ def test_save_api_key_can_create_key_with_same_name_if_other_is_expired(sample_s "name": "normal api key", "created_by": sample_service.created_by, "key_type": KeyType.NORMAL, - "expiry_date": datetime.utcnow(), + "expiry_date": utc_now(), } ) save_model_api_key(expired_api_key) @@ -153,7 +154,7 @@ def test_should_not_return_revoked_api_keys_older_than_7_days( "name": sample_service.name, "created_by": sample_service.created_by, "key_type": KeyType.NORMAL, - "expiry_date": datetime.utcnow() - timedelta(days=days_old), + "expiry_date": utc_now() - timedelta(days=days_old), } ) save_model_api_key(expired_api_key) diff --git a/tests/app/dao/test_complaint_dao.py b/tests/app/dao/test_complaint_dao.py index 2c1125790..4a073ee13 100644 --- a/tests/app/dao/test_complaint_dao.py +++ b/tests/app/dao/test_complaint_dao.py @@ -9,6 +9,7 @@ from app.dao.complaint_dao import ( ) from app.enums import TemplateType from app.models import Complaint +from app.utils import utc_now from tests.app.db import ( create_complaint, create_notification, @@ -55,7 +56,7 @@ def test_fetch_complaint_by_service_returns_one( service_id=sample_service.id, ses_feedback_id=str(uuid.uuid4()), complaint_type="abuse", - complaint_date=datetime.utcnow(), + complaint_date=utc_now(), ) save_complaint(complaint) @@ -83,22 +84,22 @@ def test_fetch_complaint_by_service_return_many(notify_db_session): service_id=service_1.id, ses_feedback_id=str(uuid.uuid4()), complaint_type="abuse", - complaint_date=datetime.utcnow(), + complaint_date=utc_now(), ) complaint_2 = Complaint( notification_id=notification_2.id, service_id=service_2.id, ses_feedback_id=str(uuid.uuid4()), complaint_type="abuse", - complaint_date=datetime.utcnow(), + complaint_date=utc_now(), ) complaint_3 = Complaint( notification_id=notification_3.id, service_id=service_2.id, ses_feedback_id=str(uuid.uuid4()), complaint_type="abuse", - complaint_date=datetime.utcnow(), - created_at=datetime.utcnow() + timedelta(minutes=1), + complaint_date=utc_now(), + created_at=utc_now() + timedelta(minutes=1), ) save_complaint(complaint_1) diff --git a/tests/app/dao/test_fact_billing_dao.py b/tests/app/dao/test_fact_billing_dao.py index 0282d6983..30f2cd1c3 100644 --- a/tests/app/dao/test_fact_billing_dao.py +++ b/tests/app/dao/test_fact_billing_dao.py @@ -23,6 +23,7 @@ from app.dao.fact_billing_dao import ( from app.dao.organization_dao import dao_add_service_to_organization from app.enums import KeyType, NotificationStatus, NotificationType, TemplateType from app.models import FactBilling +from app.utils import utc_now from tests.app.db import ( create_annual_billing, create_ft_billing, @@ -101,7 +102,7 @@ def test_fetch_billing_data_for_today_includes_data_with_the_right_key_type( key_type=key_type, ) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 1 assert results[0].notifications_sent == 2 @@ -118,7 +119,7 @@ def test_fetch_billing_data_for_day_only_calls_query_for_permission_type( sms_template = create_template(service=service, template_type=TemplateType.SMS) create_notification(template=email_template, status=NotificationStatus.DELIVERED) create_notification(template=sms_template, status=NotificationStatus.DELIVERED) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day( process_day=today.date(), check_permissions=True ) @@ -137,7 +138,7 @@ def test_fetch_billing_data_for_day_only_calls_query_for_all_channels( sms_template = create_template(service=service, template_type=TemplateType.SMS) create_notification(template=email_template, status=NotificationStatus.DELIVERED) create_notification(template=sms_template, status=NotificationStatus.DELIVERED) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day( process_day=today.date(), check_permissions=False, @@ -192,7 +193,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_template_and_notification_type create_notification(template=email_template, status=NotificationStatus.DELIVERED) create_notification(template=sms_template, status=NotificationStatus.DELIVERED) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 2 assert results[0].notifications_sent == 1 @@ -207,7 +208,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_service(notify_db_session): create_notification(template=email_template, status=NotificationStatus.DELIVERED) create_notification(template=sms_template, status=NotificationStatus.DELIVERED) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 2 assert results[0].notifications_sent == 1 @@ -228,7 +229,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_provider(notify_db_session): sent_by="sns", ) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 1 assert results[0].notifications_sent == 2 @@ -249,7 +250,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_rate_mulitplier(notify_db_sess rate_multiplier=2, ) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 2 assert results[0].notifications_sent == 1 @@ -270,7 +271,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_international(notify_db_sessio international=False, ) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 2 assert all(result.notifications_sent == 1 for result in results) @@ -286,7 +287,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_notification_type(notify_db_se create_notification(template=email_template, status=NotificationStatus.DELIVERED) create_notification(template=email_template, status=NotificationStatus.DELIVERED) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert len(results) == 2 notification_types = [x.notification_type for x in results] @@ -294,7 +295,7 @@ def test_fetch_billing_data_for_day_is_grouped_by_notification_type(notify_db_se def test_fetch_billing_data_for_day_returns_empty_list(notify_db_session): - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day(today.date()) assert results == [] @@ -307,7 +308,7 @@ def test_fetch_billing_data_for_day_uses_correct_table(notify_db_session): sms_template = create_template(service=service, template_type=TemplateType.SMS) email_template = create_template(service=service, template_type=TemplateType.EMAIL) - five_days_ago = datetime.utcnow() - timedelta(days=5) + five_days_ago = utc_now() - timedelta(days=5) create_notification( template=sms_template, status=NotificationStatus.DELIVERED, @@ -337,7 +338,7 @@ def test_fetch_billing_data_for_day_returns_list_for_given_service(notify_db_ses create_notification(template=template, status=NotificationStatus.DELIVERED) create_notification(template=template_2, status=NotificationStatus.DELIVERED) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day( process_day=today.date(), service_id=service.id ) @@ -352,7 +353,7 @@ def test_fetch_billing_data_for_day_bills_correctly_for_status(notify_db_session for status in NotificationStatus: create_notification(template=sms_template, status=status) create_notification(template=email_template, status=status) - today = datetime.utcnow() + today = utc_now() results = fetch_billing_data_for_day( process_day=today.date(), service_id=service.id ) @@ -368,13 +369,11 @@ def test_fetch_billing_data_for_day_bills_correctly_for_status(notify_db_session def test_get_rates_for_billing(notify_db_session): create_rate( - start_date=datetime.utcnow(), value=12, notification_type=NotificationType.EMAIL + start_date=utc_now(), value=12, notification_type=NotificationType.EMAIL ) + create_rate(start_date=utc_now(), value=22, notification_type=NotificationType.SMS) create_rate( - start_date=datetime.utcnow(), value=22, notification_type=NotificationType.SMS - ) - create_rate( - start_date=datetime.utcnow(), value=33, notification_type=NotificationType.EMAIL + start_date=utc_now(), value=33, notification_type=NotificationType.EMAIL ) rates = get_rates_for_billing() @@ -500,7 +499,7 @@ def test_fetch_monthly_billing_for_year_adds_data_for_today(notify_db_session): template = create_template(service=service, template_type=TemplateType.SMS) create_rate( - start_date=datetime.utcnow() - timedelta(days=1), + start_date=utc_now() - timedelta(days=1), value=0.158, notification_type=NotificationType.SMS, ) @@ -960,7 +959,7 @@ def test_fetch_usage_year_for_organization_populates_ft_billing_for_today( notify_db_session, ): create_rate( - start_date=datetime.utcnow() - timedelta(days=1), + start_date=utc_now() - timedelta(days=1), value=0.65, notification_type=NotificationType.SMS, ) @@ -968,7 +967,7 @@ def test_fetch_usage_year_for_organization_populates_ft_billing_for_today( service = create_service() template = create_template(service=service) dao_add_service_to_organization(service=service, organization_id=new_org.id) - current_year = datetime.utcnow().year + current_year = utc_now().year create_annual_billing( service_id=service.id, free_sms_fragment_limit=10, @@ -992,7 +991,7 @@ def test_fetch_usage_year_for_organization_calculates_cost_from_multiple_rates( ): old_rate_date = date(2022, 4, 29) new_rate_date = date(2022, 5, 1) - current_year = datetime.utcnow().year + current_year = utc_now().year org = create_organization(name="Organization 1") @@ -1033,7 +1032,7 @@ def test_fetch_usage_year_for_organization_calculates_cost_from_multiple_rates( @freeze_time("2022-05-01 13:30") def test_fetch_usage_year_for_organization_when_no_usage(notify_db_session): - current_year = datetime.utcnow().year + current_year = utc_now().year org = create_organization(name="Organization 1") @@ -1059,11 +1058,11 @@ def test_fetch_usage_year_for_organization_when_no_usage(notify_db_session): @freeze_time("2022-05-01 13:30") def test_fetch_usage_year_for_organization_only_queries_present_year(notify_db_session): - current_year = datetime.utcnow().year + current_year = utc_now().year last_year = current_year - 1 date_two_years_ago = date(2021, 3, 31) date_in_last_financial_year = date(2022, 3, 31) - date_in_this_year = datetime.utcnow().date() + date_in_this_year = utc_now().date() org = create_organization(name="Organization 1") @@ -1133,20 +1132,20 @@ def test_fetch_usage_year_for_organization_only_returns_data_for_live_services( dao_add_service_to_organization(service=live_service, organization_id=org.id) dao_add_service_to_organization(service=trial_service, organization_id=org.id) create_ft_billing( - local_date=datetime.utcnow().date(), + local_date=utc_now().date(), template=sms_template, rate=0.0158, billable_unit=19, notifications_sent=19, ) create_ft_billing( - local_date=datetime.utcnow().date(), + local_date=utc_now().date(), template=email_template, billable_unit=0, notifications_sent=100, ) create_ft_billing( - local_date=datetime.utcnow().date(), + local_date=utc_now().date(), template=trial_sms_template, billable_unit=200, rate=0.0158, @@ -1172,9 +1171,9 @@ def test_fetch_usage_year_for_organization_only_returns_data_for_live_services( def test_query_organization_sms_usage_for_year_handles_multiple_services( notify_db_session, ): - today = datetime.utcnow().date() - yesterday = datetime.utcnow().date() - timedelta(days=1) - current_year = datetime.utcnow().year + today = utc_now().date() + yesterday = utc_now().date() - timedelta(days=1) + current_year = utc_now().year org = create_organization(name="Organization 1") @@ -1269,7 +1268,7 @@ def test_query_organization_sms_usage_for_year_handles_multiple_rates( ): old_rate_date = date(2022, 4, 29) new_rate_date = date(2022, 5, 1) - current_year = datetime.utcnow().year + current_year = utc_now().year org = create_organization(name="Organization 1") diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index d7d5cc9cb..4c7030b2e 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -19,6 +19,7 @@ from app.dao.fact_notification_status_dao import ( ) from app.enums import KeyType, NotificationStatus, NotificationType, TemplateType from app.models import FactNotificationStatus +from app.utils import utc_now from tests.app.db import ( create_ft_notification_status, create_job, @@ -643,10 +644,8 @@ def test_fetch_monthly_template_usage_for_service(sample_service): template=template_two, count=5, ) - create_notification( - template=template_two, created_at=datetime.utcnow() - timedelta(days=1) - ) - create_notification(template=template_two, created_at=datetime.utcnow()) + create_notification(template=template_two, created_at=utc_now() - timedelta(days=1)) + create_notification(template=template_two, created_at=utc_now()) results = fetch_monthly_template_usage_for_service( datetime(2017, 4, 1), datetime(2018, 3, 31), sample_service.id ) @@ -713,7 +712,7 @@ def test_fetch_monthly_template_usage_for_service_does_join_to_notifications_if_ template=template_one, count=3, ) - create_notification(template=template_one, created_at=datetime.utcnow()) + create_notification(template=template_one, created_at=utc_now()) results = fetch_monthly_template_usage_for_service( datetime(2018, 1, 1), datetime(2018, 2, 20), template_one.service_id ) @@ -747,7 +746,7 @@ def test_fetch_monthly_template_usage_for_service_does_not_include_cancelled_sta ) create_notification( template=sample_template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.CANCELLED, ) results = fetch_monthly_template_usage_for_service( @@ -771,7 +770,7 @@ def test_fetch_monthly_template_usage_for_service_does_not_include_test_notifica ) create_notification( template=sample_template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.DELIVERED, key_type=KeyType.TEST, ) diff --git a/tests/app/dao/test_invited_user_dao.py b/tests/app/dao/test_invited_user_dao.py index cedee16ea..da52e52e7 100644 --- a/tests/app/dao/test_invited_user_dao.py +++ b/tests/app/dao/test_invited_user_dao.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta import pytest from sqlalchemy.orm.exc import NoResultFound @@ -14,6 +14,7 @@ from app.dao.invited_user_dao import ( ) from app.enums import InvitedUserStatus, PermissionType from app.models import InvitedUser +from app.utils import utc_now from tests.app.db import create_invited_user @@ -196,7 +197,7 @@ def make_invitation(user, service, age=None, email_address="test@test.com"): from_user=user, service=service, status=InvitedUserStatus.PENDING, - created_at=datetime.utcnow() - (age or timedelta(hours=0)), + created_at=utc_now() - (age or timedelta(hours=0)), permissions=PermissionType.MANAGE_SETTINGS, folder_permissions=[str(uuid.uuid4())], ) diff --git a/tests/app/dao/test_jobs_dao.py b/tests/app/dao/test_jobs_dao.py index 0831999e1..ca98257e5 100644 --- a/tests/app/dao/test_jobs_dao.py +++ b/tests/app/dao/test_jobs_dao.py @@ -20,6 +20,7 @@ from app.dao.jobs_dao import ( ) from app.enums import JobStatus, NotificationStatus from app.models import Job, NotificationType, TemplateType +from app.utils import utc_now from tests.app.db import ( create_job, create_notification, @@ -228,8 +229,8 @@ def test_update_job(sample_job): def test_set_scheduled_jobs_to_pending_gets_all_jobs_in_scheduled_state_before_now( sample_template, ): - one_minute_ago = datetime.utcnow() - timedelta(minutes=1) - one_hour_ago = datetime.utcnow() - timedelta(minutes=60) + one_minute_ago = utc_now() - timedelta(minutes=1) + one_hour_ago = utc_now() - timedelta(minutes=60) job_new = create_job( sample_template, scheduled_for=one_minute_ago, @@ -249,7 +250,7 @@ def test_set_scheduled_jobs_to_pending_gets_all_jobs_in_scheduled_state_before_n def test_set_scheduled_jobs_to_pending_gets_ignores_jobs_not_scheduled( sample_template, sample_job ): - one_minute_ago = datetime.utcnow() - timedelta(minutes=1) + one_minute_ago = utc_now() - timedelta(minutes=1) job_scheduled = create_job( sample_template, scheduled_for=one_minute_ago, @@ -268,8 +269,8 @@ def test_set_scheduled_jobs_to_pending_gets_ignores_jobs_scheduled_in_the_future def test_set_scheduled_jobs_to_pending_updates_rows(sample_template): - one_minute_ago = datetime.utcnow() - timedelta(minutes=1) - one_hour_ago = datetime.utcnow() - timedelta(minutes=60) + one_minute_ago = utc_now() - timedelta(minutes=1) + one_hour_ago = utc_now() - timedelta(minutes=60) create_job( sample_template, scheduled_for=one_minute_ago, @@ -298,7 +299,7 @@ def test_should_get_jobs_seven_days_old(sample_template): """ Jobs older than seven days are deleted, but only two day's worth (two-day window) """ - seven_days_ago = datetime.utcnow() - timedelta(days=7) + seven_days_ago = utc_now() - timedelta(days=7) within_seven_days = seven_days_ago + timedelta(seconds=1) eight_days_ago = seven_days_ago - timedelta(days=1) @@ -367,8 +368,8 @@ def test_get_jobs_for_service_doesnt_return_test_messages( @freeze_time("2016-10-31 10:00:00") def test_should_get_jobs_seven_days_old_by_scheduled_for_date(sample_service): - six_days_ago = datetime.utcnow() - timedelta(days=6) - eight_days_ago = datetime.utcnow() - timedelta(days=8) + six_days_ago = utc_now() - timedelta(days=6) + eight_days_ago = utc_now() - timedelta(days=8) sms_template = create_template(sample_service, template_type=TemplateType.SMS) create_job(sms_template, created_at=eight_days_ago) @@ -405,7 +406,7 @@ def test_find_jobs_with_missing_rows(sample_email_template): template=sample_email_template, notification_count=3, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=20), + processing_finished=utc_now() - timedelta(minutes=20), ) for i in range(0, 3): create_notification(job=healthy_job, job_row_number=i) @@ -413,7 +414,7 @@ def test_find_jobs_with_missing_rows(sample_email_template): template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=20), + processing_finished=utc_now() - timedelta(minutes=20), ) for i in range(0, 4): create_notification(job=job_with_missing_rows, job_row_number=i) @@ -431,7 +432,7 @@ def test_find_jobs_with_missing_rows_returns_nothing_for_a_job_completed_less_th template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=9), + processing_finished=utc_now() - timedelta(minutes=9), ) for i in range(0, 4): create_notification(job=job, job_row_number=i) @@ -448,7 +449,7 @@ def test_find_jobs_with_missing_rows_returns_nothing_for_a_job_completed_more_th template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(days=1), + processing_finished=utc_now() - timedelta(days=1), ) for i in range(0, 4): create_notification(job=job, job_row_number=i) @@ -474,7 +475,7 @@ def test_find_jobs_with_missing_rows_doesnt_return_jobs_that_are_not_finished( template=sample_email_template, notification_count=5, job_status=status, - processing_finished=datetime.utcnow() - timedelta(minutes=11), + processing_finished=utc_now() - timedelta(minutes=11), ) for i in range(0, 4): create_notification(job=job, job_row_number=i) @@ -489,7 +490,7 @@ def test_find_missing_row_for_job(sample_email_template): template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=11), + processing_finished=utc_now() - timedelta(minutes=11), ) create_notification(job=job, job_row_number=0) create_notification(job=job, job_row_number=1) @@ -506,7 +507,7 @@ def test_find_missing_row_for_job_more_than_one_missing_row(sample_email_templat template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=11), + processing_finished=utc_now() - timedelta(minutes=11), ) create_notification(job=job, job_row_number=0) create_notification(job=job, job_row_number=1) @@ -525,7 +526,7 @@ def test_find_missing_row_for_job_return_none_when_row_isnt_missing( template=sample_email_template, notification_count=5, job_status=JobStatus.FINISHED, - processing_finished=datetime.utcnow() - timedelta(minutes=11), + processing_finished=utc_now() - timedelta(minutes=11), ) for i in range(0, 5): create_notification(job=job, job_row_number=i) diff --git a/tests/app/dao/test_organization_dao.py b/tests/app/dao/test_organization_dao.py index a03b5fa8a..edffdd1d4 100644 --- a/tests/app/dao/test_organization_dao.py +++ b/tests/app/dao/test_organization_dao.py @@ -1,4 +1,3 @@ -import datetime import uuid import pytest @@ -18,6 +17,7 @@ from app.dao.organization_dao import ( ) from app.enums import OrganizationType from app.models import Organization, Service +from app.utils import utc_now from tests.app.db import ( create_domain, create_email_branding, @@ -65,7 +65,7 @@ def test_update_organization(notify_db_session): "name": "new name", "organization_type": OrganizationType.STATE, "agreement_signed": True, - "agreement_signed_at": datetime.datetime.utcnow(), + "agreement_signed_at": utc_now(), "agreement_signed_by_id": user.id, "agreement_signed_version": 999.99, "email_branding_id": email_branding.id, diff --git a/tests/app/dao/test_provider_details_dao.py b/tests/app/dao/test_provider_details_dao.py index 8af524fa6..b03d965d0 100644 --- a/tests/app/dao/test_provider_details_dao.py +++ b/tests/app/dao/test_provider_details_dao.py @@ -16,6 +16,7 @@ from app.dao.provider_details_dao import ( ) from app.enums import NotificationType, TemplateType from app.models import ProviderDetails, ProviderDetailsHistory +from app.utils import utc_now from tests.app.db import create_ft_billing, create_service, create_template from tests.conftest import set_config @@ -158,7 +159,7 @@ def test_adjust_provider_priority_sets_priority( _adjust_provider_priority(sns_provider, 50) - assert sns_provider.updated_at == datetime.utcnow() + assert sns_provider.updated_at == utc_now() assert sns_provider.created_by.id == notify_user.id assert sns_provider.priority == 50 diff --git a/tests/app/dao/test_service_data_retention_dao.py b/tests/app/dao/test_service_data_retention_dao.py index 1d60c619b..98f5d9f17 100644 --- a/tests/app/dao/test_service_data_retention_dao.py +++ b/tests/app/dao/test_service_data_retention_dao.py @@ -1,5 +1,4 @@ import uuid -from datetime import datetime import pytest from sqlalchemy.exc import IntegrityError @@ -13,6 +12,7 @@ from app.dao.service_data_retention_dao import ( ) from app.enums import NotificationType from app.models import ServiceDataRetention +from app.utils import utc_now from tests.app.db import create_service, create_service_data_retention @@ -102,7 +102,7 @@ def test_insert_service_data_retention(sample_service): assert results[0].service_id == sample_service.id assert results[0].notification_type == NotificationType.EMAIL assert results[0].days_of_retention == 3 - assert results[0].created_at.date() == datetime.utcnow().date() + assert results[0].created_at.date() == utc_now().date() def test_insert_service_data_retention_throws_unique_constraint(sample_service): @@ -137,8 +137,8 @@ def test_update_service_data_retention(sample_service): assert results[0].service_id == sample_service.id assert results[0].notification_type == NotificationType.SMS assert results[0].days_of_retention == 5 - assert results[0].created_at.date() == datetime.utcnow().date() - assert results[0].updated_at.date() == datetime.utcnow().date() + assert results[0].created_at.date() == utc_now().date() + assert results[0].updated_at.date() == utc_now().date() def test_update_service_data_retention_does_not_update_if_row_does_not_exist( diff --git a/tests/app/dao/test_service_email_reply_to_dao.py b/tests/app/dao/test_service_email_reply_to_dao.py index c69838bd5..851ecb870 100644 --- a/tests/app/dao/test_service_email_reply_to_dao.py +++ b/tests/app/dao/test_service_email_reply_to_dao.py @@ -31,9 +31,10 @@ def test_dao_get_reply_to_by_service_id(notify_db_session): results = dao_get_reply_to_by_service_id(service_id=service.id) assert len(results) == 3 + # TODO we had to change the order around, why? assert default_reply_to == results[0] - assert another_reply_to == results[1] - assert second_reply_to == results[2] + assert another_reply_to == results[2] + assert second_reply_to == results[1] def test_dao_get_reply_to_by_service_id_does_not_return_archived_reply_tos( diff --git a/tests/app/dao/test_service_sms_sender_dao.py b/tests/app/dao/test_service_sms_sender_dao.py index 50b2a71ff..a4a7b9be5 100644 --- a/tests/app/dao/test_service_sms_sender_dao.py +++ b/tests/app/dao/test_service_sms_sender_dao.py @@ -164,11 +164,12 @@ def test_dao_update_service_sms_sender_switches_default(notify_db_session): .order_by(ServiceSmsSender.created_at) .all() ) + # TODO hmmm we had to change the order around, why? assert len(sms_senders) == 2 - assert sms_senders[0].sms_sender == "testing" - assert not sms_senders[0].is_default - assert sms_senders[1].sms_sender == "updated" - assert sms_senders[1].is_default + assert sms_senders[1].sms_sender == "testing" + assert not sms_senders[1].is_default + assert sms_senders[0].sms_sender == "updated" + assert sms_senders[0].is_default def test_dao_update_service_sms_sender_raises_exception_when_no_default_after_update( diff --git a/tests/app/dao/test_services_dao.py b/tests/app/dao/test_services_dao.py index 565bc52e9..6441f20e0 100644 --- a/tests/app/dao/test_services_dao.py +++ b/tests/app/dao/test_services_dao.py @@ -67,6 +67,7 @@ from app.models import ( VerifyCode, user_folder_permissions, ) +from app.utils import utc_now from tests.app.db import ( create_annual_billing, create_api_key, @@ -1493,8 +1494,8 @@ def test_dao_find_services_sending_to_tv_numbers(notify_db_session, fake_uuid): status=NotificationStatus.DELIVERED, ) - start_date = datetime.utcnow() - timedelta(days=1) - end_date = datetime.utcnow() + start_date = utc_now() - timedelta(days=1) + end_date = utc_now() result = dao_find_services_sending_to_tv_numbers(start_date, end_date, threshold=4) assert len(result) == 1 @@ -1541,8 +1542,8 @@ def test_dao_find_services_with_high_failure_rates(notify_db_session, fake_uuid) status=NotificationStatus.PERMANENT_FAILURE, ) # below threshold is excluded - start_date = datetime.utcnow() - timedelta(days=1) - end_date = datetime.utcnow() + start_date = utc_now() - timedelta(days=1) + end_date = utc_now() result = dao_find_services_with_high_failure_rates( start_date, end_date, threshold=3 diff --git a/tests/app/dao/test_uploads_dao.py b/tests/app/dao/test_uploads_dao.py index 5a4fb33b8..0310c6e44 100644 --- a/tests/app/dao/test_uploads_dao.py +++ b/tests/app/dao/test_uploads_dao.py @@ -1,9 +1,10 @@ -from datetime import datetime, timedelta +from datetime import timedelta from freezegun import freeze_time from app.dao.uploads_dao import dao_get_uploads_by_service_id from app.enums import JobStatus, NotificationStatus, NotificationType, TemplateType +from app.utils import utc_now from tests.app.db import ( create_job, create_notification, @@ -47,11 +48,11 @@ def test_get_uploads_for_service(sample_template): create_service_data_retention( sample_template.service, NotificationType.SMS, days_of_retention=9 ) - job = create_job(sample_template, processing_started=datetime.utcnow()) + job = create_job(sample_template, processing_started=utc_now()) other_service = create_service(service_name="other service") other_template = create_template(service=other_service) - other_job = create_job(other_template, processing_started=datetime.utcnow()) + other_job = create_job(other_template, processing_started=utc_now()) uploads_from_db = dao_get_uploads_by_service_id(job.service_id).items other_uploads_from_db = dao_get_uploads_by_service_id(other_job.service_id).items @@ -91,16 +92,16 @@ def test_get_uploads_for_service(sample_template): def test_get_uploads_orders_by_processing_started_desc(sample_template): - days_ago = datetime.utcnow() - timedelta(days=3) + days_ago = utc_now() - timedelta(days=3) upload_1 = create_job( sample_template, - processing_started=datetime.utcnow() - timedelta(days=1), + processing_started=utc_now() - timedelta(days=1), created_at=days_ago, job_status=JobStatus.IN_PROGRESS, ) upload_2 = create_job( sample_template, - processing_started=datetime.utcnow() - timedelta(days=2), + processing_started=utc_now() - timedelta(days=2), created_at=days_ago, job_status=JobStatus.IN_PROGRESS, ) diff --git a/tests/app/dao/test_users_dao.py b/tests/app/dao/test_users_dao.py index e38a395b5..9c8770913 100644 --- a/tests/app/dao/test_users_dao.py +++ b/tests/app/dao/test_users_dao.py @@ -1,5 +1,5 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta import pytest from freezegun import freeze_time @@ -28,6 +28,7 @@ from app.dao.users_dao import ( from app.enums import AuthType, CodeType, PermissionType from app.errors import InvalidRequest from app.models import User, VerifyCode +from app.utils import utc_now from tests.app.db import ( create_permissions, create_service, @@ -59,7 +60,7 @@ def test_create_user(notify_db_session, phone_number, expected_phone_number): assert user_query.email_address == email assert user_query.id == user.id assert user_query.mobile_number == expected_phone_number - assert user_query.email_access_validated_at == datetime.utcnow() + assert user_query.email_access_validated_at == utc_now() assert not user_query.platform_admin @@ -146,8 +147,8 @@ def make_verify_code(user, age=None, expiry_age=None, code="12335", code_used=Fa verify_code = VerifyCode( code_type=CodeType.SMS, _code=code, - created_at=datetime.utcnow() - (age or timedelta(hours=0)), - expiry_datetime=datetime.utcnow() - (expiry_age or timedelta(0)), + created_at=utc_now() - (age or timedelta(hours=0)), + expiry_datetime=utc_now() - (expiry_age or timedelta(0)), user=user, code_used=code_used, ) @@ -172,16 +173,16 @@ def test_update_user_attribute(client, sample_user, user_attribute, user_value): @freeze_time("2020-01-24T12:00:00") def test_update_user_password(notify_api, notify_db_session, sample_user): - sample_user.password_changed_at = datetime.utcnow() - timedelta(days=1) + sample_user.password_changed_at = utc_now() - timedelta(days=1) password = "newpassword" assert not sample_user.check_password(password) update_user_password(sample_user, password) assert sample_user.check_password(password) - assert sample_user.password_changed_at == datetime.utcnow() + assert sample_user.password_changed_at == utc_now() def test_count_user_verify_codes(sample_user): - with freeze_time(datetime.utcnow() + timedelta(hours=1)): + with freeze_time(utc_now() + timedelta(hours=1)): make_verify_code(sample_user, code_used=True) make_verify_code(sample_user, expiry_age=timedelta(hours=2)) [make_verify_code(sample_user) for i in range(5)] diff --git a/tests/app/db.py b/tests/app/db.py index 9bd55aba8..b62f99b4e 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -69,6 +69,7 @@ from app.models import ( User, WebauthnCredential, ) +from app.utils import utc_now def create_user( @@ -265,7 +266,7 @@ def create_notification( template = job.template if created_at is None: - created_at = datetime.utcnow() + created_at = utc_now() if to_field is None: to_field = ( @@ -280,8 +281,8 @@ def create_notification( NotificationStatus.VIRUS_SCAN_FAILED, NotificationStatus.PENDING_VIRUS_CHECK, ): - sent_at = sent_at or datetime.utcnow() - updated_at = updated_at or datetime.utcnow() + sent_at = sent_at or utc_now() + updated_at = updated_at or utc_now() if not one_off and (job is None and api_key is None): # we did not specify in test - lets create it @@ -354,11 +355,11 @@ def create_notification_history( template = job.template if created_at is None: - created_at = datetime.utcnow() + created_at = utc_now() if status != NotificationStatus.CREATED: - sent_at = sent_at or datetime.utcnow() - updated_at = updated_at or datetime.utcnow() + sent_at = sent_at or utc_now() + updated_at = updated_at or utc_now() data = { "id": id or uuid.uuid4(), @@ -412,7 +413,7 @@ def create_job( "template_version": template.version, "original_file_name": original_file_name, "notification_count": notification_count, - "created_at": created_at or datetime.utcnow(), + "created_at": created_at or utc_now(), "created_by": template.created_by, "job_status": job_status, "scheduled_for": scheduled_for, @@ -456,10 +457,10 @@ def create_inbound_sms( inbound = InboundSms( service=service, - created_at=created_at or datetime.utcnow(), + created_at=created_at or utc_now(), notify_number=service.get_inbound_number(), user_number=user_number, - provider_date=provider_date or datetime.utcnow(), + provider_date=provider_date or utc_now(), provider_reference=provider_reference or "foo", content=content, provider=provider, @@ -769,7 +770,7 @@ def create_complaint(service=None, notification=None, created_at=None): service_id=service.id, ses_feedback_id=str(uuid.uuid4()), complaint_type="abuse", - complaint_date=datetime.utcnow(), + complaint_date=utc_now(), created_at=created_at if created_at else datetime.now(), ) db.session.add(complaint) diff --git a/tests/app/delivery/test_send_to_providers.py b/tests/app/delivery/test_send_to_providers.py index 0ad34fdea..af65dd766 100644 --- a/tests/app/delivery/test_send_to_providers.py +++ b/tests/app/delivery/test_send_to_providers.py @@ -1,6 +1,5 @@ import json from collections import namedtuple -from datetime import datetime from unittest.mock import ANY import pytest @@ -18,6 +17,7 @@ from app.enums import BrandType, KeyType, NotificationStatus, NotificationType from app.exceptions import NotificationTechnicalFailureException from app.models import EmailBranding, Notification from app.serialised_models import SerialisedService +from app.utils import utc_now from tests.app.db import ( create_email_branding, create_notification, @@ -105,7 +105,7 @@ def test_should_send_personalised_template_to_correct_sms_provider_and_persist( notification = Notification.query.filter_by(id=db_notification.id).one() assert notification.status == NotificationStatus.SENDING - assert notification.sent_at <= datetime.utcnow() + assert notification.sent_at <= utc_now() assert notification.sent_by == "sns" assert notification.billable_units == 1 assert notification.personalisation == {"name": "Jo"} @@ -147,7 +147,7 @@ def test_should_send_personalised_template_to_correct_email_provider_and_persist notification = Notification.query.filter_by(id=db_notification.id).one() assert notification.status == NotificationStatus.SENDING - assert notification.sent_at <= datetime.utcnow() + assert notification.sent_at <= utc_now() assert notification.sent_by == "ses" assert notification.personalisation == {"name": "Jo"} diff --git a/tests/app/inbound_sms/test_rest.py b/tests/app/inbound_sms/test_rest.py index fd45c0253..da1230a1b 100644 --- a/tests/app/inbound_sms/test_rest.py +++ b/tests/app/inbound_sms/test_rest.py @@ -4,6 +4,7 @@ import pytest from freezegun import freeze_time from app.enums import NotificationType +from app.utils import utc_now from tests.app.db import ( create_inbound_sms, create_service, @@ -243,7 +244,7 @@ def test_get_most_recent_inbound_sms_for_service_respects_data_retention( ): create_service_data_retention(sample_service, NotificationType.SMS, 5) for i in range(10): - created = datetime.utcnow() - timedelta(days=i) + created = utc_now() - timedelta(days=i) create_inbound_sms( sample_service, user_number="44770090000{}".format(i), @@ -288,7 +289,7 @@ def test_get_inbound_sms_for_service_respects_data_retention( ): create_service_data_retention(sample_service, NotificationType.SMS, 5) for i in range(10): - created = datetime.utcnow() - timedelta(days=i) + created = utc_now() - timedelta(days=i) create_inbound_sms( sample_service, user_number="44770090000{}".format(i), created_at=created ) diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index 670a02ca3..6d4112058 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -16,6 +16,7 @@ from app.enums import ( NotificationType, TemplateType, ) +from app.utils import utc_now from tests import create_admin_authorization_header from tests.app.db import ( create_ft_notification_status, @@ -152,7 +153,7 @@ def test_create_unscheduled_job_with_sender_id_in_metadata( @freeze_time("2016-01-01 12:00:00.000000") def test_create_scheduled_job(client, sample_template, mocker, fake_uuid): - scheduled_date = (datetime.utcnow() + timedelta(hours=95, minutes=59)).isoformat() + scheduled_date = (utc_now() + timedelta(hours=95, minutes=59)).isoformat() mocker.patch("app.celery.tasks.process_job.apply_async") mocker.patch( "app.job.rest.get_job_metadata_from_s3", @@ -250,7 +251,7 @@ def test_create_job_returns_400_if_file_is_invalid( def test_should_not_create_scheduled_job_more_then_96_hours_in_the_future( client, sample_template, mocker, fake_uuid ): - scheduled_date = (datetime.utcnow() + timedelta(hours=96, minutes=1)).isoformat() + scheduled_date = (utc_now() + timedelta(hours=96, minutes=1)).isoformat() mocker.patch("app.celery.tasks.process_job.apply_async") mocker.patch( "app.job.rest.get_job_metadata_from_s3", @@ -287,7 +288,7 @@ def test_should_not_create_scheduled_job_more_then_96_hours_in_the_future( def test_should_not_create_scheduled_job_in_the_past( client, sample_template, mocker, fake_uuid ): - scheduled_date = (datetime.utcnow() - timedelta(minutes=1)).isoformat() + scheduled_date = (utc_now() - timedelta(minutes=1)).isoformat() mocker.patch("app.celery.tasks.process_job.apply_async") mocker.patch( "app.job.rest.get_job_metadata_from_s3", @@ -656,7 +657,7 @@ def test_get_job_by_id_with_stats_for_old_job_where_notifications_have_been_purg old_job = create_job( sample_template, notification_count=10, - created_at=datetime.utcnow() - timedelta(days=9), + created_at=utc_now() - timedelta(days=9), job_status=JobStatus.FINISHED, ) @@ -771,8 +772,8 @@ def test_get_jobs_with_limit_days(admin_request, sample_template): def test_get_jobs_should_return_statistics(admin_request, sample_template): - now = datetime.utcnow() - earlier = datetime.utcnow() - timedelta(days=1) + now = utc_now() + earlier = utc_now() - timedelta(days=1) job_1 = create_job(sample_template, processing_started=earlier) job_2 = create_job(sample_template, processing_started=now) create_notification(job=job_1, status=NotificationStatus.CREATED) @@ -807,8 +808,8 @@ def test_get_jobs_should_return_no_stats_if_no_rows_in_notifications( admin_request, sample_template, ): - now = datetime.utcnow() - earlier = datetime.utcnow() - timedelta(days=1) + now = utc_now() + earlier = utc_now() - timedelta(days=1) job_1 = create_job(sample_template, created_at=earlier) job_2 = create_job(sample_template, created_at=now) diff --git a/tests/app/organization/test_rest.py b/tests/app/organization/test_rest.py index 914dca008..04b68884b 100644 --- a/tests/app/organization/test_rest.py +++ b/tests/app/organization/test_rest.py @@ -1,5 +1,4 @@ import uuid -from datetime import datetime import pytest from flask import current_app @@ -13,6 +12,7 @@ from app.dao.organization_dao import ( from app.dao.services_dao import dao_archive_service from app.enums import OrganizationType from app.models import AnnualBilling, Organization +from app.utils import utc_now from tests.app.db import ( create_annual_billing, create_domain, @@ -835,7 +835,7 @@ def test_get_organization_services_usage(admin_request, notify_db_session): service_id=service.id, free_sms_fragment_limit=10, financial_year_start=2019 ) create_ft_billing( - local_date=datetime.utcnow().date(), + local_date=utc_now().date(), template=template, billable_unit=19, rate=0.060, @@ -873,7 +873,7 @@ def test_get_organization_services_usage_sort_active_first( service_id=service.id, free_sms_fragment_limit=10, financial_year_start=2019 ) create_ft_billing( - local_date=datetime.utcnow().date(), + local_date=utc_now().date(), template=template, billable_unit=19, rate=0.060, diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index 5535f814b..8a97046e0 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -36,6 +36,7 @@ from app.models import ( ServiceSmsSender, User, ) +from app.utils import utc_now from tests import create_admin_authorization_header from tests.app.db import ( create_annual_billing, @@ -369,7 +370,7 @@ def test_get_service_by_id_should_404_if_no_service_for_user(notify_api, sample_ def test_get_service_by_id_returns_go_live_user_and_go_live_at( admin_request, sample_user ): - now = datetime.utcnow() + now = utc_now() service = create_service(user=sample_user, go_live_user=sample_user, go_live_at=now) json_resp = admin_request.get("service.get_service_by_id", service_id=service.id) assert json_resp["data"]["go_live_user"] == str(sample_user.id) @@ -2270,9 +2271,7 @@ def test_get_detailed_services_groups_by_service(notify_db_session): create_notification(service_1_template, status=NotificationStatus.DELIVERED) create_notification(service_1_template, status=NotificationStatus.CREATED) - data = get_detailed_services( - start_date=datetime.utcnow().date(), end_date=datetime.utcnow().date() - ) + data = get_detailed_services(start_date=utc_now().date(), end_date=utc_now().date()) data = sorted(data, key=lambda x: x["name"]) assert len(data) == 2 @@ -2315,9 +2314,7 @@ def test_get_detailed_services_includes_services_with_no_notifications( service_1_template = create_template(service_1) create_notification(service_1_template) - data = get_detailed_services( - start_date=datetime.utcnow().date(), end_date=datetime.utcnow().date() - ) + data = get_detailed_services(start_date=utc_now().date(), end_date=utc_now().date()) data = sorted(data, key=lambda x: x["name"]) assert len(data) == 2 @@ -2359,7 +2356,7 @@ def test_get_detailed_services_only_includes_todays_notifications(sample_templat with freeze_time("2015-10-10T12:00:00"): data = get_detailed_services( - start_date=datetime.utcnow().date(), end_date=datetime.utcnow().date() + start_date=utc_now().date(), end_date=utc_now().date() ) data = sorted(data, key=lambda x: x["id"]) @@ -2386,29 +2383,29 @@ def test_get_detailed_services_for_date_range( from app.service.rest import get_detailed_services create_ft_notification_status( - local_date=(datetime.utcnow() - timedelta(days=3)).date(), + local_date=(utc_now() - timedelta(days=3)).date(), service=sample_template.service, notification_type=NotificationType.SMS, ) create_ft_notification_status( - local_date=(datetime.utcnow() - timedelta(days=2)).date(), + local_date=(utc_now() - timedelta(days=2)).date(), service=sample_template.service, notification_type=NotificationType.SMS, ) create_ft_notification_status( - local_date=(datetime.utcnow() - timedelta(days=1)).date(), + local_date=(utc_now() - timedelta(days=1)).date(), service=sample_template.service, notification_type=NotificationType.SMS, ) create_notification( template=sample_template, - created_at=datetime.utcnow(), + created_at=utc_now(), status=NotificationStatus.DELIVERED, ) - start_date = (datetime.utcnow() - timedelta(days=start_date_delta)).date() - end_date = (datetime.utcnow() - timedelta(days=end_date_delta)).date() + start_date = (utc_now() - timedelta(days=start_date_delta)).date() + end_date = (utc_now() - timedelta(days=end_date_delta)).date() data = get_detailed_services( only_active=False, diff --git a/tests/app/service/test_statistics_rest.py b/tests/app/service/test_statistics_rest.py index 522c3902b..591e5ca8e 100644 --- a/tests/app/service/test_statistics_rest.py +++ b/tests/app/service/test_statistics_rest.py @@ -11,6 +11,7 @@ from app.enums import ( StatisticsType, TemplateType, ) +from app.utils import utc_now from tests.app.db import ( create_ft_notification_status, create_notification, @@ -28,7 +29,7 @@ def test_get_template_usage_by_month_returns_correct_data( template=sample_template, count=3, ) - create_notification(sample_template, created_at=datetime.utcnow()) + create_notification(sample_template, created_at=utc_now()) resp_json = admin_request.get( "service.get_monthly_template_usage", @@ -74,7 +75,7 @@ def test_get_template_usage_by_month_returns_two_templates( template=sample_template, count=3, ) - create_notification(sample_template, created_at=datetime.utcnow()) + create_notification(sample_template, created_at=utc_now()) resp_json = admin_request.get( "service.get_monthly_template_usage", diff --git a/tests/app/template/test_rest_history.py b/tests/app/template/test_rest_history.py index 6aa234de0..737b8940d 100644 --- a/tests/app/template/test_rest_history.py +++ b/tests/app/template/test_rest_history.py @@ -5,6 +5,7 @@ from flask import url_for from app.dao.templates_dao import dao_update_template from app.enums import TemplateProcessType +from app.utils import utc_now from tests import create_admin_authorization_header @@ -32,7 +33,7 @@ def test_template_history_version(notify_api, sample_user, sample_template): datetime.strptime( json_resp["data"]["created_at"], "%Y-%m-%d %H:%M:%S.%f" ).date() - == datetime.utcnow().date() + == utc_now().date() ) diff --git a/tests/app/template_statistics/test_rest.py b/tests/app/template_statistics/test_rest.py index be6b368ab..1ae65b22e 100644 --- a/tests/app/template_statistics/test_rest.py +++ b/tests/app/template_statistics/test_rest.py @@ -1,12 +1,12 @@ import uuid -from datetime import datetime, timedelta +from datetime import timedelta from unittest.mock import Mock import pytest from freezegun import freeze_time from app.enums import NotificationStatus, TemplateType -from app.utils import DATETIME_FORMAT +from app.utils import DATETIME_FORMAT, utc_now from tests.app.db import create_ft_notification_status, create_notification # get_template_statistics_for_service_by_day @@ -119,9 +119,9 @@ def test_get_template_statistics_for_service_by_day_returns_empty_list_if_no_tem def test_get_last_used_datetime_for_template(admin_request, sample_template): - date_from_notification = datetime.utcnow() - timedelta(hours=2) + date_from_notification = utc_now() - timedelta(hours=2) create_notification(template=sample_template, created_at=date_from_notification) - date_from_ft_status = (datetime.utcnow() - timedelta(days=2)).date() + date_from_ft_status = (utc_now() - timedelta(days=2)).date() create_ft_notification_status( local_date=date_from_ft_status, template=sample_template ) diff --git a/tests/app/test_commands.py b/tests/app/test_commands.py index a96eae599..7eee00bbf 100644 --- a/tests/app/test_commands.py +++ b/tests/app/test_commands.py @@ -37,6 +37,7 @@ from app.models import ( Template, User, ) +from app.utils import utc_now from tests.app.db import ( create_annual_billing, create_job, @@ -101,7 +102,7 @@ def test_update_jobs_archived_flag(notify_db_session, notify_api): sms_template = create_template(service=service, template_type=TemplateType.SMS) create_job(sms_template) - right_now = datetime.datetime.utcnow() + right_now = utc_now() tomorrow = right_now + datetime.timedelta(days=1) right_now = right_now.strftime("%Y-%m-%d") diff --git a/tests/app/test_model.py b/tests/app/test_model.py index aab74fac8..e74ef06ff 100644 --- a/tests/app/test_model.py +++ b/tests/app/test_model.py @@ -1,5 +1,3 @@ -from datetime import datetime - import pytest from freezegun import freeze_time from sqlalchemy.exc import IntegrityError @@ -26,6 +24,7 @@ from app.models import ( VerifyCode, filter_null_value_fields, ) +from app.utils import utc_now from tests.app.db import ( create_inbound_number, create_notification, @@ -385,7 +384,7 @@ def test_user_password(): def test_annual_billing_serialize(): - now = datetime.utcnow() + now = utc_now() ab = AnnualBilling() service = Service() ab.service = service @@ -449,7 +448,7 @@ def test_rate_str(): ), ) def test_organization_agreement_mou(notify_db_session, agreement_type, expected): - now = datetime.utcnow() + now = utc_now() agree = Agreement() agree.id = "whatever" agree.start_time = now @@ -469,7 +468,7 @@ def test_organization_agreement_mou(notify_db_session, agreement_type, expected) ), ) def test_organization_agreement_active(notify_db_session, agreement_status, expected): - now = datetime.utcnow() + now = utc_now() agree = Agreement() agree.id = "whatever" agree.start_time = now @@ -485,7 +484,7 @@ def test_agreement_serialize(): agree = Agreement() agree.id = "abc" - now = datetime.utcnow() + now = utc_now() agree.start_time = now agree.end_time = now serialize = agree.serialize() diff --git a/tests/notifications_utils/clients/redis/test_redis_client.py b/tests/notifications_utils/clients/redis/test_redis_client.py index 536cce967..c9eb63240 100644 --- a/tests/notifications_utils/clients/redis/test_redis_client.py +++ b/tests/notifications_utils/clients/redis/test_redis_client.py @@ -1,10 +1,10 @@ import uuid -from datetime import datetime from unittest.mock import Mock, call import pytest from freezegun import freeze_time +from app.utils import utc_now from notifications_utils.clients.redis.redis_client import RedisClient, prepare_value @@ -208,9 +208,7 @@ def test_delete_multi(mocked_redis_client): (1.2, 1.2), (uuid.UUID(int=0), "00000000-0000-0000-0000-000000000000"), pytest.param({"a": 1}, None, marks=pytest.mark.xfail(raises=ValueError)), - pytest.param( - datetime.utcnow(), None, marks=pytest.mark.xfail(raises=ValueError) - ), + pytest.param(utc_now(), None, marks=pytest.mark.xfail(raises=ValueError)), ], ) def test_prepare_value(input, output): diff --git a/tests/notifications_utils/test_letter_timings.py b/tests/notifications_utils/test_letter_timings.py index aecc9c744..f93d32e99 100644 --- a/tests/notifications_utils/test_letter_timings.py +++ b/tests/notifications_utils/test_letter_timings.py @@ -4,6 +4,7 @@ import pytest import pytz from freezegun import freeze_time +from app.utils import utc_now from notifications_utils.letter_timings import ( get_letter_timings, letter_can_be_cancelled, @@ -188,7 +189,7 @@ def test_get_estimated_delivery_date_for_letter( def test_letter_cannot_be_cancelled_if_letter_status_is_not_created_or_pending_virus_check( status, ): - notification_created_at = datetime.utcnow() + notification_created_at = utc_now() assert not letter_can_be_cancelled(status, notification_created_at) From 07650e39e7bdce43b5eb537304461c5e3b370180 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 23 May 2024 14:07:57 -0700 Subject: [PATCH 3/3] remove datetime.utcnow() --- tests/app/dao/test_service_sms_sender_dao.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/app/dao/test_service_sms_sender_dao.py b/tests/app/dao/test_service_sms_sender_dao.py index a4a7b9be5..50b2a71ff 100644 --- a/tests/app/dao/test_service_sms_sender_dao.py +++ b/tests/app/dao/test_service_sms_sender_dao.py @@ -164,12 +164,11 @@ def test_dao_update_service_sms_sender_switches_default(notify_db_session): .order_by(ServiceSmsSender.created_at) .all() ) - # TODO hmmm we had to change the order around, why? assert len(sms_senders) == 2 - assert sms_senders[1].sms_sender == "testing" - assert not sms_senders[1].is_default - assert sms_senders[0].sms_sender == "updated" - assert sms_senders[0].is_default + assert sms_senders[0].sms_sender == "testing" + assert not sms_senders[0].is_default + assert sms_senders[1].sms_sender == "updated" + assert sms_senders[1].is_default def test_dao_update_service_sms_sender_raises_exception_when_no_default_after_update(