From 03ba0ab93ddbe0525b987c53109862f36602eeb8 Mon Sep 17 00:00:00 2001 From: Niko Lindroos Date: Wed, 4 Jun 2025 18:10:31 +0300 Subject: [PATCH] feat: remove persons with cronjob when retention period has exceeded PT-1932. The `delete_retention_period_exceeding_contact_info` cronjob has deleted contact info from PalvelutarjotinEvent when the retention period (48h) has exceeded, but the persons have been deleted manually from the enrollee personal data admin view. Since it's been a manual task, it has always needed some one to take care of it. There is no reason why that job could not be done automatically with a cronjob. The `delete_retention_period_exceeding_contact_info` cronjob is now extended with that feature, but there are also new flags that can be used to run only 1 of the tasks (with the management command that the cronjob executes) and tehre is also a flag to make a dry drun, that does not acutally delete anything, but just reports how many instances it would delete if it would be ran. --- ...retention_period_exceeding_contact_info.py | 141 +++- occurrences/tests/test_commands.py | 705 ++++++++++++------ 2 files changed, 593 insertions(+), 253 deletions(-) diff --git a/occurrences/management/commands/delete_retention_period_exceeding_contact_info.py b/occurrences/management/commands/delete_retention_period_exceeding_contact_info.py index dabfd132..3e05af50 100644 --- a/occurrences/management/commands/delete_retention_period_exceeding_contact_info.py +++ b/occurrences/management/commands/delete_retention_period_exceeding_contact_info.py @@ -1,23 +1,144 @@ from django.core.management.base import BaseCommand from occurrences.models import PalvelutarjotinEvent +from organisations.models import EnrolleePersonalData class Command(BaseCommand): - help = "Delete retention period exceeding contact info from PalveluTarjotinEvents" + help = ( + "Delete retention period exceeding contact info from PalveluTarjotinEvents " + "and EnrolleePersonalData." + ) + + def add_arguments(self, parser): + """ + Adds arguments to the command to allow selective execution of deletion methods. + """ + parser.add_argument( + "--delete-event-contact-info", + action="store_true", + default=False, + help="Only delete contact info from PalveluTarjotinEvents. " + "(NOTE: This can be stacked with other flags)", + ) + parser.add_argument( + "--delete-enrollee-personal-data", + action="store_true", + default=False, + help="Only delete personal data from EnrolleePersonalData. " + "(NOTE: This can be stacked with other flags)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + default=False, + help="Perform a dry run without actually deleting any data. " + "Shows what would be deleted.", + ) def handle(self, *args, **kwargs): + """ + Handles the execution of the command based on the provided arguments. + If no flags are provided, both deletion methods are run. + If at least one flag is provided, only the methods corresponding to the + set flags will be executed. + The --dry-run flag prevents actual deletions. + """ + delete_event_contact_info = kwargs["delete_event_contact_info"] + delete_enrollee_personal_data = kwargs["delete_enrollee_personal_data"] + dry_run = kwargs["dry_run"] + + if dry_run: + self.stdout.write(self.style.WARNING("--- DRY RUN MODE ---")) + self.stdout.write(self.style.WARNING("No data will be deleted.")) + + # Determine which methods to run + if delete_event_contact_info or delete_enrollee_personal_data: + self.stdout.write( + self.style.NOTICE("Running selected deletion methods based on flags.") + ) + if delete_event_contact_info: + self.__delete_p_event_contact_info(dry_run=dry_run) + if delete_enrollee_personal_data: + self.__delete_enrollee_personal_data(dry_run=dry_run) + else: + # No flags provided, run both (default behavior) + self.stdout.write( + self.style.NOTICE( + "Running: Both deletion methods " + "(default behavior as no flags were provided)." + ) + ) + self.__delete_p_event_contact_info(dry_run=dry_run) + self.__delete_enrollee_personal_data(dry_run=dry_run) + + def __delete_p_event_contact_info(self, dry_run=False): + """ + Deletes contact information from PalvelutarjotinEvent objects + that have exceeded their retention period. + If dry_run is True, it only reports what would be deleted. + """ self.stdout.write( - "Deleting contact info from PalveluTarjotinEvents that are exceeding " + "Checking contact info from PalveluTarjotinEvents that are exceeding " "the retention period..." ) - events = PalvelutarjotinEvent.objects.contact_info_retention_period_exceeded() - num_of_deleted_contact_info = events.delete_contact_info() + try: + events = ( + PalvelutarjotinEvent.objects.contact_info_retention_period_exceeded() + ) + count_to_delete = events.count() - msg = ( - "No events are exceeding the retention period." - if num_of_deleted_contact_info == 0 - else f"Deleted contact info from {num_of_deleted_contact_info} event(s)." - ) - self.stdout.write(self.style.SUCCESS(msg)) + if dry_run: + msg_prefix = "Would delete" + deleted_contact_info_count = 0 # No actual deletion in dry run + else: + msg_prefix = "Deleted" + deleted_contact_info_count = events.delete_contact_info() + + if count_to_delete == 0: + msg = "No events are exceeding the retention period." + else: + if dry_run: + msg = f"{msg_prefix} contact info from {count_to_delete} event(s)." + else: + msg = f"{msg_prefix} contact info from {deleted_contact_info_count} event(s)." # noqa: E501 + + self.stdout.write(self.style.SUCCESS(msg)) + except Exception as e: + self.stdout.write( + self.style.ERROR(f"Error checking/deleting event contact info: {e}") + ) + + def __delete_enrollee_personal_data(self, dry_run=False): + """ + Deletes EnrolleePersonalData objects (persons) that have exceeded + their retention period. + If dry_run is True, it only reports what would be deleted. + """ + self.stdout.write("Checking persons that are exceeding the retention period...") + + try: + persons = EnrolleePersonalData.objects.retention_period_exceeded() + count_to_delete = persons.count() + + if dry_run: + msg_prefix = "Would delete" + deleted_personal_data_count = 0 # No actual deletion in dry run + else: + msg_prefix = "Deleted" + deleted_personal_data_count, _ = persons.delete() + + if count_to_delete == 0: + msg = "No personal data are exceeding the retention period." + else: + if dry_run: + msg = f"{msg_prefix} {count_to_delete} person(s)." + else: + msg = f"{msg_prefix} {deleted_personal_data_count} person(s)." + + self.stdout.write(self.style.SUCCESS(msg)) + except Exception as e: + self.stdout.write( + self.style.ERROR(f"Error checking/deleting enrollee personal data: {e}") + ) diff --git a/occurrences/tests/test_commands.py b/occurrences/tests/test_commands.py index 1d018619..618961f3 100644 --- a/occurrences/tests/test_commands.py +++ b/occurrences/tests/test_commands.py @@ -23,23 +23,28 @@ from occurrences.models import Enrolment, PalvelutarjotinEvent from occurrences.notification_services import notification_service from organisations.factories import PersonFactory +from organisations.models import EnrolleePersonalData, Person -@pytest.mark.django_db -def test_delete_retention_period_exceeding_contact_info_command(mock_get_event_data): - too_old_1 = timezone.now() - relativedelta(months=24, days=1, minutes=60) - too_old_2 = timezone.now() - relativedelta(months=24, days=1) - still_valid_1 = timezone.now() - relativedelta(months=24, days=-1) - still_valid_2 = timezone.now() - relativedelta(months=24, days=-1, minutes=60) - output = StringIO() +@pytest.fixture +def retention_test_p_events( + settings, mock_get_event_data, mock_create_event_data, mock_update_event_data +) -> dict[str, PalvelutarjotinEvent]: + period = settings.PERSONAL_DATA_RETENTION_PERIOD_MONTHS + too_old_1 = timezone.now() - relativedelta(months=period, days=1, minutes=60) + too_old_2 = timezone.now() - relativedelta(months=period, days=1) + still_valid_1 = timezone.now() - relativedelta(months=period, days=-1) + still_valid_2 = timezone.now() - relativedelta(months=period, days=-1, minutes=60) # ### CONTACT INFO SHOULD BE DELETED FROM THESE 2 - event_1 = PalvelutarjotinEventFactory() - OccurrenceFactory(p_event=event_1, start_time=too_old_1, end_time=too_old_2) + too_old_event_1 = PalvelutarjotinEventFactory() + OccurrenceFactory(p_event=too_old_event_1, start_time=too_old_1, end_time=too_old_2) - event_2 = PalvelutarjotinEventFactory() - PalvelutarjotinEvent.objects.filter(pk=event_2.pk).update(created_at=too_old_1) + too_old_event_2 = PalvelutarjotinEventFactory() + PalvelutarjotinEvent.objects.filter(pk=too_old_event_2.pk).update( + created_at=too_old_1 + ) # ### THESE 5 SHOULD NOT BE AFFECTED @@ -59,257 +64,411 @@ def test_delete_retention_period_exceeding_contact_info_command(mock_get_event_d OccurrenceFactory(p_event=event_6, start_time=too_old_1, end_time=too_old_2) OccurrenceFactory(p_event=event_6, start_time=still_valid_1, end_time=still_valid_2) - event_7 = PalvelutarjotinEventFactory() + event_7 = PalvelutarjotinEventFactory(created_at=still_valid_1) PalvelutarjotinEvent.objects.filter(pk=event_7.pk).update(created_at=still_valid_1) - call_command("delete_retention_period_exceeding_contact_info", stdout=output) + return { + "too_old_event_1": too_old_event_1, + "too_old_event_2": too_old_event_2, + "event_3": event_3, + "event_4": event_4, + "event_5": event_5, + "event_6": event_6, + "event_7": event_7, + } - events_with_deleted_contact_info = PalvelutarjotinEvent.objects.filter( - contact_info_deleted_at__isnull=False - ) - assert events_with_deleted_contact_info.count() == 3 - assert event_1 in events_with_deleted_contact_info - assert event_2 in events_with_deleted_contact_info - # this had already contact_info_deleted_at set - assert event_5 in events_with_deleted_contact_info - - output.seek(0) - assert "Deleted contact info from 2 event(s)" in output.read() - - # Run the command again, expect no events to be affected - call_command("delete_retention_period_exceeding_contact_info", stdout=output) - output.seek(0) - assert "No events are exceeding the retention period." in output.read() - - -@override_settings(NOTIFICATION_SERVICE_SMS_ENABLED=True) -@pytest.mark.django_db -@pytest.mark.parametrize( - "enrolment_status,days,expected_sms_count", - [ - # Only approved enrolments should be reminded of - (Enrolment.STATUS_PENDING, 0, 0), - (Enrolment.STATUS_CANCELLED, 0, 0), - (Enrolment.STATUS_DECLINED, 0, 0), - # Expected SMS count is double the enrolment count because - # one SMS for study_group.person and another for enrolment.person - (Enrolment.STATUS_APPROVED, 0, 3 * 2), # 1st day's enrolments x 2 - (Enrolment.STATUS_APPROVED, 1, 4 * 2), # 2nd day's enrolments x 2 - (Enrolment.STATUS_APPROVED, 2, 5 * 2), # 3rd day's enrolments x 2 - (Enrolment.STATUS_APPROVED, 3, 6 * 2), # 4th day's enrolments x 2 - (Enrolment.STATUS_APPROVED, 4, 7 * 2), # 5th day's enrolments x 2 - ], -) -def test_send_upcoming_occurrence_sms_reminders_command_count( - mocked_responses, + +@pytest.fixture +def retention_test_persons( + retention_test_p_events, mock_get_event_data, - notification_sms_template_occurrence_upcoming_en, - notification_sms_template_occurrence_upcoming_fi, - notification_sms_template_occurrence_upcoming_sv, - enrolment_status: str, - days: int, - expected_sms_count: int, -): - """ - Test send_upcoming_occurrence_sms_reminders command's sent SMS count - """ - mocked_responses.assert_all_requests_are_fired = expected_sms_count > 0 - today = localtime().replace(hour=0, minute=0, second=0, microsecond=0) - start_times = [ - # 1st day: 3 occurrences - today + relativedelta(days=0), - today + relativedelta(days=0, hours=1), - today + relativedelta(days=0, hours=23, minutes=59), - # 2nd day: 4 occurrences - today + relativedelta(days=1), - today + relativedelta(days=1, hours=1), - today + relativedelta(days=1, hours=2), - today + relativedelta(days=1, hours=23, minutes=59), - # 3rd day: 5 occurrences - today + relativedelta(days=2), - today + relativedelta(days=2, hours=1), - today + relativedelta(days=2, hours=2), - today + relativedelta(days=2, hours=3), - today + relativedelta(days=2, hours=23, minutes=59), - # 4th day: 6 occurrences - today + relativedelta(days=3), - today + relativedelta(days=3, hours=1), - today + relativedelta(days=3, hours=2), - today + relativedelta(days=3, hours=3), - today + relativedelta(days=3, hours=4), - today + relativedelta(days=3, hours=23, minutes=59), - # 5th day: 7 occurrences - today + relativedelta(days=4), - today + relativedelta(days=4, hours=1), - today + relativedelta(days=4, hours=2), - today + relativedelta(days=4, hours=3), - today + relativedelta(days=4, hours=4), - today + relativedelta(days=4, hours=5), - today + relativedelta(days=4, hours=23, minutes=59), - ] - - for start_time in start_times: - occurrence = OccurrenceFactory( - start_time=start_time, end_time=start_time + relativedelta(days=1) - ) - EnrolmentFactory( - occurrence=occurrence, - notification_type=NOTIFICATION_TYPE_SMS, - status=enrolment_status, - ) + mock_create_event_data, + mock_update_event_data, +) -> dict[str, Person]: + ( + too_old_contact_1, + too_old_contact_2, + no_enrolments_contact_3, + contact_4, + contact_5, + ) = PersonFactory.create_batch(5, user=None) - mocked_responses.add( - responses.POST, - url=notification_service.url, - body="{}", - status=200, - content_type="application/json", + EnrolmentFactory( + person=too_old_contact_1, + occurrence=retention_test_p_events["too_old_event_1"] + .occurrences.order_by("start_time") + .last(), ) - call_command("send_upcoming_occurrence_sms_reminders", days=days) - - assert len(mocked_responses.calls) == expected_sms_count + EnrolmentFactory( + occurrence=retention_test_p_events["too_old_event_1"] + .occurrences.order_by("start_time") + .last(), + study_group__person=too_old_contact_2, + ) + EnrolmentFactory( + occurrence=retention_test_p_events["event_3"].occurrences.last(), + person=contact_4, + ) + EnrolmentFactory( + occurrence=retention_test_p_events["event_3"].occurrences.last(), + person=contact_5, + ) -@override_settings(NOTIFICATION_SERVICE_SMS_ENABLED=True) -@pytest.mark.django_db -@pytest.mark.parametrize( - "language,expected_sent_message", - [ - ( - "fi", - ( - "Muistathan ilmoittautumisesi tapahtumaan " - "Raija Malka & Kaija Saariaho: Blick. " - "04.01.2020 klo 02.00. " # Timezone offset is +2 hours - "Test study group unit name. " - "Mikäli et pääse paikalle, peruutathan varauksesi sähköpostilla: " - "contact_email@example.org." + return { + "too_old_contact_1": too_old_contact_1, + "too_old_contact_2": too_old_contact_2, + "no_enrolments_contact_3": no_enrolments_contact_3, + "contact_4": contact_4, + "contact_5": contact_5, + } + + +class TestDeleteRetentionPeriodExceedingContactInfoCommand: + @pytest.mark.parametrize( + "flags", + [ + pytest.param( + { + "delete_event_contact_info": False, + "delete_enrollee_personal_data": False, + }, + id="no_flags", ), - ), - ( - "en", - ( - "Please remember your enrolment for " - "Raija Malka & Kaija Saariaho: Blick. " - "04.01.2020 at 02.00. " # Timezone offset is +2 hours - "Test study group unit name. " - "If you are unable to attend, please cancel your place by email: " - "contact_email@example.org." + pytest.param( + { + "delete_event_contact_info": True, + "delete_enrollee_personal_data": True, + }, + id="both_flags", ), - ), - ( - "sv", - ( - "Kom ihåg din anmälan till evenemanget " - "Raija Malka & Kaija Saariaho: Blick. " # - "04.01.2020 kl 02.00. " # Timezone offset is +2 hours - "Test study group unit name. " - "Om du inte kan delta, vänligen avboka din bokning via e-post: " - "contact_email@example.org." + pytest.param( + { + "delete_event_contact_info": True, + "delete_enrollee_personal_data": False, + }, + id="only_event_contact_info_flag", ), - ), - ], -) -def test_send_upcoming_occurrence_sms_reminders_command_content( - mocked_responses, - mock_get_event_data, - notification_sms_template_occurrence_upcoming_en, - notification_sms_template_occurrence_upcoming_fi, - notification_sms_template_occurrence_upcoming_sv, - language: str, - expected_sent_message: str, -): - """ - Test send_upcoming_occurrence_sms_reminders command's sent SMS content - """ - person = PersonFactory( - name="Test person name", - phone_number="123456789", - language=language, - ) - study_group = StudyGroupFactory( - group_name="Test study group name", - unit_name="Test study group unit name", - person=person, - ) - event = PalvelutarjotinEventFactory( - contact_email="contact_email@example.org", - contact_person=person, - ) - occurrence = OccurrenceFactory( - p_event=event, - start_time=localtime(), - end_time=localtime() + relativedelta(days=1), - ) - EnrolmentFactory( - occurrence=occurrence, - notification_type=NOTIFICATION_TYPE_SMS, - status=Enrolment.STATUS_APPROVED, - study_group=study_group, - person=person, + pytest.param( + { + "delete_event_contact_info": False, + "delete_enrollee_personal_data": True, + }, + id="only_enrollee_personal_data_flag", + ), + ], ) + @pytest.mark.parametrize("dry_run", [True, False], ids=["dry_run", "no_dry_run"]) + @pytest.mark.django_db + def test_delete_retention_period_exceeding_contact_info_command( + self, + flags, + dry_run, + retention_test_p_events, + retention_test_persons, + mock_get_event_data, + ): + output = StringIO() + too_old_event_1 = retention_test_p_events["too_old_event_1"] + too_old_event_2 = retention_test_p_events["too_old_event_2"] + event_5 = retention_test_p_events["event_5"] + too_old_contact_1 = retention_test_persons["too_old_contact_1"] + too_old_contact_2 = retention_test_persons["too_old_contact_2"] + no_enrolments_contact_3 = retention_test_persons["no_enrolments_contact_3"] + contact_4 = retention_test_persons["contact_4"] + contact_5 = retention_test_persons["contact_5"] + original_event_contact_count = ( + PalvelutarjotinEvent.objects.contact_info_retention_period_exceeded() + ).count() + original_person_count = EnrolleePersonalData.objects.all().count() + + call_command( + "delete_retention_period_exceeding_contact_info", + **flags, + dry_run=dry_run, + stdout=output, + ) - mocked_responses.add( - responses.POST, - url=notification_service.url, - body="{}", - status=200, - content_type="application/json", + # Should run all if all feature flags are True or none of them is True + should_run_all = ( + ( + not flags.get("delete_event_contact_info", False) + and not flags.get("delete_enrollee_personal_data", False) + ) + or flags.get("delete_event_contact_info") + and flags.get("delete_enrollee_personal_data") + ) + should_delete_event_contact_info = ( + flags.get("delete_event_contact_info", False) or should_run_all + ) + should_delete_enrollee_personal_data = ( + flags.get("delete_enrollee_personal_data", False) or should_run_all + ) + + expected_count_deleted_event_info = 2 if should_delete_event_contact_info else 0 + + expected_count_deleted_persons = ( + 3 if should_delete_enrollee_personal_data else 0 + ) + + if should_delete_event_contact_info: + if dry_run: + assert ( + PalvelutarjotinEvent.objects.contact_info_retention_period_exceeded().count() + == original_event_contact_count + == expected_count_deleted_event_info + ) + output.seek(1) + assert ( + "Would delete contact info " + f"from {expected_count_deleted_event_info} event(s)" + ) in output.read() + else: + events_with_deleted_contact_info = PalvelutarjotinEvent.objects.filter( + contact_info_deleted_at__isnull=False + ) + assert ( + events_with_deleted_contact_info.count() + == expected_count_deleted_event_info + 1 # there was 1 already + ) + assert too_old_event_1 in events_with_deleted_contact_info + assert too_old_event_2 in events_with_deleted_contact_info + # this had already contact_info_deleted_at set + assert event_5 in events_with_deleted_contact_info + + output.seek(1) + assert ( + "Deleted contact info " + f"from {expected_count_deleted_event_info} event(s)" + ) in output.read() + + # Run the command again, expect no events to be affected + call_command( + "delete_retention_period_exceeding_contact_info", + **flags, + stdout=output, + ) + output.seek(1) + assert "No events are exceeding the retention period." in output.read() + else: + assert ( + PalvelutarjotinEvent.objects.contact_info_retention_period_exceeded().count() + == original_event_contact_count + ) + + persons = EnrolleePersonalData.objects.all() + if should_delete_enrollee_personal_data: + if dry_run: + assert persons.count() == original_person_count + output.seek(1) + assert ( + f"Would delete {expected_count_deleted_persons} person(s)." + in output.read() + ) + else: + assert ( + persons.count() + == (original_person_count - expected_count_deleted_persons) + == 2 + ) + assert too_old_contact_1 not in persons + assert too_old_contact_2 not in persons + assert no_enrolments_contact_3 not in persons + assert contact_4 in persons + assert contact_5 in persons + + output.seek(1) + assert ( + f"Deleted {expected_count_deleted_persons} person(s)." + in output.read() + ) + + # Run the command again, expect no events to be affected + call_command( + "delete_retention_period_exceeding_contact_info", + **flags, + stdout=output, + ) + output.seek(1) + assert ( + "No personal data are exceeding the retention period." + in output.read() + ) + else: + assert persons.count() == original_person_count + + +class TestSendNotificationReminders: + @override_settings(NOTIFICATION_SERVICE_SMS_ENABLED=True) + @pytest.mark.django_db + @pytest.mark.parametrize( + "enrolment_status,days,expected_sms_count", + [ + # Only approved enrolments should be reminded of + (Enrolment.STATUS_PENDING, 0, 0), + (Enrolment.STATUS_CANCELLED, 0, 0), + (Enrolment.STATUS_DECLINED, 0, 0), + # Expected SMS count is double the enrolment count because + # one SMS for study_group.person and another for enrolment.person + (Enrolment.STATUS_APPROVED, 0, 3 * 2), # 1st day's enrolments x 2 + (Enrolment.STATUS_APPROVED, 1, 4 * 2), # 2nd day's enrolments x 2 + (Enrolment.STATUS_APPROVED, 2, 5 * 2), # 3rd day's enrolments x 2 + (Enrolment.STATUS_APPROVED, 3, 6 * 2), # 4th day's enrolments x 2 + (Enrolment.STATUS_APPROVED, 4, 7 * 2), # 5th day's enrolments x 2 + ], ) + def test_send_upcoming_occurrence_sms_reminders_command_count( + self, + mocked_responses, + mock_get_event_data, + notification_sms_template_occurrence_upcoming_en, + notification_sms_template_occurrence_upcoming_fi, + notification_sms_template_occurrence_upcoming_sv, + enrolment_status: str, + days: int, + expected_sms_count: int, + ): + """ + Test send_upcoming_occurrence_sms_reminders command's sent SMS count + """ + mocked_responses.assert_all_requests_are_fired = expected_sms_count > 0 + today = localtime().replace(hour=0, minute=0, second=0, microsecond=0) + start_times = [ + # 1st day: 3 occurrences + today + relativedelta(days=0), + today + relativedelta(days=0, hours=1), + today + relativedelta(days=0, hours=23, minutes=59), + # 2nd day: 4 occurrences + today + relativedelta(days=1), + today + relativedelta(days=1, hours=1), + today + relativedelta(days=1, hours=2), + today + relativedelta(days=1, hours=23, minutes=59), + # 3rd day: 5 occurrences + today + relativedelta(days=2), + today + relativedelta(days=2, hours=1), + today + relativedelta(days=2, hours=2), + today + relativedelta(days=2, hours=3), + today + relativedelta(days=2, hours=23, minutes=59), + # 4th day: 6 occurrences + today + relativedelta(days=3), + today + relativedelta(days=3, hours=1), + today + relativedelta(days=3, hours=2), + today + relativedelta(days=3, hours=3), + today + relativedelta(days=3, hours=4), + today + relativedelta(days=3, hours=23, minutes=59), + # 5th day: 7 occurrences + today + relativedelta(days=4), + today + relativedelta(days=4, hours=1), + today + relativedelta(days=4, hours=2), + today + relativedelta(days=4, hours=3), + today + relativedelta(days=4, hours=4), + today + relativedelta(days=4, hours=5), + today + relativedelta(days=4, hours=23, minutes=59), + ] + + for start_time in start_times: + occurrence = OccurrenceFactory( + start_time=start_time, end_time=start_time + relativedelta(days=1) + ) + EnrolmentFactory( + occurrence=occurrence, + notification_type=NOTIFICATION_TYPE_SMS, + status=enrolment_status, + ) - call_command("send_upcoming_occurrence_sms_reminders", days=0) + mocked_responses.add( + responses.POST, + url=notification_service.url, + body="{}", + status=200, + content_type="application/json", + ) - assert len(mocked_responses.calls) == 1 - sent_body = json.loads(mocked_responses.calls[0].request.body) - assert (len(sent_body["to"])) == 1 - assert sent_body["to"][0]["destination"] == person.phone_number - assert sent_body["text"] == expected_sent_message + call_command("send_upcoming_occurrence_sms_reminders", days=days) + assert len(mocked_responses.calls) == expected_sms_count -@override_settings(NOTIFICATION_SERVICE_SMS_ENABLED=True) -@pytest.mark.django_db -@pytest.mark.parametrize( - "notification_type,expected_sms_count", - [ - (NOTIFICATION_TYPE_SMS, 1), - (NOTIFICATION_TYPE_EMAIL, 0), - (NOTIFICATION_TYPE_ALL, 1), - ], -) -def test_send_upcoming_occurrence_sms_reminders_no_consent_given( - mocked_responses, - mock_get_event_data, - notification_sms_template_occurrence_upcoming_en, - notification_sms_template_occurrence_upcoming_fi, - notification_sms_template_occurrence_upcoming_sv, - notification_type, - expected_sms_count, -): - person = PersonFactory( - phone_number="123456789", - ) - study_group = StudyGroupFactory( - person=person, - ) - event = PalvelutarjotinEventFactory( - contact_email="contact_email@example.org", - contact_person=person, - ) - occurrence = OccurrenceFactory( - p_event=event, - start_time=localtime(), - end_time=localtime() + relativedelta(days=1), - ) - EnrolmentFactory( - occurrence=occurrence, - notification_type=notification_type, - status=Enrolment.STATUS_APPROVED, - study_group=study_group, - person=person, + @override_settings(NOTIFICATION_SERVICE_SMS_ENABLED=True) + @pytest.mark.django_db + @pytest.mark.parametrize( + "language,expected_sent_message", + [ + ( + "fi", + ( + "Muistathan ilmoittautumisesi tapahtumaan " + "Raija Malka & Kaija Saariaho: Blick. " + "04.01.2020 klo 02.00. " # Timezone offset is +2 hours + "Test study group unit name. " + "Mikäli et pääse paikalle, peruutathan varauksesi sähköpostilla: " + "contact_email@example.org." + ), + ), + ( + "en", + ( + "Please remember your enrolment for " + "Raija Malka & Kaija Saariaho: Blick. " + "04.01.2020 at 02.00. " # Timezone offset is +2 hours + "Test study group unit name. " + "If you are unable to attend, please cancel your place by email: " + "contact_email@example.org." + ), + ), + ( + "sv", + ( + "Kom ihåg din anmälan till evenemanget " + "Raija Malka & Kaija Saariaho: Blick. " # + "04.01.2020 kl 02.00. " # Timezone offset is +2 hours + "Test study group unit name. " + "Om du inte kan delta, vänligen avboka din bokning via e-post: " + "contact_email@example.org." + ), + ), + ], ) + def test_send_upcoming_occurrence_sms_reminders_command_content( + self, + mocked_responses, + mock_get_event_data, + notification_sms_template_occurrence_upcoming_en, + notification_sms_template_occurrence_upcoming_fi, + notification_sms_template_occurrence_upcoming_sv, + language: str, + expected_sent_message: str, + ): + """ + Test send_upcoming_occurrence_sms_reminders command's sent SMS content + """ + person = PersonFactory( + name="Test person name", + phone_number="123456789", + language=language, + ) + study_group = StudyGroupFactory( + group_name="Test study group name", + unit_name="Test study group unit name", + person=person, + ) + event = PalvelutarjotinEventFactory( + contact_email="contact_email@example.org", + contact_person=person, + ) + occurrence = OccurrenceFactory( + p_event=event, + start_time=localtime(), + end_time=localtime() + relativedelta(days=1), + ) + EnrolmentFactory( + occurrence=occurrence, + notification_type=NOTIFICATION_TYPE_SMS, + status=Enrolment.STATUS_APPROVED, + study_group=study_group, + person=person, + ) - if expected_sms_count > 0: mocked_responses.add( responses.POST, url=notification_service.url, @@ -318,5 +477,65 @@ def test_send_upcoming_occurrence_sms_reminders_no_consent_given( content_type="application/json", ) - call_command("send_upcoming_occurrence_sms_reminders", days=0) - assert len(mocked_responses.calls) == expected_sms_count + call_command("send_upcoming_occurrence_sms_reminders", days=0) + + assert len(mocked_responses.calls) == 1 + sent_body = json.loads(mocked_responses.calls[0].request.body) + assert (len(sent_body["to"])) == 1 + assert sent_body["to"][0]["destination"] == person.phone_number + assert sent_body["text"] == expected_sent_message + + @override_settings(NOTIFICATION_SERVICE_SMS_ENABLED=True) + @pytest.mark.django_db + @pytest.mark.parametrize( + "notification_type,expected_sms_count", + [ + (NOTIFICATION_TYPE_SMS, 1), + (NOTIFICATION_TYPE_EMAIL, 0), + (NOTIFICATION_TYPE_ALL, 1), + ], + ) + def test_send_upcoming_occurrence_sms_reminders_no_consent_given( + self, + mocked_responses, + mock_get_event_data, + notification_sms_template_occurrence_upcoming_en, + notification_sms_template_occurrence_upcoming_fi, + notification_sms_template_occurrence_upcoming_sv, + notification_type, + expected_sms_count, + ): + person = PersonFactory( + phone_number="123456789", + ) + study_group = StudyGroupFactory( + person=person, + ) + event = PalvelutarjotinEventFactory( + contact_email="contact_email@example.org", + contact_person=person, + ) + occurrence = OccurrenceFactory( + p_event=event, + start_time=localtime(), + end_time=localtime() + relativedelta(days=1), + ) + EnrolmentFactory( + occurrence=occurrence, + notification_type=notification_type, + status=Enrolment.STATUS_APPROVED, + study_group=study_group, + person=person, + ) + + if expected_sms_count > 0: + mocked_responses.add( + responses.POST, + url=notification_service.url, + body="{}", + status=200, + content_type="application/json", + ) + + call_command("send_upcoming_occurrence_sms_reminders", days=0) + assert len(mocked_responses.calls) == expected_sms_count