blob: e73488d21343600d67d096843ec22d02f79537cf [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# 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."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import json
12import mock
13import unittest
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020014import flask
Copybara854996b2021-09-07 19:36:02 +000015
16from google.appengine.ext import testbed
17
18from features import notify
19from features import notify_reasons
20from framework import emailfmt
21from framework import urls
22from proto import tracker_pb2
23from services import service_manager
24from testing import fake
25from testing import testing_helpers
26from tracker import attachment_helpers
27from tracker import tracker_bizobj
28
Copybara854996b2021-09-07 19:36:02 +000029
30def 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
41class 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
Copybara854996b2021-09-07 19:36:02 +000063 self.orig_sign_attachment_id = attachment_helpers.SignAttachmentID
64 attachment_helpers.SignAttachmentID = (
65 lambda aid: 'signed_%d' % aid)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020066 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'])
Copybara854996b2021-09-07 19:36:02 +000073 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):
Copybara854996b2021-09-07 19:36:02 +000079 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ínez9f9ade52022-10-10 23:20:11 +020098 task = notify.NotifyIssueChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +000099 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ínez9f9ade52022-10-10 23:20:11 +0200115 task = notify.NotifyIssueChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000116 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ínez9f9ade52022-10-10 23:20:11 +0200131 task = notify.NotifyBlockingChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000132 params = {
133 'send_email': 1, 'issue_id': issue2.issue_id, 'seq': 0,
134 'delta_blocker_iids': self.issue1.issue_id, 'commenter_id': 1,
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ínez9f9ade52022-10-10 23:20:11 +0200149 task = notify.NotifyBlockingChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000150 params = {
151 'send_email': 1, 'issue_id': issue2.issue_id, 'seq': 0,
152 'delta_blocker_iids': self.issue1.issue_id, 'commenter_id': 1}
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ínez9f9ade52022-10-10 23:20:11 +0200168 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000169 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ínez9f9ade52022-10-10 23:20:11 +0200199 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000200 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'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200233 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000234 params = {
235 'send_email': 1, 'seq': 0,
236 'issue_ids': '%d' % (self.issue1.issue_id),
237 'old_owner_ids': '1', 'commenter_id': 1}
238 mr = testing_helpers.MakeMonorailRequest(
239 user_info={'user_id': 1},
240 params=params,
241 method='POST',
242 services=self.services)
243 result = task.HandleRequest(mr)
244 self.VerifyParams(result, params)
245
246 call_args_list = self.get_filtered_task_call_args(
247 create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
248 self.assertEqual(2, len(call_args_list))
249
250 self.assertItemsEqual(
251 ['user@example.com', 'mailing-list@example.com'],
252 result['notified'])
253
254 for (args, _kwargs) in call_args_list:
255 task = args[0]
256 body = json.loads(task['app_engine_http_request']['body'].decode())
257 # obfuscated email for non-members
258 if 'user' in body['to']:
259 self.assertIn(u'\u2026', body['from_addr'])
260 # Full email for members
261 if 'member' in body['to']:
262 self.assertNotIn(u'\u2026', body['from_addr'])
263
264 @mock.patch('framework.cloud_tasks_helpers.create_task')
265 def testNotifyBulkChangeTask_SubscriberGetsEmail(self, create_task_mock):
266 """If a user subscription matches the issue, notify that user."""
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200267 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000268 params = {
269 'send_email': 1,
270 'issue_ids': '%d' % (self.issue1.issue_id),
271 'seq': 0,
272 'old_owner_ids': '1', 'commenter_id': 1}
273 mr = testing_helpers.MakeMonorailRequest(
274 user_info={'user_id': 1},
275 params=params,
276 method='POST',
277 services=self.services)
278 self.services.user.TestAddUser('subscriber@example.com', 4)
279 sq = tracker_bizobj.MakeSavedQuery(
280 1, 'all open issues', 2, '', subscription_mode='immediate',
281 executes_in_project_ids=[self.issue1.project_id])
282 self.services.features.UpdateUserSavedQueries('cnxn', 4, [sq])
283 result = task.HandleRequest(mr)
284 self.VerifyParams(result, params)
285
286 call_args_list = self.get_filtered_task_call_args(
287 create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
288 self.assertEqual(2, len(call_args_list))
289
290 @mock.patch('framework.cloud_tasks_helpers.create_task')
291 def testNotifyBulkChangeTask_CCAndSubscriberListsIssueOnce(
292 self, create_task_mock):
293 """If a user both CCs and subscribes, include issue only once."""
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200294 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000295 params = {
296 'send_email': 1,
297 'issue_ids': '%d' % (self.issue1.issue_id),
298 'seq': 0,
299 'old_owner_ids': '1', 'commenter_id': 1}
300 mr = testing_helpers.MakeMonorailRequest(
301 user_info={'user_id': 1},
302 params=params,
303 method='POST',
304 services=self.services)
305 self.services.user.TestAddUser('subscriber@example.com', 4)
306 self.issue1.cc_ids = [4]
307 sq = tracker_bizobj.MakeSavedQuery(
308 1, 'all open issues', 2, '', subscription_mode='immediate',
309 executes_in_project_ids=[self.issue1.project_id])
310 self.services.features.UpdateUserSavedQueries('cnxn', 4, [sq])
311 result = task.HandleRequest(mr)
312 self.VerifyParams(result, params)
313
314 call_args_list = self.get_filtered_task_call_args(
315 create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
316 self.assertEqual(2, len(call_args_list))
317
318 found = False
319 for (args, _kwargs) in call_args_list:
320 task = args[0]
321 body = json.loads(task['app_engine_http_request']['body'].decode())
322 if body['to'] == 'subscriber@example.com':
323 found = True
324 task_body = body['body']
325 self.assertEqual(1, task_body.count('Issue %d' % self.issue1.local_id))
326 self.assertTrue(found)
327
328 @mock.patch('framework.cloud_tasks_helpers.create_task')
329 def testNotifyBulkChangeTask_Spam(self, _create_task_mock):
330 """A spam issue is excluded from notification emails."""
331 issue2 = MakeTestIssue(
332 project_id=12345, local_id=2, owner_id=2, reporter_id=1,
333 is_spam=True)
334 self.services.issue.TestAddIssue(issue2)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200335 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000336 params = {
337 'send_email': 1,
338 'issue_ids': '%d,%d' % (self.issue1.issue_id, issue2.issue_id),
339 'seq': 0,
340 'old_owner_ids': '1,1', 'commenter_id': 1}
341 mr = testing_helpers.MakeMonorailRequest(
342 user_info={'user_id': 1},
343 params=params,
344 method='POST',
345 services=self.services)
346 result = task.HandleRequest(mr)
347 self.assertEqual(1, len(result['notified']))
348
349 def testFormatBulkIssues_Normal_Single(self):
350 """A user may see full notification details for all changed issues."""
351 self.issue1.summary = 'one summary'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200352 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000353 users_by_id = {}
354 commenter_view = None
355 config = self.services.config.GetProjectConfig('cnxn', 12345)
356 addrperm = notify_reasons.AddrPerm(
357 False, 'nonmember@example.com', self.nonmember,
358 notify_reasons.REPLY_NOT_ALLOWED, None)
359
360 subject, body = task._FormatBulkIssues(
361 [self.issue1], users_by_id, commenter_view, 'localhost:8080',
362 'test comment', [], config, addrperm)
363
364 self.assertIn('one summary', subject)
365 self.assertIn('one summary', body)
366 self.assertIn('test comment', body)
367
368 def testFormatBulkIssues_Normal_Multiple(self):
369 """A user may see full notification details for all changed issues."""
370 self.issue1.summary = 'one summary'
371 self.issue2.summary = 'two summary'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200372 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000373 users_by_id = {}
374 commenter_view = None
375 config = self.services.config.GetProjectConfig('cnxn', 12345)
376 addrperm = notify_reasons.AddrPerm(
377 False, 'nonmember@example.com', self.nonmember,
378 notify_reasons.REPLY_NOT_ALLOWED, None)
379
380 subject, body = task._FormatBulkIssues(
381 [self.issue1, self.issue2], users_by_id, commenter_view, 'localhost:8080',
382 'test comment', [], config, addrperm)
383
384 self.assertIn('2 issues changed', subject)
385 self.assertIn('one summary', body)
386 self.assertIn('two summary', body)
387 self.assertIn('test comment', body)
388
389 def testFormatBulkIssues_LinkOnly_Single(self):
390 """A user may not see full notification details for some changed issue."""
391 self.issue1.summary = 'one summary'
392 self.issue1.labels = ['Restrict-View-Google']
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200393 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000394 users_by_id = {}
395 commenter_view = None
396 config = self.services.config.GetProjectConfig('cnxn', 12345)
397 addrperm = notify_reasons.AddrPerm(
398 False, 'nonmember@example.com', self.nonmember,
399 notify_reasons.REPLY_NOT_ALLOWED, None)
400
401 subject, body = task._FormatBulkIssues(
402 [self.issue1], users_by_id, commenter_view, 'localhost:8080',
403 'test comment', [], config, addrperm)
404
405 self.assertIn('issue 1', subject)
406 self.assertNotIn('one summary', subject)
407 self.assertNotIn('one summary', body)
408 self.assertNotIn('test comment', body)
409
410 def testFormatBulkIssues_LinkOnly_Multiple(self):
411 """A user may not see full notification details for some changed issue."""
412 self.issue1.summary = 'one summary'
413 self.issue1.labels = ['Restrict-View-Google']
414 self.issue2.summary = 'two summary'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200415 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000416 users_by_id = {}
417 commenter_view = None
418 config = self.services.config.GetProjectConfig('cnxn', 12345)
419 addrperm = notify_reasons.AddrPerm(
420 False, 'nonmember@example.com', self.nonmember,
421 notify_reasons.REPLY_NOT_ALLOWED, None)
422
423 subject, body = task._FormatBulkIssues(
424 [self.issue1, self.issue2], users_by_id, commenter_view, 'localhost:8080',
425 'test comment', [], config, addrperm)
426
427 self.assertIn('2 issues', subject)
428 self.assertNotIn('summary', subject)
429 self.assertNotIn('one summary', body)
430 self.assertIn('two summary', body)
431 self.assertNotIn('test comment', body)
432
433 @mock.patch('framework.cloud_tasks_helpers.create_task')
434 def testNotifyApprovalChangeTask_Normal(self, _create_task_mock):
435 config = self.services.config.GetProjectConfig('cnxn', 12345)
436 config.field_defs = [
437 # issue's User field with any_comment is notified.
438 tracker_bizobj.MakeFieldDef(
439 121, 12345, 'TL', tracker_pb2.FieldTypes.USER_TYPE,
440 '', '', False, False, False, None, None, None, False, '',
441 None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action',
442 'TL, notified on everything', False),
443 # issue's User field with never is not notified.
444 tracker_bizobj.MakeFieldDef(
445 122, 12345, 'silentTL', tracker_pb2.FieldTypes.USER_TYPE,
446 '', '', False, False, False, None, None, None, False, '',
447 None, tracker_pb2.NotifyTriggers.NEVER, 'no_action',
448 'TL, notified on nothing', False),
449 # approval's User field with any_comment is notified.
450 tracker_bizobj.MakeFieldDef(
451 123, 12345, 'otherapprovalTL', tracker_pb2.FieldTypes.USER_TYPE,
452 '', '', False, False, False, None, None, None, False, '',
453 None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action',
454 'TL on the approvers team', False, approval_id=3),
455 # another approval's User field with any_comment is not notified.
456 tracker_bizobj.MakeFieldDef(
457 124, 12345, 'otherapprovalTL', tracker_pb2.FieldTypes.USER_TYPE,
458 '', '', False, False, False, None, None, None, False, '',
459 None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action',
460 'TL on another approvers team', False, approval_id=4),
461 tracker_bizobj.MakeFieldDef(
462 3, 12345, 'Goat-Approval', tracker_pb2.FieldTypes.APPROVAL_TYPE,
463 '', '', False, False, False, None, None, None, False, '',
464 None, tracker_pb2.NotifyTriggers.NEVER, 'no_action',
465 'Get Approval from Goats', False)
466 ]
467 self.services.config.StoreConfig('cnxn', config)
468
469 # Custom user_type field TLs
470 self.services.user.TestAddUser('TL@example.com', 111)
471 self.services.user.TestAddUser('silentTL@example.com', 222)
472 self.services.user.TestAddUser('approvalTL@example.com', 333)
473 self.services.user.TestAddUser('otherapprovalTL@example.com', 444)
474
475 # Approvers
476 self.services.user.TestAddUser('approver_old@example.com', 777)
477 self.services.user.TestAddUser('approver_new@example.com', 888)
478 self.services.user.TestAddUser('approver_still@example.com', 999)
479 self.services.user.TestAddUser('approver_group@example.com', 666)
480 self.services.user.TestAddUser('group_mem1@example.com', 661)
481 self.services.user.TestAddUser('group_mem2@example.com', 662)
482 self.services.user.TestAddUser('group_mem3@example.com', 663)
483 self.services.usergroup.TestAddGroupSettings(
484 666, 'approver_group@example.com')
485 self.services.usergroup.TestAddMembers(666, [661, 662, 663])
486 canary_phase = tracker_pb2.Phase(
487 name='Canary', phase_id=1, rank=1)
488 approval_values = [
489 tracker_pb2.ApprovalValue(approval_id=3,
490 approver_ids=[888, 999, 666, 661])]
491 approval_issue = MakeTestIssue(
492 project_id=12345, local_id=2, owner_id=2, reporter_id=1,
493 is_spam=True)
494 approval_issue.phases = [canary_phase]
495 approval_issue.approval_values = approval_values
496 approval_issue.field_values = [
497 tracker_bizobj.MakeFieldValue(121, None, None, 111, None, None, False),
498 tracker_bizobj.MakeFieldValue(122, None, None, 222, None, None, False),
499 tracker_bizobj.MakeFieldValue(123, None, None, 333, None, None, False),
500 tracker_bizobj.MakeFieldValue(124, None, None, 444, None, None, False),
501 ]
502 self.services.issue.TestAddIssue(approval_issue)
503
504 amend = tracker_bizobj.MakeApprovalApproversAmendment([888], [777])
505
506 comment = tracker_pb2.IssueComment(
507 project_id=12345, user_id=999, issue_id=approval_issue.issue_id,
508 amendments=[amend], timestamp=1234567890, content='just a comment.')
509 attach = tracker_pb2.Attachment(
510 attachment_id=4567, filename='sploot.jpg', mimetype='image/png',
511 gcs_object_id='/pid/attachments/abcd', filesize=(1024 * 1023))
512 comment.attachments.append(attach)
513 self.services.issue.TestAddComment(comment, approval_issue.local_id)
514 self.services.issue.TestAddAttachment(
515 attach, comment.id, approval_issue.issue_id)
516
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200517 task = notify.NotifyApprovalChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000518 params = {
519 'send_email': 1,
520 'issue_id': approval_issue.issue_id,
521 'approval_id': 3,
522 'comment_id': comment.id,
523 }
524 mr = testing_helpers.MakeMonorailRequest(
525 user_info={'user_id': 1},
526 params=params,
527 method='POST',
528 services=self.services)
529 result = task.HandleRequest(mr)
530 self.assertTrue('just a comment' in result['tasks'][0]['body'])
531 self.assertTrue('Approvers: -appro...' in result['tasks'][0]['body'])
532 self.assertTrue('sploot.jpg' in result['tasks'][0]['body'])
533 self.assertTrue(
534 '/issues/attachment?aid=4567' in result['tasks'][0]['body'])
535 self.assertItemsEqual(
536 ['user@example.com', 'approver_old@example.com',
537 'approver_new@example.com', 'TL@example.com',
538 'approvalTL@example.com', 'group_mem1@example.com',
539 'group_mem2@example.com', 'group_mem3@example.com'],
540 result['notified'])
541
542 # Test no approvers/groups notified
543 # Status change to NEED_INFO does not email approvers.
544 amend2 = tracker_bizobj.MakeApprovalStatusAmendment(
545 tracker_pb2.ApprovalStatus.NEED_INFO)
546 comment2 = tracker_pb2.IssueComment(
547 project_id=12345, user_id=999, issue_id=approval_issue.issue_id,
548 amendments=[amend2], timestamp=1234567891, content='')
549 self.services.issue.TestAddComment(comment2, approval_issue.local_id)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200550 task = notify.NotifyApprovalChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000551 params = {
552 'send_email': 1,
553 'issue_id': approval_issue.issue_id,
554 'approval_id': 3,
555 'comment_id': comment2.id,
556 }
557 mr = testing_helpers.MakeMonorailRequest(
558 user_info={'user_id': 1},
559 params=params,
560 method='POST',
561 services=self.services)
562 result = task.HandleRequest(mr)
563
564 self.assertIsNotNone(result['tasks'][0].get('references'))
565 self.assertEqual(result['tasks'][0]['reply_to'], emailfmt.NoReplyAddress())
566 self.assertTrue('Status: need_info' in result['tasks'][0]['body'])
567 self.assertItemsEqual(
568 ['user@example.com', 'TL@example.com', 'approvalTL@example.com'],
569 result['notified'])
570
571 def testNotifyApprovalChangeTask_GetApprovalEmailRecipients(self):
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200572 task = notify.NotifyApprovalChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000573 issue = fake.MakeTestIssue(789, 1, 'summary', 'New', 111)
574 approval_value = tracker_pb2.ApprovalValue(
575 approver_ids=[222, 333],
576 status=tracker_pb2.ApprovalStatus.APPROVED)
577 comment = tracker_pb2.IssueComment(
578 project_id=789, user_id=1, issue_id=78901)
579
580 # Comment with not amendments notifies everyone.
581 rids = task._GetApprovalEmailRecipients(
582 approval_value, comment, issue, [777, 888])
583 self.assertItemsEqual(rids, [111, 222, 333, 777, 888])
584
585 # New APPROVED status notifies owners and any_comment users.
586 amendment = tracker_bizobj.MakeApprovalStatusAmendment(
587 tracker_pb2.ApprovalStatus.APPROVED)
588 comment.amendments = [amendment]
589 rids = task._GetApprovalEmailRecipients(
590 approval_value, comment, issue, [777, 888])
591 self.assertItemsEqual(rids, [111, 777, 888])
592
593 # New REVIEW_REQUESTED status notifies approvers.
594 approval_value.status = tracker_pb2.ApprovalStatus.REVIEW_REQUESTED
595 amendment = tracker_bizobj.MakeApprovalStatusAmendment(
596 tracker_pb2.ApprovalStatus.REVIEW_REQUESTED)
597 comment.amendments = [amendment]
598 rids = task._GetApprovalEmailRecipients(
599 approval_value, comment, issue, [777, 888])
600 self.assertItemsEqual(rids, [222, 333])
601
602 # Approvers change notifies everyone.
603 amendment = tracker_bizobj.MakeApprovalApproversAmendment(
604 [222], [555])
605 comment.amendments = [amendment]
606 approval_value.approver_ids = [222]
607 rids = task._GetApprovalEmailRecipients(
608 approval_value, comment, issue, [777], omit_ids=[444, 333])
609 self.assertItemsEqual(rids, [111, 222, 555, 777])
610
611 @mock.patch('framework.cloud_tasks_helpers.create_task')
612 def testNotifyRulesDeletedTask(self, _create_task_mock):
613 self.services.project.TestAddProject(
614 'proj', owner_ids=[777, 888], project_id=789)
615 self.services.user.TestAddUser('owner1@test.com', 777)
616 self.services.user.TestAddUser('cow@test.com', 888)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200617 task = notify.NotifyRulesDeletedTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000618 params = {'project_id': 789,
619 'filter_rules': 'if green make yellow,if orange make blue'}
620 mr = testing_helpers.MakeMonorailRequest(
621 params=params,
622 method='POST',
623 services=self.services)
624 result = task.HandleRequest(mr)
625 self.assertEqual(len(result['tasks']), 2)
626 body = result['tasks'][0]['body']
627 self.assertTrue('if green make yellow' in body)
628 self.assertTrue('if green make yellow' in body)
629 self.assertTrue('/p/proj/adminRules' in body)
630 self.assertItemsEqual(
631 ['cow@test.com', 'owner1@test.com'], result['notified'])
632
633 def testOutboundEmailTask_Normal(self):
634 """We can send an email."""
635 params = {
636 'from_addr': 'requester@example.com',
637 'reply_to': 'user@example.com',
638 'to': 'user@example.com',
639 'subject': 'Test subject'}
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200640 data = json.dumps(params)
641 res = self.app.test_client().post('/_task/outboundEmail.do', data=data)
642 res_string = res.get_data()[5:]
643 res_json = json.loads(res_string)
644 self.assertEqual(params['from_addr'], res_json['sender'])
645 self.assertEqual(params['subject'], res_json['subject'])
Copybara854996b2021-09-07 19:36:02 +0000646
647 def testOutboundEmailTask_MissingTo(self):
648 """We skip emails that don't specify the To-line."""
649 params = {
650 'from_addr': 'requester@example.com',
651 'reply_to': 'user@example.com',
652 'subject': 'Test subject'}
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200653 data = json.dumps(params)
654 res = self.app.test_client().post('/_task/outboundEmail.do', data=data)
655 res_string = res.get_data()[5:]
656 res_json = json.loads(res_string)
657 self.assertEqual(
658 'Skipping because no "to" address found.', res_json['note'])
659 self.assertNotIn('from_addr', res_string)
Copybara854996b2021-09-07 19:36:02 +0000660
661 def testOutboundEmailTask_BannedUser(self):
662 """We don't send emails to banned users.."""
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200663 self.servlet.services.user.TestAddUser(
664 'banned@example.com', 404, banned=True)
Copybara854996b2021-09-07 19:36:02 +0000665 params = {
666 'from_addr': 'requester@example.com',
667 'reply_to': 'user@example.com',
668 'to': 'banned@example.com',
669 'subject': 'Test subject'}
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200670 data = json.dumps(params)
671 res = self.app.test_client().post('/_task/outboundEmail.do', data=data)
672 res_string = res.get_data()[5:]
673 res_json = json.loads(res_string)
674 self.assertEqual('Skipping because user is banned.', res_json['note'])
675 self.assertNotIn('from_addr', res_string)