diff --git a/app/__main__.py b/app/__main__.py index 608ba2e4..6647eb7d 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -311,6 +311,8 @@ def sla_overdue_applications() -> None: ): with rollback_on_error(session): days_passed = application.days_waiting_for_lender(session) + + # Email lenders if the SLA days are dwindling. if days_passed > application.lender.sla_days * app_settings.progress_to_remind_started_applications: if "email" not in overdue_lenders[application.lender.id]: overdue_lenders[application.lender.id]["email"] = application.lender.email_group @@ -318,6 +320,7 @@ def sla_overdue_applications() -> None: overdue_lenders[application.lender.id]["count"] += 1 + # Email administrators if the SLA days are exceeded. if days_passed > application.lender.sla_days: application.overdued_at = datetime.now(application.created_at.tzinfo) @@ -333,7 +336,10 @@ def sla_overdue_applications() -> None: for lender_id, lender_data in overdue_lenders.items(): message_id = mail.send_overdue_application_email_to_lender( - aws.ses_client, lender_data["name"], lender_data["count"], lender_data["email"] + aws.ses_client, + lender_name=lender_data["name"], + lender_email=lender_data["email"], + amount=lender_data["count"], ) models.Message.create( session, diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 69948366..2c53b07c 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta +import pytest from typer.testing import CliRunner from app import __main__, models @@ -9,31 +10,73 @@ runner = CliRunner() -def test_send_reminders_intro(session, mock_send_templated_email, pending_application): - pending_application.expired_at = datetime.now(pending_application.tz) + timedelta(seconds=1) +@pytest.mark.parametrize( + ("seconds", "call_count"), + [ + (0, 0), + (1, 1), + (app_settings.reminder_days_before_expiration * 86_400, 1), + (app_settings.reminder_days_before_expiration * 86_400 + 1, 0), + ], +) +def test_send_reminders_intro(session, mock_send_templated_email, pending_application, seconds, call_count): + pending_application.expired_at = datetime.now(pending_application.tz) + timedelta(seconds=seconds) session.commit() - with assert_change(mock_send_templated_email, "call_count", 1): + with assert_change(mock_send_templated_email, "call_count", call_count): result = runner.invoke(__main__.app, ["send-reminders"]) assert_success( - result, "Sending 1 BORROWER_PENDING_APPLICATION_REMINDER...\nSending 0 BORROWER_PENDING_SUBMIT_REMINDER...\n" + result, + ( + f"Sending {call_count} BORROWER_PENDING_APPLICATION_REMINDER...\n" + "Sending 0 BORROWER_PENDING_SUBMIT_REMINDER...\n" + ), + ) + + # If run a second time, reminder is not sent. + with assert_change(mock_send_templated_email, "call_count", 0): + result = runner.invoke(__main__.app, ["send-reminders"]) + + assert_success( + result, "Sending 0 BORROWER_PENDING_APPLICATION_REMINDER...\nSending 0 BORROWER_PENDING_SUBMIT_REMINDER...\n" ) -def test_send_reminders_submit(session, mock_send_templated_email, accepted_application): +@pytest.mark.parametrize( + ("seconds", "call_count"), + [ + (0, 0), + (1, 1), + (app_settings.reminder_days_before_lapsed * 86_400, 1), + (app_settings.reminder_days_before_lapsed * 86_400 + 1, 0), + ], +) +def test_send_reminders_submit(session, mock_send_templated_email, accepted_application, seconds, call_count): accepted_application.borrower_accepted_at = ( datetime.now(accepted_application.tz) - timedelta(days=app_settings.days_to_change_to_lapsed) - + timedelta(days=app_settings.reminder_days_before_lapsed) + + timedelta(seconds=seconds) ) session.commit() - with assert_change(mock_send_templated_email, "call_count", 1): + with assert_change(mock_send_templated_email, "call_count", call_count): result = runner.invoke(__main__.app, ["send-reminders"]) assert_success( - result, "Sending 0 BORROWER_PENDING_APPLICATION_REMINDER...\nSending 1 BORROWER_PENDING_SUBMIT_REMINDER...\n" + result, + ( + "Sending 0 BORROWER_PENDING_APPLICATION_REMINDER...\n" + f"Sending {call_count} BORROWER_PENDING_SUBMIT_REMINDER...\n" + ), + ) + + # If run a second time, reminder is not sent. + with assert_change(mock_send_templated_email, "call_count", 0): + result = runner.invoke(__main__.app, ["send-reminders"]) + + assert_success( + result, "Sending 0 BORROWER_PENDING_APPLICATION_REMINDER...\nSending 0 BORROWER_PENDING_SUBMIT_REMINDER...\n" ) @@ -53,41 +96,51 @@ def test_set_lapsed_applications(session, pending_application): session.commit() result = runner.invoke(__main__.app, ["update-applications-to-lapsed"]) + session.expire_all() assert_success(result) + assert pending_application.status == models.ApplicationStatus.LAPSED + assert pending_application.application_lapsed_at is not None -def test_set_lapsed_applications_no_lapsed(pending_application): +def test_set_lapsed_applications_no_lapsed(session, pending_application): result = runner.invoke(__main__.app, ["update-applications-to-lapsed"]) + session.expire_all() assert_success(result) - - -def test_send_overdue_reminders(reset_database, session, mock_send_templated_email, started_application): - started_application.lender_started_at = datetime.now(started_application.tz) - timedelta( - days=started_application.lender.sla_days + 1 - ) - session.commit() - - with assert_change(mock_send_templated_email, "call_count", 2): # to admin and lender - result = runner.invoke(__main__.app, ["sla-overdue-applications"]) - - assert_success(result) - - -def test_send_overdue_reminders_empty(session, mock_send_templated_email, started_application): + assert pending_application.status == models.ApplicationStatus.PENDING + assert pending_application.application_lapsed_at is None + + +@pytest.mark.parametrize( + ("seconds", "call_count", "overdue"), + [ + (4 * 86_400, 0, False), + (5 * 86_400, 1, False), + (7 * 86_400, 1, False), + (8 * 86_400, 2, True), + ], +) +def test_send_overdue_reminders( + reset_database, session, mock_send_templated_email, started_application, seconds, call_count, overdue +): # Lapse all applications already in the database. session.query(models.Application).filter(models.Application.id != started_application.id).update( {"status": models.ApplicationStatus.LAPSED} ) - started_application.lender_started_at = datetime.now(started_application.tz) + started_application.lender_started_at = datetime.now(started_application.tz) - timedelta(seconds=seconds) session.commit() - with assert_change(mock_send_templated_email, "call_count", 0): + with assert_change(mock_send_templated_email, "call_count", call_count): result = runner.invoke(__main__.app, ["sla-overdue-applications"]) + session.expire_all() assert_success(result) + if overdue: + assert started_application.overdued_at is not None + else: + assert started_application.overdued_at is None def test_remove_data(session, declined_application): @@ -97,14 +150,33 @@ def test_remove_data(session, declined_application): session.commit() result = runner.invoke(__main__.app, ["remove-dated-application-data"]) + session.expire_all() assert_success(result) - - -def test_remove_data_no_dated_application(pending_application): + assert declined_application.award.previous is True + assert declined_application.primary_email == "" + assert declined_application.archived_at is not None + assert declined_application.borrower_documents == [] + assert declined_application.borrower.legal_name == "" + assert declined_application.borrower.email == "" + assert declined_application.borrower.address == "" + assert declined_application.borrower.legal_identifier == "" + assert declined_application.borrower.source_data == {} + + +def test_remove_data_no_dated_application(session, pending_application): result = runner.invoke(__main__.app, ["remove-dated-application-data"]) + session.expire_all() assert_success(result) + assert pending_application.award.previous is False + assert pending_application.primary_email != "" + assert pending_application.archived_at is None + # documents and legal_name are empty, like in the fixture. + assert pending_application.borrower.email != "" + assert pending_application.borrower.address != "" + assert pending_application.borrower.legal_identifier != "" + assert pending_application.borrower.source_data != {} def test_update_statistic(engine): diff --git a/tests/conftest.py b/tests/conftest.py index 22bce951..da368d4d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -254,7 +254,7 @@ def borrower(session): legal_name="", # tests expect this to be in missing_data email="test@example.com", address="Direccion: Test Address\nCiudad: Test City\nProvincia: No provisto\nEstado: No provisto", - legal_identifier="", + legal_identifier="Test NIT", type="Test Organization Type", sector="", size=models.BorrowerSize.NOT_INFORMED,