From 3c78ed613209c80f31d57a0701139bc778a4730d Mon Sep 17 00:00:00 2001 From: joybytes <171517790+joybytes@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:47:40 +0000 Subject: [PATCH 1/2] feat: Remove ability to receive text messages We are automating the following query ``` select * from service_sms_senders where service_id= 'service_id'; select * from inbound_numbers where service_id = 'service_id'; select * from service_permissions where service_id= 'service_id'; delete from service_permissions where service_id = 'service_id' and permission = 'inbound_sms'; delete from service_sms_senders where service_id = 'service_id' and inbound_number_id is not null; -- run one of the two following queries -- if we wish to release the number (if it has never been used) update inbound_numbers set service_id = null, updated_at = now() where service_id = 'service_id' and active; -- if we wish to archive the number instead (if it has been used before so we dont want new services receiving the prev service's messages) update inbound_numbers set service_id = null, updated_at = now(), active = false where service_id = 'service_id' and active; ``` - Remove inbound_sms permission from service - Remove service_sms_sender with inbound_number_id - Disassociate inbound_number with service - If the number is being release, leave 'active' status as True, if being archived, update 'active' to False --- app/dao/inbound_numbers_dao.py | 32 ++++++- app/dao/service_permissions_dao.py | 10 ++- app/dao/service_sms_sender_dao.py | 14 +++ app/inbound_sms/inbound_sms_schemas.py | 14 +++ app/inbound_sms/rest.py | 26 +++++- tests/app/conftest.py | 17 ++++ tests/app/dao/test_inbound_numbers_dao.py | 91 +++++++++++++++++++- tests/app/dao/test_service_sms_sender_dao.py | 28 ++++++ tests/app/inbound_sms/test_rest.py | 91 ++++++++++++++++++++ 9 files changed, 315 insertions(+), 8 deletions(-) diff --git a/app/dao/inbound_numbers_dao.py b/app/dao/inbound_numbers_dao.py index 1e02cc87f1..cd5c0b2b3a 100644 --- a/app/dao/inbound_numbers_dao.py +++ b/app/dao/inbound_numbers_dao.py @@ -1,5 +1,8 @@ +from uuid import UUID + from app import db -from app.dao.dao_utils import autocommit +from app.constants import INBOUND_SMS_TYPE +from app.dao.dao_utils import autocommit, transaction from app.models import InboundNumber @@ -41,3 +44,30 @@ def dao_allocate_number_for_service(service_id, inbound_number_id): if not updated: raise Exception(f"Inbound number: {inbound_number_id} is not available") return InboundNumber.query.get(inbound_number_id) + + +def archive_or_release_inbound_number_for_service(service_id: UUID, archive: bool, commit=True): + update_data = { + "service_id": None, + } + + if archive: + update_data["active"] = False + + result = InboundNumber.query.filter_by(service_id=service_id, active=True).update( + update_data, synchronize_session="fetch" + ) + + if commit: + db.session.commit() + return result + + +def dao_remove_inbound_sms_for_service(service_id, archive): + from app.dao.service_permissions_dao import dao_remove_service_permission + from app.dao.service_sms_sender_dao import dao_remove_inbound_sms_senders + + with transaction(): + dao_remove_service_permission(service_id, INBOUND_SMS_TYPE, commit=False) + dao_remove_inbound_sms_senders(service_id, commit=False) + archive_or_release_inbound_number_for_service(service_id, archive, commit=False) diff --git a/app/dao/service_permissions_dao.py b/app/dao/service_permissions_dao.py index 486b01a596..fcca2f8151 100644 --- a/app/dao/service_permissions_dao.py +++ b/app/dao/service_permissions_dao.py @@ -13,9 +13,11 @@ def dao_add_service_permission(service_id, permission): db.session.add(service_permission) -def dao_remove_service_permission(service_id, permission): - deleted = ServicePermission.query.filter( +def dao_remove_service_permission(service_id, permission, commit=True): + result = ServicePermission.query.filter( ServicePermission.service_id == service_id, ServicePermission.permission == permission ).delete() - db.session.commit() - return deleted + + if commit: + db.session.commit() + return result diff --git a/app/dao/service_sms_sender_dao.py b/app/dao/service_sms_sender_dao.py index be347e6b93..695117cb24 100644 --- a/app/dao/service_sms_sender_dao.py +++ b/app/dao/service_sms_sender_dao.py @@ -1,3 +1,5 @@ +from uuid import UUID + from sqlalchemy import desc from app import db @@ -106,3 +108,15 @@ def _raise_when_no_default(old_default): # check that the update is not updating the only default to false if not old_default: raise Exception("You must have at least one SMS sender as the default.", 400) + + +def dao_remove_inbound_sms_senders(service_id: UUID, commit=True): + result = ( + ServiceSmsSender.query.filter_by(service_id=service_id) + .filter(ServiceSmsSender.inbound_number_id.isnot(None)) + .delete(synchronize_session="fetch") + ) + + if commit: + db.session.commit() + return result diff --git a/app/inbound_sms/inbound_sms_schemas.py b/app/inbound_sms/inbound_sms_schemas.py index d910efdeb2..2e405265c8 100644 --- a/app/inbound_sms/inbound_sms_schemas.py +++ b/app/inbound_sms/inbound_sms_schemas.py @@ -6,3 +6,17 @@ "phone_number": {"type": "string"}, }, } + +remove_inbound_sms_for_service_schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Schema for validating the input to remove inbound SMS capability", + "type": "object", + "properties": { + "archive": { + "type": "boolean", + "description": "Indicates whether to archive the inbound number or release it.", + }, + }, + "required": ["archive"], + "additionalProperties": False, +} diff --git a/app/inbound_sms/rest.py b/app/inbound_sms/rest.py index aa09dd9edb..2d439f5dc0 100644 --- a/app/inbound_sms/rest.py +++ b/app/inbound_sms/rest.py @@ -1,6 +1,9 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint, current_app, jsonify, request from notifications_utils.recipient_validation.phone_number import try_validate_and_format_phone_number +from app.dao.inbound_numbers_dao import ( + dao_remove_inbound_sms_for_service, +) from app.dao.inbound_sms_dao import ( dao_count_inbound_sms_for_service, dao_get_inbound_sms_by_id, @@ -9,7 +12,10 @@ ) from app.dao.service_data_retention_dao import fetch_service_data_retention_by_notification_type from app.errors import register_errors -from app.inbound_sms.inbound_sms_schemas import get_inbound_sms_for_service_schema +from app.inbound_sms.inbound_sms_schemas import ( + get_inbound_sms_for_service_schema, + remove_inbound_sms_for_service_schema, +) from app.schema_validation import validate inbound_sms = Blueprint("inbound_sms", __name__, url_prefix="/service//inbound-sms") @@ -60,3 +66,19 @@ def get_inbound_by_id(service_id, inbound_sms_id): message = dao_get_inbound_sms_by_id(service_id, inbound_sms_id) return jsonify(message.serialize()), 200 + + +@inbound_sms.route("/remove", methods=["POST"]) +def remove_inbound_sms_for_service(service_id): + data = request.get_json() + validate(data, remove_inbound_sms_for_service_schema) + + archive = data["archive"] + + try: + dao_remove_inbound_sms_for_service(service_id, archive) + return jsonify({}), 200 + + except Exception as e: + current_app.logger.error("error removing inbound SMS for service %s: %s", service_id, e) + return jsonify({"message": str(e)}), 500 diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 9bf21133df..5adde4e0de 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -39,6 +39,7 @@ from app.history_meta import create_history from app.models import ( ApiKey, + InboundSmsHistory, InvitedUser, Job, Notification, @@ -1043,6 +1044,22 @@ def sample_inbound_numbers(sample_service): return inbound_numbers +@pytest.fixture +def sample_inbound_sms_history(notify_db_session, sample_service): + inbound_sms = InboundSmsHistory( + id=uuid.uuid4(), + created_at=datetime.utcnow(), + service_id=sample_service.id, + notify_number="3", + provider_date=datetime.utcnow(), + provider_reference="reference-id", + provider="firetext", + ) + notify_db_session.add(inbound_sms) + notify_db_session.commit() + return inbound_sms + + @pytest.fixture def sample_organisation(notify_db_session): org = Organisation(name="sample organisation") diff --git a/tests/app/dao/test_inbound_numbers_dao.py b/tests/app/dao/test_inbound_numbers_dao.py index e225899eb8..33af25f6ae 100644 --- a/tests/app/dao/test_inbound_numbers_dao.py +++ b/tests/app/dao/test_inbound_numbers_dao.py @@ -1,14 +1,19 @@ import pytest -from sqlalchemy.exc import IntegrityError +from sqlalchemy.exc import IntegrityError, SQLAlchemyError +from app.constants import INBOUND_SMS_TYPE from app.dao.inbound_numbers_dao import ( + archive_or_release_inbound_number_for_service, dao_allocate_number_for_service, dao_get_available_inbound_numbers, + dao_get_inbound_number, dao_get_inbound_number_for_service, dao_get_inbound_numbers, + dao_remove_inbound_sms_for_service, dao_set_inbound_number_active_flag, dao_set_inbound_number_to_service, ) +from app.dao.service_sms_sender_dao import dao_add_sms_sender_for_service, dao_get_sms_senders_by_service_id from app.models import InboundNumber from tests.app.db import create_inbound_number, create_service @@ -104,3 +109,87 @@ def test_dao_allocate_number_for_service_raises_if_invalid_inbound_number(notify with pytest.raises(Exception) as exc: dao_allocate_number_for_service(service_id=service.id, inbound_number_id=fake_uuid) assert "is not available" in str(exc.value) + + +def test_archive_or_release_inbound_number_for_service_archive(sample_service, sample_inbound_numbers): + inbound = next((inbound for inbound in sample_inbound_numbers if inbound.service_id == sample_service.id), None) + + archive_or_release_inbound_number_for_service(sample_service.id, True) + + updated_inbound = InboundNumber.query.filter_by(number=inbound.number).one_or_none() + + assert updated_inbound.service_id is None + assert updated_inbound.active is False + + +def test_archive_or_release_inbound_number_for_service_release(sample_service, sample_inbound_numbers): + inbound = next((inbound for inbound in sample_inbound_numbers if inbound.service_id == sample_service.id), None) + + archive_or_release_inbound_number_for_service(sample_service.id, False) + + updated_inbound = InboundNumber.query.filter_by(number=inbound.number).one_or_none() + + assert updated_inbound.service_id is None + assert updated_inbound.active is True + + +@pytest.mark.parametrize( + "archive, inbound_number, expected_active_status", + [ + (True, "7654321", False), + (False, "1234567", True), + ], +) +def test_dao_remove_inbound_sms_for_service_success( + admin_request, sample_service_full_permissions, archive, inbound_number, expected_active_status +): + service = sample_service_full_permissions + service_inbound = dao_get_inbound_number_for_service(service.id) + dao_add_sms_sender_for_service(service.id, inbound_number, is_default=True, inbound_number_id=service_inbound.id) + sms_senders = dao_get_sms_senders_by_service_id(service.id) + + assert (service.has_permission(INBOUND_SMS_TYPE)) is True + assert any(x.inbound_number_id is not None and x.sms_sender == inbound_number for x in sms_senders) is True + assert service_inbound.active is True + assert service_inbound.service_id is not None + + dao_remove_inbound_sms_for_service(service.id, archive=archive) + + sms_senders = dao_get_sms_senders_by_service_id(service.id) + updated_service_inbound = dao_get_inbound_number_for_service(service.id) + inbound = dao_get_inbound_number(service_inbound.id) + + assert (service.has_permission(INBOUND_SMS_TYPE)) is False + assert any(x.inbound_number_id is not None and x.sms_sender == inbound_number for x in sms_senders) is False + assert updated_service_inbound is None + assert inbound.service_id is None + assert inbound.active is expected_active_status + + +def test_dao_remove_inbound_sms_for_service_failure(sample_service_full_permissions, mocker, notify_db_session): + inbound_number = "76543953521" + service = sample_service_full_permissions + service_inbound = dao_get_inbound_number_for_service(service.id) + dao_add_sms_sender_for_service(service.id, inbound_number, is_default=True, inbound_number_id=service_inbound.id) + + sms_senders = dao_get_sms_senders_by_service_id(service.id) + assert service.has_permission(INBOUND_SMS_TYPE) is True + assert any(x.inbound_number_id is not None and x.sms_sender == inbound_number for x in sms_senders) is True + assert service_inbound.active is True + assert service_inbound.service_id is not None + + with mocker.patch( + "app.dao.inbound_numbers_dao.archive_or_release_inbound_number_for_service", side_effect=SQLAlchemyError + ): + with pytest.raises(SQLAlchemyError): + dao_remove_inbound_sms_for_service(service.id, archive=True) + + sms_senders_after = dao_get_sms_senders_by_service_id(service.id) + updated_service_inbound = dao_get_inbound_number_for_service(service.id) + inbound = dao_get_inbound_number(service_inbound.id) + + assert service.has_permission(INBOUND_SMS_TYPE) is True + assert any(x.inbound_number_id is not None and x.sms_sender == inbound_number for x in sms_senders_after) is True + assert updated_service_inbound is not None + assert inbound.service_id == service.id + assert inbound.active is True diff --git a/tests/app/dao/test_service_sms_sender_dao.py b/tests/app/dao/test_service_sms_sender_dao.py index b4049d26a5..5f91d5511c 100644 --- a/tests/app/dao/test_service_sms_sender_dao.py +++ b/tests/app/dao/test_service_sms_sender_dao.py @@ -8,6 +8,7 @@ dao_add_sms_sender_for_service, dao_get_service_sms_senders_by_id, dao_get_sms_senders_by_service_id, + dao_remove_inbound_sms_senders, dao_update_service_sms_sender, update_existing_sms_sender_with_inbound_number, ) @@ -212,3 +213,30 @@ def test_archive_sms_sender_raises_an_error_if_attempting_to_archive_an_inbound_ assert "You cannot delete an inbound number" in str(e.value) assert not inbound_number.archived + + +def test_dao_remove_inbound_sms_senders(notify_db_session): + inbound_number = "7654321" + service = create_service_with_inbound_number(inbound_number, service_name="inbound number service") + sms_senders = dao_get_sms_senders_by_service_id(service.id) + assert len(sms_senders) == 1 + assert any(x.sms_sender == inbound_number for x in sms_senders) is True + assert sms_senders[0].inbound_number_id is not None + + # adding null inbound_number_id for sms_sender + dao_add_sms_sender_for_service(service.id, "second", is_default=True) + + sms_senders = dao_get_sms_senders_by_service_id(service.id) + assert len(sms_senders) == 2 + assert any(x.inbound_number_id is None for x in sms_senders) is True + + # removing only rows that are not null + dao_remove_inbound_sms_senders(service.id) + + sms_senders = dao_get_sms_senders_by_service_id(service.id) + assert len(sms_senders) == 1 + + # check value with null inbound_number_id is still present + assert any(x.inbound_number_id is None for x in sms_senders) is True + # check value that had inbound_number_id is gone + assert any(x.inbound_number_id is not None for x in sms_senders) is False diff --git a/tests/app/inbound_sms/test_rest.py b/tests/app/inbound_sms/test_rest.py index ce1beb54ee..57aa67ec6f 100644 --- a/tests/app/inbound_sms/test_rest.py +++ b/tests/app/inbound_sms/test_rest.py @@ -1,8 +1,10 @@ +import uuid from datetime import datetime, timedelta import pytest from freezegun import freeze_time +from app.constants import INBOUND_SMS_TYPE from tests.app.db import ( create_inbound_sms, create_service, @@ -223,3 +225,92 @@ def test_get_inbound_sms_for_service_respects_data_retention(admin_request, samp "2017-04-06T12:00:00.000000Z", "2017-04-05T12:00:00.000000Z", ] + + +@pytest.mark.parametrize( + "payload, expected_error", + [ + # Missing required field + ({}, "archive is a required property"), + # Invalid field type + ({"archive": "not-a-boolean"}, "archive not-a-boolean is not of type boolean"), + # Additional fields not allowed + ( + {"archive": True, "extra_field": "not allowed"}, + "Additional properties are not allowed (extra_field was unexpected)", + ), + ], +) +def test_remove_inbound_sms_for_service(admin_request, sample_service, payload, expected_error): + response = admin_request.post( + "inbound_sms.remove_inbound_sms_for_service", service_id=sample_service.id, _data=payload, _expected_status=400 + ) + + assert response["errors"][0]["message"] == expected_error + + +@pytest.mark.parametrize( + "payload, archive", + [ + ({"archive": True}, True), + ({"archive": False}, False), + ], +) +def test_remove_inbound_sms_for_service_success( + admin_request, sample_service_full_permissions, payload, archive, mocker +): + service = sample_service_full_permissions + mock_remove_inbound = mocker.patch("app.inbound_sms.rest.dao_remove_inbound_sms_for_service") + + with mock_remove_inbound: + admin_request.post( + "inbound_sms.remove_inbound_sms_for_service", + service_id=service.id, + _data=payload, + _expected_status=200, + ) + + mock_remove_inbound.assert_called_once_with(service.id, archive) + + +def test_remove_inbound_sms_for_service_dao_error(admin_request, sample_service_full_permissions, mocker, caplog): + service = sample_service_full_permissions + payload = {"archive": True} + mock_remove_inbound = mocker.patch("app.inbound_sms.rest.dao_remove_inbound_sms_for_service") + + with mock_remove_inbound("app.routes.inbound_sms.dao_remove_inbound_sms_for_service"): + mock_remove_inbound.side_effect = Exception("some error") + response = admin_request.post( + "inbound_sms.remove_inbound_sms_for_service", + service_id=service.id, + _data=payload, + _expected_status=500, + ) + + mock_remove_inbound.assert_called_with(service.id, True) + + assert f"error removing inbound SMS for service {service.id}: some error" in caplog.messages + assert response["message"] == "some error" + + +def test_remove_inbound_sms_for_service_success_without_sms_type_permission(admin_request, sample_service): + service = create_service_with_inbound_number( + inbound_number="76543953521", service_name=f"service name {uuid.uuid4()}" + ) + assert service.has_permission(INBOUND_SMS_TYPE) is False + + admin_request.post( + "inbound_sms.remove_inbound_sms_for_service", + service_id=service.id, + _data={"archive": True}, + _expected_status=200, + ) + + +def test_remove_inbound_sms_for_service_success_without_inbound_number(admin_request, sample_service): + admin_request.post( + "inbound_sms.remove_inbound_sms_for_service", + service_id=sample_service.id, + _data={"archive": True}, + _expected_status=200, + ) From 76e4666b187b72bd2921b3c00d7ab8f0dc344741 Mon Sep 17 00:00:00 2001 From: joybytes <171517790+joybytes@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:06:14 +0000 Subject: [PATCH 2/2] feat: Get most recent usage date for inbound number associated to service - Look through the following tables to get most recent date for inbound sms use: notification, inbound sms and inbound sms history - Endpoint created to showcase information on the front-end in regards to number activity to prevent us from going into the DB to check the latest use for the number when deciding to archive or release this number --- app/dao/inbound_sms_dao.py | 48 ++++++++++++++++++++++ app/inbound_sms/rest.py | 14 +++++++ tests/app/dao/test_inbound_sms_dao.py | 57 +++++++++++++++++++++++++++ tests/app/inbound_sms/test_rest.py | 36 +++++++++++++++++ 4 files changed, 155 insertions(+) diff --git a/app/dao/inbound_sms_dao.py b/app/dao/inbound_sms_dao.py index 8a7a1387c0..f851113d83 100644 --- a/app/dao/inbound_sms_dao.py +++ b/app/dao/inbound_sms_dao.py @@ -1,3 +1,6 @@ +from datetime import datetime +from uuid import UUID + from flask import current_app from sqlalchemy import and_, desc from sqlalchemy.dialects.postgresql import insert @@ -7,8 +10,10 @@ from app.constants import SMS_TYPE from app.dao.dao_utils import autocommit from app.models import ( + InboundNumber, InboundSms, InboundSmsHistory, + Notification, Service, ServiceDataRetention, ) @@ -179,3 +184,46 @@ def dao_get_paginated_most_recent_inbound_sms_by_user_number_for_service(service ) return q.paginate(page=page, per_page=current_app.config["PAGE_SIZE"]) + + +def dao_get_most_recent_inbound_usage_date(service_id: UUID, inbound: InboundNumber) -> datetime | None: + last_notification = ( + Notification.query.filter( + Notification.reply_to_text == inbound.number, + Notification.service_id == service_id, + ) + .order_by(Notification.created_at.desc()) + .first() + ) + + last_inbound_sms = ( + InboundSms.query.filter( + InboundSms.notify_number == inbound.number, + InboundSms.service_id == service_id, + ) + .order_by(InboundSms.created_at.desc()) + .first() + ) + + last_inbound_sms_history = ( + InboundSmsHistory.query.filter( + InboundSmsHistory.notify_number == inbound.number, + InboundSmsHistory.service_id == service_id, + ) + .order_by(InboundSmsHistory.created_at.desc()) + .first() + ) + + most_recent_usage = max( + filter( + None, + [ + last_notification.created_at if last_notification else None, + last_inbound_sms.created_at if last_inbound_sms else None, + last_inbound_sms_history.created_at if last_inbound_sms_history else None, + ], + ), + default=None, + ) + + return most_recent_usage diff --git a/app/inbound_sms/rest.py b/app/inbound_sms/rest.py index 2d439f5dc0..d8dffaddf8 100644 --- a/app/inbound_sms/rest.py +++ b/app/inbound_sms/rest.py @@ -2,12 +2,14 @@ from notifications_utils.recipient_validation.phone_number import try_validate_and_format_phone_number from app.dao.inbound_numbers_dao import ( + dao_get_inbound_number_for_service, dao_remove_inbound_sms_for_service, ) from app.dao.inbound_sms_dao import ( dao_count_inbound_sms_for_service, dao_get_inbound_sms_by_id, dao_get_inbound_sms_for_service, + dao_get_most_recent_inbound_usage_date, dao_get_paginated_most_recent_inbound_sms_by_user_number_for_service, ) from app.dao.service_data_retention_dao import fetch_service_data_retention_by_notification_type @@ -82,3 +84,15 @@ def remove_inbound_sms_for_service(service_id): except Exception as e: current_app.logger.error("error removing inbound SMS for service %s: %s", service_id, e) return jsonify({"message": str(e)}), 500 + + +@inbound_sms.route("/most-recent-usage", methods=["GET"]) +def get_most_recent_inbound_usage_date(service_id): + inbound = dao_get_inbound_number_for_service(service_id) + + if not inbound: + return jsonify(message="inbound not found"), 404 + + most_recent_date = dao_get_most_recent_inbound_usage_date(service_id, inbound) + + return jsonify(most_recent_date=most_recent_date), 200 diff --git a/tests/app/dao/test_inbound_sms_dao.py b/tests/app/dao/test_inbound_sms_dao.py index 35f1ecc565..80cc66367a 100644 --- a/tests/app/dao/test_inbound_sms_dao.py +++ b/tests/app/dao/test_inbound_sms_dao.py @@ -4,10 +4,13 @@ from freezegun import freeze_time from app import db +from app.constants import KEY_TYPE_NORMAL +from app.dao.inbound_numbers_dao import dao_get_inbound_number_for_service from app.dao.inbound_sms_dao import ( dao_count_inbound_sms_for_service, dao_get_inbound_sms_by_id, dao_get_inbound_sms_for_service, + dao_get_most_recent_inbound_usage_date, dao_get_paginated_inbound_sms_for_service_for_public_api, dao_get_paginated_most_recent_inbound_sms_by_user_number_for_service, delete_inbound_sms_older_than_retention, @@ -15,8 +18,11 @@ from app.models import InboundSmsHistory from tests.app.db import ( create_inbound_sms, + create_job, + create_notification, create_service, create_service_data_retention, + create_template, ) from tests.conftest import set_config @@ -353,3 +359,54 @@ def test_most_recent_inbound_sms_only_returns_values_within_7_days(sample_servic assert len(res.items) == 1 assert res.items[0].content == "new" + + +def test_dao_get_most_recent_inbound_usage_date_notifications_table(sample_service, sample_inbound_numbers): + template = create_template(service=sample_service) + job = create_job(template=template) + inbound = dao_get_inbound_number_for_service(sample_service.id) + + notification_time = datetime.utcnow() + + create_notification( + template=template, + job=job, + job_row_number=None, + to_field=None, + status="created", + reference=None, + created_at=notification_time, + sent_at=None, + billable_units=1, + personalisation=None, + api_key=None, + key_type=KEY_TYPE_NORMAL, + reply_to_text=inbound.number, + ) + + most_recent_date = dao_get_most_recent_inbound_usage_date(sample_service.id, inbound) + assert most_recent_date == notification_time + + +def test_dao_get_most_recent_inbound_usage_date_inbound_sms_table(sample_service, sample_inbound_numbers): + sms_time = datetime.utcnow() + create_inbound_sms(sample_service, created_at=sms_time) + inbound = dao_get_inbound_number_for_service(sample_service.id) + + most_recent_date = dao_get_most_recent_inbound_usage_date(sample_service.id, inbound) + assert most_recent_date == sms_time + + +def test_dao_get_most_recent_inbound_usage_date_inbound_sms_history_table( + sample_service, sample_inbound_numbers, sample_inbound_sms_history +): + inbound = next((x for x in sample_inbound_numbers if x.service_id == sample_service.id), None) + most_recent_date = dao_get_most_recent_inbound_usage_date(sample_service.id, inbound) + + assert most_recent_date is not None + assert most_recent_date.date() == sample_inbound_sms_history.created_at.date() + + +def test_dao_get_most_recent_inbound_usage_date_no_recent_notifications(sample_service, sample_inbound_numbers): + inbound = next((x for x in sample_inbound_numbers if x.service_id == sample_service.id), None) + assert dao_get_most_recent_inbound_usage_date(sample_service.id, inbound) is None diff --git a/tests/app/inbound_sms/test_rest.py b/tests/app/inbound_sms/test_rest.py index 57aa67ec6f..6bddd5bfe2 100644 --- a/tests/app/inbound_sms/test_rest.py +++ b/tests/app/inbound_sms/test_rest.py @@ -2,6 +2,7 @@ from datetime import datetime, timedelta import pytest +from dateutil.parser import parse from freezegun import freeze_time from app.constants import INBOUND_SMS_TYPE @@ -314,3 +315,38 @@ def test_remove_inbound_sms_for_service_success_without_inbound_number(admin_req _data={"archive": True}, _expected_status=200, ) + + +def test_get_most_recent_inbound_usage_date_success( + admin_request, sample_service, sample_inbound_numbers, sample_inbound_sms_history +): + response = admin_request.get( + "inbound_sms.get_most_recent_inbound_usage_date", service_id=sample_service.id, _expected_status=200 + ) + + assert response is not None + response_date = parse(response["most_recent_date"]) + + assert response_date.date() == datetime.utcnow().date() + + +def test_get_most_recent_inbound_usage_date_success_no_usage_found( + admin_request, sample_service, sample_inbound_numbers +): + response = admin_request.get( + "inbound_sms.get_most_recent_inbound_usage_date", service_id=sample_service.id, _expected_status=200 + ) + + assert response is not None + assert response["most_recent_date"] is None + + +def test_get_most_recent_inbound_usage_date_404_no_inbound( + admin_request, + sample_service, +): + response = admin_request.get( + "inbound_sms.get_most_recent_inbound_usage_date", service_id=sample_service.id, _expected_status=404 + ) + + assert response["message"] == "inbound not found"