Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | # Copyright 2016 The Chromium Authors |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 4 | |
| 5 | """Tests for notify.py.""" |
| 6 | from __future__ import print_function |
| 7 | from __future__ import division |
| 8 | from __future__ import absolute_import |
| 9 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 10 | import flask |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 11 | import json |
| 12 | import mock |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 13 | import six |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 14 | import unittest |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 15 | |
| 16 | from google.appengine.ext import testbed |
| 17 | |
| 18 | from features import notify |
| 19 | from features import notify_reasons |
| 20 | from framework import emailfmt |
| 21 | from framework import urls |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 22 | from mrproto import tracker_pb2 |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 23 | from services import service_manager |
| 24 | from testing import fake |
| 25 | from testing import testing_helpers |
| 26 | from tracker import attachment_helpers |
| 27 | from tracker import tracker_bizobj |
| 28 | |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 29 | |
| 30 | def MakeTestIssue(project_id, local_id, owner_id, reporter_id, is_spam=False): |
| 31 | issue = tracker_pb2.Issue() |
| 32 | issue.project_id = project_id |
| 33 | issue.local_id = local_id |
| 34 | issue.issue_id = 1000 * project_id + local_id |
| 35 | issue.owner_id = owner_id |
| 36 | issue.reporter_id = reporter_id |
| 37 | issue.is_spam = is_spam |
| 38 | return issue |
| 39 | |
| 40 | |
| 41 | class NotifyTaskHandleRequestTest(unittest.TestCase): |
| 42 | |
| 43 | def setUp(self): |
| 44 | self.services = service_manager.Services( |
| 45 | user=fake.UserService(), |
| 46 | usergroup=fake.UserGroupService(), |
| 47 | project=fake.ProjectService(), |
| 48 | config=fake.ConfigService(), |
| 49 | issue=fake.IssueService(), |
| 50 | issue_star=fake.IssueStarService(), |
| 51 | features=fake.FeaturesService()) |
| 52 | self.requester = self.services.user.TestAddUser('requester@example.com', 1) |
| 53 | self.nonmember = self.services.user.TestAddUser('user@example.com', 2) |
| 54 | self.member = self.services.user.TestAddUser('member@example.com', 3) |
| 55 | self.project = self.services.project.TestAddProject( |
| 56 | 'test-project', owner_ids=[1, 3], project_id=12345) |
| 57 | self.issue1 = MakeTestIssue( |
| 58 | project_id=12345, local_id=1, owner_id=2, reporter_id=1) |
| 59 | self.issue2 = MakeTestIssue( |
| 60 | project_id=12345, local_id=2, owner_id=2, reporter_id=1) |
| 61 | self.services.issue.TestAddIssue(self.issue1) |
| 62 | |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 63 | self.orig_sign_attachment_id = attachment_helpers.SignAttachmentID |
| 64 | attachment_helpers.SignAttachmentID = ( |
| 65 | lambda aid: 'signed_%d' % aid) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 66 | self.servlet = notify.OutboundEmailTask(services=self.services) |
| 67 | self.app = flask.Flask('test_app') |
| 68 | self.app.config['TESTING'] = True |
| 69 | self.app.add_url_rule( |
| 70 | '/_task/outboundEmail.do', |
| 71 | view_func=self.servlet.PostOutboundEmailTask, |
| 72 | methods=['POST']) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 73 | self.testbed = testbed.Testbed() |
| 74 | self.testbed.activate() |
| 75 | self.testbed.init_memcache_stub() |
| 76 | self.testbed.init_datastore_v3_stub() |
| 77 | |
| 78 | def tearDown(self): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 79 | attachment_helpers.SignAttachmentID = self.orig_sign_attachment_id |
| 80 | |
| 81 | def get_filtered_task_call_args(self, create_task_mock, relative_uri): |
| 82 | return [ |
| 83 | (args, kwargs) |
| 84 | for (args, kwargs) in create_task_mock.call_args_list |
| 85 | if args[0]['app_engine_http_request']['relative_uri'] == relative_uri |
| 86 | ] |
| 87 | |
| 88 | def VerifyParams(self, result, params): |
| 89 | self.assertEqual( |
| 90 | bool(params['send_email']), result['params']['send_email']) |
| 91 | if 'issue_id' in params: |
| 92 | self.assertEqual(params['issue_id'], result['params']['issue_id']) |
| 93 | if 'issue_ids' in params: |
| 94 | self.assertEqual([int(p) for p in params['issue_ids'].split(',')], |
| 95 | result['params']['issue_ids']) |
| 96 | |
| 97 | def testNotifyIssueChangeTask_Normal(self): |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 98 | task = notify.NotifyIssueChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 99 | params = {'send_email': 1, 'issue_id': 12345001, 'seq': 0, |
| 100 | 'commenter_id': 2} |
| 101 | mr = testing_helpers.MakeMonorailRequest( |
| 102 | user_info={'user_id': 1}, |
| 103 | params=params, |
| 104 | method='POST', |
| 105 | services=self.services) |
| 106 | result = task.HandleRequest(mr) |
| 107 | self.VerifyParams(result, params) |
| 108 | |
| 109 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 110 | def testNotifyIssueChangeTask_Spam(self, _create_task_mock): |
| 111 | issue = MakeTestIssue( |
| 112 | project_id=12345, local_id=1, owner_id=1, reporter_id=1, |
| 113 | is_spam=True) |
| 114 | self.services.issue.TestAddIssue(issue) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 115 | task = notify.NotifyIssueChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 116 | params = {'send_email': 0, 'issue_id': issue.issue_id, 'seq': 0, |
| 117 | 'commenter_id': 2} |
| 118 | mr = testing_helpers.MakeMonorailRequest( |
| 119 | user_info={'user_id': 1}, |
| 120 | params=params, |
| 121 | method='POST', |
| 122 | services=self.services) |
| 123 | result = task.HandleRequest(mr) |
| 124 | self.assertEqual(0, len(result['notified'])) |
| 125 | |
| 126 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 127 | def testNotifyBlockingChangeTask_Normal(self, _create_task_mock): |
| 128 | issue2 = MakeTestIssue( |
| 129 | project_id=12345, local_id=2, owner_id=2, reporter_id=1) |
| 130 | self.services.issue.TestAddIssue(issue2) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 131 | task = notify.NotifyBlockingChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 132 | params = { |
| 133 | 'send_email': 1, 'issue_id': issue2.issue_id, 'seq': 0, |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 134 | 'delta_blocker_iids': str(self.issue1.issue_id), 'commenter_id': 1, |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 135 | 'hostport': 'bugs.chromium.org'} |
| 136 | mr = testing_helpers.MakeMonorailRequest( |
| 137 | user_info={'user_id': 1}, |
| 138 | params=params, |
| 139 | method='POST', |
| 140 | services=self.services) |
| 141 | result = task.HandleRequest(mr) |
| 142 | self.VerifyParams(result, params) |
| 143 | |
| 144 | def testNotifyBlockingChangeTask_Spam(self): |
| 145 | issue2 = MakeTestIssue( |
| 146 | project_id=12345, local_id=2, owner_id=2, reporter_id=1, |
| 147 | is_spam=True) |
| 148 | self.services.issue.TestAddIssue(issue2) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 149 | task = notify.NotifyBlockingChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 150 | params = { |
| 151 | 'send_email': 1, 'issue_id': issue2.issue_id, 'seq': 0, |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 152 | 'delta_blocker_iids': str(self.issue1.issue_id), 'commenter_id': 1} |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 153 | mr = testing_helpers.MakeMonorailRequest( |
| 154 | user_info={'user_id': 1}, |
| 155 | params=params, |
| 156 | method='POST', |
| 157 | services=self.services) |
| 158 | result = task.HandleRequest(mr) |
| 159 | self.assertEqual(0, len(result['notified'])) |
| 160 | |
| 161 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 162 | def testNotifyBulkChangeTask_Normal(self, create_task_mock): |
| 163 | """We generate email tasks for each user involved in the issues.""" |
| 164 | issue2 = MakeTestIssue( |
| 165 | project_id=12345, local_id=2, owner_id=2, reporter_id=1) |
| 166 | issue2.cc_ids = [3] |
| 167 | self.services.issue.TestAddIssue(issue2) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 168 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 169 | params = { |
| 170 | 'send_email': 1, 'seq': 0, |
| 171 | 'issue_ids': '%d,%d' % (self.issue1.issue_id, issue2.issue_id), |
| 172 | 'old_owner_ids': '1,1', 'commenter_id': 1} |
| 173 | mr = testing_helpers.MakeMonorailRequest( |
| 174 | user_info={'user_id': 1}, |
| 175 | params=params, |
| 176 | method='POST', |
| 177 | services=self.services) |
| 178 | result = task.HandleRequest(mr) |
| 179 | self.VerifyParams(result, params) |
| 180 | |
| 181 | call_args_list = self.get_filtered_task_call_args( |
| 182 | create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do') |
| 183 | self.assertEqual(2, len(call_args_list)) |
| 184 | |
| 185 | for (args, _kwargs) in call_args_list: |
| 186 | task = args[0] |
| 187 | body = json.loads(task['app_engine_http_request']['body'].decode()) |
| 188 | if 'user' in body['to']: |
| 189 | self.assertIn(u'\u2026', body['from_addr']) |
| 190 | # Full email for members |
| 191 | if 'member' in body['to']: |
| 192 | self.assertNotIn(u'\u2026', body['from_addr']) |
| 193 | |
| 194 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 195 | def testNotifyBulkChangeTask_AlsoNotify(self, create_task_mock): |
| 196 | """We generate email tasks for also-notify addresses.""" |
| 197 | self.issue1.derived_notify_addrs = [ |
| 198 | 'mailing-list@example.com', 'member@example.com'] |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 199 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 200 | params = { |
| 201 | 'send_email': 1, 'seq': 0, |
| 202 | 'issue_ids': '%d' % (self.issue1.issue_id), |
| 203 | 'old_owner_ids': '1', 'commenter_id': 1} |
| 204 | mr = testing_helpers.MakeMonorailRequest( |
| 205 | user_info={'user_id': 1}, |
| 206 | params=params, |
| 207 | method='POST', |
| 208 | services=self.services) |
| 209 | result = task.HandleRequest(mr) |
| 210 | self.VerifyParams(result, params) |
| 211 | |
| 212 | call_args_list = self.get_filtered_task_call_args( |
| 213 | create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do') |
| 214 | self.assertEqual(3, len(call_args_list)) |
| 215 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 216 | six.assertCountEqual( |
| 217 | self, |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 218 | ['user@example.com', 'mailing-list@example.com', 'member@example.com'], |
| 219 | result['notified']) |
| 220 | for (args, _kwargs) in call_args_list: |
| 221 | task = args[0] |
| 222 | body = json.loads(task['app_engine_http_request']['body'].decode()) |
| 223 | # obfuscated email for non-members |
| 224 | if 'user' in body['to']: |
| 225 | self.assertIn(u'\u2026', body['from_addr']) |
| 226 | # Full email for members |
| 227 | if 'member' in body['to']: |
| 228 | self.assertNotIn(u'\u2026', body['from_addr']) |
| 229 | |
| 230 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 231 | def testNotifyBulkChangeTask_ProjectNotify(self, create_task_mock): |
| 232 | """We generate email tasks for project.issue_notify_address.""" |
| 233 | self.project.issue_notify_address = 'mailing-list@example.com' |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 234 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 235 | params = { |
| 236 | 'send_email': 1, 'seq': 0, |
| 237 | 'issue_ids': '%d' % (self.issue1.issue_id), |
| 238 | 'old_owner_ids': '1', 'commenter_id': 1} |
| 239 | mr = testing_helpers.MakeMonorailRequest( |
| 240 | user_info={'user_id': 1}, |
| 241 | params=params, |
| 242 | method='POST', |
| 243 | services=self.services) |
| 244 | result = task.HandleRequest(mr) |
| 245 | self.VerifyParams(result, params) |
| 246 | |
| 247 | call_args_list = self.get_filtered_task_call_args( |
| 248 | create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do') |
| 249 | self.assertEqual(2, len(call_args_list)) |
| 250 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 251 | six.assertCountEqual( |
| 252 | self, ['user@example.com', 'mailing-list@example.com'], |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 253 | result['notified']) |
| 254 | |
| 255 | for (args, _kwargs) in call_args_list: |
| 256 | task = args[0] |
| 257 | body = json.loads(task['app_engine_http_request']['body'].decode()) |
| 258 | # obfuscated email for non-members |
| 259 | if 'user' in body['to']: |
| 260 | self.assertIn(u'\u2026', body['from_addr']) |
| 261 | # Full email for members |
| 262 | if 'member' in body['to']: |
| 263 | self.assertNotIn(u'\u2026', body['from_addr']) |
| 264 | |
| 265 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 266 | def testNotifyBulkChangeTask_SubscriberGetsEmail(self, create_task_mock): |
| 267 | """If a user subscription matches the issue, notify that user.""" |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 268 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 269 | params = { |
| 270 | 'send_email': 1, |
| 271 | 'issue_ids': '%d' % (self.issue1.issue_id), |
| 272 | 'seq': 0, |
| 273 | 'old_owner_ids': '1', 'commenter_id': 1} |
| 274 | mr = testing_helpers.MakeMonorailRequest( |
| 275 | user_info={'user_id': 1}, |
| 276 | params=params, |
| 277 | method='POST', |
| 278 | services=self.services) |
| 279 | self.services.user.TestAddUser('subscriber@example.com', 4) |
| 280 | sq = tracker_bizobj.MakeSavedQuery( |
| 281 | 1, 'all open issues', 2, '', subscription_mode='immediate', |
| 282 | executes_in_project_ids=[self.issue1.project_id]) |
| 283 | self.services.features.UpdateUserSavedQueries('cnxn', 4, [sq]) |
| 284 | result = task.HandleRequest(mr) |
| 285 | self.VerifyParams(result, params) |
| 286 | |
| 287 | call_args_list = self.get_filtered_task_call_args( |
| 288 | create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do') |
| 289 | self.assertEqual(2, len(call_args_list)) |
| 290 | |
| 291 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 292 | def testNotifyBulkChangeTask_CCAndSubscriberListsIssueOnce( |
| 293 | self, create_task_mock): |
| 294 | """If a user both CCs and subscribes, include issue only once.""" |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 295 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 296 | params = { |
| 297 | 'send_email': 1, |
| 298 | 'issue_ids': '%d' % (self.issue1.issue_id), |
| 299 | 'seq': 0, |
| 300 | 'old_owner_ids': '1', 'commenter_id': 1} |
| 301 | mr = testing_helpers.MakeMonorailRequest( |
| 302 | user_info={'user_id': 1}, |
| 303 | params=params, |
| 304 | method='POST', |
| 305 | services=self.services) |
| 306 | self.services.user.TestAddUser('subscriber@example.com', 4) |
| 307 | self.issue1.cc_ids = [4] |
| 308 | sq = tracker_bizobj.MakeSavedQuery( |
| 309 | 1, 'all open issues', 2, '', subscription_mode='immediate', |
| 310 | executes_in_project_ids=[self.issue1.project_id]) |
| 311 | self.services.features.UpdateUserSavedQueries('cnxn', 4, [sq]) |
| 312 | result = task.HandleRequest(mr) |
| 313 | self.VerifyParams(result, params) |
| 314 | |
| 315 | call_args_list = self.get_filtered_task_call_args( |
| 316 | create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do') |
| 317 | self.assertEqual(2, len(call_args_list)) |
| 318 | |
| 319 | found = False |
| 320 | for (args, _kwargs) in call_args_list: |
| 321 | task = args[0] |
| 322 | body = json.loads(task['app_engine_http_request']['body'].decode()) |
| 323 | if body['to'] == 'subscriber@example.com': |
| 324 | found = True |
| 325 | task_body = body['body'] |
| 326 | self.assertEqual(1, task_body.count('Issue %d' % self.issue1.local_id)) |
| 327 | self.assertTrue(found) |
| 328 | |
| 329 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 330 | def testNotifyBulkChangeTask_Spam(self, _create_task_mock): |
| 331 | """A spam issue is excluded from notification emails.""" |
| 332 | issue2 = MakeTestIssue( |
| 333 | project_id=12345, local_id=2, owner_id=2, reporter_id=1, |
| 334 | is_spam=True) |
| 335 | self.services.issue.TestAddIssue(issue2) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 336 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 337 | params = { |
| 338 | 'send_email': 1, |
| 339 | 'issue_ids': '%d,%d' % (self.issue1.issue_id, issue2.issue_id), |
| 340 | 'seq': 0, |
| 341 | 'old_owner_ids': '1,1', 'commenter_id': 1} |
| 342 | mr = testing_helpers.MakeMonorailRequest( |
| 343 | user_info={'user_id': 1}, |
| 344 | params=params, |
| 345 | method='POST', |
| 346 | services=self.services) |
| 347 | result = task.HandleRequest(mr) |
| 348 | self.assertEqual(1, len(result['notified'])) |
| 349 | |
| 350 | def testFormatBulkIssues_Normal_Single(self): |
| 351 | """A user may see full notification details for all changed issues.""" |
| 352 | self.issue1.summary = 'one summary' |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 353 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 354 | users_by_id = {} |
| 355 | commenter_view = None |
| 356 | config = self.services.config.GetProjectConfig('cnxn', 12345) |
| 357 | addrperm = notify_reasons.AddrPerm( |
| 358 | False, 'nonmember@example.com', self.nonmember, |
| 359 | notify_reasons.REPLY_NOT_ALLOWED, None) |
| 360 | |
| 361 | subject, body = task._FormatBulkIssues( |
| 362 | [self.issue1], users_by_id, commenter_view, 'localhost:8080', |
| 363 | 'test comment', [], config, addrperm) |
| 364 | |
| 365 | self.assertIn('one summary', subject) |
| 366 | self.assertIn('one summary', body) |
| 367 | self.assertIn('test comment', body) |
| 368 | |
| 369 | def testFormatBulkIssues_Normal_Multiple(self): |
| 370 | """A user may see full notification details for all changed issues.""" |
| 371 | self.issue1.summary = 'one summary' |
| 372 | self.issue2.summary = 'two summary' |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 373 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 374 | users_by_id = {} |
| 375 | commenter_view = None |
| 376 | config = self.services.config.GetProjectConfig('cnxn', 12345) |
| 377 | addrperm = notify_reasons.AddrPerm( |
| 378 | False, 'nonmember@example.com', self.nonmember, |
| 379 | notify_reasons.REPLY_NOT_ALLOWED, None) |
| 380 | |
| 381 | subject, body = task._FormatBulkIssues( |
| 382 | [self.issue1, self.issue2], users_by_id, commenter_view, 'localhost:8080', |
| 383 | 'test comment', [], config, addrperm) |
| 384 | |
| 385 | self.assertIn('2 issues changed', subject) |
| 386 | self.assertIn('one summary', body) |
| 387 | self.assertIn('two summary', body) |
| 388 | self.assertIn('test comment', body) |
| 389 | |
| 390 | def testFormatBulkIssues_LinkOnly_Single(self): |
| 391 | """A user may not see full notification details for some changed issue.""" |
| 392 | self.issue1.summary = 'one summary' |
| 393 | self.issue1.labels = ['Restrict-View-Google'] |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 394 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 395 | users_by_id = {} |
| 396 | commenter_view = None |
| 397 | config = self.services.config.GetProjectConfig('cnxn', 12345) |
| 398 | addrperm = notify_reasons.AddrPerm( |
| 399 | False, 'nonmember@example.com', self.nonmember, |
| 400 | notify_reasons.REPLY_NOT_ALLOWED, None) |
| 401 | |
| 402 | subject, body = task._FormatBulkIssues( |
| 403 | [self.issue1], users_by_id, commenter_view, 'localhost:8080', |
| 404 | 'test comment', [], config, addrperm) |
| 405 | |
| 406 | self.assertIn('issue 1', subject) |
| 407 | self.assertNotIn('one summary', subject) |
| 408 | self.assertNotIn('one summary', body) |
| 409 | self.assertNotIn('test comment', body) |
| 410 | |
| 411 | def testFormatBulkIssues_LinkOnly_Multiple(self): |
| 412 | """A user may not see full notification details for some changed issue.""" |
| 413 | self.issue1.summary = 'one summary' |
| 414 | self.issue1.labels = ['Restrict-View-Google'] |
| 415 | self.issue2.summary = 'two summary' |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 416 | task = notify.NotifyBulkChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 417 | users_by_id = {} |
| 418 | commenter_view = None |
| 419 | config = self.services.config.GetProjectConfig('cnxn', 12345) |
| 420 | addrperm = notify_reasons.AddrPerm( |
| 421 | False, 'nonmember@example.com', self.nonmember, |
| 422 | notify_reasons.REPLY_NOT_ALLOWED, None) |
| 423 | |
| 424 | subject, body = task._FormatBulkIssues( |
| 425 | [self.issue1, self.issue2], users_by_id, commenter_view, 'localhost:8080', |
| 426 | 'test comment', [], config, addrperm) |
| 427 | |
| 428 | self.assertIn('2 issues', subject) |
| 429 | self.assertNotIn('summary', subject) |
| 430 | self.assertNotIn('one summary', body) |
| 431 | self.assertIn('two summary', body) |
| 432 | self.assertNotIn('test comment', body) |
| 433 | |
| 434 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 435 | def testNotifyApprovalChangeTask_Normal(self, _create_task_mock): |
| 436 | config = self.services.config.GetProjectConfig('cnxn', 12345) |
| 437 | config.field_defs = [ |
| 438 | # issue's User field with any_comment is notified. |
| 439 | tracker_bizobj.MakeFieldDef( |
| 440 | 121, 12345, 'TL', tracker_pb2.FieldTypes.USER_TYPE, |
| 441 | '', '', False, False, False, None, None, None, False, '', |
| 442 | None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action', |
| 443 | 'TL, notified on everything', False), |
| 444 | # issue's User field with never is not notified. |
| 445 | tracker_bizobj.MakeFieldDef( |
| 446 | 122, 12345, 'silentTL', tracker_pb2.FieldTypes.USER_TYPE, |
| 447 | '', '', False, False, False, None, None, None, False, '', |
| 448 | None, tracker_pb2.NotifyTriggers.NEVER, 'no_action', |
| 449 | 'TL, notified on nothing', False), |
| 450 | # approval's User field with any_comment is notified. |
| 451 | tracker_bizobj.MakeFieldDef( |
| 452 | 123, 12345, 'otherapprovalTL', tracker_pb2.FieldTypes.USER_TYPE, |
| 453 | '', '', False, False, False, None, None, None, False, '', |
| 454 | None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action', |
| 455 | 'TL on the approvers team', False, approval_id=3), |
| 456 | # another approval's User field with any_comment is not notified. |
| 457 | tracker_bizobj.MakeFieldDef( |
| 458 | 124, 12345, 'otherapprovalTL', tracker_pb2.FieldTypes.USER_TYPE, |
| 459 | '', '', False, False, False, None, None, None, False, '', |
| 460 | None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action', |
| 461 | 'TL on another approvers team', False, approval_id=4), |
| 462 | tracker_bizobj.MakeFieldDef( |
| 463 | 3, 12345, 'Goat-Approval', tracker_pb2.FieldTypes.APPROVAL_TYPE, |
| 464 | '', '', False, False, False, None, None, None, False, '', |
| 465 | None, tracker_pb2.NotifyTriggers.NEVER, 'no_action', |
| 466 | 'Get Approval from Goats', False) |
| 467 | ] |
| 468 | self.services.config.StoreConfig('cnxn', config) |
| 469 | |
| 470 | # Custom user_type field TLs |
| 471 | self.services.user.TestAddUser('TL@example.com', 111) |
| 472 | self.services.user.TestAddUser('silentTL@example.com', 222) |
| 473 | self.services.user.TestAddUser('approvalTL@example.com', 333) |
| 474 | self.services.user.TestAddUser('otherapprovalTL@example.com', 444) |
| 475 | |
| 476 | # Approvers |
| 477 | self.services.user.TestAddUser('approver_old@example.com', 777) |
| 478 | self.services.user.TestAddUser('approver_new@example.com', 888) |
| 479 | self.services.user.TestAddUser('approver_still@example.com', 999) |
| 480 | self.services.user.TestAddUser('approver_group@example.com', 666) |
| 481 | self.services.user.TestAddUser('group_mem1@example.com', 661) |
| 482 | self.services.user.TestAddUser('group_mem2@example.com', 662) |
| 483 | self.services.user.TestAddUser('group_mem3@example.com', 663) |
| 484 | self.services.usergroup.TestAddGroupSettings( |
| 485 | 666, 'approver_group@example.com') |
| 486 | self.services.usergroup.TestAddMembers(666, [661, 662, 663]) |
| 487 | canary_phase = tracker_pb2.Phase( |
| 488 | name='Canary', phase_id=1, rank=1) |
| 489 | approval_values = [ |
| 490 | tracker_pb2.ApprovalValue(approval_id=3, |
| 491 | approver_ids=[888, 999, 666, 661])] |
| 492 | approval_issue = MakeTestIssue( |
| 493 | project_id=12345, local_id=2, owner_id=2, reporter_id=1, |
| 494 | is_spam=True) |
| 495 | approval_issue.phases = [canary_phase] |
| 496 | approval_issue.approval_values = approval_values |
| 497 | approval_issue.field_values = [ |
| 498 | tracker_bizobj.MakeFieldValue(121, None, None, 111, None, None, False), |
| 499 | tracker_bizobj.MakeFieldValue(122, None, None, 222, None, None, False), |
| 500 | tracker_bizobj.MakeFieldValue(123, None, None, 333, None, None, False), |
| 501 | tracker_bizobj.MakeFieldValue(124, None, None, 444, None, None, False), |
| 502 | ] |
| 503 | self.services.issue.TestAddIssue(approval_issue) |
| 504 | |
| 505 | amend = tracker_bizobj.MakeApprovalApproversAmendment([888], [777]) |
| 506 | |
| 507 | comment = tracker_pb2.IssueComment( |
| 508 | project_id=12345, user_id=999, issue_id=approval_issue.issue_id, |
| 509 | amendments=[amend], timestamp=1234567890, content='just a comment.') |
| 510 | attach = tracker_pb2.Attachment( |
| 511 | attachment_id=4567, filename='sploot.jpg', mimetype='image/png', |
| 512 | gcs_object_id='/pid/attachments/abcd', filesize=(1024 * 1023)) |
| 513 | comment.attachments.append(attach) |
| 514 | self.services.issue.TestAddComment(comment, approval_issue.local_id) |
| 515 | self.services.issue.TestAddAttachment( |
| 516 | attach, comment.id, approval_issue.issue_id) |
| 517 | |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 518 | task = notify.NotifyApprovalChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 519 | params = { |
| 520 | 'send_email': 1, |
| 521 | 'issue_id': approval_issue.issue_id, |
| 522 | 'approval_id': 3, |
| 523 | 'comment_id': comment.id, |
| 524 | } |
| 525 | mr = testing_helpers.MakeMonorailRequest( |
| 526 | user_info={'user_id': 1}, |
| 527 | params=params, |
| 528 | method='POST', |
| 529 | services=self.services) |
| 530 | result = task.HandleRequest(mr) |
| 531 | self.assertTrue('just a comment' in result['tasks'][0]['body']) |
| 532 | self.assertTrue('Approvers: -appro...' in result['tasks'][0]['body']) |
| 533 | self.assertTrue('sploot.jpg' in result['tasks'][0]['body']) |
| 534 | self.assertTrue( |
| 535 | '/issues/attachment?aid=4567' in result['tasks'][0]['body']) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 536 | six.assertCountEqual( |
| 537 | self, [ |
| 538 | 'user@example.com', 'approver_old@example.com', |
| 539 | 'approver_new@example.com', 'TL@example.com', |
| 540 | 'approvalTL@example.com', 'group_mem1@example.com', |
| 541 | 'group_mem2@example.com', 'group_mem3@example.com' |
| 542 | ], result['notified']) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 543 | |
| 544 | # Test no approvers/groups notified |
| 545 | # Status change to NEED_INFO does not email approvers. |
| 546 | amend2 = tracker_bizobj.MakeApprovalStatusAmendment( |
| 547 | tracker_pb2.ApprovalStatus.NEED_INFO) |
| 548 | comment2 = tracker_pb2.IssueComment( |
| 549 | project_id=12345, user_id=999, issue_id=approval_issue.issue_id, |
| 550 | amendments=[amend2], timestamp=1234567891, content='') |
| 551 | self.services.issue.TestAddComment(comment2, approval_issue.local_id) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 552 | task = notify.NotifyApprovalChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 553 | params = { |
| 554 | 'send_email': 1, |
| 555 | 'issue_id': approval_issue.issue_id, |
| 556 | 'approval_id': 3, |
| 557 | 'comment_id': comment2.id, |
| 558 | } |
| 559 | mr = testing_helpers.MakeMonorailRequest( |
| 560 | user_info={'user_id': 1}, |
| 561 | params=params, |
| 562 | method='POST', |
| 563 | services=self.services) |
| 564 | result = task.HandleRequest(mr) |
| 565 | |
| 566 | self.assertIsNotNone(result['tasks'][0].get('references')) |
| 567 | self.assertEqual(result['tasks'][0]['reply_to'], emailfmt.NoReplyAddress()) |
| 568 | self.assertTrue('Status: need_info' in result['tasks'][0]['body']) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 569 | six.assertCountEqual( |
| 570 | self, ['user@example.com', 'TL@example.com', 'approvalTL@example.com'], |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 571 | result['notified']) |
| 572 | |
| 573 | def testNotifyApprovalChangeTask_GetApprovalEmailRecipients(self): |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 574 | task = notify.NotifyApprovalChangeTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 575 | issue = fake.MakeTestIssue(789, 1, 'summary', 'New', 111) |
| 576 | approval_value = tracker_pb2.ApprovalValue( |
| 577 | approver_ids=[222, 333], |
| 578 | status=tracker_pb2.ApprovalStatus.APPROVED) |
| 579 | comment = tracker_pb2.IssueComment( |
| 580 | project_id=789, user_id=1, issue_id=78901) |
| 581 | |
| 582 | # Comment with not amendments notifies everyone. |
| 583 | rids = task._GetApprovalEmailRecipients( |
| 584 | approval_value, comment, issue, [777, 888]) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 585 | six.assertCountEqual(self, rids, [111, 222, 333, 777, 888]) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 586 | |
| 587 | # New APPROVED status notifies owners and any_comment users. |
| 588 | amendment = tracker_bizobj.MakeApprovalStatusAmendment( |
| 589 | tracker_pb2.ApprovalStatus.APPROVED) |
| 590 | comment.amendments = [amendment] |
| 591 | rids = task._GetApprovalEmailRecipients( |
| 592 | approval_value, comment, issue, [777, 888]) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 593 | six.assertCountEqual(self, rids, [111, 777, 888]) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 594 | |
| 595 | # New REVIEW_REQUESTED status notifies approvers. |
| 596 | approval_value.status = tracker_pb2.ApprovalStatus.REVIEW_REQUESTED |
| 597 | amendment = tracker_bizobj.MakeApprovalStatusAmendment( |
| 598 | tracker_pb2.ApprovalStatus.REVIEW_REQUESTED) |
| 599 | comment.amendments = [amendment] |
| 600 | rids = task._GetApprovalEmailRecipients( |
| 601 | approval_value, comment, issue, [777, 888]) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 602 | six.assertCountEqual(self, rids, [222, 333]) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 603 | |
| 604 | # Approvers change notifies everyone. |
| 605 | amendment = tracker_bizobj.MakeApprovalApproversAmendment( |
| 606 | [222], [555]) |
| 607 | comment.amendments = [amendment] |
| 608 | approval_value.approver_ids = [222] |
| 609 | rids = task._GetApprovalEmailRecipients( |
| 610 | approval_value, comment, issue, [777], omit_ids=[444, 333]) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 611 | six.assertCountEqual(self, rids, [111, 222, 555, 777]) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 612 | |
| 613 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 614 | def testNotifyRulesDeletedTask(self, _create_task_mock): |
| 615 | self.services.project.TestAddProject( |
| 616 | 'proj', owner_ids=[777, 888], project_id=789) |
| 617 | self.services.user.TestAddUser('owner1@test.com', 777) |
| 618 | self.services.user.TestAddUser('cow@test.com', 888) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 619 | task = notify.NotifyRulesDeletedTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 620 | params = {'project_id': 789, |
| 621 | 'filter_rules': 'if green make yellow,if orange make blue'} |
| 622 | mr = testing_helpers.MakeMonorailRequest( |
| 623 | params=params, |
| 624 | method='POST', |
| 625 | services=self.services) |
| 626 | result = task.HandleRequest(mr) |
| 627 | self.assertEqual(len(result['tasks']), 2) |
| 628 | body = result['tasks'][0]['body'] |
| 629 | self.assertTrue('if green make yellow' in body) |
| 630 | self.assertTrue('if green make yellow' in body) |
| 631 | self.assertTrue('/p/proj/adminRules' in body) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 632 | six.assertCountEqual( |
| 633 | self, ['cow@test.com', 'owner1@test.com'], result['notified']) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 634 | |
| 635 | def testOutboundEmailTask_Normal(self): |
| 636 | """We can send an email.""" |
| 637 | params = { |
| 638 | 'from_addr': 'requester@example.com', |
| 639 | 'reply_to': 'user@example.com', |
| 640 | 'to': 'user@example.com', |
| 641 | 'subject': 'Test subject'} |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 642 | data = json.dumps(params) |
| 643 | res = self.app.test_client().post('/_task/outboundEmail.do', data=data) |
| 644 | res_string = res.get_data()[5:] |
| 645 | res_json = json.loads(res_string) |
| 646 | self.assertEqual(params['from_addr'], res_json['sender']) |
| 647 | self.assertEqual(params['subject'], res_json['subject']) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 648 | |
| 649 | def testOutboundEmailTask_MissingTo(self): |
| 650 | """We skip emails that don't specify the To-line.""" |
| 651 | params = { |
| 652 | 'from_addr': 'requester@example.com', |
| 653 | 'reply_to': 'user@example.com', |
| 654 | 'subject': 'Test subject'} |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 655 | data = json.dumps(params) |
| 656 | res = self.app.test_client().post('/_task/outboundEmail.do', data=data) |
| 657 | res_string = res.get_data()[5:] |
| 658 | res_json = json.loads(res_string) |
| 659 | self.assertEqual( |
| 660 | 'Skipping because no "to" address found.', res_json['note']) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 661 | self.assertNotIn(b'from_addr', res_string) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 662 | |
| 663 | def testOutboundEmailTask_BannedUser(self): |
| 664 | """We don't send emails to banned users..""" |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 665 | self.servlet.services.user.TestAddUser( |
| 666 | 'banned@example.com', 404, banned=True) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 667 | params = { |
| 668 | 'from_addr': 'requester@example.com', |
| 669 | 'reply_to': 'user@example.com', |
| 670 | 'to': 'banned@example.com', |
| 671 | 'subject': 'Test subject'} |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 672 | data = json.dumps(params) |
| 673 | res = self.app.test_client().post('/_task/outboundEmail.do', data=data) |
| 674 | res_string = res.get_data()[5:] |
| 675 | res_json = json.loads(res_string) |
| 676 | self.assertEqual('Skipping because user is banned.', res_json['note']) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 677 | self.assertNotIn(b'from_addr', res_string) |