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