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