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 testNotificationOptout(self):
176 """ensure opt-out addresses don't get notifications"""
177 PatchChangeNotification(patch = self.patch,
178 orig_state = self.patch.state).save()
179 self._expireNotifications()
181 EmailOptout(email = self.submitter.email).save()
183 errors = send_notifications()
184 self.assertEquals(errors, [])
185 self.assertEquals(len(mail.outbox), 0)
187 def testNotificationMerge(self):
188 patches = [self.patch,
189 Patch(project = self.project, msgid = 'testpatch-2',
190 name = 'testpatch 2', content = '',
191 submitter = self.submitter)]
193 for patch in patches:
195 PatchChangeNotification(patch = patch,
196 orig_state = patch.state).save()
198 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
199 self._expireNotifications()
200 errors = send_notifications()
201 self.assertEquals(errors, [])
202 self.assertEquals(len(mail.outbox), 1)
204 self.assertTrue(patches[0].get_absolute_url() in msg.body)
205 self.assertTrue(patches[1].get_absolute_url() in msg.body)
207 def testUnexpiredNotificationMerge(self):
208 """Test that when there are multiple pending notifications, with
209 at least one within the notification delay, that other notifications
211 patches = [self.patch,
212 Patch(project = self.project, msgid = 'testpatch-2',
213 name = 'testpatch 2', content = '',
214 submitter = self.submitter)]
216 for patch in patches:
218 PatchChangeNotification(patch = patch,
219 orig_state = patch.state).save()
221 self.assertEquals(PatchChangeNotification.objects.count(), len(patches))
222 self._expireNotifications()
224 # update one notification, to bring it out of the notification delay
225 patches[0].state = State.objects.exclude(pk = patches[0].state.pk)[0]
228 # the updated notification should prevent the other from being sent
229 errors = send_notifications()
230 self.assertEquals(errors, [])
231 self.assertEquals(len(mail.outbox), 0)
233 # expire the updated notification
234 self._expireNotifications()
236 errors = send_notifications()
237 self.assertEquals(errors, [])
238 self.assertEquals(len(mail.outbox), 1)
240 self.assertTrue(patches[0].get_absolute_url() in msg.body)
241 self.assertTrue(patches[1].get_absolute_url() in msg.body)