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 fixtures = ['default_states']
33 """Tests for the creation & update of the PatchChangeNotification model"""
36 self.project = defaults.project
37 self.project.send_notifications = True
39 self.submitter = defaults.patch_author_person
41 self.patch = Patch(project = self.project, msgid = 'testpatch',
42 name = 'testpatch', content = '',
43 submitter = self.submitter)
47 self.submitter.delete()
50 def testPatchCreation(self):
51 """Ensure we don't get a notification on create"""
53 self.assertEqual(PatchChangeNotification.objects.count(), 0)
55 def testPatchUninterestingChange(self):
56 """Ensure we don't get a notification for "uninteresting" changes"""
58 self.patch.archived = True
60 self.assertEqual(PatchChangeNotification.objects.count(), 0)
62 def testPatchChange(self):
63 """Ensure we get a notification for interesting patch changes"""
65 oldstate = self.patch.state
66 state = State.objects.exclude(pk = oldstate.pk)[0]
68 self.patch.state = state
70 self.assertEqual(PatchChangeNotification.objects.count(), 1)
71 notification = PatchChangeNotification.objects.all()[0]
72 self.assertEqual(notification.patch, self.patch)
73 self.assertEqual(notification.orig_state, oldstate)
75 def testNotificationCancelled(self):
76 """Ensure we cancel notifications that are no longer valid"""
78 oldstate = self.patch.state
79 state = State.objects.exclude(pk = oldstate.pk)[0]
81 self.patch.state = state
83 self.assertEqual(PatchChangeNotification.objects.count(), 1)
85 self.patch.state = oldstate
87 self.assertEqual(PatchChangeNotification.objects.count(), 0)
89 def testNotificationUpdated(self):
90 """Ensure we update notifications when the patch has a second change,
91 but keep the original patch details"""
93 oldstate = self.patch.state
94 newstates = State.objects.exclude(pk = oldstate.pk)[:2]
96 self.patch.state = newstates[0]
98 self.assertEqual(PatchChangeNotification.objects.count(), 1)
99 notification = PatchChangeNotification.objects.all()[0]
100 self.assertEqual(notification.orig_state, oldstate)
101 orig_timestamp = notification.last_modified
103 self.patch.state = newstates[1]
105 self.assertEqual(PatchChangeNotification.objects.count(), 1)
106 notification = PatchChangeNotification.objects.all()[0]
107 self.assertEqual(notification.orig_state, oldstate)
108 self.assertTrue(notification.last_modified >= orig_timestamp)
110 def testProjectNotificationsDisabled(self):
111 """Ensure we don't see notifications created when a project is
112 configured not to send them"""
113 self.project.send_notifications = False
117 oldstate = self.patch.state
118 state = State.objects.exclude(pk = oldstate.pk)[0]
120 self.patch.state = state
122 self.assertEqual(PatchChangeNotification.objects.count(), 0)
124 class PatchNotificationEmailTest(TestCase):
125 fixtures = ['default_states']
128 self.project = defaults.project
129 self.project.send_notifications = True
131 self.submitter = defaults.patch_author_person
132 self.submitter.save()
133 self.patch = Patch(project = self.project, msgid = 'testpatch',
134 name = 'testpatch', content = '',
135 submitter = self.submitter)
140 self.submitter.delete()
141 self.project.delete()
143 def _expireNotifications(self, **kwargs):
144 timestamp = datetime.datetime.now() - \
145 datetime.timedelta(minutes =
146 settings.NOTIFICATION_DELAY_MINUTES + 1)
148 qs = PatchChangeNotification.objects.all()
150 qs = qs.filter(**kwargs)
152 qs.update(last_modified = timestamp)
154 def testNoNotifications(self):
155 self.assertEquals(send_notifications(), [])
157 def testNoReadyNotifications(self):
158 """ We shouldn't see immediate notifications"""
159 PatchChangeNotification(patch = self.patch,
160 orig_state = self.patch.state).save()
162 errors = send_notifications()
163 self.assertEquals(errors, [])
164 self.assertEquals(len(mail.outbox), 0)
166 def testNotifications(self):
167 PatchChangeNotification(patch = self.patch,
168 orig_state = self.patch.state).save()
169 self._expireNotifications()
171 errors = send_notifications()
172 self.assertEquals(errors, [])
173 self.assertEquals(len(mail.outbox), 1)
175 self.assertEquals(msg.to, [self.submitter.email])
176 self.assertTrue(self.patch.get_absolute_url() in msg.body)
178 def testNotificationEscaping(self):
179 self.patch.name = 'Patch name with " character'
181 PatchChangeNotification(patch = self.patch,
182 orig_state = self.patch.state).save()
183 self._expireNotifications()
185 errors = send_notifications()
186 self.assertEquals(errors, [])
187 self.assertEquals(len(mail.outbox), 1)
189 self.assertEquals(msg.to, [self.submitter.email])
190 self.assertFalse('"' in msg.body)
192 def testNotificationOptout(self):
193 """ensure opt-out addresses don't get notifications"""
194 PatchChangeNotification(patch = self.patch,
195 orig_state = self.patch.state).save()
196 self._expireNotifications()
198 EmailOptout(email = self.submitter.email).save()
200 errors = send_notifications()
201 self.assertEquals(errors, [])
202 self.assertEquals(len(mail.outbox), 0)
204 def testNotificationMerge(self):
205 patches = [self.patch,
206 Patch(project = self.project, msgid = 'testpatch-2',
207 name = 'testpatch 2', content = '',
208 submitter = self.submitter)]
210 for patch in patches:
212 PatchChangeNotification(patch = patch,
213 orig_state = patch.state).save()
215 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
216 self._expireNotifications()
217 errors = send_notifications()
218 self.assertEquals(errors, [])
219 self.assertEquals(len(mail.outbox), 1)
221 self.assertTrue(patches[0].get_absolute_url() in msg.body)
222 self.assertTrue(patches[1].get_absolute_url() in msg.body)
224 def testUnexpiredNotificationMerge(self):
225 """Test that when there are multiple pending notifications, with
226 at least one within the notification delay, that other notifications
228 patches = [self.patch,
229 Patch(project = self.project, msgid = 'testpatch-2',
230 name = 'testpatch 2', content = '',
231 submitter = self.submitter)]
233 for patch in patches:
235 PatchChangeNotification(patch = patch,
236 orig_state = patch.state).save()
238 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
239 self._expireNotifications()
241 # update one notification, to bring it out of the notification delay
242 patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0]
245 # the updated notification should prevent the other from being sent
246 errors = send_notifications()
247 self.assertEquals(errors, [])
248 self.assertEquals(len(mail.outbox), 0)
250 # expire the updated notification
251 self._expireNotifications()
253 errors = send_notifications()
254 self.assertEquals(errors, [])
255 self.assertEquals(len(mail.outbox), 1)
257 self.assertTrue(patches[0].get_absolute_url() in msg.body)
258 self.assertTrue(patches[1].get_absolute_url() in msg.body)