You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by rj...@apache.org on 2014/11/15 02:14:53 UTC
svn commit: r1639823 [18/29] - in
/bloodhound/branches/trac-1.0.2-integration/trac: ./ contrib/
contrib/cgi-bin/ contrib/workflow/ doc/ doc/dev/ doc/utils/ sample-plugins/
sample-plugins/permissions/ sample-plugins/workflow/ trac/ trac/admin/
trac/admi...
Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/model.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/model.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/model.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/model.py Sat Nov 15 01:14:46 2014
@@ -1,23 +1,37 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2013 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.org/wiki/TracLicense.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
from __future__ import with_statement
from datetime import datetime, timedelta
-import os.path
from StringIO import StringIO
import tempfile
import shutil
import unittest
+import trac.tests.compat
from trac import core
from trac.attachment import Attachment
from trac.core import TracError, implements
from trac.resource import ResourceNotFound
+from trac.test import EnvironmentStub
from trac.ticket.model import (
Ticket, Component, Milestone, Priority, Type, Version
)
+from trac.ticket.roadmap import MilestoneModule
from trac.ticket.api import (
IMilestoneChangeListener, ITicketChangeListener, TicketSystem
)
-from trac.test import EnvironmentStub
from trac.tests.resource import TestResourceChangeListener
from trac.util.datefmt import from_utimestamp, to_utimestamp, utc
@@ -41,6 +55,36 @@ class TestTicketChangeListener(core.Comp
self.action = 'deleted'
self.ticket = ticket
+ # the listener has no ticket_comment_modified and ticket_change_deleted
+
+
+class TestTicketChangeListener_2(core.Component):
+ implements(ITicketChangeListener)
+
+ def ticket_created(self, ticket):
+ pass
+
+ def ticket_changed(self, ticket, comment, author, old_values):
+ pass
+
+ def ticket_deleted(self, ticket):
+ pass
+
+ def ticket_comment_modified(self, ticket, cdate, author, comment,
+ old_comment):
+ self.action = 'comment_modified'
+ self.ticket = ticket
+ self.cdate = cdate
+ self.author = author
+ self.comment = comment
+ self.old_comment = old_comment
+
+ def ticket_change_deleted(self, ticket, cdate, changes):
+ self.action = 'change_deleted'
+ self.ticket = ticket
+ self.cdate = cdate
+ self.changes = changes
+
class TicketTestCase(unittest.TestCase):
@@ -118,9 +162,9 @@ class TicketTestCase(unittest.TestCase):
log = ticket3.get_changelog()
self.assertEqual(len(log), 3)
ok_vals = ['foo', 'summary', 'comment']
- self.failUnless(log[0][2] in ok_vals)
- self.failUnless(log[1][2] in ok_vals)
- self.failUnless(log[2][2] in ok_vals)
+ self.assertIn(log[0][2], ok_vals)
+ self.assertIn(log[1][2], ok_vals)
+ self.assertIn(log[2][2], ok_vals)
def test_create_ticket_5(self):
ticket3 = self._modify_a_ticket()
@@ -157,7 +201,7 @@ class TicketTestCase(unittest.TestCase):
ticket.save_changes()
for change in ticket.get_changelog():
- self.assertEqual(None, change[1])
+ self.assertIsNone(change[1])
def test_comment_with_whitespace_only_is_not_saved(self):
ticket = Ticket(self.env)
@@ -310,7 +354,7 @@ class TicketTestCase(unittest.TestCase):
self.assertEqual('john', ticket['reporter'])
# An unknown field
- assert ticket['bar'] is None
+ self.assertIsNone(ticket['bar'])
# Custom field
self.assertEqual('bar', ticket['foo'])
@@ -599,6 +643,19 @@ class TicketCommentEditTestCase(TicketCo
self.assertEqual((i, t[i], 'joe (%d)' % i,
'Comment 1 (%d)' % i), history[i])
+ def test_change_listener_comment_modified(self):
+ listener = TestTicketChangeListener_2(self.env)
+ ticket = Ticket(self.env, self.id)
+ ticket.modify_comment(cdate=self.t2, author='jack',
+ comment='New Comment 2', when=datetime.now(utc))
+
+ self.assertEqual('comment_modified', listener.action)
+ self.assertEqual(ticket, listener.ticket)
+ self.assertEqual(self.t2, listener.cdate)
+ self.assertEqual('jack', listener.author)
+ self.assertEqual('New Comment 2', listener.comment)
+ self.assertEqual('Comment 2', listener.old_comment)
+
class TicketCommentDeleteTestCase(TicketCommentTestCase):
@@ -632,8 +689,8 @@ class TicketCommentDeleteTestCase(Ticket
ticket.delete_change(cnum=4, when=t)
self.assertEqual('a, b', ticket['keywords'])
self.assertEqual('change3', ticket['foo'])
- self.assertEqual(None, ticket.get_change(cnum=4))
- self.assertNotEqual(None, ticket.get_change(cnum=3))
+ self.assertIsNone(ticket.get_change(cnum=4))
+ self.assertIsNotNone(ticket.get_change(cnum=3))
self.assertEqual(t, ticket.time_changed)
def test_delete_last_comment_when_custom_field_gone(self):
@@ -651,13 +708,13 @@ class TicketCommentDeleteTestCase(Ticket
ticket.delete_change(cnum=4, when=t)
self.assertEqual('a, b', ticket['keywords'])
# 'foo' is no longer defined for the ticket
- self.assertEqual(None, ticket['foo'])
+ self.assertIsNone(ticket['foo'])
# however, 'foo=change3' is still in the database
self.assertEqual([('change3',)], self.env.db_query("""
SELECT value FROM ticket_custom WHERE ticket=%s AND name='foo'
""", (self.id,)))
- self.assertEqual(None, ticket.get_change(cnum=4))
- self.assertNotEqual(None, ticket.get_change(cnum=3))
+ self.assertIsNone(ticket.get_change(cnum=4))
+ self.assertIsNotNone(ticket.get_change(cnum=3))
self.assertEqual(t, ticket.time_changed)
def test_delete_last_comment_by_date(self):
@@ -668,8 +725,8 @@ class TicketCommentDeleteTestCase(Ticket
ticket.delete_change(cdate=self.t4, when=t)
self.assertEqual('a, b', ticket['keywords'])
self.assertEqual('change3', ticket['foo'])
- self.assertEqual(None, ticket.get_change(cdate=self.t4))
- self.assertNotEqual(None, ticket.get_change(cdate=self.t3))
+ self.assertIsNone(ticket.get_change(cdate=self.t4))
+ self.assertIsNotNone(ticket.get_change(cdate=self.t3))
self.assertEqual(t, ticket.time_changed)
def test_delete_mid_comment(self):
@@ -680,7 +737,7 @@ class TicketCommentDeleteTestCase(Ticket
foo=dict(author='joe', old='change3', new='change4'))
t = datetime.now(utc)
ticket.delete_change(cnum=3, when=t)
- self.assertEqual(None, ticket.get_change(cnum=3))
+ self.assertIsNone(ticket.get_change(cnum=3))
self.assertEqual('a', ticket['keywords'])
self.assertChange(ticket, 4, self.t4, 'joe',
comment=dict(author='joe', old='4', new='Comment 4'),
@@ -696,7 +753,7 @@ class TicketCommentDeleteTestCase(Ticket
foo=dict(author='joe', old='change3', new='change4'))
t = datetime.now(utc)
ticket.delete_change(cdate=self.t3, when=t)
- self.assertEqual(None, ticket.get_change(cdate=self.t3))
+ self.assertIsNone(ticket.get_change(cdate=self.t3))
self.assertEqual('a', ticket['keywords'])
self.assertChange(ticket, 4, self.t4, 'joe',
comment=dict(author='joe', old='4', new='Comment 4'),
@@ -718,7 +775,7 @@ class TicketCommentDeleteTestCase(Ticket
keywords=dict(author='joe', old='1, 2', new='a'),
foo=dict(author='joe', old='change3', new='change4'))
ticket.delete_change(3)
- self.assertEqual(None, ticket.get_change(3))
+ self.assertIsNone(ticket.get_change(3))
self.assertEqual('a', ticket['keywords'])
self.assertChange(ticket, 4, self.t4, 'joe',
comment=dict(author='joe', old='4', new='Comment 4'),
@@ -734,6 +791,25 @@ class TicketCommentDeleteTestCase(Ticket
ticket.delete_change(1, when=t)
self.assertEqual(t, ticket.time_changed)
+ def test_ticket_change_deleted(self):
+ listener = TestTicketChangeListener_2(self.env)
+ ticket = Ticket(self.env, self.id)
+
+ ticket.delete_change(cdate=self.t3, when=datetime.now(utc))
+ self.assertEqual('change_deleted', listener.action)
+ self.assertEqual(ticket, listener.ticket)
+ self.assertEqual(self.t3, listener.cdate)
+ self.assertEqual(dict(keywords=('a, b, c', 'a, b'),
+ foo=('change2', 'change3')),
+ listener.changes)
+
+ ticket.delete_change(cnum=2, when=datetime.now(utc))
+ self.assertEqual('change_deleted', listener.action)
+ self.assertEqual(ticket, listener.ticket)
+ self.assertEqual(self.t2, listener.cdate)
+ self.assertEqual(dict(owner=('john', 'jack'),
+ foo=('change 1', 'change2')),
+ listener.changes)
class EnumTestCase(unittest.TestCase):
@@ -752,14 +828,14 @@ class EnumTestCase(unittest.TestCase):
prio = Priority(self.env)
prio.name = 'foo'
prio.insert()
- self.assertEqual(True, prio.exists)
+ self.assertTrue(prio.exists)
def test_priority_insert_with_value(self):
prio = Priority(self.env)
prio.name = 'bar'
prio.value = 100
prio.insert()
- self.assertEqual(True, prio.exists)
+ self.assertTrue(prio.exists)
def test_priority_update(self):
prio = Priority(self.env, 'major')
@@ -772,7 +848,7 @@ class EnumTestCase(unittest.TestCase):
prio = Priority(self.env, 'major')
self.assertEqual('3', prio.value)
prio.delete()
- self.assertEqual(False, prio.exists)
+ self.assertFalse(prio.exists)
self.assertRaises(TracError, Priority, self.env, 'major')
prio = Priority(self.env, 'minor')
self.assertEqual('3', prio.value)
@@ -807,8 +883,9 @@ class MilestoneTestCase(unittest.TestCas
def setUp(self):
self.env = EnvironmentStub(default_data=True)
- self.env.path = os.path.join(tempfile.gettempdir(), 'trac-tempenv')
- os.mkdir(self.env.path)
+ self.env.path = tempfile.mkdtemp(prefix='trac-tempenv-')
+ self.created_at = datetime(2001, 1, 1, tzinfo=utc)
+ self.updated_at = self.created_at + timedelta(seconds=1)
def tearDown(self):
shutil.rmtree(self.env.path)
@@ -820,12 +897,25 @@ class MilestoneTestCase(unittest.TestCas
setattr(milestone, k, v)
return milestone
+ def _insert_ticket(self, when=None, **kwargs):
+ ticket = Ticket(self.env)
+ for name, value in kwargs.iteritems():
+ ticket[name] = value
+ ticket.insert(when or self.created_at)
+ return ticket
+
+ def _update_ticket(self, ticket, author=None, comment=None, when=None,
+ **kwargs):
+ for name, value in kwargs.iteritems():
+ ticket[name] = value
+ ticket.save_changes(author, comment, when or self.updated_at)
+
def test_new_milestone(self):
milestone = Milestone(self.env)
- self.assertEqual(False, milestone.exists)
- self.assertEqual(None, milestone.name)
- self.assertEqual(None, milestone.due)
- self.assertEqual(None, milestone.completed)
+ self.assertFalse(milestone.exists)
+ self.assertIsNone(milestone.name)
+ self.assertIsNone(milestone.due)
+ self.assertIsNone(milestone.completed)
self.assertEqual('', milestone.description)
def test_new_milestone_empty_name(self):
@@ -834,20 +924,20 @@ class MilestoneTestCase(unittest.TestCas
milestone being correctly detected as non-existent.
"""
milestone = Milestone(self.env, '')
- self.assertEqual(False, milestone.exists)
- self.assertEqual(None, milestone.name)
- self.assertEqual(None, milestone.due)
- self.assertEqual(None, milestone.completed)
+ self.assertFalse(milestone.exists)
+ self.assertIsNone(milestone.name)
+ self.assertIsNone(milestone.due)
+ self.assertIsNone(milestone.completed)
self.assertEqual('', milestone.description)
def test_existing_milestone(self):
self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
milestone = Milestone(self.env, 'Test')
- self.assertEqual(True, milestone.exists)
+ self.assertTrue(milestone.exists)
self.assertEqual('Test', milestone.name)
- self.assertEqual(None, milestone.due)
- self.assertEqual(None, milestone.completed)
+ self.assertIsNone(milestone.due)
+ self.assertIsNone(milestone.completed)
self.assertEqual('', milestone.description)
def test_create_and_update_milestone(self):
@@ -868,35 +958,119 @@ class MilestoneTestCase(unittest.TestCas
WHERE name='Test'
"""))
+ def test_move_tickets(self):
+ self.env.db_transaction.executemany(
+ "INSERT INTO milestone (name) VALUES (%s)",
+ [('Test',), ('Testing',)])
+ tkt1 = self._insert_ticket(status='new', summary='Foo',
+ milestone='Test')
+ tkt2 = self._insert_ticket(status='new', summary='Bar',
+ milestone='Test')
+ self._update_ticket(tkt2, status='closed', resolution='fixed')
+ milestone = Milestone(self.env, 'Test')
+ milestone.move_tickets('Testing', 'anonymous', 'Move tickets')
+
+ tkt1 = Ticket(self.env, tkt1.id)
+ tkt2 = Ticket(self.env, tkt2.id)
+ self.assertEqual('Testing', tkt1['milestone'])
+ self.assertEqual('Testing', tkt2['milestone'])
+ self.assertEqual(tkt1['changetime'], tkt2['changetime'])
+ self.assertNotEqual(self.updated_at, tkt1['changetime'])
+
+ def test_move_tickets_exclude_closed(self):
+ self.env.db_transaction.executemany(
+ "INSERT INTO milestone (name) VALUES (%s)",
+ [('Test',), ('Testing',)])
+ tkt1 = self._insert_ticket(status='new', summary='Foo',
+ milestone='Test')
+ tkt2 = self._insert_ticket(status='new', summary='Bar',
+ milestone='Test')
+ self._update_ticket(tkt2, status='closed', resolution='fixed')
+ milestone = Milestone(self.env, 'Test')
+ milestone.move_tickets('Testing', 'anonymous', 'Move tickets',
+ exclude_closed=True)
+
+ tkt1 = Ticket(self.env, tkt1.id)
+ tkt2 = Ticket(self.env, tkt2.id)
+ self.assertEqual('Testing', tkt1['milestone'])
+ self.assertEqual('Test', tkt2['milestone'])
+ self.assertNotEqual(self.updated_at, tkt1['changetime'])
+ self.assertEqual(self.updated_at, tkt2['changetime'])
+
+ def test_move_tickets_target_doesnt_exist(self):
+ self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
+ tkt1 = self._insert_ticket(status='new', summary='Foo',
+ milestone='Test')
+ tkt2 = self._insert_ticket(status='new', summary='Bar',
+ milestone='Test')
+ milestone = Milestone(self.env, 'Test')
+ self.assertRaises(ResourceNotFound, milestone.move_tickets,
+ 'Testing', 'anonymous')
+
+ tkt1 = Ticket(self.env, tkt1.id)
+ tkt2 = Ticket(self.env, tkt2.id)
+ self.assertEqual('Test', tkt1['milestone'])
+ self.assertEqual('Test', tkt2['milestone'])
+ self.assertNotEqual(self.updated_at, tkt1['changetime'])
+ self.assertNotEqual(self.updated_at, tkt2['changetime'])
+
def test_create_milestone_without_name(self):
milestone = Milestone(self.env)
self.assertRaises(TracError, milestone.insert)
def test_delete_milestone(self):
self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
-
+ tkt1 = self._insert_ticket(status='new', summary='Foo',
+ milestone='Test')
+ tkt2 = self._insert_ticket(status='new', summary='Bar',
+ milestone='Test')
+ self._update_ticket(tkt2, status='closed', resolution='fixed')
milestone = Milestone(self.env, 'Test')
milestone.delete()
- self.assertEqual(False, milestone.exists)
+ self.assertFalse(milestone.exists)
self.assertEqual([],
self.env.db_query("SELECT * FROM milestone WHERE name='Test'"))
- def test_delete_milestone_retarget_tickets(self):
- self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
+ tkt1 = Ticket(self.env, tkt1.id)
+ tkt2 = Ticket(self.env, tkt2.id)
+ self.assertEqual('', tkt1['milestone'])
+ self.assertEqual('', tkt2['milestone'])
+ self.assertEqual(tkt1['changetime'], tkt2['changetime'])
+ self.assertNotEqual(self.updated_at, tkt1['changetime'])
+
+ def test_delete_milestone_with_attachment(self):
+ milestone = Milestone(self.env)
+ milestone.name = 'MilestoneWithAttachment'
+ milestone.insert()
+
+ attachment = Attachment(self.env, 'milestone', milestone.name)
+ attachment.insert('foo.txt', StringIO(), 0, 1)
- tkt1 = Ticket(self.env)
- tkt1.populate({'summary': 'Foo', 'milestone': 'Test'})
- tkt1.insert()
- tkt2 = Ticket(self.env)
- tkt2.populate({'summary': 'Bar', 'milestone': 'Test'})
- tkt2.insert()
+ milestone.delete()
+ self.assertEqual(False, milestone.exists)
+
+ attachments = Attachment.select(self.env, 'milestone', milestone.name)
+ self.assertRaises(StopIteration, attachments.next)
+ def test_delete_milestone_retarget_tickets(self):
+ self.env.db_transaction.executemany(
+ "INSERT INTO milestone (name) VALUES (%s)",
+ [('Test',), ('Other',)])
+ tkt1 = self._insert_ticket(status='new', summary='Foo',
+ milestone='Test')
+ tkt2 = self._insert_ticket(status='new', summary='Bar',
+ milestone='Test')
+ self._update_ticket(tkt2, status='closed', resolution='fixed')
milestone = Milestone(self.env, 'Test')
milestone.delete(retarget_to='Other')
- self.assertEqual(False, milestone.exists)
+ self.assertFalse(milestone.exists)
- self.assertEqual('Other', Ticket(self.env, tkt1.id)['milestone'])
- self.assertEqual('Other', Ticket(self.env, tkt2.id)['milestone'])
+ tkt1 = Ticket(self.env, tkt1.id)
+ tkt2 = Ticket(self.env, tkt2.id)
+ self.assertEqual('Other', tkt1['milestone'])
+ self.assertEqual('Other', tkt2['milestone'])
+ self.assertEqual(tkt1['changetime'], tkt2['changetime'])
+ self.assertNotEqual(self.updated_at, tkt1['changetime'])
def test_update_milestone(self):
self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
@@ -920,23 +1094,6 @@ class MilestoneTestCase(unittest.TestCas
milestone.name = None
self.assertRaises(TracError, milestone.update)
- def test_update_milestone_update_tickets(self):
- self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
-
- tkt1 = Ticket(self.env)
- tkt1.populate({'summary': 'Foo', 'milestone': 'Test'})
- tkt1.insert()
- tkt2 = Ticket(self.env)
- tkt2.populate({'summary': 'Bar', 'milestone': 'Test'})
- tkt2.insert()
-
- milestone = Milestone(self.env, 'Test')
- milestone.name = 'Testing'
- milestone.update()
-
- self.assertEqual('Testing', Ticket(self.env, tkt1.id)['milestone'])
- self.assertEqual('Testing', Ticket(self.env, tkt2.id)['milestone'])
-
def test_rename_milestone(self):
milestone = Milestone(self.env)
milestone.name = 'OldName'
@@ -958,6 +1115,24 @@ class MilestoneTestCase(unittest.TestCas
self.assertEqual('foo.txt', attachments.next().filename)
self.assertRaises(StopIteration, attachments.next)
+ def test_rename_milestone_retarget_tickets(self):
+ self.env.db_transaction("INSERT INTO milestone (name) VALUES ('Test')")
+ tkt1 = self._insert_ticket(status='new', summary='Foo',
+ milestone='Test')
+ tkt2 = self._insert_ticket(status='new', summary='Bar',
+ milestone='Test')
+ self._update_ticket(tkt2, status='closed', resolution='fixed')
+ milestone = Milestone(self.env, 'Test')
+ milestone.name = 'Testing'
+ milestone.update()
+
+ tkt1 = Ticket(self.env, tkt1.id)
+ tkt2 = Ticket(self.env, tkt2.id)
+ self.assertEqual('Testing', tkt1['milestone'])
+ self.assertEqual('Testing', tkt2['milestone'])
+ self.assertEqual(tkt1['changetime'], tkt2['changetime'])
+ self.assertNotEqual(self.updated_at, tkt1['changetime'])
+
def test_select_milestones(self):
self.env.db_transaction.executemany(
"INSERT INTO milestone (name) VALUES (%s)",
@@ -965,9 +1140,9 @@ class MilestoneTestCase(unittest.TestCas
milestones = list(Milestone.select(self.env))
self.assertEqual('1.0', milestones[0].name)
- assert milestones[0].exists
+ self.assertTrue(milestones[0].exists)
self.assertEqual('2.0', milestones[1].name)
- assert milestones[1].exists
+ self.assertTrue(milestones[1].exists)
def test_change_listener_created(self):
listener = TestMilestoneChangeListener(self.env)
@@ -999,10 +1174,10 @@ class MilestoneTestCase(unittest.TestCas
listener = TestMilestoneChangeListener(self.env)
milestone = self._create_milestone(name='Milestone 1')
milestone.insert()
- self.assertEqual(True, milestone.exists)
+ self.assertTrue(milestone.exists)
milestone.delete()
self.assertEqual('Milestone 1', milestone.name)
- self.assertEqual(False, milestone.exists)
+ self.assertFalse(milestone.exists)
self.assertEqual('deleted', listener.action)
self.assertEqual(milestone, listener.milestone)
@@ -1180,13 +1355,13 @@ class TicketResourceChangeListenerTestCa
def suite():
suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(TicketTestCase, 'test'))
- suite.addTest(unittest.makeSuite(TicketCommentEditTestCase, 'test'))
- suite.addTest(unittest.makeSuite(TicketCommentDeleteTestCase, 'test'))
- suite.addTest(unittest.makeSuite(EnumTestCase, 'test'))
- suite.addTest(unittest.makeSuite(MilestoneTestCase, 'test'))
- suite.addTest(unittest.makeSuite(ComponentTestCase, 'test'))
- suite.addTest(unittest.makeSuite(VersionTestCase, 'test'))
+ suite.addTest(unittest.makeSuite(TicketTestCase))
+ suite.addTest(unittest.makeSuite(TicketCommentEditTestCase))
+ suite.addTest(unittest.makeSuite(TicketCommentDeleteTestCase))
+ suite.addTest(unittest.makeSuite(EnumTestCase))
+ suite.addTest(unittest.makeSuite(MilestoneTestCase))
+ suite.addTest(unittest.makeSuite(ComponentTestCase))
+ suite.addTest(unittest.makeSuite(VersionTestCase))
suite.addTest(unittest.makeSuite(
ComponentResourceChangeListenerTestCase, 'test'))
suite.addTest(unittest.makeSuite(
Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/notification.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/notification.py?rev=1639823&r1=1639822&r2=1639823&view=diff
==============================================================================
--- bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/notification.py (original)
+++ bloodhound/branches/trac-1.0.2-integration/trac/trac/ticket/tests/notification.py Sat Nov 15 01:14:46 2014
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2005-2009 Edgewall Software
+# Copyright (C) 2005-2013 Edgewall Software
# Copyright (C) 2005-2006 Emmanuel Blot <em...@free.fr>
# All rights reserved.
#
@@ -16,126 +16,244 @@
# (lsmithson@open-networks.co.uk) extensible Python SMTP Server
#
-from trac.util.datefmt import utc
-from trac.ticket.model import Ticket
-from trac.ticket.notification import TicketNotifyEmail
-from trac.test import EnvironmentStub, Mock, MockPerm
-from trac.tests.notification import SMTPThreadedServer, parse_smtp_message, \
- smtp_address
-
import base64
-from datetime import datetime
import os
import quopri
import re
import unittest
+from datetime import datetime
+
+import trac.tests.compat
+from trac.test import EnvironmentStub, Mock, MockPerm
+from trac.tests.notification import SMTP_TEST_PORT, SMTPThreadedServer,\
+ parse_smtp_message
+from trac.ticket.model import Ticket
+from trac.ticket.notification import TicketNotifyEmail
+from trac.ticket.web_ui import TicketModule
+from trac.util.datefmt import utc
-SMTP_TEST_PORT = 7000 + os.getpid() % 1000
MAXBODYWIDTH = 76
notifysuite = None
-class NotificationTestCase(unittest.TestCase):
- """Notification test cases that send email over SMTP"""
+class RecipientTestCase(unittest.TestCase):
+ """Notification test cases for email recipients."""
def setUp(self):
self.env = EnvironmentStub(default_data=True)
self.env.config.set('project', 'name', 'TracTest')
self.env.config.set('notification', 'smtp_enabled', 'true')
- self.env.config.set('notification', 'always_notify_owner', 'true')
- self.env.config.set('notification', 'always_notify_reporter', 'true')
- self.env.config.set('notification', 'smtp_always_cc',
- 'joe.user@example.net, joe.bar@example.net')
- self.env.config.set('notification', 'use_public_cc', 'true')
self.env.config.set('notification', 'smtp_port', str(SMTP_TEST_PORT))
- self.env.config.set('notification', 'smtp_server','localhost')
- self.req = Mock(href=self.env.href, abs_href=self.env.abs_href, tz=utc,
- perm=MockPerm())
def tearDown(self):
- """Signal the notification test suite that a test is over"""
notifysuite.tear_down()
self.env.reset_db()
- def test_recipients(self):
- """To/Cc recipients"""
+ def test_no_recipients(self):
+ """No recipient case"""
ticket = Ticket(self.env)
- ticket['reporter'] = '"Joe User" < joe.user@example.org >'
- ticket['owner'] = 'joe.user@example.net'
- ticket['cc'] = 'joe.user@example.com, joe.bar@example.org, ' \
- 'joe.bar@example.net'
+ ticket['reporter'] = 'anonymous'
ticket['summary'] = 'Foo'
ticket.insert()
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
recipients = notifysuite.smtpd.get_recipients()
- # checks there is no duplicate in the recipient list
- rcpts = []
- for r in recipients:
- self.failIf(r in rcpts)
- rcpts.append(r)
- # checks that all cc recipients have been notified
- cc_list = self.env.config.get('notification', 'smtp_always_cc')
- cc_list = "%s, %s" % (cc_list, ticket['cc'])
- for r in cc_list.replace(',', ' ').split():
- self.failIf(r not in recipients)
- # checks that owner has been notified
- self.failIf(smtp_address(ticket['owner']) not in recipients)
- # checks that reporter has been notified
- self.failIf(smtp_address(ticket['reporter']) not in recipients)
-
- def test_no_recipient(self):
- """No recipient case"""
- self.env.config.set('notification', 'smtp_always_cc', '')
+ sender = notifysuite.smtpd.get_sender()
+ message = notifysuite.smtpd.get_message()
+ self.assertEqual(0, len(recipients))
+ self.assertIsNone(sender)
+ self.assertIsNone(message)
+
+ def test_new_ticket_recipients(self):
+ """Report and CC list should be in recipient list for new tickets."""
+ always_cc = ('joe.user@example.net', 'joe.bar@example.net')
+ ticket_cc = ('joe.user@example.com', 'joe.bar@example.org')
+ self.env.config.set('notification', 'smtp_always_cc',
+ ', '.join(always_cc))
ticket = Ticket(self.env)
- ticket['reporter'] = 'anonymous'
- ticket['summary'] = 'Foo'
+ ticket['reporter'] = 'joe.bar@example.org'
+ ticket['owner'] = 'joe.user@example.net'
+ ticket['cc'] = ' '.join(ticket_cc)
+ ticket['summary'] = 'New ticket recipients'
ticket.insert()
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
- sender = notifysuite.smtpd.get_sender()
recipients = notifysuite.smtpd.get_recipients()
- message = notifysuite.smtpd.get_message()
- # checks that no message has been sent
- self.failIf(recipients)
- self.failIf(sender)
- self.failIf(message)
+ for r in always_cc + ticket_cc + \
+ (ticket['owner'], ticket['reporter']):
+ self.assertIn(r, recipients)
def test_cc_only(self):
"""Notification w/o explicit recipients but Cc: (#3101)"""
+ always_cc = ('joe.user@example.net', 'joe.bar@example.net')
+ self.env.config.set('notification', 'smtp_always_cc',
+ ', '.join(always_cc))
ticket = Ticket(self.env)
ticket['summary'] = 'Foo'
ticket.insert()
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
recipients = notifysuite.smtpd.get_recipients()
- # checks that all cc recipients have been notified
- cc_list = self.env.config.get('notification', 'smtp_always_cc')
- for r in cc_list.replace(',', ' ').split():
- self.failIf(r not in recipients)
+ for r in always_cc:
+ self.assertIn(r, recipients)
+
+ def test_always_notify_updater(self):
+ """The `always_notify_updater` option."""
+ def _test_updater(enabled):
+ self.env.config.set('notification', 'always_notify_updater',
+ enabled)
+ ticket = Ticket(self.env)
+ ticket['reporter'] = 'joe.user@example.org'
+ ticket['summary'] = u'This is a súmmäry'
+ ticket.insert()
+ now = datetime.now(utc)
+ ticket.save_changes('joe.bar2@example.com', 'This is a change',
+ when=now)
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=False, modtime=now)
+ recipients = notifysuite.smtpd.get_recipients()
+ if enabled:
+ self.assertEqual(1, len(recipients))
+ self.assertIn('joe.bar2@example.com', recipients)
+ else:
+ self.assertEqual(0, len(recipients))
+ self.assertNotIn('joe.bar2@example.com', recipients)
+
+ # Validate with and without a default domain
+ for enable in False, True:
+ _test_updater(enable)
+
+ def test_always_notify_owner(self):
+ """The `always_notify_owner` option."""
+ def _test_reporter(enabled):
+ self.env.config.set('notification', 'always_notify_owner',
+ enabled)
+ self.env.config.set('notification', 'always_notify_updater',
+ 'false')
+ ticket = Ticket(self.env)
+ ticket['summary'] = 'Foo'
+ ticket['reporter'] = u'joe@example.org'
+ ticket['owner'] = u'jim@example.org'
+ ticket.insert()
+ now = datetime.now(utc)
+ ticket.save_changes('joe@example.org', 'this is my comment',
+ when=now)
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=True, modtime=now)
+ recipients = notifysuite.smtpd.get_recipients()
+ if enabled:
+ self.assertEqual(1, len(recipients))
+ self.assertEqual('jim@example.org', recipients[0])
+ else:
+ self.assertEqual(0, len(recipients))
+
+ for enable in False, True:
+ _test_reporter(enable)
+
+ def test_always_notify_reporter(self):
+ """Notification to reporter w/ updater option disabled (#3780)"""
+ def _test_reporter(enabled):
+ self.env.config.set('notification', 'always_notify_updater',
+ 'false')
+ self.env.config.set('notification', 'always_notify_reporter',
+ enabled)
+ ticket = Ticket(self.env)
+ ticket['summary'] = 'Foo'
+ ticket['reporter'] = u'joe@example.org'
+ ticket.insert()
+ now = datetime.now(utc)
+ ticket.save_changes('joe@example.org', 'this is my comment',
+ when=now)
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=True, modtime=now)
+ recipients = notifysuite.smtpd.get_recipients()
+ if enabled:
+ self.assertEqual(1, len(recipients))
+ self.assertEqual('joe@example.org', recipients[0])
+ else:
+ self.assertEqual(0, len(recipients))
+
+ for enable in False, True:
+ _test_reporter(enable)
+
+ def test_no_duplicates(self):
+ """Email addresses should be found only once in the recipient list."""
+ self.env.config.set('notification', 'smtp_always_cc',
+ 'joe.user@example.com')
+ ticket = Ticket(self.env)
+ ticket['reporter'] = 'joe.user@example.com'
+ ticket['owner'] = 'joe.user@example.com'
+ ticket['cc'] = 'joe.user@example.com'
+ ticket['summary'] = 'No duplicates'
+ ticket.insert()
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=True)
+ recipients = notifysuite.smtpd.get_recipients()
+ self.assertEqual(1, len(recipients))
+ self.assertIn('joe.user@example.com', recipients)
+
+ def test_long_forms(self):
+ """Long forms of SMTP email addresses 'Display Name <address>'"""
+ self.env.config.set('notification', 'always_notify_owner', True)
+ ticket = Ticket(self.env)
+ ticket['reporter'] = '"Joe" <jo...@example.com>'
+ ticket['owner'] = 'Joe <jo...@example.net>'
+ ticket['cc'] = 'Joe < joe.user@example.org >'
+ ticket['summary'] = 'Long form'
+ ticket.insert()
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=True)
+ recipients = notifysuite.smtpd.get_recipients()
+ self.assertEqual(3, len(recipients))
+ self.assertIn('joe.user@example.com', recipients)
+ self.assertIn('joe.user@example.net', recipients)
+ self.assertIn('joe.user@example.org', recipients)
+
+
+class NotificationTestCase(unittest.TestCase):
+ """Notification test cases that send email over SMTP"""
+
+ def setUp(self):
+ self.env = EnvironmentStub(default_data=True)
+ self.env.config.set('project', 'name', 'TracTest')
+ self.env.config.set('notification', 'smtp_enabled', 'true')
+ self.env.config.set('notification', 'always_notify_owner', 'true')
+ self.env.config.set('notification', 'always_notify_reporter', 'true')
+ self.env.config.set('notification', 'smtp_always_cc',
+ 'joe.user@example.net, joe.bar@example.net')
+ self.env.config.set('notification', 'use_public_cc', 'true')
+ self.env.config.set('notification', 'smtp_port', str(SMTP_TEST_PORT))
+ self.env.config.set('notification', 'smtp_server', 'localhost')
+ self.req = Mock(href=self.env.href, abs_href=self.env.abs_href, tz=utc,
+ perm=MockPerm())
+
+ def tearDown(self):
+ """Signal the notification test suite that a test is over"""
+ notifysuite.tear_down()
+ self.env.reset_db()
def test_structure(self):
"""Basic SMTP message structure (headers, body)"""
ticket = Ticket(self.env)
ticket['reporter'] = '"Joe User" <jo...@example.org>'
- ticket['owner'] = 'joe.user@example.net'
- ticket['cc'] = 'joe.user@example.com, joe.bar@example.org, ' \
- 'joe.bar@example.net'
+ ticket['owner'] = 'joe.user@example.net'
+ ticket['cc'] = 'joe.user@example.com, joe.bar@example.org, ' \
+ 'joe.bar@example.net'
ticket['summary'] = 'This is a summary'
ticket.insert()
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# checks for header existence
- self.failIf(not headers)
- # checks for body existance
- self.failIf(not body)
+ self.assertTrue(headers)
+ # checks for body existence
+ self.assertTrue(body)
# checks for expected headers
- self.failIf('Date' not in headers)
- self.failIf('Subject' not in headers)
- self.failIf('Message-ID' not in headers)
- self.failIf('From' not in headers)
+ self.assertIn('Date', headers)
+ self.assertIn('Subject', headers)
+ self.assertIn('Message-ID', headers)
+ self.assertIn('From', headers)
def test_date(self):
"""Date format compliance (RFC822)
@@ -148,7 +266,7 @@ class NotificationTestCase(unittest.Test
date_re = re.compile(date_str)
# python time module does not detect incorrect time values
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
- months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \
+ months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
tz = ['UT', 'GMT', 'EST', 'EDT', 'CST', 'CDT', 'MST', 'MDT',
'PST', 'PDT']
@@ -159,24 +277,23 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
- self.failIf('Date' not in headers)
+ headers, body = parse_smtp_message(message)
+ self.assertIn('Date', headers)
mo = date_re.match(headers['Date'])
- self.failIf(not mo)
+ self.assertTrue(mo)
if mo.group('day'):
- self.failIf(mo.group('day') not in days)
- self.failIf(int(mo.group('dm')) not in range(1, 32))
- self.failIf(mo.group('month') not in months)
- self.failIf(int(mo.group('hour')) not in range(0, 24))
+ self.assertIn(mo.group('day'), days)
+ self.assertIn(int(mo.group('dm')), range(1, 32))
+ self.assertIn(mo.group('month'), months)
+ self.assertIn(int(mo.group('hour')), range(0, 24))
if mo.group('tz'):
- self.failIf(mo.group('tz') not in tz)
+ self.assertIn(mo.group('tz'), tz)
def test_bcc_privacy(self):
"""Visibility of recipients"""
- def run_bcc_feature(public):
+ def run_bcc_feature(public_cc):
# CC list should be private
- self.env.config.set('notification', 'use_public_cc',
- 'true' if public else 'false')
+ self.env.config.set('notification', 'use_public_cc', public_cc)
self.env.config.set('notification', 'smtp_always_bcc',
'joe.foobar@example.net')
ticket = Ticket(self.env)
@@ -186,15 +303,15 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
- if public:
+ headers, body = parse_smtp_message(message)
+ if public_cc:
# Msg should have a To list
- self.failIf('To' not in headers)
+ self.assertIn('To', headers)
# Extract the list of 'To' recipients from the message
to = [rcpt.strip() for rcpt in headers['To'].split(',')]
else:
# Msg should not have a To list
- self.failIf('To' in headers)
+ self.assertNotIn('To', headers)
# Extract the list of 'To' recipients from the message
to = []
# Extract the list of 'Cc' recipients from the message
@@ -207,20 +324,20 @@ class NotificationTestCase(unittest.Test
for rcpt in cclist:
# Each recipient of the 'Cc' list should appear
# in the 'Cc' header
- self.failIf(rcpt not in cc)
+ self.assertIn(rcpt, cc)
# Check the message has actually been sent to the recipients
- self.failIf(rcpt not in rcptlist)
+ self.assertIn(rcpt, rcptlist)
# Build the list of the expected 'Bcc' recipients
bccrcpt = self.env.config.get('notification', 'smtp_always_bcc')
bcclist = [bccr.strip() for bccr in bccrcpt.split(',')]
for rcpt in bcclist:
# Check none of the 'Bcc' recipients appears
# in the 'To' header
- self.failIf(rcpt in to)
+ self.assertNotIn(rcpt, to)
# Check the message has actually been sent to the recipients
- self.failIf(rcpt not in rcptlist)
- run_bcc_feature(True)
- run_bcc_feature(False)
+ self.assertIn(rcpt, rcptlist)
+ for public in False, True:
+ run_bcc_feature(public)
def test_short_login(self):
"""Email addresses without a FQDN"""
@@ -233,31 +350,30 @@ class NotificationTestCase(unittest.Test
# send a notification even if other addresses are not valid
self.env.config.set('notification', 'smtp_always_cc',
'joe.bar@example.net')
- if enabled:
- self.env.config.set('notification', 'use_short_addr', 'true')
+ self.env.config.set('notification', 'use_short_addr', enabled)
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# Msg should not have a 'To' header
if not enabled:
- self.failIf('To' in headers)
+ self.assertNotIn('To', headers)
else:
tolist = [addr.strip() for addr in headers['To'].split(',')]
# Msg should have a 'Cc' field
- self.failIf('Cc' not in headers)
+ self.assertIn('Cc', headers)
cclist = [addr.strip() for addr in headers['Cc'].split(',')]
if enabled:
# Msg should be delivered to the reporter
- self.failIf(ticket['reporter'] not in tolist)
+ self.assertIn(ticket['reporter'], tolist)
else:
# Msg should not be delivered to joeuser
- self.failIf(ticket['reporter'] in cclist)
+ self.assertNotIn(ticket['reporter'], cclist)
# Msg should still be delivered to the always_cc list
- self.failIf(self.env.config.get('notification',
- 'smtp_always_cc') not in cclist)
+ self.assertIn(self.env.config.get('notification',
+ 'smtp_always_cc'), cclist)
# Validate with and without the short addr option enabled
- for enable in [False, True]:
+ for enable in False, True:
_test_short_login(enable)
def test_default_domain(self):
@@ -282,21 +398,21 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# Msg should always have a 'Cc' field
- self.failIf('Cc' not in headers)
+ self.assertIn('Cc', headers)
cclist = [addr.strip() for addr in headers['Cc'].split(',')]
- self.failIf('joewithdom@example.com' not in cclist)
- self.failIf('joe.bar@example.net' not in cclist)
- if not enabled:
- self.failIf(len(cclist) != 2)
- self.failIf('joenodom' in cclist)
+ self.assertIn('joewithdom@example.com', cclist)
+ self.assertIn('joe.bar@example.net', cclist)
+ if enabled:
+ self.assertEqual(3, len(cclist))
+ self.assertIn('joenodom@example.org', cclist)
else:
- self.failIf(len(cclist) != 3)
- self.failIf('joenodom@example.org' not in cclist)
+ self.assertEqual(2, len(cclist))
+ self.assertNotIn('joenodom', cclist)
# Validate with and without a default domain
- for enable in [False, True]:
+ for enable in False, True:
_test_default_domain(enable)
def test_email_map(self):
@@ -317,15 +433,15 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# Msg should always have a 'To' field
- self.failIf('To' not in headers)
+ self.assertIn('To', headers)
tolist = [addr.strip() for addr in headers['To'].split(',')]
# 'To' list should have been resolved to the real email address
- self.failIf('user-joe@example.com' not in tolist)
- self.failIf('user-jim@example.com' not in tolist)
- self.failIf('joeuser' in tolist)
- self.failIf('jim@domain' in tolist)
+ self.assertIn('user-joe@example.com', tolist)
+ self.assertIn('user-jim@example.com', tolist)
+ self.assertNotIn('joeuser', tolist)
+ self.assertNotIn('jim@domain', tolist)
def test_from_author(self):
"""Using the reporter or change author as the notification sender"""
@@ -346,7 +462,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('"Joe User" <us...@example.com>', headers['From'])
# Ticket change uses the change author
ticket['summary'] = 'Modified summary'
@@ -354,7 +470,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('"Jim User" <us...@example.com>', headers['From'])
# Known author without name uses e-mail address only
ticket['summary'] = 'Final summary'
@@ -362,7 +478,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('user-noname@example.com', headers['From'])
# Known author without e-mail uses smtp_from and smtp_from_name
ticket['summary'] = 'Other summary'
@@ -370,7 +486,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('"My Trac" <tr...@example.com>', headers['From'])
# Unknown author with name and e-mail address
ticket['summary'] = 'Some summary'
@@ -378,7 +494,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('"Test User" <te...@example.com>', headers['From'])
# Unknown author with e-mail address only
ticket['summary'] = 'Some summary'
@@ -386,7 +502,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('test@example.com', headers['From'])
# Unknown author uses smtp_from and smtp_from_name
ticket['summary'] = 'Better summary'
@@ -394,7 +510,7 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
self.assertEqual('"My Trac" <tr...@example.com>', headers['From'])
def test_ignore_domains(self):
@@ -412,16 +528,16 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# Msg should always have a 'To' field
- self.failIf('To' not in headers)
+ self.assertIn('To', headers)
tolist = [addr.strip() for addr in headers['To'].split(',')]
# 'To' list should not contain addresses with non-SMTP domains
- self.failIf('kerberos@example.com' in tolist)
- self.failIf('kerberos@example.org' in tolist)
+ self.assertNotIn('kerberos@example.com', tolist)
+ self.assertNotIn('kerberos@example.org', tolist)
# 'To' list should have been resolved to the actual email address
- self.failIf('kerb@example.net' not in tolist)
- self.failIf(len(tolist) != 1)
+ self.assertIn('kerb@example.net', tolist)
+ self.assertEqual(1, len(tolist))
def test_admit_domains(self):
"""SMTP domain inclusion"""
@@ -436,16 +552,16 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# Msg should always have a 'To' field
- self.failIf('Cc' not in headers)
+ self.assertIn('Cc', headers)
cclist = [addr.strip() for addr in headers['Cc'].split(',')]
# 'Cc' list should contain addresses with SMTP included domains
- self.failIf('joe.user@localdomain' not in cclist)
- self.failIf('joe.user@server' not in cclist)
+ self.assertIn('joe.user@localdomain', cclist)
+ self.assertIn('joe.user@server', cclist)
# 'Cc' list should not contain non-FQDN domains
- self.failIf('joe.user@unknown' in cclist)
- self.failIf(len(cclist) != 2+2)
+ self.assertNotIn('joe.user@unknown', cclist)
+ self.assertEqual(4, len(cclist))
def test_multiline_header(self):
"""Encoded headers split into multiple lines"""
@@ -458,11 +574,11 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
# Discards the project name & ticket number
subject = headers['Subject']
summary = subject[subject.find(':')+2:]
- self.failIf(ticket['summary'] != summary)
+ self.assertEqual(ticket['summary'], summary)
def test_mimebody_b64(self):
"""MIME Base64/utf-8 encoding"""
@@ -472,8 +588,7 @@ class NotificationTestCase(unittest.Test
ticket['summary'] = u'This is a long enough summary to cause Trac ' \
u'to generate a multi-line (2 lines) súmmäry'
ticket.insert()
- self._validate_mimebody((base64, 'base64', 'utf-8'), \
- ticket, True)
+ self._validate_mimebody((base64, 'base64', 'utf-8'), ticket, True)
def test_mimebody_qp(self):
"""MIME QP/utf-8 encoding"""
@@ -493,8 +608,7 @@ class NotificationTestCase(unittest.Test
ticket['reporter'] = 'joe.user'
ticket['summary'] = u'This is a summary'
ticket.insert()
- self._validate_mimebody((None, '7bit', 'utf-8'), \
- ticket, True)
+ self._validate_mimebody((None, '7bit', 'utf-8'), ticket, True)
def test_mimebody_none_8bit(self):
"""MIME None encoding resulting in 8bit"""
@@ -503,8 +617,7 @@ class NotificationTestCase(unittest.Test
ticket['reporter'] = 'joe.user'
ticket['summary'] = u'This is a summary for Jöe Usèr'
ticket.insert()
- self._validate_mimebody((None, '8bit', 'utf-8'), \
- ticket, True)
+ self._validate_mimebody((None, '8bit', 'utf-8'), ticket, True)
def test_md5_digest(self):
"""MD5 digest w/ non-ASCII recipient address (#3491)"""
@@ -518,118 +631,77 @@ class NotificationTestCase(unittest.Test
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
+ self.assertEqual('joe.user@example.org', headers['To'])
+
+ def test_previous_cc_list(self):
+ """Members removed from CC list receive notifications"""
+ ticket = Ticket(self.env)
+ ticket['summary'] = 'Foo'
+ ticket['cc'] = 'joe.user1@example.net'
+ ticket.insert()
+ ticket['cc'] = 'joe.user2@example.net'
+ now = datetime.now(utc)
+ ticket.save_changes('joe.bar@example.com', 'Removed from cc', now)
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=False, modtime=now)
+ recipients = notifysuite.smtpd.get_recipients()
+ self.assertIn('joe.user1@example.net', recipients)
+ self.assertIn('joe.user2@example.net', recipients)
- def test_updater(self):
- """No-self-notification option"""
- def _test_updater(disable):
- if disable:
- self.env.config.set('notification', 'always_notify_updater',
- 'false')
+ def test_previous_owner(self):
+ """Previous owner is notified when ticket is reassigned (#2311)
+ if always_notify_owner is set to True"""
+ def _test_owner(enabled):
+ self.env.config.set('notification', 'always_notify_owner', enabled)
ticket = Ticket(self.env)
- ticket['reporter'] = 'joe.user@example.org'
- ticket['summary'] = u'This is a súmmäry'
- ticket['cc'] = 'joe.bar@example.com'
+ ticket['summary'] = 'Foo'
+ ticket['owner'] = prev_owner = 'joe.user1@example.net'
ticket.insert()
- ticket['component'] = 'dummy'
+ ticket['owner'] = new_owner = 'joe.user2@example.net'
now = datetime.now(utc)
- ticket.save_changes('joe.bar2@example.com', 'This is a change',
- when=now)
+ ticket.save_changes('joe.bar@example.com', 'Changed owner', now)
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=False, modtime=now)
- message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
- # checks for header existence
- self.failIf(not headers)
- # checks for updater in the 'To' recipient list
- self.failIf('To' not in headers)
- tolist = [addr.strip() for addr in headers['To'].split(',')]
- if disable:
- self.failIf('joe.bar2@example.com' in tolist)
+ recipients = notifysuite.smtpd.get_recipients()
+ if enabled:
+ self.assertIn(prev_owner, recipients)
+ self.assertIn(new_owner, recipients)
else:
- self.failIf('joe.bar2@example.com' not in tolist)
-
- # Validate with and without a default domain
- for disable in [False, True]:
- _test_updater(disable)
+ self.assertNotIn(prev_owner, recipients)
+ self.assertNotIn(new_owner, recipients)
- def test_updater_only(self):
- """Notification w/ updater, w/o any other recipient (#4188)"""
- self.env.config.set('notification', 'always_notify_owner', 'false')
- self.env.config.set('notification', 'always_notify_reporter', 'false')
- self.env.config.set('notification', 'always_notify_updater', 'true')
- self.env.config.set('notification', 'smtp_always_cc', '')
- self.env.config.set('notification', 'smtp_always_bcc', '')
- self.env.config.set('notification', 'use_public_cc', 'false')
- self.env.config.set('notification', 'use_short_addr', 'false')
- self.env.config.set('notification', 'smtp_replyto',
- 'joeuser@example.net')
- ticket = Ticket(self.env)
- ticket['summary'] = 'Foo'
- ticket.insert()
- ticket['summary'] = 'Bar'
- ticket['component'] = 'New value'
- ticket.save_changes('joe@example.com', 'this is my comment')
- tn = TicketNotifyEmail(self.env)
- tn.notify(ticket, newticket=True)
- recipients = notifysuite.smtpd.get_recipients()
- self.failIf(recipients is None)
- self.failIf(len(recipients) != 1)
- self.failIf(recipients[0] != 'joe@example.com')
-
- def test_updater_is_reporter(self):
- """Notification to reporter w/ updater option disabled (#3780)"""
- self.env.config.set('notification', 'always_notify_owner', 'false')
- self.env.config.set('notification', 'always_notify_reporter', 'true')
- self.env.config.set('notification', 'always_notify_updater', 'false')
- self.env.config.set('notification', 'smtp_always_cc', '')
- self.env.config.set('notification', 'smtp_always_bcc', '')
- self.env.config.set('notification', 'use_public_cc', 'false')
- self.env.config.set('notification', 'use_short_addr', 'false')
- self.env.config.set('notification', 'smtp_replyto',
- 'joeuser@example.net')
- ticket = Ticket(self.env)
- ticket['summary'] = 'Foo'
- ticket['reporter'] = u'joe@example.org'
- ticket.insert()
- ticket['summary'] = 'Bar'
- ticket['component'] = 'New value'
- ticket.save_changes('joe@example.org', 'this is my comment')
- tn = TicketNotifyEmail(self.env)
- tn.notify(ticket, newticket=True)
- recipients = notifysuite.smtpd.get_recipients()
- self.failIf(recipients is None)
- self.failIf(len(recipients) != 1)
- self.failIf(recipients[0] != 'joe@example.org')
+ for enable in False, True:
+ _test_owner(enable)
def _validate_mimebody(self, mime, ticket, newtk):
"""Body of a ticket notification message"""
- (mime_decoder, mime_name, mime_charset) = mime
+ mime_decoder, mime_name, mime_charset = mime
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=newtk)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
- self.failIf('MIME-Version' not in headers)
- self.failIf('Content-Type' not in headers)
- self.failIf('Content-Transfer-Encoding' not in headers)
- self.failIf(not re.compile(r"1.\d").match(headers['MIME-Version']))
+ headers, body = parse_smtp_message(message)
+ self.assertIn('MIME-Version', headers)
+ self.assertIn('Content-Type', headers)
+ self.assertIn('Content-Transfer-Encoding', headers)
+ self.assertTrue(re.compile(r"1.\d").match(headers['MIME-Version']))
type_re = re.compile(r'^text/plain;\scharset="([\w\-\d]+)"$')
charset = type_re.match(headers['Content-Type'])
- self.failIf(not charset)
+ self.assertTrue(charset)
charset = charset.group(1)
- self.assertEqual(charset, mime_charset)
+ self.assertEqual(mime_charset, charset)
self.assertEqual(headers['Content-Transfer-Encoding'], mime_name)
# checks the width of each body line
for line in body.splitlines():
- self.failIf(len(line) > MAXBODYWIDTH)
- # attempts to decode the body, following the specified MIME endoding
+ self.assertTrue(len(line) <= MAXBODYWIDTH)
+ # attempts to decode the body, following the specified MIME encoding
# and charset
try:
if mime_decoder:
body = mime_decoder.decodestring(body)
body = unicode(body, charset)
except Exception, e:
- raise AssertionError, e
+ raise AssertionError(e)
# now processes each line of the body
bodylines = body.splitlines()
# body starts with one of more summary lines, first line is prefixed
@@ -637,25 +709,25 @@ class NotificationTestCase(unittest.Test
# finds the banner after the summary
banner_delim_re = re.compile(r'^\-+\+\-+$')
bodyheader = []
- while ( not banner_delim_re.match(bodylines[0]) ):
+ while not banner_delim_re.match(bodylines[0]):
bodyheader.append(bodylines.pop(0))
# summary should be present
- self.failIf(not bodyheader)
+ self.assertTrue(bodyheader)
# banner should not be empty
- self.failIf(not bodylines)
+ self.assertTrue(bodylines)
# extracts the ticket ID from the first line
- (tknum, bodyheader[0]) = bodyheader[0].split(' ', 1)
- self.assertEqual(tknum[0], '#')
+ tknum, bodyheader[0] = bodyheader[0].split(' ', 1)
+ self.assertEqual('#', tknum[0])
try:
tkid = int(tknum[1:-1])
- self.assertEqual(tkid, 1)
+ self.assertEqual(1, tkid)
except ValueError:
- raise AssertionError, "invalid ticket number"
- self.assertEqual(tknum[-1], ':')
+ raise AssertionError("invalid ticket number")
+ self.assertEqual(':', tknum[-1])
summary = ' '.join(bodyheader)
self.assertEqual(summary, ticket['summary'])
# now checks the banner contents
- self.failIf(not banner_delim_re.match(bodylines[0]))
+ self.assertTrue(banner_delim_re.match(bodylines[0]))
banner = True
footer = None
props = {}
@@ -667,11 +739,11 @@ class NotificationTestCase(unittest.Test
if banner:
# parse banner and fill in a property dict
properties = line.split('|')
- self.assertEqual(len(properties), 2)
+ self.assertEqual(2, len(properties))
for prop in properties:
if prop.strip() == '':
continue
- (k, v) = prop.split(':')
+ k, v = prop.split(':')
props[k.strip().lower()] = v.strip()
# detect footer marker (weak detection)
if not footer:
@@ -679,10 +751,10 @@ class NotificationTestCase(unittest.Test
footer = 0
continue
# check footer
- if footer != None:
+ if footer is not None:
footer += 1
# invalid footer detection
- self.failIf(footer > 3)
+ self.assertTrue(footer <= 3)
# check ticket link
if line[:11] == 'Ticket URL:':
ticket_link = self.env.abs_href.ticket(ticket.id)
@@ -693,12 +765,13 @@ class NotificationTestCase(unittest.Test
xlist = ['summary', 'description', 'comment', 'time', 'changetime']
# check banner content (field exists, msg value matches ticket value)
for p in [prop for prop in ticket.values.keys() if prop not in xlist]:
- self.failIf(not props.has_key(p))
+ self.assertIn(p, props)
# Email addresses might be obfuscated
if '@' in ticket[p] and '@' in props[p]:
- self.failIf(props[p].split('@')[0] != ticket[p].split('@')[0])
+ self.assertEqual(props[p].split('@')[0],
+ ticket[p].split('@')[0])
else:
- self.failIf(props[p] != ticket[p])
+ self.assertEqual(props[p], ticket[p])
def test_props_format_ambiwidth_single(self):
self.env.config.set('notification', 'mime_encoding', 'none')
@@ -827,7 +900,7 @@ Resolution: fixed |
Type: defect | Status: new
Priority: major | Milestone: milestone1
Component: Lorem ipsum dolor sit amet, | Version: 2.0
- consectetur adipisicing elit, sed do eiusmod | Keywords:
+ consectetur adipisicing elit, sed do eiusmod |
tempor incididunt ut labore et dolore magna |
aliqua. Ut enim ad minim veniam, quis nostrud |
exercitation ullamco laboris nisi ut aliquip |
@@ -837,7 +910,7 @@ Resolution: fixed |
Excepteur sint occaecat cupidatat non |
proident, sunt in culpa qui officia deserunt |
mollit anim id est laborum. |
-Resolution: fixed |"""
+Resolution: fixed | Keywords:"""
self._validate_props_format(formatted, ticket)
def test_props_format_wrap_leftside_unicode(self):
@@ -865,10 +938,10 @@ Resolution: fixed
Type: defect | Status: new
Priority: major | Milestone: milestone1
Component: Trac 㯠BSD ã©ã¤ã»ã³ã¹ã®ãã¨ã§é
å¸ | Version: 2.0
- ããã¦ãã¾ãã[1:]ãã®ã©ã¤ã»ã³ã¹ã®å
¨æã¯ãé
| Keywords:
+ ããã¦ãã¾ãã[1:]ãã®ã©ã¤ã»ã³ã¹ã®å
¨æã¯ãé
|
å¸ãã¡ã¤ã«ã«å«ã¾ãã¦ãã [3:COPYING] ãã¡ã¤ã« |
ã¨åããã®ã[2:ãªã³ã©ã¤ã³]ã§åç
§ã§ãã¾ãã |
-Resolution: fixed |"""
+Resolution: fixed | Keywords:"""
self._validate_props_format(formatted, ticket)
def test_props_format_wrap_rightside(self):
@@ -893,7 +966,7 @@ Resolution: fixed
u'culpa qui officia deserunt mollit anim id ' \
u'est laborum.'
ticket['component'] = u'component1'
- ticket['version'] = u'2.0'
+ ticket['version'] = u'2.0 Standard and International Edition'
ticket['resolution'] = u'fixed'
ticket['keywords'] = u''
ticket.insert()
@@ -901,8 +974,8 @@ Resolution: fixed
Reporter: anonymous | Owner: somebody
Type: defect | Status: new
Priority: major | Milestone: Lorem ipsum dolor sit amet,
- Component: component1 | consectetur adipisicing elit, sed do eiusmod
-Resolution: fixed | tempor incididunt ut labore et dolore magna
+ | consectetur adipisicing elit, sed do eiusmod
+ | tempor incididunt ut labore et dolore magna
| aliqua. Ut enim ad minim veniam, quis nostrud
| exercitation ullamco laboris nisi ut aliquip ex
| ea commodo consequat. Duis aute irure dolor in
@@ -911,8 +984,9 @@ Resolution: fixed | tempor incid
| occaecat cupidatat non proident, sunt in culpa
| qui officia deserunt mollit anim id est
| laborum.
- | Version: 2.0
- | Keywords:"""
+ Component: component1 | Version: 2.0 Standard and International
+ | Edition
+Resolution: fixed | Keywords:"""
self._validate_props_format(formatted, ticket)
def test_props_format_wrap_rightside_unicode(self):
@@ -937,10 +1011,10 @@ Resolution: fixed | tempor incid
Reporter: anonymous | Owner: somebody
Type: defect | Status: new
Priority: major | Milestone: Trac å¨ç»è¿ä¿®æ¹çBSDåè®®ä¸åå¸ã
- Component: component1 | [1:]åè®®ç宿´ææ¬å¯ä»¥[2:å¨çº¿æ¥ç]ä¹å¯å¨åå¸ç
-Resolution: fixed | ç [3:COPYING] æä»¶ä¸æ¾å°ã
- | Version: 2.0
- | Keywords:"""
+ | [1:]åè®®ç宿´ææ¬å¯ä»¥[2:å¨çº¿æ¥ç]ä¹å¯å¨åå¸ç
+ | ç [3:COPYING] æä»¶ä¸æ¾å°ã
+ Component: component1 | Version: 2.0
+Resolution: fixed | Keywords:"""
self._validate_props_format(formatted, ticket)
def test_props_format_wrap_bothsides(self):
@@ -964,31 +1038,33 @@ Resolution: fixed | ç [3:CO
u'occaecat cupidatat non proident, sunt in ' \
u'culpa qui officia deserunt mollit anim id ' \
u'est laborum.'
- ticket['component'] = ticket['milestone']
+ ticket['component'] = (u'Lorem ipsum dolor sit amet, consectetur '
+ u'adipisicing elit, sed do eiusmod tempor '
+ u'incididunt ut labore et dolore magna aliqua.')
ticket['version'] = u'2.0'
ticket['resolution'] = u'fixed'
- ticket['keywords'] = u''
+ ticket['keywords'] = u'Ut enim ad minim veniam, ....'
ticket.insert()
formatted = """\
Reporter: anonymous | Owner: somebody
Type: defect | Status: new
Priority: major | Milestone: Lorem ipsum dolor sit
- Component: Lorem ipsum dolor sit | amet, consectetur adipisicing elit,
- amet, consectetur adipisicing | sed do eiusmod tempor incididunt ut
- elit, sed do eiusmod tempor | labore et dolore magna aliqua. Ut
- incididunt ut labore et dolore | enim ad minim veniam, quis nostrud
- magna aliqua. Ut enim ad minim | exercitation ullamco laboris nisi
- veniam, quis nostrud exercitation | ut aliquip ex ea commodo consequat.
- ullamco laboris nisi ut aliquip | Duis aute irure dolor in
- ex ea commodo consequat. Duis | reprehenderit in voluptate velit
- aute irure dolor in reprehenderit | esse cillum dolore eu fugiat nulla
- in voluptate velit esse cillum | pariatur. Excepteur sint occaecat
- dolore eu fugiat nulla pariatur. | cupidatat non proident, sunt in
- Excepteur sint occaecat cupidatat | culpa qui officia deserunt mollit
- non proident, sunt in culpa qui | anim id est laborum.
- officia deserunt mollit anim id | Version: 2.0
- est laborum. | Keywords:
-Resolution: fixed |"""
+ | amet, consectetur adipisicing elit,
+ | sed do eiusmod tempor incididunt ut
+ | labore et dolore magna aliqua. Ut
+ | enim ad minim veniam, quis nostrud
+ | exercitation ullamco laboris nisi
+ | ut aliquip ex ea commodo consequat.
+ | Duis aute irure dolor in
+ | reprehenderit in voluptate velit
+ | esse cillum dolore eu fugiat nulla
+ Component: Lorem ipsum dolor sit | pariatur. Excepteur sint occaecat
+ amet, consectetur adipisicing | cupidatat non proident, sunt in
+ elit, sed do eiusmod tempor | culpa qui officia deserunt mollit
+ incididunt ut labore et dolore | anim id est laborum.
+ magna aliqua. | Version: 2.0
+Resolution: fixed | Keywords: Ut enim ad minim
+ | veniam, ...."""
self._validate_props_format(formatted, ticket)
def test_props_format_wrap_bothsides_unicode(self):
@@ -1011,7 +1087,7 @@ Resolution: fixed |""
u'ã«å«ã¾ãã¦ãã[3:CÐPYING]ãã¡ã¤' \
u'ã«ã¨åããã®ã[2:ãªã³ã©ã¤ã³]ã§' \
u'åç
§ã§ãã¾ãã'
- ticket['version'] = u'2.0'
+ ticket['version'] = u'2.0 International Edition'
ticket['resolution'] = u'fixed'
ticket['keywords'] = u''
ticket.insert()
@@ -1022,17 +1098,74 @@ Resolution: fixed |""
Component: Trac 㯠BSD ã©ã¤ã»ã³ã¹ | è®®ä¸åå¸ã[1:]åè®®ç宿´ææ¬å¯ä»¥[2:
ã®ãã¨ã§é
å¸ããã¦ãã¾ãã[1:]ã | å¨çº¿æ¥ç]ä¹å¯å¨åå¸çç [3:COPYING]
ã®ã©ã¤ã»ã³ã¹ã®å
¨æã¯ãâ»é
å¸ãã¡ | æä»¶ä¸æ¾å°ã
- ã¤ã«ã«å«ã¾ãã¦ãã[3:CÐPYING]ã | Version: 2.0
- ã¡ã¤ã«ã¨åããã®ã[2:ãªã³ã©ã¤ã³] | Keywords:
+ ã¤ã«ã«å«ã¾ãã¦ãã[3:CÐPYING]ã | Version: 2.0 International
+ ã¡ã¤ã«ã¨åããã®ã[2:ãªã³ã©ã¤ã³] | Edition
ã§åç
§ã§ãã¾ãã |
-Resolution: fixed |"""
+Resolution: fixed | Keywords:"""
+ self._validate_props_format(formatted, ticket)
+
+ def test_props_format_wrap_ticket_10283(self):
+ self.env.config.set('notification', 'mime_encoding', 'none')
+ for name, value in (('blockedby', 'text'),
+ ('blockedby.label', 'Blocked by'),
+ ('blockedby.order', '6'),
+ ('blocking', 'text'),
+ ('blocking.label', 'Blocking'),
+ ('blocking.order', '5'),
+ ('deployment', 'text'),
+ ('deployment.label', 'Deployment state'),
+ ('deployment.order', '1'),
+ ('nodes', 'text'),
+ ('nodes.label', 'Related nodes'),
+ ('nodes.order', '3'),
+ ('privacy', 'text'),
+ ('privacy.label', 'Privacy sensitive'),
+ ('privacy.order', '2'),
+ ('sensitive', 'text'),
+ ('sensitive.label', 'Security sensitive'),
+ ('sensitive.order', '4')):
+ self.env.config.set('ticket-custom', name, value)
+
+ ticket = Ticket(self.env)
+ ticket['summary'] = u'This is a summary'
+ ticket['reporter'] = u'anonymous'
+ ticket['owner'] = u'somebody'
+ ticket['type'] = u'defect'
+ ticket['status'] = u'closed'
+ ticket['priority'] = u'normal'
+ ticket['milestone'] = u'iter_01'
+ ticket['component'] = u'XXXXXXXXXXXXXXXXXXXXXXXXXX'
+ ticket['resolution'] = u'fixed'
+ ticket['keywords'] = u''
+ ticket['deployment'] = ''
+ ticket['privacy'] = '0'
+ ticket['nodes'] = 'XXXXXXXXXX'
+ ticket['sensitive'] = '0'
+ ticket['blocking'] = ''
+ ticket['blockedby'] = ''
+ ticket.insert()
+
+ formatted = """\
+ Reporter: anonymous | Owner:
+ | somebody
+ Type: defect | Status:
+ | closed
+ Priority: normal | Milestone:
+ | iter_01
+ Component: XXXXXXXXXXXXXXXXXXXXXXXXXX | Resolution:
+ | fixed
+ Keywords: | Deployment state:
+ Privacy sensitive: 0 | Related nodes:
+ | XXXXXXXXXX
+Security sensitive: 0 | Blocking:
+ Blocked by: |"""
self._validate_props_format(formatted, ticket)
def _validate_props_format(self, expected, ticket):
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
message = notifysuite.smtpd.get_message()
- (headers, body) = parse_smtp_message(message)
+ headers, body = parse_smtp_message(message)
bodylines = body.splitlines()
# Extract ticket properties
delim_re = re.compile(r'^\-+\+\-+$')
@@ -1052,7 +1185,7 @@ Resolution: fixed |""
ticket.insert()
tn = TicketNotifyEmail(self.env)
tn.notify(ticket, newticket=True)
- self.assertNotEqual(None, notifysuite.smtpd.get_message())
+ self.assertIsNotNone(notifysuite.smtpd.get_message())
self.assertEqual('My Summary', ticket['summary'])
self.assertEqual('Some description', ticket['description'])
valid_fieldnames = set([f['name'] for f in ticket.fields])
@@ -1069,6 +1202,52 @@ Resolution: fixed |""
tn.ticket = ticket
tn.get_message_id('foo')
+ def test_mime_meta_characters_in_from_header(self):
+ """MIME encoding with meta characters in From header"""
+
+ self.env.config.set('notification', 'smtp_from', 'trac@example.com')
+ self.env.config.set('notification', 'mime_encoding', 'base64')
+ ticket = Ticket(self.env)
+ ticket['reporter'] = 'joeuser'
+ ticket['summary'] = 'This is a summary'
+ ticket.insert()
+ tn = TicketNotifyEmail(self.env)
+
+ def notify(from_name):
+ self.env.config.set('notification', 'smtp_from_name', from_name)
+ tn.notify(ticket, newticket=True)
+ message = notifysuite.smtpd.get_message()
+ headers, body = parse_smtp_message(message)
+ return message, headers, body
+
+ message, headers, body = notify(u'Träc')
+ self.assertEqual(r'"=?utf-8?b?VHLDpGM=?=" <tr...@example.com>',
+ headers['From'])
+ message, headers, body = notify(u'Trac\\')
+ self.assertEqual(r'"Trac\\" <tr...@example.com>', headers['From'])
+ message, headers, body = notify(u'Trac"')
+ self.assertEqual(r'"Trac\"" <tr...@example.com>', headers['From'])
+ message, headers, body = notify(u'=?utf-8?b?****?=')
+ self.assertEqual('"=?utf-8?b?PT91dGYtOD9iPyoqKio/PQ==?=" '
+ '<tr...@example.com>', headers['From'])
+
+ def test_mime_meta_characters_in_subject_header(self):
+ """MIME encoding with meta characters in Subject header"""
+
+ self.env.config.set('notification', 'smtp_from', 'trac@example.com')
+ self.env.config.set('notification', 'mime_encoding', 'base64')
+ summary = u'=?utf-8?q?****?='
+ ticket = Ticket(self.env)
+ ticket['reporter'] = 'joeuser'
+ ticket['summary'] = summary
+ ticket.insert()
+ tn = TicketNotifyEmail(self.env)
+ tn.notify(ticket, newticket=True)
+ message = notifysuite.smtpd.get_message()
+ headers, body = parse_smtp_message(message)
+ self.assertIn('\nSubject: =?utf-8?b?', message) # is mime-encoded
+ self.assertEqual(summary,
+ re.split(r' #[0-9]+: ', headers['Subject'], 1)[1])
class NotificationTestSuite(unittest.TestSuite):
@@ -1079,18 +1258,20 @@ class NotificationTestSuite(unittest.Tes
unittest.TestSuite.__init__(self)
self.smtpd = SMTPThreadedServer(SMTP_TEST_PORT)
self.smtpd.start()
- self.addTest(unittest.makeSuite(NotificationTestCase, 'test'))
+ self.addTest(unittest.makeSuite(RecipientTestCase))
+ self.addTest(unittest.makeSuite(NotificationTestCase))
self.remaining = self.countTestCases()
def tear_down(self):
"""Reset the local SMTP test server"""
self.smtpd.cleanup()
- self.remaining = self.remaining-1
+ self.remaining -= 1
if self.remaining > 0:
return
# stop the SMTP test server when all tests have been completed
self.smtpd.stop()
+
def suite():
global notifysuite
if not notifysuite:
@@ -1098,4 +1279,4 @@ def suite():
return notifysuite
if __name__ == '__main__':
- unittest.TextTestRunner(verbosity=2).run(suite())
+ unittest.main(defaultTest='suite')