FossilRepo

fossilrepo / tests / test_email_templates.py
Blame History Raw 291 lines
1
"""Tests for HTML email notification templates and updated sending logic.
2
3
Verifies that notify_project_event and send_digest produce HTML emails
4
using the templates, include plain text fallbacks, and respect delivery
5
mode preferences.
6
"""
7
8
from unittest.mock import patch
9
10
import pytest
11
from django.contrib.auth.models import User
12
from django.template.loader import render_to_string
13
14
from fossil.notifications import Notification, NotificationPreference, ProjectWatch, notify_project_event
15
16
# --- Template rendering tests ---
17
18
19
@pytest.mark.django_db
20
class TestNotificationTemplateRendering:
21
def test_notification_template_renders(self):
22
html = render_to_string(
23
"email/notification.html",
24
{
25
"event_type": "checkin",
26
"project_name": "My Project",
27
"message": "Added new feature",
28
"action_url": "/projects/my-project/fossil/checkin/abc123/",
29
"project_url": "/projects/my-project/",
30
"unsubscribe_url": "/projects/my-project/fossil/watch/",
31
"preferences_url": "/auth/notifications/",
32
},
33
)
34
assert "fossil<span>repo</span>" in html
35
assert "My Project" in html
36
assert "Added new feature" in html
37
assert "checkin" in html
38
assert "View Details" in html
39
assert "/projects/my-project/fossil/checkin/abc123/" in html
40
assert "Unsubscribe" in html
41
42
def test_notification_template_without_action_url(self):
43
html = render_to_string(
44
"email/notification.html",
45
{
46
"event_type": "ticket",
47
"project_name": "My Project",
48
"message": "New ticket filed",
49
"action_url": "",
50
"project_url": "/projects/my-project/",
51
"unsubscribe_url": "/projects/my-project/fossil/watch/",
52
"preferences_url": "/auth/notifications/",
53
},
54
)
55
assert "View Details" not in html
56
assert "New ticket filed" in html
57
58
def test_notification_template_event_types(self):
59
for event_type in ["checkin", "ticket", "wiki", "release", "forum"]:
60
html = render_to_string(
61
"email/notification.html",
62
{
63
"event_type": event_type,
64
"project_name": "Test",
65
"message": "Test message",
66
"action_url": "",
67
"project_url": "/projects/test/",
68
"unsubscribe_url": "/projects/test/fossil/watch/",
69
"preferences_url": "/auth/notifications/",
70
},
71
)
72
assert event_type in html
73
74
def test_digest_template_renders(self):
75
class MockNotif:
76
def __init__(self, event_type, title, project_name):
77
self.event_type = event_type
78
self.title = title
79
80
class MockProject:
81
name = project_name
82
83
self.project = MockProject()
84
85
notifications = [
86
MockNotif("checkin", "Added login page", "Frontend"),
87
MockNotif("ticket", "Bug: 404 on settings", "Backend"),
88
MockNotif("wiki", "Updated README", "Docs"),
89
]
90
html = render_to_string(
91
"email/digest.html",
92
{
93
"digest_type": "daily",
94
"count": 3,
95
"notifications": notifications,
96
"overflow_count": 0,
97
"dashboard_url": "/",
98
"preferences_url": "/auth/notifications/",
99
},
100
)
101
assert "Daily Digest" in html
102
assert "3 update" in html
103
assert "Frontend" in html
104
assert "Backend" in html
105
assert "Docs" in html
106
assert "Added login page" in html
107
assert "View All Notifications" in html
108
109
def test_digest_template_overflow(self):
110
html = render_to_string(
111
"email/digest.html",
112
{
113
"digest_type": "weekly",
114
"count": 75,
115
"notifications": [],
116
"overflow_count": 25,
117
"dashboard_url": "/",
118
"preferences_url": "/auth/notifications/",
119
},
120
)
121
assert "Weekly Digest" in html
122
assert "75 update" in html
123
assert "25 more" in html
124
125
126
# --- notify_project_event HTML email tests ---
127
128
129
@pytest.mark.django_db
130
class TestNotifyProjectEventHTML:
131
@pytest.fixture
132
def watcher_user(self, db, admin_user, sample_project):
133
user = User.objects.create_user(username="watcher_email", email="[email protected]", password="testpass123")
134
ProjectWatch.objects.create(user=user, project=sample_project, email_enabled=True, created_by=admin_user)
135
return user
136
137
@pytest.fixture
138
def daily_watcher(self, db, admin_user, sample_project):
139
user = User.objects.create_user(username="daily_watcher", email="[email protected]", password="testpass123")
140
ProjectWatch.objects.create(user=user, project=sample_project, email_enabled=True, created_by=admin_user)
141
NotificationPreference.objects.create(user=user, delivery_mode="daily")
142
return user
143
144
def test_immediate_sends_html_email(self, watcher_user, sample_project):
145
with patch("fossil.notifications.send_mail") as mock_send:
146
notify_project_event(
147
project=sample_project,
148
event_type="checkin",
149
title="New commit",
150
body="Added login feature",
151
url="/projects/frontend-app/fossil/checkin/abc/",
152
)
153
154
mock_send.assert_called_once()
155
call_kwargs = mock_send.call_args.kwargs
156
assert "html_message" in call_kwargs
157
assert "fossil<span>repo</span>" in call_kwargs["html_message"]
158
assert "checkin" in call_kwargs["html_message"]
159
assert "Added login feature" in call_kwargs["html_message"]
160
# Plain text fallback is also present
161
assert call_kwargs["message"] != ""
162
163
def test_immediate_subject_format(self, watcher_user, sample_project):
164
with patch("fossil.notifications.send_mail") as mock_send:
165
notify_project_event(
166
project=sample_project,
167
event_type="ticket",
168
title="Bug report: login broken",
169
body="Users can't log in",
170
)
171
172
call_kwargs = mock_send.call_args.kwargs
173
assert "[Frontend App]" in call_kwargs["subject"]
174
assert "ticket:" in call_kwargs["subject"]
175
176
def test_daily_user_not_emailed_immediately(self, daily_watcher, sample_project):
177
with patch("fossil.notifications.send_mail") as mock_send:
178
notify_project_event(
179
project=sample_project,
180
event_type="checkin",
181
title="New commit",
182
body="Some change",
183
)
184
185
mock_send.assert_not_called()
186
# But notification record is still created for digest
187
assert Notification.objects.filter(user=daily_watcher).count() == 1
188
189
def test_notification_created_for_immediate_user(self, watcher_user, sample_project):
190
with patch("fossil.notifications.send_mail"):
191
notify_project_event(
192
project=sample_project,
193
event_type="wiki",
194
title="Wiki updated",
195
body="New page",
196
)
197
198
notif = Notification.objects.get(user=watcher_user)
199
assert notif.event_type == "wiki"
200
assert notif.title == "Wiki updated"
201
assert notif.emailed is True
202
203
204
# --- send_digest HTML email tests ---
205
206
207
@pytest.mark.django_db
208
class TestSendDigestHTML:
209
@pytest.fixture
210
def daily_user(self, db):
211
user = User.objects.create_user(username="daily_html", email="[email protected]", password="testpass123")
212
NotificationPreference.objects.create(user=user, delivery_mode="daily")
213
return user
214
215
def test_digest_sends_html_email(self, daily_user, sample_project):
216
for i in range(3):
217
Notification.objects.create(
218
user=daily_user,
219
project=sample_project,
220
event_type="checkin",
221
title=f"Commit #{i}",
222
)
223
224
from fossil.tasks import send_digest
225
226
with patch("django.core.mail.send_mail") as mock_send:
227
send_digest.apply(kwargs={"mode": "daily"})
228
229
mock_send.assert_called_once()
230
call_kwargs = mock_send.call_args.kwargs
231
assert "html_message" in call_kwargs
232
assert "Daily Digest" in call_kwargs["html_message"]
233
assert "3 update" in call_kwargs["html_message"]
234
assert 'fossil<span style="color: #DC394C;">repo</span>' in call_kwargs["html_message"]
235
# Plain text fallback
236
assert "3 new notifications" in call_kwargs["message"]
237
238
def test_digest_html_includes_project_names(self, daily_user, sample_project):
239
Notification.objects.create(
240
user=daily_user,
241
project=sample_project,
242
event_type="ticket",
243
title="Bug filed",
244
)
245
246
from fossil.tasks import send_digest
247
248
with patch("django.core.mail.send_mail") as mock_send:
249
send_digest.apply(kwargs={"mode": "daily"})
250
251
call_kwargs = mock_send.call_args.kwargs
252
assert sample_project.name in call_kwargs["html_message"]
253
254
def test_digest_html_overflow_message(self, daily_user, sample_project):
255
for i in range(55):
256
Notification.objects.create(
257
user=daily_user,
258
project=sample_project,
259
event_type="checkin",
260
title=f"Commit #{i}",
261
)
262
263
from fossil.tasks import send_digest
264
265
with patch("django.core.mail.send_mail") as mock_send:
266
send_digest.apply(kwargs={"mode": "daily"})
267
268
call_kwargs = mock_send.call_args.kwargs
269
assert "5 more" in call_kwargs["html_message"]
270
271
def test_weekly_digest_html(self, db):
272
user = User.objects.create_user(username="weekly_html", email="[email protected]", password="testpass123")
273
NotificationPreference.objects.create(user=user, delivery_mode="weekly")
274
275
from organization.models import Organization
276
from projects.models import Project
277
278
org = Organization.objects.create(name="Test Org Digest")
279
project = Project.objects.create(name="Digest Project", organization=org, visibility="private")
280
281
Notification.objects.create(user=user, project=project, event_type="wiki", title="Wiki edit")
282
283
from fossil.tasks import send_digest
284
285
with patch("django.core.mail.send_mail") as mock_send:
286
send_digest.apply(kwargs={"mode": "weekly"})
287
288
mock_send.assert_called_once()
289
call_kwargs = mock_send.call_args.kwargs
290
assert "Weekly Digest" in call_kwargs["html_message"]
291

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button