Skip to content

Commit

Permalink
Add email templates for removed file from release (#7498)
Browse files Browse the repository at this point in the history
Until now, when there are multiple contributors on a single
the project, if one of them deletes a file from certain release
the other contributors don't get any notification,
which is problematic.

Connected with issue #5714

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
  • Loading branch information
MVrachev committed Mar 20, 2020
1 parent 6eda363 commit f8c866c
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 6 deletions.
212 changes: 212 additions & 0 deletions tests/unit/email/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,218 @@ def test_send_removed_project_release_emai_to_owner(
]


class TestRemovedReleaseFileEmail:
def test_send_removed_project_release_file_email_to_owner(
self, pyramid_request, pyramid_config, monkeypatch
):
stub_user = pretend.stub(
username="username",
name="",
email="email@example.com",
primary_email=pretend.stub(email="email@example.com", verified=True),
)
stub_submitter_user = pretend.stub(
username="submitterusername",
name="",
email="submiteremail@example.com",
primary_email=pretend.stub(
email="submiteremail@example.com", verified=True
),
)

subject_renderer = pyramid_config.testing_add_renderer(
"email/removed-project-release-file/subject.txt"
)
subject_renderer.string_response = "Email Subject"
body_renderer = pyramid_config.testing_add_renderer(
"email/removed-project-release-file/body.txt"
)
body_renderer.string_response = "Email Body"
html_renderer = pyramid_config.testing_add_renderer(
"email/removed-project-release-file/body.html"
)
html_renderer.string_response = "Email HTML Body"

send_email = pretend.stub(
delay=pretend.call_recorder(lambda *args, **kwargs: None)
)
pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email)
monkeypatch.setattr(email, "send_email", send_email)

release = pretend.stub(
version="0.0.0",
project=pretend.stub(name="test_project"),
created=datetime.datetime(2017, 2, 5, 0, 0, 0, 0),
)

result = email.send_removed_project_release_file_email(
pyramid_request,
[stub_user, stub_submitter_user],
file="test-file-0.0.0.tar.gz",
release=release,
submitter_name=stub_submitter_user.username,
submitter_role="Owner",
recipient_role="Owner",
)

assert result == {
"file": "test-file-0.0.0.tar.gz",
"project_name": release.project.name,
"release_version": release.version,
"submitter_name": stub_submitter_user.username,
"submitter_role": "owner",
"recipient_role_descr": "an owner",
}

subject_renderer.assert_(project_name="test_project")
subject_renderer.assert_(release_version="0.0.0")
body_renderer.assert_(file="test-file-0.0.0.tar.gz")
body_renderer.assert_(release_version="0.0.0")
body_renderer.assert_(project_name="test_project")
body_renderer.assert_(submitter_name=stub_submitter_user.username)
body_renderer.assert_(submitter_role="owner")
body_renderer.assert_(recipient_role_descr="an owner")

assert pyramid_request.task.calls == [
pretend.call(send_email),
pretend.call(send_email),
]

assert send_email.delay.calls == [
pretend.call(
"username <email@example.com>",
attr.asdict(
EmailMessage(
subject="Email Subject",
body_text="Email Body",
body_html=(
"<html>\n<head></head>\n"
"<body><p>Email HTML Body</p></body>\n</html>\n"
),
),
),
),
pretend.call(
"submitterusername <submiteremail@example.com>",
attr.asdict(
EmailMessage(
subject="Email Subject",
body_text="Email Body",
body_html=(
"<html>\n<head></head>\n"
"<body><p>Email HTML Body</p></body>\n</html>\n"
),
)
),
),
]

def test_send_removed_project_release_file_email_to_maintainer(
self, pyramid_request, pyramid_config, monkeypatch
):
stub_user = pretend.stub(
username="username",
name="",
email="email@example.com",
primary_email=pretend.stub(email="email@example.com", verified=True),
)
stub_submitter_user = pretend.stub(
username="submitterusername",
name="",
email="submiteremail@example.com",
primary_email=pretend.stub(
email="submiteremail@example.com", verified=True
),
)

subject_renderer = pyramid_config.testing_add_renderer(
"email/removed-project-release-file/subject.txt"
)
subject_renderer.string_response = "Email Subject"
body_renderer = pyramid_config.testing_add_renderer(
"email/removed-project-release-file/body.txt"
)
body_renderer.string_response = "Email Body"
html_renderer = pyramid_config.testing_add_renderer(
"email/removed-project-release-file/body.html"
)
html_renderer.string_response = "Email HTML Body"

send_email = pretend.stub(
delay=pretend.call_recorder(lambda *args, **kwargs: None)
)
pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email)
monkeypatch.setattr(email, "send_email", send_email)

release = pretend.stub(
version="0.0.0",
project=pretend.stub(name="test_project"),
created=datetime.datetime(2017, 2, 5, 0, 0, 0, 0),
)

result = email.send_removed_project_release_file_email(
pyramid_request,
[stub_user, stub_submitter_user],
file="test-file-0.0.0.tar.gz",
release=release,
submitter_name=stub_submitter_user.username,
submitter_role="Owner",
recipient_role="Maintainer",
)

assert result == {
"file": "test-file-0.0.0.tar.gz",
"project_name": release.project.name,
"release_version": release.version,
"submitter_name": stub_submitter_user.username,
"submitter_role": "owner",
"recipient_role_descr": "a maintainer",
}

subject_renderer.assert_(project_name="test_project")
subject_renderer.assert_(release_version="0.0.0")
body_renderer.assert_(file="test-file-0.0.0.tar.gz")
body_renderer.assert_(release_version="0.0.0")
body_renderer.assert_(project_name="test_project")
body_renderer.assert_(submitter_name=stub_submitter_user.username)
body_renderer.assert_(submitter_role="owner")
body_renderer.assert_(recipient_role_descr="a maintainer")

