Skip to content

Commit f8c866c

Browse files
authored
Add email templates for removed file from release (#7498)
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 <[email protected]>
1 parent 6eda363 commit f8c866c

File tree

8 files changed

+382
-6
lines changed

8 files changed

+382
-6
lines changed

tests/unit/email/test_init.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,218 @@ def test_send_removed_project_release_emai_to_owner(
14901490
]
14911491

14921492

1493+
class TestRemovedReleaseFileEmail:
1494+
def test_send_removed_project_release_file_email_to_owner(
1495+
self, pyramid_request, pyramid_config, monkeypatch
1496+
):
1497+
stub_user = pretend.stub(
1498+
username="username",
1499+
name="",
1500+
1501+
primary_email=pretend.stub(email="[email protected]", verified=True),
1502+
)
1503+
stub_submitter_user = pretend.stub(
1504+
username="submitterusername",
1505+
name="",
1506+
1507+
primary_email=pretend.stub(
1508+
email="[email protected]", verified=True
1509+
),
1510+
)
1511+
1512+
subject_renderer = pyramid_config.testing_add_renderer(
1513+
"email/removed-project-release-file/subject.txt"
1514+
)
1515+
subject_renderer.string_response = "Email Subject"
1516+
body_renderer = pyramid_config.testing_add_renderer(
1517+
"email/removed-project-release-file/body.txt"
1518+
)
1519+
body_renderer.string_response = "Email Body"
1520+
html_renderer = pyramid_config.testing_add_renderer(
1521+
"email/removed-project-release-file/body.html"
1522+
)
1523+
html_renderer.string_response = "Email HTML Body"
1524+
1525+
send_email = pretend.stub(
1526+
delay=pretend.call_recorder(lambda *args, **kwargs: None)
1527+
)
1528+
pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email)
1529+
monkeypatch.setattr(email, "send_email", send_email)
1530+
1531+
release = pretend.stub(
1532+
version="0.0.0",
1533+
project=pretend.stub(name="test_project"),
1534+
created=datetime.datetime(2017, 2, 5, 0, 0, 0, 0),
1535+
)
1536+
1537+
result = email.send_removed_project_release_file_email(
1538+
pyramid_request,
1539+
[stub_user, stub_submitter_user],
1540+
file="test-file-0.0.0.tar.gz",
1541+
release=release,
1542+
submitter_name=stub_submitter_user.username,
1543+
submitter_role="Owner",
1544+
recipient_role="Owner",
1545+
)
1546+
1547+
assert result == {
1548+
"file": "test-file-0.0.0.tar.gz",
1549+
"project_name": release.project.name,
1550+
"release_version": release.version,
1551+
"submitter_name": stub_submitter_user.username,
1552+
"submitter_role": "owner",
1553+
"recipient_role_descr": "an owner",
1554+
}
1555+
1556+
subject_renderer.assert_(project_name="test_project")
1557+
subject_renderer.assert_(release_version="0.0.0")
1558+
body_renderer.assert_(file="test-file-0.0.0.tar.gz")
1559+
body_renderer.assert_(release_version="0.0.0")
1560+
body_renderer.assert_(project_name="test_project")
1561+
body_renderer.assert_(submitter_name=stub_submitter_user.username)
1562+
body_renderer.assert_(submitter_role="owner")
1563+
body_renderer.assert_(recipient_role_descr="an owner")
1564+
1565+
assert pyramid_request.task.calls == [
1566+
pretend.call(send_email),
1567+
pretend.call(send_email),
1568+
]
1569+
1570+
assert send_email.delay.calls == [
1571+
pretend.call(
1572+
"username <[email protected]>",
1573+
attr.asdict(
1574+
EmailMessage(
1575+
subject="Email Subject",
1576+
body_text="Email Body",
1577+
body_html=(
1578+
"<html>\n<head></head>\n"
1579+
"<body><p>Email HTML Body</p></body>\n</html>\n"
1580+
),
1581+
),
1582+
),
1583+
),
1584+
pretend.call(
1585+
"submitterusername <[email protected]>",
1586+
attr.asdict(
1587+
EmailMessage(
1588+
subject="Email Subject",
1589+
body_text="Email Body",
1590+
body_html=(
1591+
"<html>\n<head></head>\n"
1592+
"<body><p>Email HTML Body</p></body>\n</html>\n"
1593+
),
1594+
)
1595+
),
1596+
),
1597+
]
1598+
1599+
def test_send_removed_project_release_file_email_to_maintainer(
1600+
self, pyramid_request, pyramid_config, monkeypatch
1601+
):
1602+
stub_user = pretend.stub(
1603+
username="username",
1604+
name="",
1605+
1606+
primary_email=pretend.stub(email="[email protected]", verified=True),
1607+
)
1608+
stub_submitter_user = pretend.stub(
1609+
username="submitterusername",
1610+
name="",
1611+
1612+
primary_email=pretend.stub(
1613+
email="[email protected]", verified=True
1614+
),
1615+
)
1616+
1617+
subject_renderer = pyramid_config.testing_add_renderer(
1618+
"email/removed-project-release-file/subject.txt"
1619+
)
1620+
subject_renderer.string_response = "Email Subject"
1621+
body_renderer = pyramid_config.testing_add_renderer(
1622+
"email/removed-project-release-file/body.txt"
1623+
)
1624+
body_renderer.string_response = "Email Body"
1625+
html_renderer = pyramid_config.testing_add_renderer(
1626+
"email/removed-project-release-file/body.html"
1627+
)
1628+
html_renderer.string_response = "Email HTML Body"
1629+
1630+
send_email = pretend.stub(
1631+
delay=pretend.call_recorder(lambda *args, **kwargs: None)
1632+
)
1633+
pyramid_request.task = pretend.call_recorder(lambda *args, **kwargs: send_email)
1634+
monkeypatch.setattr(email, "send_email", send_email)
1635+
1636+
release = pretend.stub(
1637+
version="0.0.0",
1638+
project=pretend.stub(name="test_project"),
1639+
created=datetime.datetime(2017, 2, 5, 0, 0, 0, 0),
1640+
)
1641+
1642+
result = email.send_removed_project_release_file_email(
1643+
pyramid_request,
1644+
[stub_user, stub_submitter_user],
1645+
file="test-file-0.0.0.tar.gz",
1646+
release=release,
1647+
submitter_name=stub_submitter_user.username,
1648+
submitter_role="Owner",
1649+
recipient_role="Maintainer",
1650+
)
1651+
1652+
assert result == {
1653+
"file": "test-file-0.0.0.tar.gz",
1654+
"project_name": release.project.name,
1655+
"release_version": release.version,
1656+
"submitter_name": stub_submitter_user.username,
1657+
"submitter_role": "owner",
1658+
"recipient_role_descr": "a maintainer",
1659+
}
1660+
1661+
subject_renderer.assert_(project_name="test_project")
1662+
subject_renderer.assert_(release_version="0.0.0")
1663+
body_renderer.assert_(file="test-file-0.0.0.tar.gz")
1664+
body_renderer.assert_(release_version="0.0.0")
1665+
body_renderer.assert_(project_name="test_project")
1666+
body_renderer.assert_(submitter_name=stub_submitter_user.username)
1667+
body_renderer.assert_(submitter_role="owner")
1668+
body_renderer.assert_(recipient_role_descr="a maintainer")
1669+
1670+
assert pyramid_request.task.calls == [
1671+
pretend.call(send_email),
1672+
pretend.call(send_email),
1673+
]
1674+
1675+
assert send_email.delay.calls == [
1676+
pretend.call(
1677+
"username <[email protected]>",
1678+
attr.asdict(
1679+
EmailMessage(
1680+
subject="Email Subject",
1681+
body_text="Email Body",
1682+
body_html=(
1683+
"<html>\n<head></head>\n"
1684+
"<body><p>Email HTML Body</p></body>\n</html>\n"
1685+
),
1686+
),
1687+
),
1688+
),
1689+
pretend.call(
1690+
"submitterusername <[email protected]>",
1691+
attr.asdict(
1692+
EmailMessage(
1693+
subject="Email Subject",
1694+
body_text="Email Body",
1695+
body_html=(
1696+
"<html>\n<head></head>\n"
1697+
"<body><p>Email HTML Body</p></body>\n</html>\n"
1698+
),
1699+
)
1700+
),
1701+
),
1702+
]
1703+
1704+
14931705
class TestTwoFactorEmail:
14941706
@pytest.mark.parametrize(
14951707
("action", "method", "pretty_method"),

tests/unit/manage/test_views.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2813,7 +2813,7 @@ def test_delete_project_release_file_disallow_deletion(self):
28132813
)
28142814
]
28152815

