From 55526be2d29d3f43d84dfa80e4f99717f1794bb0 Mon Sep 17 00:00:00 2001 From: Aadesh Baral <67958673+Aadesh-Baral@users.noreply.github.com> Date: Wed, 27 Jul 2022 13:02:24 +0545 Subject: [PATCH] Send project progress email to contributors (#5262) --- backend/config.py | 3 + backend/models/postgis/project.py | 9 + backend/models/postgis/statuses.py | 8 + backend/services/mapping_service.py | 2 +- backend/services/messaging/smtp_service.py | 63 ++++- .../messaging/templates/been_some_time.html | 189 -------------- .../templates/encourage_mapper_en.html | 244 ++++++++++++++++++ .../templates/project_transfer_alert_en.html | 4 +- backend/services/project_service.py | 42 ++- backend/services/validator_service.py | 2 +- example.env | 4 + migrations/versions/3b8b0956b217_.py | 30 +++ .../tasking-manager.template.js | 6 + .../services/messaging/test_smtp_service.py | 48 ++++ .../unit/services/test_mapping_service.py | 6 + .../unit/services/test_project_service.py | 91 +++++++ 16 files changed, 557 insertions(+), 194 deletions(-) delete mode 100644 backend/services/messaging/templates/been_some_time.html create mode 100644 backend/services/messaging/templates/encourage_mapper_en.html create mode 100644 migrations/versions/3b8b0956b217_.py diff --git a/backend/config.py b/backend/config.py index 523b49abb7..d2efbc9300 100644 --- a/backend/config.py +++ b/backend/config.py @@ -102,6 +102,9 @@ class EnvironmentConfig: MAIL_DEFAULT_SENDER = os.getenv("TM_EMAIL_FROM_ADDRESS", None) MAIL_DEBUG = True if LOG_LEVEL == "DEBUG" else False + # If disabled project update emails will not be sent. + SEND_PROJECT_EMAIL_UPDATES = os.getenv("TM_SEND_PROJECT_EMAIL_UPDATES", True) + # Languages offered by the Tasking Manager # Please note that there must be exactly the same number of Codes as languages. SUPPORTED_LANGUAGES = { diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index 22acfba7e3..8c05c218d2 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -152,6 +152,7 @@ class Project(db.Model): extra_id_params = db.Column(db.String) rapid_power_user = db.Column(db.Boolean, default=False) last_updated = db.Column(db.DateTime, default=timestamp) + progress_email_sent = db.Column(db.Boolean, default=False) license_id = db.Column(db.Integer, db.ForeignKey("licenses.id", name="fk_licenses")) geometry = db.Column(Geometry("MULTIPOLYGON", srid=4326), nullable=False) centroid = db.Column(Geometry("POINT", srid=4326), nullable=False) @@ -1164,6 +1165,14 @@ def calculate_tasks_percent( return int(tasks_validated / (total_tasks - tasks_bad_imagery) * 100) elif target == "bad_imagery": return int((tasks_bad_imagery / total_tasks) * 100) + elif target == "project_completion": + # To calculate project completion we assign 2 points to each task + # one for mapping and one for validation + return int( + (tasks_mapped + (tasks_validated * 2)) + / ((total_tasks - tasks_bad_imagery) * 2) + * 100 + ) except ZeroDivisionError: return 0 diff --git a/backend/models/postgis/statuses.py b/backend/models/postgis/statuses.py index acd3c9f101..94cac1ded8 100644 --- a/backend/models/postgis/statuses.py +++ b/backend/models/postgis/statuses.py @@ -158,3 +158,11 @@ class OrganisationType(Enum): FREE = 1 DISCOUNTED = 2 FULL_FEE = 3 + + +class EncouragingEmailType(Enum): + """ Describes the type of encouraging email sent to users """ + + PROJECT_PROGRESS = 1 # Send encouraging email to mappers when a project they have contributed to make progress + PROJECT_COMPLETE = 2 # Send encouraging email to mappers when a project they have contributed to is complete + BEEN_SOME_TIME = 3 # Send encouraging email to mappers who haven't been active for some time on the site diff --git a/backend/services/mapping_service.py b/backend/services/mapping_service.py index f15099ac0d..c4f02d3752 100644 --- a/backend/services/mapping_service.py +++ b/backend/services/mapping_service.py @@ -149,7 +149,7 @@ def unlock_task_after_mapping(mapped_task: MappedTaskDTO) -> TaskDTO: ) task.unlock_task(mapped_task.user_id, new_state, mapped_task.comment) - + ProjectService.send_email_on_project_progress(mapped_task.project_id) return task.as_dto_with_instructions(mapped_task.preferred_locale) @staticmethod diff --git a/backend/services/messaging/smtp_service.py b/backend/services/messaging/smtp_service.py index 150fc00dda..2fecc2c3f6 100644 --- a/backend/services/messaging/smtp_service.py +++ b/backend/services/messaging/smtp_service.py @@ -3,7 +3,9 @@ from flask import current_app from flask_mail import Message -from backend import mail +from backend import mail, create_app +from backend.models.postgis.message import Message as PostgisMessage +from backend.models.postgis.statuses import EncouragingEmailType from backend.services.messaging.template_service import ( get_template, format_username_link, @@ -57,6 +59,65 @@ def send_contact_admin_email(data): subject = "New contact from {name}".format(name=data.get("name")) SMTPService._send_message(email_to, subject, message, message) + @staticmethod + def send_email_to_contributors_on_project_progress( + email_type: str, + project_id: int = None, + project_name: str = None, + project_completion: int = None, + ): + """ Sends an encouraging email to a users when a project they have contributed to make progress""" + from backend.services.users.user_service import UserService + + app = ( + create_app() + ) # Because message-all run on background thread it needs it's own app context + with app.app_context(): + if email_type == EncouragingEmailType.PROJECT_PROGRESS.value: + subject = "The project you have contributed to has made progress." + elif email_type == EncouragingEmailType.PROJECT_COMPLETE.value: + subject = "The project you have contributed to has been completed." + values = { + "EMAIL_TYPE": email_type, + "PROJECT_ID": project_id, + "PROJECT_NAME": project_name, + "PROJECT_COMPLETION": project_completion, + } + contributor_ids = PostgisMessage.get_all_contributors(project_id) + for contributor_id in contributor_ids: + contributor = UserService.get_user_by_id(contributor_id[0]) + values["USERNAME"] = contributor.username + if email_type == EncouragingEmailType.PROJECT_COMPLETE.value: + recommended_projects = UserService.get_recommended_projects( + contributor.username, "en" + ).results + projects = [] + for recommended_project in recommended_projects: + projects.append( + { + "org_logo": recommended_project.organisation_logo, + "priority": recommended_project.priority, + "name": recommended_project.name, + "id": recommended_project.project_id, + "description": recommended_project.short_description, + "total_contributors": recommended_project.total_contributors, + "difficulty": recommended_project.mapper_level, + "progress": recommended_project.percent_mapped, + "due_date": recommended_project.due_date, + } + ) + + values["PROJECTS"] = projects + html_template = get_template("encourage_mapper_en.html", values) + if ( + contributor.email_address + and contributor.is_email_verified + and contributor.projects_notifications + ): + SMTPService._send_message( + contributor.email_address, subject, html_template + ) + @staticmethod def send_email_alert( to_address: str, diff --git a/backend/services/messaging/templates/been_some_time.html b/backend/services/messaging/templates/been_some_time.html deleted file mode 100644 index 0d36c28427..0000000000 --- a/backend/services/messaging/templates/been_some_time.html +++ /dev/null @@ -1,189 +0,0 @@ -{% extends "base.html" %} -{% block content %} -
-

- We haven't seen you in a while. -

-

- We noticed that you haven't mapped any task with Tasking Manager - yet. Need a little help to get started?

How about exploring - this list of projects we selected for you? -

-
- - - - - - {% for projects in [values["PROJECTS"][:2], values["PROJECTS"][2:]] %} - - {% for project in projects %} - - {% if loop.index ==1 %} - - {% endif %} - {% endfor %} - - - - - {% endfor %} -
-
- -
- - - -
- logo of the organization - - {% if project.priority == "URGENT" %} -

- {% elif project.priority == "HIGH" %} -

- {% elif project.priority == "MEDIUM" %} -

- {% elif project.priority == "LOW" %} -

- {% endif %} - {{ project.priority }} -

-
-

- #{{project.id}} -

-

- {{ project.name }} -

-

- {{ project.description }} -

-
- -
-
 
 
- -
-
-{% endblock %} \ No newline at end of file diff --git a/backend/services/messaging/templates/encourage_mapper_en.html b/backend/services/messaging/templates/encourage_mapper_en.html new file mode 100644 index 0000000000..5deeadd060 --- /dev/null +++ b/backend/services/messaging/templates/encourage_mapper_en.html @@ -0,0 +1,244 @@ +{% extends "base.html" %} +{% block content %} +
+ {% if values["EMAIL_TYPE"]==1 %} +
+

+ Hi {{ values["USERNAME"] }} +

+

+ you recently participated in the mapping project - + {{values['PROJECT_NAME']}} - on the + {{values["ORG_CODE"]}} Tasking Manager. + We want to inform you the project has reached {{ values["PROJECT_COMPLETION"] }}% of completeness. +

+ Please login and help us to finish the project! +
+ Thank you! +

+
+ {% elif values["EMAIL_TYPE"]==2 %} +
+

+ Hi {{ values["USERNAME"] }} +

+

+ you recently participated in the mapping project - + {{values['PROJECT_NAME']}} - on the + {{values["ORG_CODE"]}} Tasking Manager. + We want to inform you the project has been completed. It is time to celebrate! +
+ Do you want to continue? How about exploring this list of projects we selected for you? +

+
+ {% elif values["EMAIL_TYPE"]==3 %} +
+

+ We haven't seen you in a while. +

+

+ We noticed that you haven't mapped any task with Tasking Manager + yet. Need a little help to get started?

How about exploring + this list of projects we selected for you? +

+
+ {% endif %} + {% if values["EMAIL_TYPE"] !=1 %} +
+ + + + + + {% for projects in [values["PROJECTS"][:2], values["PROJECTS"][2:]] %} + + {% for project in projects %} + + {% if loop.index ==1 %} + + {% endif %} + {% endfor %} + + + + + {% endfor %} +
+
+ +
+ + + +
+ logo of the organization + + {% if project.priority == "URGENT" %} +

+ {% elif project.priority == "HIGH" %} +

+ {% elif project.priority == "MEDIUM" %} +

+ {% elif project.priority == "LOW" %} +

+ {% endif %} + {{ project.priority }} +

+
+

+ #{{project.id}} +

+

+ {{ project.name }} +

+

+ {{ project.description }} +

+
+ +
+
 
 
+ +
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/backend/services/messaging/templates/project_transfer_alert_en.html b/backend/services/messaging/templates/project_transfer_alert_en.html index 95b7b0551d..6563842a17 100644 --- a/backend/services/messaging/templates/project_transfer_alert_en.html +++ b/backend/services/messaging/templates/project_transfer_alert_en.html @@ -10,7 +10,9 @@ has been transferred to {{values['TRANSFERRED_TO']}} by - {{values['TRANSFERRED_BY']}}. + {{values['TRANSFERRED_BY']}} + on + {{values["ORG_CODE"]}} Tasking Manager.



Please ignore this email if you have received it by mistake.
diff --git a/backend/services/project_service.py b/backend/services/project_service.py index 59c331263f..11d351e587 100644 --- a/backend/services/project_service.py +++ b/backend/services/project_service.py @@ -1,5 +1,7 @@ +import threading from cachetools import TTLCache, cached from flask import current_app + from backend.models.dtos.mapping_dto import TaskDTOs from backend.models.dtos.project_dto import ( ProjectDTO, @@ -10,8 +12,8 @@ ProjectContribDTO, ProjectSearchResultsDTO, ) - from backend.models.postgis.organisation import Organisation +from backend.models.postgis.project_info import ProjectInfo from backend.models.postgis.project import Project, ProjectStatus, MappingLevel from backend.models.postgis.statuses import ( MappingNotAllowed, @@ -19,9 +21,11 @@ MappingPermission, ValidationPermission, TeamRoles, + EncouragingEmailType, ) from backend.models.postgis.task import Task, TaskHistory from backend.models.postgis.utils import NotFound +from backend.services.messaging.smtp_service import SMTPService from backend.services.users.user_service import UserService from backend.services.project_search_service import ProjectSearchService from backend.services.project_admin_service import ProjectAdminService @@ -563,3 +567,39 @@ def get_project_organisation(project_id: int) -> Organisation: raise NotFound() return project.organisation + + @staticmethod + def send_email_on_project_progress(project_id): + """ Send email to all contributors on project progress """ + if not current_app.config["SEND_PROJECT_EMAIL_UPDATES"]: + return + project = ProjectService.get_project_by_id(project_id) + + project_completion = Project.calculate_tasks_percent( + "project_completion", + project.total_tasks, + project.tasks_mapped, + project.tasks_validated, + project.tasks_bad_imagery, + ) + if project_completion == 50 and project.progress_email_sent: + return # Don't send progress email if it's already sent + if project_completion in [50, 100]: + email_type = ( + EncouragingEmailType.PROJECT_COMPLETE.value + if project_completion == 100 + else EncouragingEmailType.PROJECT_PROGRESS.value + ) + project_title = ProjectInfo.get_dto_for_locale( + project_id, project.default_locale + ).name + project.progress_email_sent = True + threading.Thread( + target=SMTPService.send_email_to_contributors_on_project_progress, + args=( + email_type, + project_id, + project_title, + project_completion, + ), + ).start() diff --git a/backend/services/validator_service.py b/backend/services/validator_service.py index eef75c56e3..5346fdd9e2 100644 --- a/backend/services/validator_service.py +++ b/backend/services/validator_service.py @@ -190,7 +190,7 @@ def unlock_tasks_after_validation( issues=task_mapping_issues, ) dtos.append(task.as_dto_with_instructions(validated_dto.preferred_locale)) - + ProjectService.send_email_on_project_progress(validated_dto.project_id) task_dtos = TaskDTOs() task_dtos.tasks = dtos diff --git a/example.env b/example.env index 661d6673c3..6e8a321143 100644 --- a/example.env +++ b/example.env @@ -147,6 +147,10 @@ POSTGRES_PASSWORD=tm # TM_SMTP_USE_TLS=0 # TM_SMTP_USE_SSL=1 +# If disabled project update emails will not be sent. +# Set it disabled in case of testing instances +TM_SEND_PROJECT_EMAIL_UPDATES = 1 + # TM_SERVICE_DESK # If the organisation has a service desk, configures the link # in the Contact page and Fallback Component to point to it diff --git a/migrations/versions/3b8b0956b217_.py b/migrations/versions/3b8b0956b217_.py new file mode 100644 index 0000000000..85c20c3ce3 --- /dev/null +++ b/migrations/versions/3b8b0956b217_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 3b8b0956b217 +Revises: bcb474128817 +Create Date: 2022-07-27 10:30:55.193989 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "3b8b0956b217" +down_revision = "bcb474128817" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", sa.Column("progress_email_sent", sa.Boolean(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "progress_email_sent") + # ### end Alembic commands ### diff --git a/scripts/aws/cloudformation/tasking-manager.template.js b/scripts/aws/cloudformation/tasking-manager.template.js index a5afe07aa8..e8ce2b9df7 100644 --- a/scripts/aws/cloudformation/tasking-manager.template.js +++ b/scripts/aws/cloudformation/tasking-manager.template.js @@ -140,6 +140,12 @@ const Parameters = { AllowedValues: ['1', '0'], Default: '1' }, + TaskingManagerSendProjectUpdateEmails:{ + Description: 'TM_SEND_PROJECT_UPDATE_EMAILS environment variable', + Type: 'Number', + AllowedValues: ['1', '0'], + Default: '1' + }, TaskingManagerDefaultChangesetComment: { Description: 'TM_DEFAULT_CHANGESET_COMMENT environment variable', Type: 'String' diff --git a/tests/backend/integration/services/messaging/test_smtp_service.py b/tests/backend/integration/services/messaging/test_smtp_service.py index 00a7d27df4..800630f305 100644 --- a/tests/backend/integration/services/messaging/test_smtp_service.py +++ b/tests/backend/integration/services/messaging/test_smtp_service.py @@ -3,8 +3,12 @@ from unittest.mock import patch, MagicMock from flask import current_app +from backend.models.postgis.message import Message +from backend.models.postgis.statuses import EncouragingEmailType from backend.services.messaging.smtp_service import SMTPService +from backend.services.users.user_service import UserService from tests.backend.base import BaseTestCase +from tests.backend.helpers.test_helpers import return_canned_user class TestSMTPService(BaseTestCase): @@ -168,3 +172,47 @@ def test_send_message_sends_mail_if_sender_is_defined(self): # Act/Assert SMTPService._send_message(to_address, subject, content, content) + + @patch.object(UserService, "get_recommended_projects") + @patch.object(SMTPService, "_send_message") + @patch.object(UserService, "get_user_by_id") + @patch.object(Message, "get_all_contributors") + def test_send_email_to_contributors_on_project_progress( + self, + mock_get_all_contributors, + mock_get_user_by_id, + mock_send_message, + mock_recommended_projects, + ): + # Arrange + mock_get_all_contributors.return_value = [(123456,)] + test_user = return_canned_user() + test_user.email_address = self.to_address + mock_get_user_by_id.return_value = test_user + + # Test email is not sent if user email is not verified + SMTPService.send_email_to_contributors_on_project_progress( + EncouragingEmailType.PROJECT_PROGRESS.value, 1, "test", 50 + ) + self.assertFalse(mock_send_message.called) + + # Test email is not sent if user has projects notifications disabled + test_user.is_email_verified = True + test_user.projects_notifications = False + SMTPService.send_email_to_contributors_on_project_progress( + EncouragingEmailType.PROJECT_PROGRESS.value, 1, "test", 50 + ) + self.assertFalse(mock_send_message.called) + + # Test email is sent if user has projects notifications enabled and email is verified + test_user.projects_notifications = True + SMTPService.send_email_to_contributors_on_project_progress( + EncouragingEmailType.PROJECT_PROGRESS.value, 1, "test", 50 + ) + mock_send_message.assert_called() + + # Test Recommended projects is sent on project complete email + SMTPService.send_email_to_contributors_on_project_progress( + EncouragingEmailType.PROJECT_COMPLETE.value, 1, "test", 50 + ) + mock_recommended_projects.assert_called() diff --git a/tests/backend/unit/services/test_mapping_service.py b/tests/backend/unit/services/test_mapping_service.py index 2f34e237a9..06a04253f9 100644 --- a/tests/backend/unit/services/test_mapping_service.py +++ b/tests/backend/unit/services/test_mapping_service.py @@ -124,6 +124,7 @@ def test_if_new_state_not_acceptable_raise_error(self, mock_task): with self.assertRaises(MappingServiceError): MappingService.unlock_task_after_mapping(self.mapped_task_dto) + @patch.object(ProjectService, "send_email_on_project_progress") @patch.object(ProjectInfo, "get_dto_for_locale") @patch.object(Task, "get_per_task_instructions") @patch.object(StatsService, "update_stats_after_task_state_change") @@ -142,6 +143,7 @@ def test_unlock_with_comment_sets_history( mock_instructions, mock_state, mock_project_name, + mock_send_email, ): # Arrange self.task_stub.task_status = TaskStatus.LOCKED_FOR_MAPPING.value @@ -152,11 +154,13 @@ def test_unlock_with_comment_sets_history( # Act test_task = MappingService.unlock_task_after_mapping(self.mapped_task_dto) + mock_send_email.assert_called() # Assert mock_send_message.assert_called() self.assertEqual(TaskAction.COMMENT.name, test_task.task_history[0].action) self.assertEqual(test_task.task_history[0].action_text, "Test comment") + @patch.object(ProjectService, "send_email_on_project_progress") @patch.object(Task, "get_per_task_instructions") @patch.object(StatsService, "update_stats_after_task_state_change") @patch.object(Task, "update") @@ -171,6 +175,7 @@ def test_unlock_with_status_change_sets_history( mock_stats, mock_instructions, mock_state, + mock_send_email, ): # Arrange self.task_stub.task_status = TaskStatus.LOCKED_FOR_MAPPING.value @@ -181,6 +186,7 @@ def test_unlock_with_status_change_sets_history( test_task = MappingService.unlock_task_after_mapping(self.mapped_task_dto) # Assert + mock_send_email.assert_called() self.assertEqual(TaskAction.STATE_CHANGE.name, test_task.task_history[0].action) self.assertEqual(test_task.task_history[0].action_text, TaskStatus.MAPPED.name) self.assertEqual(TaskStatus.MAPPED.name, test_task.task_status) diff --git a/tests/backend/unit/services/test_project_service.py b/tests/backend/unit/services/test_project_service.py index 3cbe2f4c22..f986f61258 100644 --- a/tests/backend/unit/services/test_project_service.py +++ b/tests/backend/unit/services/test_project_service.py @@ -1,4 +1,7 @@ from unittest.mock import patch +from flask import current_app + +from backend.services.messaging.smtp_service import SMTPService from backend.services.project_service import ( ProjectService, Project, @@ -8,6 +11,7 @@ UserService, MappingNotAllowed, ValidatingNotAllowed, + ProjectInfo, ) from backend.services.project_service import ProjectAdminService from backend.models.dtos.project_dto import LockedTasksForUser @@ -16,6 +20,12 @@ class TestProjectService(BaseTestCase): + def setUp(self): + super().setUp() + current_app.config[ + "SEND_PROJECT_EMAIL_UPDATES" + ] = True # Set to true to test email sending + @patch.object(Project, "get") def test_project_service_raises_error_if_project_not_found(self, mock_project): mock_project.return_value = None @@ -185,3 +195,84 @@ def test_user_permitted_to_validate( allowed, reason = ProjectService.is_user_permitted_to_validate(1, 1) self.assertFalse(allowed) self.assertEqual(reason, ValidatingNotAllowed.USER_NOT_ACCEPTED_LICENSE) + + @patch.object(SMTPService, "send_email_to_contributors_on_project_progress") + @patch.object(Project, "calculate_tasks_percent") + @patch.object(ProjectInfo, "get_dto_for_locale") + @patch.object(ProjectService, "get_project_by_id") + def test_send_email_on_project_progress_sends_email_on_fifty_percent_progress( + self, mock_project, mock_project_info, mock_project_completion, mock_send_email + ): + # Arrange + mock_project.return_value = Project() + mock_project_info.name.return_value = "TEST_PROJECT" + mock_project_completion.return_value = 50 + # Act + ProjectService.send_email_on_project_progress(1) + # Assert + mock_send_email.assert_called() + + @patch.object(SMTPService, "send_email_to_contributors_on_project_progress") + @patch.object(Project, "calculate_tasks_percent") + @patch.object(ProjectInfo, "get_dto_for_locale") + @patch.object(ProjectService, "get_project_by_id") + def test_send_email_on_project_progress_sends_email_on_project_completion( + self, mock_project, mock_project_info, mock_project_completion, mock_send_email + ): + # Arrange + mock_project.return_value = Project() + mock_project_info.name.return_value = "TEST_PROJECT" + mock_project_completion.return_value = 100 + # Act + ProjectService.send_email_on_project_progress(1) + # Assert + mock_send_email.assert_called() + + @patch.object(SMTPService, "send_email_to_contributors_on_project_progress") + @patch.object(Project, "calculate_tasks_percent") + @patch.object(ProjectInfo, "get_dto_for_locale") + @patch.object(ProjectService, "get_project_by_id") + def test_send_email_on_project_progress_doesnt_send_email_except_on_fifty_and_hundred_percent( + self, mock_project, mock_project_info, mock_project_completion, mock_send_email + ): + # Arrange + mock_project.return_value = Project() + mock_project_info.name.return_value = "TEST_PROJECT" + mock_project_completion.return_value = 80 + # Act + ProjectService.send_email_on_project_progress(1) + # Assert + self.assertFalse(mock_send_email.called) + + @patch.object(SMTPService, "send_email_to_contributors_on_project_progress") + @patch.object(Project, "calculate_tasks_percent") + @patch.object(ProjectService, "get_project_by_id") + def test_send_email_on_project_progress_doesnt_send_email_if_email_already_sent( + self, mock_project, mock_project_completion, mock_send_email + ): + # Arrange + canned_project = Project() + canned_project.progress_email_sent = True + mock_project.return_value = canned_project + mock_project.progress_email_sent.return_value = True + mock_project_completion.return_value = 50 + # Act + ProjectService.send_email_on_project_progress(1) + # Assert + self.assertFalse(mock_send_email.called) + + @patch.object(SMTPService, "send_email_to_contributors_on_project_progress") + @patch.object(ProjectService, "get_project_by_id") + def test_send_email_on_project_progress_doesnt_send_email_if_send_project_update_email_is_disabled( + self, mock_project, mock_send_email + ): + # Arrange + mock_project.return_value = Project() + current_app.config["SEND_PROJECT_EMAIL_UPDATES"] = False + # Act + ProjectService.send_email_on_project_progress(1) + # Assert + current_app.config[ + "SEND_PROJECT_EMAIL_UPDATES" + ] = True # Set to true for other tests + self.assertFalse(mock_send_email.called)