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