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?
-
-
-
-{% 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 %}
+
+ {% 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)