2816-
def test_delete_project_release_file(self, db_request):
2816+
def test_delete_project_release_file(self, monkeypatch, db_request):
28172817
user = UserFactory.create()
28182818

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

2837+
get_user_role_in_project = pretend.call_recorder(
2838+
lambda project_name, username, req: "Owner"
2839+
)
2840+
monkeypatch.setattr(views, "get_user_role_in_project", get_user_role_in_project)
2841+
2842+
get_project_contributors = pretend.call_recorder(
2843+
lambda project_name, req: [db_request.user]
2844+
)
2845+
monkeypatch.setattr(views, "get_project_contributors", get_project_contributors)
2846+
2847+
send_removed_project_release_file_email = pretend.call_recorder(
2848+
lambda req, user, **k: None
2849+
)
2850+
monkeypatch.setattr(
2851+
views,
2852+
"send_removed_project_release_file_email",
2853+
send_removed_project_release_file_email,
2854+
)
2855+
28372856
view = views.ManageProjectRelease(release, db_request)
28382857

28392858
result = view.delete_project_release_file()
@@ -2865,6 +2884,27 @@ def test_delete_project_release_file(self, db_request):
28652884
)
28662885
]
28672886

2887+
assert get_user_role_in_project.calls == [
2888+
pretend.call(project.name, db_request.user.username, db_request,),
2889+
pretend.call(project.name, db_request.user.username, db_request,),
2890+
]
2891+
2892+
assert get_project_contributors.calls == [
2893+
pretend.call(project.name, db_request,)
2894+
]
2895+
2896+
assert send_removed_project_release_file_email.calls == [
2897+
pretend.call(
2898+
db_request,
2899+
db_request.user,
2900+
file=release_file.filename,
2901+
release=release,
2902+
submitter_name=db_request.user.username,
2903+
submitter_role="Owner",
2904+
recipient_role="Owner",
2905+
)
2906+
]
2907+
28682908
def test_delete_project_release_file_no_confirm(self):
28692909
release = pretend.stub(version="1.2.3", project=pretend.stub(name="foobar"))
28702910
request = pretend.stub(

warehouse/email/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,24 @@ def send_removed_project_release_email(
247247
}
248248

249249

250+
@_email("removed-project-release-file")
251+
def send_removed_project_release_file_email(
252+
request, user, *, file, release, submitter_name, submitter_role, recipient_role
253+
):
254+
recipient_role_descr = "an owner"
255+
if recipient_role == "Maintainer":
256+
recipient_role_descr = "a maintainer"
257+
258+
return {
259+
"file": file,
260+
"project_name": release.project.name,
261+
"release_version": release.version,
262+
"submitter_name": submitter_name,
263+
"submitter_role": submitter_role.lower(),
264+
"recipient_role_descr": recipient_role_descr,
265+
}
266+
267+
250268
def includeme(config):
251269
email_sending_class = config.maybe_dotted(config.registry.settings["mail.backend"])
252270
config.register_service_factory(email_sending_class.create_service, IEmailSender)

warehouse/locale/messages.pot

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,25 +169,25 @@ msgstr ""
169169
msgid "Email address ${email_address} verified. ${confirm_message}."
170170
msgstr ""
171171

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

176-
#: warehouse/manage/views.py:668 warehouse/manage/views.py:704
176+
#: warehouse/manage/views.py:669 warehouse/manage/views.py:705
177177
msgid ""
178178
"You must provision a two factor method before recovery codes can be "
179179
"generated"
180180
msgstr ""
181181

182-
#: warehouse/manage/views.py:679
182+
#: warehouse/manage/views.py:680
183183
msgid "Recovery codes already generated"
184184
msgstr ""
185185

186-
#: warehouse/manage/views.py:680
186+
#: warehouse/manage/views.py:681
187187
msgid "Generating new recovery codes will invalidate your existing codes."
188188
msgstr ""
189189

190-
#: warehouse/manage/views.py:730
190+
#: warehouse/manage/views.py:731
191191
msgid "Invalid credentials. Try again"
192192
msgstr ""
193193

warehouse/manage/views.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
send_primary_email_change_email,
4141
send_removed_project_email,
4242
send_removed_project_release_email,
43+
send_removed_project_release_file_email,
4344
send_two_factor_added_email,
4445
send_two_factor_removed_email,
4546
)
@@ -1242,6 +1243,26 @@ def _error(message):
12421243
},
12431244
)
12441245

1246+
submitter_role = get_user_role_in_project(
1247+
project_name, self.request.user.username, self.request
1248+
)
1249+
contributors = get_project_contributors(project_name, self.request)
1250+
1251+
for contributor in contributors:
1252+
contributor_role = get_user_role_in_project(
1253+
project_name, contributor.username, self.request
1254+
)
1255+
1256+
send_removed_project_release_file_email(
1257+
self.request,
1258+
contributor,
1259+
file=release_file.filename,
1260+
release=self.release,
1261+
submitter_name=self.request.user.username,
1262+
submitter_role=submitter_role,
1263+
recipient_role=contributor_role,
1264+
)
1265+
12451266
self.request.db.delete(release_file)
12461267

12471268
self.request.session.flash(

0 commit comments

Comments
 (0)