1 # Patchwork - automated patch tracking system
2 # Copyright (C) 2011 Jeremy Kerr <jk@ozlabs.org>
4 # This file is part of the Patchwork package.
6 # Patchwork is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # Patchwork is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with Patchwork; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 from django.test import TestCase
22 from django.core import mail
23 from django.conf import settings
24 from patchwork.models import Patch, State, PatchChangeNotification, EmailOptout
25 from patchwork.tests.utils import defaults
26 from patchwork.utils import send_notifications
28 class PatchNotificationModelTest(TestCase):
29 fixtures = ['default_states']
31 """Tests for the creation & update of the PatchChangeNotification model"""
34 self.project = defaults.project
35 self.project.send_notifications = True
37 self.submitter = defaults.patch_author_person
39 self.patch = Patch(project = self.project, msgid = 'testpatch',
40 name = 'testpatch', content = '',
41 submitter = self.submitter)
45 self.submitter.delete()
48 def testPatchCreation(self):
49 """Ensure we don't get a notification on create"""
51 self.assertEqual(PatchChangeNotification.objects.count(), 0)
53 def testPatchUninterestingChange(self):
54 """Ensure we don't get a notification for "uninteresting" changes"""
56 self.patch.archived = True
58 self.assertEqual(PatchChangeNotification.objects.count(), 0)
60 def testPatchChange(self):
61 """Ensure we get a notification for interesting patch changes"""
63 oldstate = self.patch.state
64 state = State.objects.exclude(pk = oldstate.pk)[0]
66 self.patch.state = state
68 self.assertEqual(PatchChangeNotification.objects.count(), 1)
69 notification = PatchChangeNotification.objects.all()[0]
70 self.assertEqual(notification.patch, self.patch)
71 self.assertEqual(notification.orig_state, oldstate)
73 def testNotificationCancelled(self):
74 """Ensure we cancel notifications that are no longer valid"""
76 oldstate = self.patch.state
77 state = State.objects.exclude(pk = oldstate.pk)[0]
79 self.patch.state = state
81 self.assertEqual(PatchChangeNotification.objects.count(), 1)
83 self.patch.state = oldstate
85 self.assertEqual(PatchChangeNotification.objects.count(), 0)
87 def testNotificationUpdated(self):
88 """Ensure we update notifications when the patch has a second change,
89 but keep the original patch details"""
91 oldstate = self.patch.state
92 newstates = State.objects.exclude(pk = oldstate.pk)[:2]
94 self.patch.state = newstates[0]
96 self.assertEqual(PatchChangeNotification.objects.count(), 1)
97 notification = PatchChangeNotification.objects.all()[0]
98 self.assertEqual(notification.orig_state, oldstate)
99 orig_timestamp = notification.last_modified
101 self.patch.state = newstates[1]
103 self.assertEqual(PatchChangeNotification.objects.count(), 1)
104 notification = PatchChangeNotification.objects.all()[0]
105 self.assertEqual(notification.orig_state, oldstate)
106 self.assertTrue(notification.last_modified >= orig_timestamp)
108 def testProjectNotificationsDisabled(self):
109 """Ensure we don't see notifications created when a project is
110 configured not to send them"""
111 self.project.send_notifications = False
115 oldstate = self.patch.state
116 state = State.objects.exclude(pk = oldstate.pk)[0]
118 self.patch.state = state
120 self.assertEqual(PatchChangeNotification.objects.count(), 0)
122 class PatchNotificationEmailTest(TestCase):
123 fixtures = ['default_states']
126 self.project = defaults.project
127 self.project.send_notifications = True
129 self.submitter = defaults.patch_author_person
130 self.submitter.save()
131 self.patch = Patch(project = self.project, msgid = 'testpatch',
132 name = 'testpatch', content = '',
133 submitter = self.submitter)
138 self.submitter.delete()
139 self.project.delete()
141 def _expireNotifications(self, **kwargs):
142 timestamp = datetime.datetime.now() - \
143 datetime.timedelta(minutes =
144 settings.NOTIFICATION_DELAY_MINUTES + 1)
146 qs = PatchChangeNotification.objects.all()
148 qs = qs.filter(**kwargs)
150 qs.update(last_modified = timestamp)
152 def testNoNotifications(self):
153 self.assertEquals(send_notifications(), [])
155 def testNoReadyNotifications(self):
156 """ We shouldn't see immediate notifications"""
157 PatchChangeNotification(patch = self.patch,
158 orig_state = self.patch.state).save()
160 errors = send_notifications()
161 self.assertEquals(errors, [])
162 self.assertEquals(len(mail.outbox), 0)
164 def testNotifications(self):
165 PatchChangeNotification(patch = self.patch,
166 orig_state = self.patch.state).save()
167 self._expireNotifications()
169 errors = send_notifications()
170 self.assertEquals(errors, [])
171 self.assertEquals(len(mail.outbox), 1)
173 self.assertEquals(msg.to, [self.submitter.email])
174 self.assertTrue(self.patch.get_absolute_url() in msg.body)
176 def testNotificationEscaping(self):
177 self.patch.name = 'Patch name with " character'
179 PatchChangeNotification(patch = self.patch,
180 orig_state = self.patch.state).save()
181 self._expireNotifications()
183 errors = send_notifications()
184 self.assertEquals(errors, [])
185 self.assertEquals(len(mail.outbox), 1)
187 self.assertEquals(msg.to, [self.submitter.email])
188 self.assertFalse('"' in msg.body)
190 def testNotificationOptout(self):
191 """ensure opt-out addresses don't get notifications"""
192 PatchChangeNotification(patch = self.patch,
193 orig_state = self.patch.state).save()
194 self._expireNotifications()
196 EmailOptout(email = self.submitter.email).save()
198 errors = send_notifications()
199 self.assertEquals(errors, [])
200 self.assertEquals(len(mail.outbox), 0)
202 def testNotificationMerge(self):
203 patches = [self.patch,
204 Patch(project = self.project, msgid = 'testpatch-2',
205 name = 'testpatch 2', content = '',
206 submitter = self.submitter)]
208 for patch in patches:
210 PatchChangeNotification(patch = patch,
211 orig_state = patch.state).save()
213 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
214 self._expireNotifications()
215 errors = send_notifications()
216 self.assertEquals(errors, [])
217 self.assertEquals(len(mail.outbox), 1)
219 self.assertTrue(patches[0].get_absolute_url() in msg.body)
220 self.assertTrue(patches[1].get_absolute_url() in msg.body)
222 def testUnexpiredNotificationMerge(self):
223 """Test that when there are multiple pending notifications, with
224 at least one within the notification delay, that other notifications
226 patches = [self.patch,
227 Patch(project = self.project, msgid = 'testpatch-2',
228 name = 'testpatch 2', content = '',
229 submitter = self.submitter)]
231 for patch in patches:
233 PatchChangeNotification(patch = patch,
234 orig_state = patch.state).save()
236 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
237 self._expireNotifications()
239 # update one notification, to bring it out of the notification delay
240 patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0]
243 # the updated notification should prevent the other from being sent
244 errors = send_notifications()
245 self.assertEquals(errors, [])
246 self.assertEquals(len(mail.outbox), 0)
248 # expire the updated notification
249 self._expireNotifications()
251 errors = send_notifications()
252 self.assertEquals(errors, [])
253 self.assertEquals(len(mail.outbox), 1)
255 self.assertTrue(patches[0].get_absolute_url() in msg.body)
256 self.assertTrue(patches[1].get_absolute_url() in msg.body)