Index: appengine/monorail/features/test/notify_helpers_test.py |
diff --git a/appengine/monorail/features/test/notify_helpers_test.py b/appengine/monorail/features/test/notify_helpers_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7267269e93b76f646ff2d24c3462025de81a6fb3 |
--- /dev/null |
+++ b/appengine/monorail/features/test/notify_helpers_test.py |
@@ -0,0 +1,384 @@ |
+# -*- coding: utf8 -*- |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is govered by a BSD-style |
+# license that can be found in the LICENSE file or at |
+# https://developers.google.com/open-source/licenses/bsd |
+ |
+"""Tests for notify_helpers.py.""" |
+ |
+import unittest |
+ |
+from features import notify_helpers |
+from framework import emailfmt |
+from framework import framework_views |
+from services import service_manager |
+from testing import fake |
+ |
+ |
+REPLY_NOT_ALLOWED = notify_helpers.REPLY_NOT_ALLOWED |
+REPLY_MAY_COMMENT = notify_helpers.REPLY_MAY_COMMENT |
+REPLY_MAY_UPDATE = notify_helpers.REPLY_MAY_UPDATE |
+ |
+ |
+class ComputeIssueChangeAddressPermListTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self.users_by_id = { |
+ 111L: framework_views.UserView(111L, 'owner@example.com', True), |
+ 222L: framework_views.UserView(222L, 'member@example.com', True), |
+ 999L: framework_views.UserView(999L, 'visitor@example.com', True), |
+ } |
+ self.services = service_manager.Services( |
+ project=fake.ProjectService(), |
+ config=fake.ConfigService(), |
+ issue=fake.IssueService(), |
+ user=fake.UserService(), |
+ usergroup=fake.UserGroupService()) |
+ self.services.user.TestAddUser('owner@example.com', 111L) |
+ self.services.user.TestAddUser('member@example.com', 222L) |
+ self.services.user.TestAddUser('visitor@example.com', 999L) |
+ self.project = self.services.project.TestAddProject( |
+ 'proj', owner_ids=[111L], committer_ids=[222L]) |
+ self.project.process_inbound_email = True |
+ self.issue = fake.MakeTestIssue( |
+ self.project.project_id, 1, 'summary', 'New', 111L) |
+ |
+ def testEmptyIDs(self): |
+ cnxn = 'fake cnxn' |
+ addr_perm_list = notify_helpers.ComputeIssueChangeAddressPermList( |
+ cnxn, [], self.project, self.issue, self.services, [], {}, |
+ pref_check_function=lambda *args: True) |
+ self.assertEqual([], addr_perm_list) |
+ |
+ def testRecipientIsMember(self): |
+ cnxn = 'fake cnxn' |
+ ids_to_consider = [111L, 222L, 999L] |
+ addr_perm_list = notify_helpers.ComputeIssueChangeAddressPermList( |
+ cnxn, ids_to_consider, self.project, self.issue, self.services, set(), |
+ self.users_by_id, pref_check_function=lambda *args: True) |
+ self.assertEqual( |
+ [(True, 'owner@example.com', REPLY_MAY_UPDATE), |
+ (True, 'member@example.com', REPLY_MAY_UPDATE), |
+ (False, 'visitor@example.com', REPLY_MAY_COMMENT)], |
+ addr_perm_list) |
+ |
+ |
+class ComputeProjectAndIssueNotificationAddrListTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self.services = service_manager.Services( |
+ project=fake.ProjectService(), |
+ user=fake.UserService()) |
+ self.project = self.services.project.TestAddProject('project') |
+ self.services.user.TestAddUser('alice@gmail.com', 111L) |
+ self.services.user.TestAddUser('bob@gmail.com', 222L) |
+ self.services.user.TestAddUser('fred@gmail.com', 555L) |
+ |
+ def testNotifyAddress(self): |
+ # No mailing list or filter rules are defined |
+ addr_perm_list = notify_helpers.ComputeProjectNotificationAddrList( |
+ self.project, True, set()) |
+ self.assertListEqual([], addr_perm_list) |
+ |
+ # Only mailing list is notified. |
+ self.project.issue_notify_address = 'mailing-list@domain.com' |
+ addr_perm_list = notify_helpers.ComputeProjectNotificationAddrList( |
+ self.project, True, set()) |
+ self.assertListEqual( |
+ [(False, 'mailing-list@domain.com', REPLY_NOT_ALLOWED)], |
+ addr_perm_list) |
+ |
+ # No one is notified because mailing list was already notified. |
+ omit_addrs = {'mailing-list@domain.com'} |
+ addr_perm_list = notify_helpers.ComputeProjectNotificationAddrList( |
+ self.project, False, omit_addrs) |
+ self.assertListEqual([], addr_perm_list) |
+ |
+ # No one is notified because anon users cannot view. |
+ addr_perm_list = notify_helpers.ComputeProjectNotificationAddrList( |
+ self.project, False, set()) |
+ self.assertListEqual([], addr_perm_list) |
+ |
+ def testFilterRuleNotifyAddresses(self): |
+ issue = fake.MakeTestIssue( |
+ self.project.project_id, 1, 'summary', 'New', 555L) |
+ issue.derived_notify_addrs.extend(['notify@domain.com']) |
+ |
+ addr_perm_list = notify_helpers.ComputeIssueNotificationAddrList( |
+ issue, set()) |
+ self.assertListEqual( |
+ [(False, 'notify@domain.com', REPLY_NOT_ALLOWED)], |
+ addr_perm_list) |
+ |
+ # Also-notify addresses can be omitted (e.g., if it is the same as |
+ # the email address of the user who made the change). |
+ addr_perm_list = notify_helpers.ComputeIssueNotificationAddrList( |
+ issue, {'notify@domain.com'}) |
+ self.assertListEqual([], addr_perm_list) |
+ |
+ |
+class MakeBulletedEmailWorkItemsTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self.project = fake.Project(project_name='proj1') |
+ self.commenter_view = framework_views.UserView( |
+ 111L, 'test@example.com', True) |
+ |
+ def testEmptyAddrs(self): |
+ """Test the case where we found zero users to notify.""" |
+ email_tasks = notify_helpers.MakeBulletedEmailWorkItems( |
+ [], 'subject', 'body', 'body', self.project, 'example.com', |
+ self.commenter_view) |
+ self.assertEqual([], email_tasks) |
+ email_tasks = notify_helpers.MakeBulletedEmailWorkItems( |
+ [([], 'reason')], 'subject', 'body', 'body', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertEqual([], email_tasks) |
+ |
+ |
+class MakeEmailWorkItemTest(unittest.TestCase): |
+ |
+ def setUp(self): |
+ self.project = fake.Project(project_name='proj1') |
+ self.project.process_inbound_email = True |
+ self.commenter_view = framework_views.UserView( |
+ 111L, 'test@example.com', True) |
+ self.expected_html_footer = ( |
+ 'You received this message because:<br/> 1. reason<br/><br/>You may ' |
+ 'adjust your notification preferences at:<br/><a href="https://' |
+ 'example.com/hosting/settings">https://example.com/hosting/settings' |
+ '</a>') |
+ |
+ def testBodySelection(self): |
+ """We send non-members the email body that is indented for non-members.""" |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view) |
+ |
+ self.assertEqual('a@a.com', email_task['to']) |
+ self.assertEqual('subject', email_task['subject']) |
+ self.assertIn('body non', email_task['body']) |
+ self.assertEqual( |
+ emailfmt.FormatFromAddr(self.project, commenter_view=self.commenter_view, |
+ can_reply_to=False), |
+ email_task['from_addr']) |
+ self.assertEqual(emailfmt.NoReplyAddress(), email_task['reply_to']) |
+ |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'body mem', 'body mem', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertIn('body mem', email_task['body']) |
+ |
+ def testHtmlBody_NoDetailUrl(self): |
+ """"An html body is not be sent if detail_url is not specified.""" |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view, detail_url=None) |
+ |
+ self.assertIsNone(email_task['html_body']) |
+ |
+ def testHtmlBody_WithDetailUrl(self): |
+ """"An html body is sent if a detail_url is specified.""" |
+ detail_url = 'http://test-detail-url.com/id=1234' |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view, detail_url=detail_url) |
+ |
+ expected_html_body = ( |
+ notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % ( |
+ detail_url, |
+ 'body non-- <br/>%s' % self.expected_html_footer)) |
+ self.assertEquals(expected_html_body, email_task['html_body']) |
+ |
+ def testHtmlBody_WithUnicodeChars(self): |
+ """"An html body is sent if a detail_url is specified.""" |
+ detail_url = 'http://test-detail-url.com/id=1234' |
+ unicode_content = '\xe2\x9d\xa4 â â' |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', unicode_content, 'unused body mem', |
+ self.project, 'example.com', self.commenter_view, detail_url=detail_url) |
+ |
+ expected_html_body = ( |
+ notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % ( |
+ detail_url, |
+ '%s-- <br/>%s' % (unicode_content.decode('utf-8'), |
+ self.expected_html_footer))) |
+ self.assertEquals(expected_html_body, email_task['html_body']) |
+ |
+ def testHtmlBody_WithLinks(self): |
+ """"An html body is sent if a detail_url is specified.""" |
+ detail_url = 'http://test-detail-url.com/id=1234' |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'test google.com test', 'unused body mem', |
+ self.project, 'example.com', self.commenter_view, detail_url=detail_url) |
+ |
+ expected_html_body = ( |
+ notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % ( |
+ detail_url, |
+ 'test <a href="http://google.com">google.com</a> test-- <br/>%s' % ( |
+ self.expected_html_footer))) |
+ self.assertEquals(expected_html_body, email_task['html_body']) |
+ |
+ def testHtmlBody_LinkWithinTags(self): |
+ """"An html body is sent with correct <a href>s.""" |
+ detail_url = 'http://test-detail-url.com/id=1234' |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'test <http://google.com> test', 'unused body', |
+ self.project, 'example.com', self.commenter_view, detail_url=detail_url) |
+ |
+ expected_html_body = ( |
+ notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % ( |
+ detail_url, |
+ 'test <a href="http://google.com"><http://google.com></a> ' |
+ 'test-- <br/>%s' % self.expected_html_footer)) |
+ self.assertEquals(expected_html_body, email_task['html_body']) |
+ |
+ def testHtmlBody_EmailWithinTags(self): |
+ """"An html body is sent with correct <a href>s.""" |
+ detail_url = 'http://test-detail-url.com/id=1234' |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'test <t@chromium.org> <a@chromium.org> test', |
+ 'unused body mem', self.project, 'example.com', self.commenter_view, |
+ detail_url=detail_url) |
+ |
+ expected_html_body = ( |
+ notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % ( |
+ detail_url, |
+ 'test <a href="mailto:t@chromium.org"><t@chromium.org></a> ' |
+ '<a href="mailto:a@chromium.org"><a@chromium.org></a> ' |
+ 'test-- <br/>%s' % self.expected_html_footer)) |
+ self.assertEquals(expected_html_body, email_task['html_body']) |
+ |
+ def testHtmlBody_WithEscapedHtml(self): |
+ """"An html body is sent with html content escaped.""" |
+ detail_url = 'http://test-detail-url.com/id=1234' |
+ body_with_html_content = ( |
+ '<a href="http://www.google.com">test</a> \'something\'') |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (False, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', body_with_html_content, 'unused body mem', |
+ self.project, 'example.com', self.commenter_view, detail_url=detail_url) |
+ |
+ escaped_body_with_html_content = ( |
+ '<a href="http://www.google.com">test</a> ' |
+ ''something'') |
+ notify_helpers._MakeNotificationFooter( |
+ ['reason'], REPLY_NOT_ALLOWED, 'example.com') |
+ expected_html_body = ( |
+ notify_helpers.HTML_BODY_WITH_GMAIL_ACTION_TEMPLATE % ( |
+ detail_url, |
+ '%s-- <br/>%s' % (escaped_body_with_html_content, |
+ self.expected_html_footer))) |
+ self.assertEquals(expected_html_body, email_task['html_body']) |
+ |
+ def testReplyInvitation(self): |
+ """We include a footer about replying that is appropriate for that user.""" |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_NOT_ALLOWED), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertEqual(emailfmt.NoReplyAddress(), email_task['reply_to']) |
+ self.assertNotIn('Reply to this email', email_task['body']) |
+ |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_MAY_COMMENT), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertEqual( |
+ '%s@%s' % (self.project.project_name, emailfmt.MailDomain()), |
+ email_task['reply_to']) |
+ self.assertIn('Reply to this email to add a comment', email_task['body']) |
+ self.assertNotIn('make changes', email_task['body']) |
+ |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_MAY_UPDATE), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertEqual( |
+ '%s@%s' % (self.project.project_name, emailfmt.MailDomain()), |
+ email_task['reply_to']) |
+ self.assertIn('Reply to this email to add a comment', email_task['body']) |
+ self.assertIn('make updates', email_task['body']) |
+ |
+ def testInboundEmailDisabled(self): |
+ """We don't invite replies if they are disabled for this project.""" |
+ self.project.process_inbound_email = False |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_MAY_UPDATE), |
+ ['reason'], 'subject', 'body non', 'body mem', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertEqual(emailfmt.NoReplyAddress(), email_task['reply_to']) |
+ |
+ def testReasons(self): |
+ """The footer lists reasons why that email was sent to that user.""" |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_MAY_UPDATE), |
+ ['Funny', 'Caring', 'Near'], 'subject', 'body', 'body', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertIn('because:', email_task['body']) |
+ self.assertIn('1. Funny', email_task['body']) |
+ self.assertIn('2. Caring', email_task['body']) |
+ self.assertIn('3. Near', email_task['body']) |
+ |
+ email_task = notify_helpers._MakeEmailWorkItem( |
+ (True, 'a@a.com', REPLY_MAY_UPDATE), |
+ [], 'subject', 'body', 'body', self.project, |
+ 'example.com', self.commenter_view) |
+ self.assertNotIn('because', email_task['body']) |
+ |
+ |
+class MakeNotificationFooterTest(unittest.TestCase): |
+ |
+ def testMakeNotificationFooter_NoReason(self): |
+ footer = notify_helpers._MakeNotificationFooter( |
+ [], REPLY_NOT_ALLOWED, 'example.com') |
+ self.assertEqual('', footer) |
+ |
+ def testMakeNotificationFooter_WithReason(self): |
+ footer = notify_helpers._MakeNotificationFooter( |
+ ['REASON'], REPLY_NOT_ALLOWED, 'example.com') |
+ self.assertIn('REASON', footer) |
+ self.assertIn('https://example.com/hosting/settings', footer) |
+ |
+ footer = notify_helpers._MakeNotificationFooter( |
+ ['REASON'], REPLY_NOT_ALLOWED, 'example.com') |
+ self.assertIn('REASON', footer) |
+ self.assertIn('https://example.com/hosting/settings', footer) |
+ |
+ def testMakeNotificationFooter_ManyReasons(self): |
+ footer = notify_helpers._MakeNotificationFooter( |
+ ['Funny', 'Caring', 'Warmblooded'], REPLY_NOT_ALLOWED, |
+ 'example.com') |
+ self.assertIn('Funny', footer) |
+ self.assertIn('Caring', footer) |
+ self.assertIn('Warmblooded', footer) |
+ |
+ def testMakeNotificationFooter_WithReplyInstructions(self): |
+ footer = notify_helpers._MakeNotificationFooter( |
+ ['REASON'], REPLY_NOT_ALLOWED, 'example.com') |
+ self.assertNotIn('Reply', footer) |
+ self.assertIn('https://example.com/hosting/settings', footer) |
+ |
+ footer = notify_helpers._MakeNotificationFooter( |
+ ['REASON'], REPLY_MAY_COMMENT, 'example.com') |
+ self.assertIn('add a comment', footer) |
+ self.assertNotIn('make updates', footer) |
+ self.assertIn('https://example.com/hosting/settings', footer) |
+ |
+ footer = notify_helpers._MakeNotificationFooter( |
+ ['REASON'], REPLY_MAY_UPDATE, 'example.com') |
+ self.assertIn('add a comment', footer) |
+ self.assertIn('make updates', footer) |
+ self.assertIn('https://example.com/hosting/settings', footer) |
+ |
+ |
+if __name__ == '__main__': |
+ unittest.main() |