assert pyramid_request.task.calls == [
pretend.call(send_email),
pretend.call(send_email),
]

assert send_email.delay.calls == [
pretend.call(
"username <email@example.com>",
attr.asdict(
EmailMessage(
subject="Email Subject",
body_text="Email Body",
body_html=(
"<html>\n<head></head>\n"
"<body><p>Email HTML Body</p></body>\n</html>\n"
),
),
),
),
pretend.call(
"submitterusername <submiteremail@example.com>",
attr.asdict(
EmailMessage(
subject="Email Subject",
body_text="Email Body",
body_html=(
"<html>\n<head></head>\n"
"<body><p>Email HTML Body</p></body>\n</html>\n"
),
)
),
),
]


class TestTwoFactorEmail:
@pytest.mark.parametrize(
("action", "method", "pretty_method"),
Expand Down
42 changes: 41 additions & 1 deletion tests/unit/manage/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2813,7 +2813,7 @@ def test_delete_project_release_file_disallow_deletion(self):
)
]

def test_delete_project_release_file(self, db_request):
def test_delete_project_release_file(self, monkeypatch, db_request):
user = UserFactory.create()

project = ProjectFactory.create(name="foobar")
Expand All @@ -2834,6 +2834,25 @@ def test_delete_project_release_file(self, db_request):
db_request.user = user
db_request.remote_addr = "1.2.3.4"

get_user_role_in_project = pretend.call_recorder(
lambda project_name, username, req: "Owner"
)
monkeypatch.setattr(views, "get_user_role_in_project", get_user_role_in_project)

get_project_contributors = pretend.call_recorder(
lambda project_name, req: [db_request.user]
)
monkeypatch.setattr(views, "get_project_contributors", get_project_contributors)

send_removed_project_release_file_email = pretend.call_recorder(
lambda req, user, **k: None
)
monkeypatch.setattr(
views,
"send_removed_project_release_file_email",
send_removed_project_release_file_email,
)

view = views.ManageProjectRelease(release, db_request)

result = view.delete_project_release_file()
Expand Down Expand Up @@ -2865,6 +2884,27 @@ def test_delete_project_release_file(self, db_request):
)
]

assert get_user_role_in_project.calls == [
pretend.call(project.name, db_request.user.username, db_request,),
pretend.call(project.name, db_request.user.username, db_request,),
]

assert get_project_contributors.calls == [
pretend.call(project.name, db_request,)
]

assert send_removed_project_release_file_email.calls == [
pretend.call(
db_request,
db_request.user,
file=release_file.filename,
release=release,
submitter_name=db_request.user.username,
submitter_role="Owner",
recipient_role="Owner",
)
]

def test_delete_project_release_file_no_confirm(self):
release = pretend.stub(version="1.2.3", project=pretend.stub(name="foobar"))
request = pretend.stub(
Expand Down
18 changes: 18 additions & 0 deletions warehouse/email/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,24 @@ def send_removed_project_release_email(
}


@_email("removed-project-release-file")
def send_removed_project_release_file_email(
request, user, *, file, release, submitter_name, submitter_role, recipient_role
):
recipient_role_descr = "an owner"
if recipient_role == "Maintainer":
recipient_role_descr = "a maintainer"

return {
"file": file,
"project_name": release.project.name,
"release_version": release.version,
"submitter_name": submitter_name,
"submitter_role": submitter_role.lower(),
"recipient_role_descr": recipient_role_descr,
}


def includeme(config):
email_sending_class = config.maybe_dotted(config.registry.settings["mail.backend"])
config.register_service_factory(email_sending_class.create_service, IEmailSender)
Expand Down
10 changes: 5 additions & 5 deletions warehouse/locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -169,25 +169,25 @@ msgstr ""
msgid "Email address ${email_address} verified. ${confirm_message}."
msgstr ""

#: warehouse/manage/views.py:187
#: warehouse/manage/views.py:188
msgid "Email ${email_address} added - check your email for a verification link"
msgstr ""

#: warehouse/manage/views.py:668 warehouse/manage/views.py:704
#: warehouse/manage/views.py:669 warehouse/manage/views.py:705
msgid ""
"You must provision a two factor method before recovery codes can be "
"generated"
msgstr ""

#: warehouse/manage/views.py:679
#: warehouse/manage/views.py:680
msgid "Recovery codes already generated"
msgstr ""

#: warehouse/manage/views.py:680
#: warehouse/manage/views.py:681
msgid "Generating new recovery codes will invalidate your existing codes."
msgstr ""

#: warehouse/manage/views.py:730
#: warehouse/manage/views.py:731
msgid "Invalid credentials. Try again"
msgstr ""

Expand Down
21 changes: 21 additions & 0 deletions warehouse/manage/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
send_primary_email_change_email,
send_removed_project_email,
send_removed_project_release_email,
send_removed_project_release_file_email,
send_two_factor_added_email,
send_two_factor_removed_email,
)
Expand Down Expand Up @@ -1242,6 +1243,26 @@ def _error(message):
},
)

submitter_role = get_user_role_in_project(
project_name, self.request.user.username, self.request
)
contributors = get_project_contributors(project_name, self.request)

for contributor in contributors:
contributor_role = get_user_role_in_project(
project_name, contributor.username, self.request
)

send_removed_project_release_file_email(
self.request,
contributor,
file=release_file.filename,
release=self.release,
submitter_name=self.request.user.username,
submitter_role=submitter_role,
recipient_role=contributor_role,
)

self.request.db.delete(release_file)

self.request.session.flash(
Expand Down

0 comments on commit f8c866c

Please sign in to comment.