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.urlresolvers import reverse
23 from django.core import mail
24 from django.conf import settings
25 from django.db.utils import IntegrityError
26 from patchwork.models import Patch, State, PatchChangeNotification, EmailOptout
27 from patchwork.tests.utils import defaults, create_maintainer
28 from patchwork.utils import send_notifications
30 class PatchNotificationModelTest(TestCase):
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):
125 self.project = defaults.project
126 self.project.send_notifications = True
128 self.submitter = defaults.patch_author_person
129 self.submitter.save()
130 self.patch = Patch(project = self.project, msgid = 'testpatch',
131 name = 'testpatch', content = '',
132 submitter = self.submitter)
137 self.submitter.delete()
138 self.project.delete()
140 def _expireNotifications(self, **kwargs):
141 timestamp = datetime.datetime.now() - \
142 datetime.timedelta(minutes =
143 settings.NOTIFICATION_DELAY_MINUTES + 1)
145 qs = PatchChangeNotification.objects.all()
147 qs = qs.filter(**kwargs)
149 qs.update(last_modified = timestamp)
151 def testNoNotifications(self):
152 self.assertEquals(send_notifications(), [])
154 def testNoReadyNotifications(self):
155 """ We shouldn't see immediate notifications"""
156 PatchChangeNotification(patch = self.patch,
157 orig_state = self.patch.state).save()
159 errors = send_notifications()
160 self.assertEquals(errors, [])
161 self.assertEquals(len(mail.outbox), 0)
163 def testNotifications(self):
164 PatchChangeNotification(patch = self.patch,
165 orig_state = self.patch.state).save()
166 self._expireNotifications()
168 errors = send_notifications()
169 self.assertEquals(errors, [])
170 self.assertEquals(len(mail.outbox), 1)
172 self.assertEquals(msg.to, [self.submitter.email])
173 self.assertTrue(self.patch.get_absolute_url() in msg.body)
175 def testNotificationEscaping(self):
176 self.patch.name = 'Patch name with " character'
178 PatchChangeNotification(patch = self.patch,
179 orig_state = self.patch.state).save()
180 self._expireNotifications()
182 errors = send_notifications()
183 self.assertEquals(errors, [])
184 self.assertEquals(len(mail.outbox), 1)
186 self.assertEquals(msg.to, [self.submitter.email])
187 self.assertFalse('"' in msg.body)
189 def testNotificationOptout(self):
190 """ensure opt-out addresses don't get notifications"""
191 PatchChangeNotification(patch = self.patch,
192 orig_state = self.patch.state).save()
193 self._expireNotifications()
195 EmailOptout(email = self.submitter.email).save()
197 errors = send_notifications()
198 self.assertEquals(errors, [])
199 self.assertEquals(len(mail.outbox), 0)
201 def testNotificationMerge(self):
202 patches = [self.patch,
203 Patch(project = self.project, msgid = 'testpatch-2',
204 name = 'testpatch 2', content = '',
205 submitter = self.submitter)]
207 for patch in patches:
209 PatchChangeNotification(patch = patch,
210 orig_state = patch.state).save()
212 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
213 self._expireNotifications()
214 errors = send_notifications()
215 self.assertEquals(errors, [])
216 self.assertEquals(len(mail.outbox), 1)
218 self.assertTrue(patches[0].get_absolute_url() in msg.body)
219 self.assertTrue(patches[1].get_absolute_url() in msg.body)
221 def testUnexpiredNotificationMerge(self):
222 """Test that when there are multiple pending notifications, with
223 at least one within the notification delay, that other notifications
225 patches = [self.patch,
226 Patch(project = self.project, msgid = 'testpatch-2',
227 name = 'testpatch 2', content = '',
228 submitter = self.submitter)]
230 for patch in patches:
232 PatchChangeNotification(patch = patch,
233 orig_state = patch.state).save()
235 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
236 self._expireNotifications()
238 # update one notification, to bring it out of the notification delay
239 patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0]
242 # the updated notification should prevent the other from being sent
243 errors = send_notifications()
244 self.assertEquals(errors, [])
245 self.assertEquals(len(mail.outbox), 0)
247 # expire the updated notification
248 self._expireNotifications()
250 errors = send_notifications()
251 self.assertEquals(errors, [])
252 self.assertEquals(len(mail.outbox), 1)
254 self.assertTrue(patches[0].get_absolute_url() in msg.body)
255 self.assertTrue(patches[1].get_absolute_url() in msg.body)