diff --git a/app/clients/performance_platform/performance_platform_client.py b/app/clients/performance_platform/performance_platform_client.py index 91e43a210..d8685f0f3 100644 --- a/app/clients/performance_platform/performance_platform_client.py +++ b/app/clients/performance_platform/performance_platform_client.py @@ -1,11 +1,11 @@ import base64 import json -from datetime import datetime, timedelta +from datetime import datetime import requests from flask import current_app -from app.utils import get_midnight_for_day_before, get_london_midnight_in_utc, get_utc_time_in_bst +from app.utils import get_midnight_for_day_before, get_london_midnight_in_utc, convert_utc_time_in_bst class PerformancePlatformClient: @@ -27,7 +27,7 @@ class PerformancePlatformClient: def send_performance_stats(self, date, channel, count, period): if self.active: payload = { - '_timestamp': get_utc_time_in_bst(date).isoformat(), + '_timestamp': convert_utc_time_in_bst(date).isoformat(), 'service': 'govuk-notify', 'channel': channel, 'count': count, diff --git a/app/models.py b/app/models.py index f52692f5e..54cc098b2 100644 --- a/app/models.py +++ b/app/models.py @@ -29,7 +29,7 @@ from app import ( ) from app.history_meta import Versioned -from app.utils import get_utc_time_in_bst +from app.utils import convert_utc_time_in_bst SMS_TYPE = 'sms' EMAIL_TYPE = 'email' @@ -832,7 +832,7 @@ class Notification(db.Model): }[self.template.template_type].get(self.status, self.status) def serialize_for_csv(self): - created_at_in_bst = get_utc_time_in_bst(self.created_at) + created_at_in_bst = convert_utc_time_in_bst(self.created_at) serialized = { "row_number": '' if self.job_row_number is None else self.job_row_number + 1, "recipient": self.to, @@ -873,7 +873,7 @@ class Notification(db.Model): "sent_at": self.sent_at.strftime(DATETIME_FORMAT) if self.sent_at else None, "completed_at": self.completed_at(), "scheduled_for": self.scheduled_notification.scheduled_for.strftime( - DATETIME_FORMAT) if self.scheduled_notification else None + "%Y-%m-%d %H") if self.scheduled_notification else None } return serialized diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index bb88f1265..bfa16d306 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -15,7 +15,7 @@ from app.dao.notifications_dao import (dao_create_notification, dao_created_scheduled_notification) from app.models import SMS_TYPE, Notification, KEY_TYPE_TEST, EMAIL_TYPE, ScheduledNotification from app.v2.errors import BadRequestError, SendNotificationToQueueError -from app.utils import get_template_instance, cache_key_for_service_template_counter +from app.utils import get_template_instance, cache_key_for_service_template_counter, convert_bst_to_utc def create_content_for_notification(template, personalisation): @@ -125,6 +125,7 @@ def simulated_recipient(to_address, notification_type): def persist_scheduled_notification(notification_id, scheduled_for): + scheduled_datetime = convert_bst_to_utc(datetime.strptime(scheduled_for, "%Y-%m-%d %H")) scheduled_notification = ScheduledNotification(notification_id=notification_id, - scheduled_for=scheduled_for) + scheduled_for=scheduled_datetime) dao_created_scheduled_notification(scheduled_notification) diff --git a/app/schema_validation/__init__.py b/app/schema_validation/__init__.py index fc134e2ce..4d9d0bc55 100644 --- a/app/schema_validation/__init__.py +++ b/app/schema_validation/__init__.py @@ -22,13 +22,13 @@ def validate(json_to_validate, schema): return True @format_checker.checks('datetime', raises=ValidationError) - def validate_schema_datetime(instance): + def validate_schema_date_with_hour(instance): if isinstance(instance, str): try: - datetime.strptime(instance, "%Y-%m-%d %H:%M:%S") + datetime.strptime(instance, "%Y-%m-%d %H") except ValueError as e: raise ValidationError("datetime format is invalid. Use the format: " - "YYYY-MM-DD HH:MM:SS, for example 2017-05-30 13:00:00") + "YYYY-MM-DD HH, for example 2017-05-30 13") return True validator = Draft4Validator(schema, format_checker=format_checker) diff --git a/app/utils.py b/app/utils.py index 34bb1ec3a..d93ea0613 100644 --- a/app/utils.py +++ b/app/utils.py @@ -51,10 +51,14 @@ def get_midnight_for_day_before(date): return get_london_midnight_in_utc(day_before) -def get_utc_time_in_bst(utc_dt): +def convert_utc_time_in_bst(utc_dt): return pytz.utc.localize(utc_dt).astimezone(local_timezone).replace(tzinfo=None) +def convert_bst_to_utc(date): + return local_timezone.localize(date).astimezone(pytz.UTC).replace(tzinfo=None) + + def get_london_month_from_utc_column(column): """ Where queries need to count notifications by month it needs to be diff --git a/app/v2/notifications/notification_schemas.py b/app/v2/notifications/notification_schemas.py index d75375fcc..6aa4dcb77 100644 --- a/app/v2/notifications/notification_schemas.py +++ b/app/v2/notifications/notification_schemas.py @@ -1,10 +1,7 @@ -from datetime import datetime - -from app import DATETIME_FORMAT from app.models import NOTIFICATION_STATUS_TYPES, TEMPLATE_TYPES from app.schema_validation.definitions import (uuid, personalisation) -# this may belong in a templates module + template = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "template schema", @@ -204,8 +201,7 @@ def create_post_sms_response_from_notification(notification, body, from_number, "template": __create_template_from_notification(notification=notification, url_root=url_root, service_id=service_id), - "scheduled_for": datetime.strptime(scheduled_for, - "%Y-%m-%d %H:%M:%S").strftime(DATETIME_FORMAT) if scheduled_for else None + "scheduled_for": scheduled_for if scheduled_for else None } @@ -223,8 +219,7 @@ def create_post_email_response_from_notification(notification, content, subject, "template": __create_template_from_notification(notification=notification, url_root=url_root, service_id=service_id), - "scheduled_for": datetime.strptime(scheduled_for, - "%Y-%m-%d %H:%M:%S").strftime(DATETIME_FORMAT) if scheduled_for else None + "scheduled_for": scheduled_for if scheduled_for else None } diff --git a/tests/app/celery/test_scheduled_tasks.py b/tests/app/celery/test_scheduled_tasks.py index f55076d61..a2564e1cd 100644 --- a/tests/app/celery/test_scheduled_tasks.py +++ b/tests/app/celery/test_scheduled_tasks.py @@ -417,13 +417,13 @@ def test_should_send_all_scheduled_notifications_to_deliver_queue(notify_db, sample_template, mocker): mocked = mocker.patch('app.celery.provider_tasks.deliver_sms') message_to_deliver = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, - template=sample_template, scheduled_for="2017-05-01 13:50:00") + template=sample_template, scheduled_for="2017-05-01 13") sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, - template=sample_template, scheduled_for="2017-05-01 10:50:00", status='delivered') + template=sample_template, scheduled_for="2017-05-01 10", status='delivered') sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, template=sample_template) sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, - template=sample_template, scheduled_for="2017-05-01 14:30:00") + template=sample_template, scheduled_for="2017-05-01 14") send_scheduled_notifications() diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 7e859324f..26e3e4dc0 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -494,7 +494,7 @@ def sample_notification( scheduled_notification = ScheduledNotification(id=uuid.uuid4(), notification_id=notification.id, scheduled_for=datetime.strptime(scheduled_for, - "%Y-%m-%d %H:%M:%S")) + "%Y-%m-%d %H")) db.session.add(scheduled_notification) db.session.commit() diff --git a/tests/app/dao/test_notification_dao.py b/tests/app/dao/test_notification_dao.py index 832c80949..34e27c15b 100644 --- a/tests/app/dao/test_notification_dao.py +++ b/tests/app/dao/test_notification_dao.py @@ -716,10 +716,10 @@ def test_save_notification_with_no_job(sample_template, mmg_provider): assert notification_from_db.status == 'created' -def test_get_notification_by_id(notify_db, notify_db_session, client, sample_template): +def test_get_notification_by_id(notify_db, notify_db_session, sample_template): notification = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, template=sample_template, - scheduled_for='2017-05-05 14:00:00', + scheduled_for='2017-05-05 14', status='created') notification_from_db = get_notification_with_personalisation( sample_template.service.id, @@ -727,7 +727,7 @@ def test_get_notification_by_id(notify_db, notify_db_session, client, sample_tem key_type=None ) assert notification == notification_from_db - assert notification_from_db.scheduled_notification.scheduled_for == datetime(2017, 5, 5, 14, 0) + assert notification_from_db.scheduled_notification.scheduled_for == datetime(2017, 5, 5, 14) def test_get_notifications_by_reference(notify_db, notify_db_session, sample_service): @@ -1702,8 +1702,9 @@ def test_dao_get_notifications_by_to_field_search_ignores_spaces(sample_template def test_dao_created_scheduled_notification(sample_notification): + scheduled_notification = ScheduledNotification(notification_id=sample_notification.id, - scheduled_for="2017-01-05 14:00:00") + scheduled_for=datetime.strptime("2017-01-05 14", "%Y-%m-%d %H")) dao_created_scheduled_notification(scheduled_notification) saved_notification = ScheduledNotification.query.all() assert len(saved_notification) == 1 @@ -1713,10 +1714,10 @@ def test_dao_created_scheduled_notification(sample_notification): def test_dao_get_scheduled_notifications(notify_db, notify_db_session, sample_template): notification_1 = sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, - template=sample_template, scheduled_for='2017-05-05 14:00:00', + template=sample_template, scheduled_for='2017-05-05 14', status='created') sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, - template=sample_template, scheduled_for='2017-05-04 14:00:00', status='delivered') + template=sample_template, scheduled_for='2017-05-04 14', status='delivered') sample_notification(notify_db=notify_db, notify_db_session=notify_db_session, template=sample_template, status='created') scheduled_notifications = dao_get_scheduled_notifications() diff --git a/tests/app/notifications/test_process_notification.py b/tests/app/notifications/test_process_notification.py index f697047e7..dc0e64b57 100644 --- a/tests/app/notifications/test_process_notification.py +++ b/tests/app/notifications/test_process_notification.py @@ -7,13 +7,14 @@ from sqlalchemy.exc import SQLAlchemyError from freezegun import freeze_time from collections import namedtuple -from app.models import Template, Notification, NotificationHistory +from app.models import Template, Notification, NotificationHistory, ScheduledNotification from app.notifications import SendNotificationToQueueError from app.notifications.process_notifications import ( create_content_for_notification, persist_notification, send_notification_to_queue, - simulated_recipient + simulated_recipient, + persist_scheduled_notification ) from notifications_utils.recipients import validate_and_format_phone_number, validate_and_format_email_address from app.utils import cache_key_for_service_template_counter @@ -358,3 +359,11 @@ def test_persist_notification_with_international_info_does_not_store_for_email( assert persisted_notification.international is False assert persisted_notification.phone_prefix is None assert persisted_notification.rate_multiplier is None + + +def test_persist_scheduled_notification(sample_notification): + persist_scheduled_notification(sample_notification.id, '2017-05-12 14') + scheduled_notification = ScheduledNotification.query.all() + assert len(scheduled_notification) == 1 + assert scheduled_notification[0].notification_id == sample_notification.id + assert scheduled_notification[0].scheduled_for == datetime.datetime(2017, 5, 12, 13, 0) diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index 5e45bdf7f..7bb8360af 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -4,8 +4,8 @@ import pytest from app.utils import ( get_london_midnight_in_utc, get_midnight_for_day_before, - get_utc_time_in_bst -) + convert_utc_time_in_bst, + convert_bst_to_utc) @pytest.mark.parametrize('date, expected_date', [ @@ -32,7 +32,15 @@ def test_get_midnight_for_day_before_returns_expected_date(date, expected_date): (datetime(2017, 3, 28, 10, 0), datetime(2017, 3, 28, 11, 0)), (datetime(2017, 10, 28, 1, 0), datetime(2017, 10, 28, 2, 0)), (datetime(2017, 10, 29, 1, 0), datetime(2017, 10, 29, 1, 0)), + (datetime(2017, 5, 12, 14), datetime(2017, 5, 12, 15, 0)) ]) def test_get_utc_in_bst_returns_expected_date(date, expected_date): - ret_date = get_utc_time_in_bst(date) + ret_date = convert_utc_time_in_bst(date) assert ret_date == expected_date + + +def test_convert_bst_to_utc(): + bst = "2017-05-12 13" + bst_datetime = datetime.strptime(bst, "%Y-%m-%d %H") + utc = convert_bst_to_utc(bst_datetime) + assert utc == datetime(2017, 5, 12, 12, 0) diff --git a/tests/app/v2/notifications/test_get_notifications.py b/tests/app/v2/notifications/test_get_notifications.py index 2b8fd0832..30a35b5aa 100644 --- a/tests/app/v2/notifications/test_get_notifications.py +++ b/tests/app/v2/notifications/test_get_notifications.py @@ -20,12 +20,12 @@ def test_get_notification_by_id_returns_200( ): sample_notification = create_sample_notification( notify_db, notify_db_session, billable_units=billable_units, sent_by=provider, - scheduled_for="2017-05-12 14:00:00" + scheduled_for="2017-05-12 14" ) another = create_sample_notification( notify_db, notify_db_session, billable_units=billable_units, sent_by=provider, - scheduled_for="2017-06-12 14:00:00" + scheduled_for="2017-06-12 14" ) auth_header = create_authorization_header(service_id=sample_notification.service_id) response = client.get( @@ -63,7 +63,7 @@ def test_get_notification_by_id_returns_200( "subject": None, 'sent_at': sample_notification.sent_at, 'completed_at': sample_notification.completed_at(), - 'scheduled_for': '2017-05-12T14:00:00.000000Z' + 'scheduled_for': '2017-05-12 14' } assert json_response == expected_response @@ -139,7 +139,7 @@ def test_get_notification_by_reference_returns_200(client, notify_db, notify_db_ def test_get_notifications_returns_scheduled_for(client, notify_db, notify_db_session): sample_notification_with_reference = create_sample_notification( - notify_db, notify_db_session, client_reference='some-client-reference', scheduled_for='2017-05-23 16:00:00') + notify_db, notify_db_session, client_reference='some-client-reference', scheduled_for='2017-05-23 16') auth_header = create_authorization_header(service_id=sample_notification_with_reference.service_id) response = client.get( @@ -153,7 +153,7 @@ def test_get_notifications_returns_scheduled_for(client, notify_db, notify_db_se assert len(json_response['notifications']) == 1 assert json_response['notifications'][0]['id'] == str(sample_notification_with_reference.id) - assert json_response['notifications'][0]['scheduled_for'] == "2017-05-23T16:00:00.000000Z" + assert json_response['notifications'][0]['scheduled_for'] == "2017-05-23 16" def test_get_notification_by_reference_nonexistent_reference_returns_no_notifications(client, sample_service): diff --git a/tests/app/v2/notifications/test_notification_schemas.py b/tests/app/v2/notifications/test_notification_schemas.py index 44794f9de..5e0cb354f 100644 --- a/tests/app/v2/notifications/test_notification_schemas.py +++ b/tests/app/v2/notifications/test_notification_schemas.py @@ -360,7 +360,7 @@ def test_get_notifications_response_with_email_and_phone_number(): def test_post_schema_valid_scheduled_for(schema): j = {"template_id": str(uuid.uuid4()), "email_address": "joe@gmail.com", - "scheduled_for": "2017-05-12 13:00:00"} + "scheduled_for": "2017-05-12 13"} if schema == post_email_request_schema: j.update({"email_address": "joe@gmail.com"}) else: @@ -369,7 +369,7 @@ def test_post_schema_valid_scheduled_for(schema): @pytest.mark.parametrize("invalid_datetime", - ["2017-05-12 13:00", "13:00:00 2017-01-01"]) + ["2017-05-12 13:00:00", "13:00:00 2017-01-01"]) @pytest.mark.parametrize("schema", [post_email_request_schema, post_sms_request_schema]) def test_post_email_schema_invalid_scheduled_for(invalid_datetime, schema): @@ -385,4 +385,4 @@ def test_post_email_schema_invalid_scheduled_for(invalid_datetime, schema): assert error['status_code'] == 400 assert error['errors'] == [{'error': 'ValidationError', 'message': "scheduled_for datetime format is invalid. Use the format: " - "YYYY-MM-DD HH:MM:SS, for example 2017-05-30 13:00:00"}] + "YYYY-MM-DD HH, for example 2017-05-30 13"}] diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index 4a6abbb63..e0903ef6b 100644 --- a/tests/app/v2/notifications/test_post_notifications.py +++ b/tests/app/v2/notifications/test_post_notifications.py @@ -1,8 +1,8 @@ import uuid + import pytest from flask import json -from app import DATETIME_FORMAT from app.models import Notification, ScheduledNotification from app.v2.errors import RateLimitError from tests import create_authorization_header @@ -358,7 +358,7 @@ def test_post_notification_with_scheduled_for(client, sample_template, sample_em data = { key_send_to: send_to, 'template_id': str(sample_email_template.id) if notification_type == 'email' else str(sample_template.id), - 'scheduled_for': '2017-05-14 15:00:00' + 'scheduled_for': '2017-05-14 14' } auth_header = create_authorization_header(service_id=sample_template.service_id) @@ -370,4 +370,4 @@ def test_post_notification_with_scheduled_for(client, sample_template, sample_em scheduled_notification = ScheduledNotification.query.all() assert len(scheduled_notification) == 1 assert resp_json["id"] == str(scheduled_notification[0].notification_id) - assert resp_json["scheduled_for"] == scheduled_notification[0].scheduled_for.strftime(DATETIME_FORMAT) + assert resp_json["scheduled_for"] == '2017-05-14 14'