blob: e4f00a313b25bae01b8700a8d2bbb4153f3f7af1 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Tests for notify.py."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010010import flask
Copybara854996b2021-09-07 19:36:02 +000011import json
12import mock
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010013import six
Copybara854996b2021-09-07 19:36:02 +000014import unittest
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
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010022from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000023from 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,
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100134 'delta_blocker_iids': str(self.issue1.issue_id), 'commenter_id': 1,
Copybara854996b2021-09-07 19:36:02 +0000135 '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,
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100152 'delta_blocker_iids': str(self.issue1.issue_id), 'commenter_id': 1}
Copybara854996b2021-09-07 19:36:02 +0000153 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
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100216 six.assertCountEqual(
217 self,
Copybara854996b2021-09-07 19:36:02 +0000218 ['user@example.com', 'mailing-list@example.com', 'member@example.com'],
219 result['notified'])
220 for (args, _kwargs) in call_args_list:
221 task = args[0]
222 body = json.loads(task['app_engine_http_request']['body'].decode())
223 # obfuscated email for non-members
224 if 'user' in body['to']:
225 self.assertIn(u'\u2026', body['from_addr'])
226 # Full email for members
227 if 'member' in body['to']:
228 self.assertNotIn(u'\u2026', body['from_addr'])
229
230 @mock.patch('framework.cloud_tasks_helpers.create_task')
231 def testNotifyBulkChangeTask_ProjectNotify(self, create_task_mock):
232 """We generate email tasks for project.issue_notify_address."""
233 self.project.issue_notify_address = 'mailing-list@example.com'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200234 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000235 params = {
236 'send_email': 1, 'seq': 0,
237 'issue_ids': '%d' % (self.issue1.issue_id),
238 'old_owner_ids': '1', 'commenter_id': 1}
239 mr = testing_helpers.MakeMonorailRequest(
240 user_info={'user_id': 1},
241 params=params,
242 method='POST',
243 services=self.services)
244 result = task.HandleRequest(mr)
245 self.VerifyParams(result, params)
246
247 call_args_list = self.get_filtered_task_call_args(
248 create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
249 self.assertEqual(2, len(call_args_list))
250
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100251 six.assertCountEqual(
252 self, ['user@example.com', 'mailing-list@example.com'],
Copybara854996b2021-09-07 19:36:02 +0000253 result['notified'])
254
255 for (args, _kwargs) in call_args_list:
256 task = args[0]
257 body = json.loads(task['app_engine_http_request']['body'].decode())
258 # obfuscated email for non-members
259 if 'user' in body['to']:
260 self.assertIn(u'\u2026', body['from_addr'])
261 # Full email for members
262 if 'member' in body['to']:
263 self.assertNotIn(u'\u2026', body['from_addr'])
264
265 @mock.patch('framework.cloud_tasks_helpers.create_task')
266 def testNotifyBulkChangeTask_SubscriberGetsEmail(self, create_task_mock):
267 """If a user subscription matches the issue, notify that user."""
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200268 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000269 params = {
270 'send_email': 1,
271 'issue_ids': '%d' % (self.issue1.issue_id),
272 'seq': 0,
273 'old_owner_ids': '1', 'commenter_id': 1}
274 mr = testing_helpers.MakeMonorailRequest(
275 user_info={'user_id': 1},
276 params=params,
277 method='POST',
278 services=self.services)
279 self.services.user.TestAddUser('subscriber@example.com', 4)
280 sq = tracker_bizobj.MakeSavedQuery(
281 1, 'all open issues', 2, '', subscription_mode='immediate',
282 executes_in_project_ids=[self.issue1.project_id])
283 self.services.features.UpdateUserSavedQueries('cnxn', 4, [sq])
284 result = task.HandleRequest(mr)
285 self.VerifyParams(result, params)
286
287 call_args_list = self.get_filtered_task_call_args(
288 create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
289 self.assertEqual(2, len(call_args_list))
290
291 @mock.patch('framework.cloud_tasks_helpers.create_task')
292 def testNotifyBulkChangeTask_CCAndSubscriberListsIssueOnce(
293 self, create_task_mock):
294 """If a user both CCs and subscribes, include issue only once."""
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200295 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000296 params = {
297 'send_email': 1,
298 'issue_ids': '%d' % (self.issue1.issue_id),
299 'seq': 0,
300 'old_owner_ids': '1', 'commenter_id': 1}
301 mr = testing_helpers.MakeMonorailRequest(
302 user_info={'user_id': 1},
303 params=params,
304 method='POST',
305 services=self.services)
306 self.services.user.TestAddUser('subscriber@example.com', 4)
307 self.issue1.cc_ids = [4]
308 sq = tracker_bizobj.MakeSavedQuery(
309 1, 'all open issues', 2, '', subscription_mode='immediate',
310 executes_in_project_ids=[self.issue1.project_id])
311 self.services.features.UpdateUserSavedQueries('cnxn', 4, [sq])
312 result = task.HandleRequest(mr)
313 self.VerifyParams(result, params)
314
315 call_args_list = self.get_filtered_task_call_args(
316 create_task_mock, urls.OUTBOUND_EMAIL_TASK + '.do')
317 self.assertEqual(2, len(call_args_list))
318
319 found = False
320 for (args, _kwargs) in call_args_list:
321 task = args[0]
322 body = json.loads(task['app_engine_http_request']['body'].decode())
323 if body['to'] == 'subscriber@example.com':
324 found = True
325 task_body = body['body']
326 self.assertEqual(1, task_body.count('Issue %d' % self.issue1.local_id))
327 self.assertTrue(found)
328
329 @mock.patch('framework.cloud_tasks_helpers.create_task')
330 def testNotifyBulkChangeTask_Spam(self, _create_task_mock):
331 """A spam issue is excluded from notification emails."""
332 issue2 = MakeTestIssue(
333 project_id=12345, local_id=2, owner_id=2, reporter_id=1,
334 is_spam=True)
335 self.services.issue.TestAddIssue(issue2)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200336 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000337 params = {
338 'send_email': 1,
339 'issue_ids': '%d,%d' % (self.issue1.issue_id, issue2.issue_id),
340 'seq': 0,
341 'old_owner_ids': '1,1', 'commenter_id': 1}
342 mr = testing_helpers.MakeMonorailRequest(
343 user_info={'user_id': 1},
344 params=params,
345 method='POST',
346 services=self.services)
347 result = task.HandleRequest(mr)
348 self.assertEqual(1, len(result['notified']))
349
350 def testFormatBulkIssues_Normal_Single(self):
351 """A user may see full notification details for all changed issues."""
352 self.issue1.summary = 'one summary'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200353 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000354 users_by_id = {}
355 commenter_view = None
356 config = self.services.config.GetProjectConfig('cnxn', 12345)
357 addrperm = notify_reasons.AddrPerm(
358 False, 'nonmember@example.com', self.nonmember,
359 notify_reasons.REPLY_NOT_ALLOWED, None)
360
361 subject, body = task._FormatBulkIssues(
362 [self.issue1], users_by_id, commenter_view, 'localhost:8080',
363 'test comment', [], config, addrperm)
364
365 self.assertIn('one summary', subject)
366 self.assertIn('one summary', body)
367 self.assertIn('test comment', body)
368
369 def testFormatBulkIssues_Normal_Multiple(self):
370 """A user may see full notification details for all changed issues."""
371 self.issue1.summary = 'one summary'
372 self.issue2.summary = 'two summary'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200373 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000374 users_by_id = {}
375 commenter_view = None
376 config = self.services.config.GetProjectConfig('cnxn', 12345)
377 addrperm = notify_reasons.AddrPerm(
378 False, 'nonmember@example.com', self.nonmember,
379 notify_reasons.REPLY_NOT_ALLOWED, None)
380
381 subject, body = task._FormatBulkIssues(
382 [self.issue1, self.issue2], users_by_id, commenter_view, 'localhost:8080',
383 'test comment', [], config, addrperm)
384
385 self.assertIn('2 issues changed', subject)
386 self.assertIn('one summary', body)
387 self.assertIn('two summary', body)
388 self.assertIn('test comment', body)
389
390 def testFormatBulkIssues_LinkOnly_Single(self):
391 """A user may not see full notification details for some changed issue."""
392 self.issue1.summary = 'one summary'
393 self.issue1.labels = ['Restrict-View-Google']
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200394 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000395 users_by_id = {}
396 commenter_view = None
397 config = self.services.config.GetProjectConfig('cnxn', 12345)
398 addrperm = notify_reasons.AddrPerm(
399 False, 'nonmember@example.com', self.nonmember,
400 notify_reasons.REPLY_NOT_ALLOWED, None)
401
402 subject, body = task._FormatBulkIssues(
403 [self.issue1], users_by_id, commenter_view, 'localhost:8080',
404 'test comment', [], config, addrperm)
405
406 self.assertIn('issue 1', subject)
407 self.assertNotIn('one summary', subject)
408 self.assertNotIn('one summary', body)
409 self.assertNotIn('test comment', body)
410
411 def testFormatBulkIssues_LinkOnly_Multiple(self):
412 """A user may not see full notification details for some changed issue."""
413 self.issue1.summary = 'one summary'
414 self.issue1.labels = ['Restrict-View-Google']
415 self.issue2.summary = 'two summary'
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200416 task = notify.NotifyBulkChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000417 users_by_id = {}
418 commenter_view = None
419 config = self.services.config.GetProjectConfig('cnxn', 12345)
420 addrperm = notify_reasons.AddrPerm(
421 False, 'nonmember@example.com', self.nonmember,
422 notify_reasons.REPLY_NOT_ALLOWED, None)
423
424 subject, body = task._FormatBulkIssues(
425 [self.issue1, self.issue2], users_by_id, commenter_view, 'localhost:8080',
426 'test comment', [], config, addrperm)
427
428 self.assertIn('2 issues', subject)
429 self.assertNotIn('summary', subject)
430 self.assertNotIn('one summary', body)
431 self.assertIn('two summary', body)
432 self.assertNotIn('test comment', body)
433
434 @mock.patch('framework.cloud_tasks_helpers.create_task')
435 def testNotifyApprovalChangeTask_Normal(self, _create_task_mock):
436 config = self.services.config.GetProjectConfig('cnxn', 12345)
437 config.field_defs = [
438 # issue's User field with any_comment is notified.
439 tracker_bizobj.MakeFieldDef(
440 121, 12345, 'TL', tracker_pb2.FieldTypes.USER_TYPE,
441 '', '', False, False, False, None, None, None, False, '',
442 None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action',
443 'TL, notified on everything', False),
444 # issue's User field with never is not notified.
445 tracker_bizobj.MakeFieldDef(
446 122, 12345, 'silentTL', tracker_pb2.FieldTypes.USER_TYPE,
447 '', '', False, False, False, None, None, None, False, '',
448 None, tracker_pb2.NotifyTriggers.NEVER, 'no_action',
449 'TL, notified on nothing', False),
450 # approval's User field with any_comment is notified.
451 tracker_bizobj.MakeFieldDef(
452 123, 12345, 'otherapprovalTL', tracker_pb2.FieldTypes.USER_TYPE,
453 '', '', False, False, False, None, None, None, False, '',
454 None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action',
455 'TL on the approvers team', False, approval_id=3),
456 # another approval's User field with any_comment is not notified.
457 tracker_bizobj.MakeFieldDef(
458 124, 12345, 'otherapprovalTL', tracker_pb2.FieldTypes.USER_TYPE,
459 '', '', False, False, False, None, None, None, False, '',
460 None, tracker_pb2.NotifyTriggers.ANY_COMMENT, 'no_action',
461 'TL on another approvers team', False, approval_id=4),
462 tracker_bizobj.MakeFieldDef(
463 3, 12345, 'Goat-Approval', tracker_pb2.FieldTypes.APPROVAL_TYPE,
464 '', '', False, False, False, None, None, None, False, '',
465 None, tracker_pb2.NotifyTriggers.NEVER, 'no_action',
466 'Get Approval from Goats', False)
467 ]
468 self.services.config.StoreConfig('cnxn', config)
469
470 # Custom user_type field TLs
471 self.services.user.TestAddUser('TL@example.com', 111)
472 self.services.user.TestAddUser('silentTL@example.com', 222)
473 self.services.user.TestAddUser('approvalTL@example.com', 333)
474 self.services.user.TestAddUser('otherapprovalTL@example.com', 444)
475
476 # Approvers
477 self.services.user.TestAddUser('approver_old@example.com', 777)
478 self.services.user.TestAddUser('approver_new@example.com', 888)
479 self.services.user.TestAddUser('approver_still@example.com', 999)
480 self.services.user.TestAddUser('approver_group@example.com', 666)
481 self.services.user.TestAddUser('group_mem1@example.com', 661)
482 self.services.user.TestAddUser('group_mem2@example.com', 662)
483 self.services.user.TestAddUser('group_mem3@example.com', 663)
484 self.services.usergroup.TestAddGroupSettings(
485 666, 'approver_group@example.com')
486 self.services.usergroup.TestAddMembers(666, [661, 662, 663])
487 canary_phase = tracker_pb2.Phase(
488 name='Canary', phase_id=1, rank=1)
489 approval_values = [
490 tracker_pb2.ApprovalValue(approval_id=3,
491 approver_ids=[888, 999, 666, 661])]
492 approval_issue = MakeTestIssue(
493 project_id=12345, local_id=2, owner_id=2, reporter_id=1,
494 is_spam=True)
495 approval_issue.phases = [canary_phase]
496 approval_issue.approval_values = approval_values
497 approval_issue.field_values = [
498 tracker_bizobj.MakeFieldValue(121, None, None, 111, None, None, False),
499 tracker_bizobj.MakeFieldValue(122, None, None, 222, None, None, False),
500 tracker_bizobj.MakeFieldValue(123, None, None, 333, None, None, False),
501 tracker_bizobj.MakeFieldValue(124, None, None, 444, None, None, False),
502 ]
503 self.services.issue.TestAddIssue(approval_issue)
504
505 amend = tracker_bizobj.MakeApprovalApproversAmendment([888], [777])
506
507 comment = tracker_pb2.IssueComment(
508 project_id=12345, user_id=999, issue_id=approval_issue.issue_id,
509 amendments=[amend], timestamp=1234567890, content='just a comment.')
510 attach = tracker_pb2.Attachment(
511 attachment_id=4567, filename='sploot.jpg', mimetype='image/png',
512 gcs_object_id='/pid/attachments/abcd', filesize=(1024 * 1023))
513 comment.attachments.append(attach)
514 self.services.issue.TestAddComment(comment, approval_issue.local_id)
515 self.services.issue.TestAddAttachment(
516 attach, comment.id, approval_issue.issue_id)
517
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200518 task = notify.NotifyApprovalChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000519 params = {
520 'send_email': 1,
521 'issue_id': approval_issue.issue_id,
522 'approval_id': 3,
523 'comment_id': comment.id,
524 }
525 mr = testing_helpers.MakeMonorailRequest(
526 user_info={'user_id': 1},
527 params=params,
528 method='POST',
529 services=self.services)
530 result = task.HandleRequest(mr)
531 self.assertTrue('just a comment' in result['tasks'][0]['body'])
532 self.assertTrue('Approvers: -appro...' in result['tasks'][0]['body'])
533 self.assertTrue('sploot.jpg' in result['tasks'][0]['body'])
534 self.assertTrue(
535 '/issues/attachment?aid=4567' in result['tasks'][0]['body'])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100536 six.assertCountEqual(
537 self, [
538 'user@example.com', 'approver_old@example.com',
539 'approver_new@example.com', 'TL@example.com',
540 'approvalTL@example.com', 'group_mem1@example.com',
541 'group_mem2@example.com', 'group_mem3@example.com'
542 ], result['notified'])
Copybara854996b2021-09-07 19:36:02 +0000543
544 # Test no approvers/groups notified
545 # Status change to NEED_INFO does not email approvers.
546 amend2 = tracker_bizobj.MakeApprovalStatusAmendment(
547 tracker_pb2.ApprovalStatus.NEED_INFO)
548 comment2 = tracker_pb2.IssueComment(
549 project_id=12345, user_id=999, issue_id=approval_issue.issue_id,
550 amendments=[amend2], timestamp=1234567891, content='')
551 self.services.issue.TestAddComment(comment2, approval_issue.local_id)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200552 task = notify.NotifyApprovalChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000553 params = {
554 'send_email': 1,
555 'issue_id': approval_issue.issue_id,
556 'approval_id': 3,
557 'comment_id': comment2.id,
558 }
559 mr = testing_helpers.MakeMonorailRequest(
560 user_info={'user_id': 1},
561 params=params,
562 method='POST',
563 services=self.services)
564 result = task.HandleRequest(mr)
565
566 self.assertIsNotNone(result['tasks'][0].get('references'))
567 self.assertEqual(result['tasks'][0]['reply_to'], emailfmt.NoReplyAddress())
568 self.assertTrue('Status: need_info' in result['tasks'][0]['body'])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100569 six.assertCountEqual(
570 self, ['user@example.com', 'TL@example.com', 'approvalTL@example.com'],
Copybara854996b2021-09-07 19:36:02 +0000571 result['notified'])
572
573 def testNotifyApprovalChangeTask_GetApprovalEmailRecipients(self):
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200574 task = notify.NotifyApprovalChangeTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000575 issue = fake.MakeTestIssue(789, 1, 'summary', 'New', 111)
576 approval_value = tracker_pb2.ApprovalValue(
577 approver_ids=[222, 333],
578 status=tracker_pb2.ApprovalStatus.APPROVED)
579 comment = tracker_pb2.IssueComment(
580 project_id=789, user_id=1, issue_id=78901)
581
582 # Comment with not amendments notifies everyone.
583 rids = task._GetApprovalEmailRecipients(
584 approval_value, comment, issue, [777, 888])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100585 six.assertCountEqual(self, rids, [111, 222, 333, 777, 888])
Copybara854996b2021-09-07 19:36:02 +0000586
587 # New APPROVED status notifies owners and any_comment users.
588 amendment = tracker_bizobj.MakeApprovalStatusAmendment(
589 tracker_pb2.ApprovalStatus.APPROVED)
590 comment.amendments = [amendment]
591 rids = task._GetApprovalEmailRecipients(
592 approval_value, comment, issue, [777, 888])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100593 six.assertCountEqual(self, rids, [111, 777, 888])
Copybara854996b2021-09-07 19:36:02 +0000594
595 # New REVIEW_REQUESTED status notifies approvers.
596 approval_value.status = tracker_pb2.ApprovalStatus.REVIEW_REQUESTED
597 amendment = tracker_bizobj.MakeApprovalStatusAmendment(
598 tracker_pb2.ApprovalStatus.REVIEW_REQUESTED)
599 comment.amendments = [amendment]
600 rids = task._GetApprovalEmailRecipients(
601 approval_value, comment, issue, [777, 888])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100602 six.assertCountEqual(self, rids, [222, 333])
Copybara854996b2021-09-07 19:36:02 +0000603
604 # Approvers change notifies everyone.
605 amendment = tracker_bizobj.MakeApprovalApproversAmendment(
606 [222], [555])
607 comment.amendments = [amendment]
608 approval_value.approver_ids = [222]
609 rids = task._GetApprovalEmailRecipients(
610 approval_value, comment, issue, [777], omit_ids=[444, 333])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100611 six.assertCountEqual(self, rids, [111, 222, 555, 777])
Copybara854996b2021-09-07 19:36:02 +0000612
613 @mock.patch('framework.cloud_tasks_helpers.create_task')
614 def testNotifyRulesDeletedTask(self, _create_task_mock):
615 self.services.project.TestAddProject(
616 'proj', owner_ids=[777, 888], project_id=789)
617 self.services.user.TestAddUser('owner1@test.com', 777)
618 self.services.user.TestAddUser('cow@test.com', 888)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200619 task = notify.NotifyRulesDeletedTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +0000620 params = {'project_id': 789,
621 'filter_rules': 'if green make yellow,if orange make blue'}
622 mr = testing_helpers.MakeMonorailRequest(
623 params=params,
624 method='POST',
625 services=self.services)
626 result = task.HandleRequest(mr)
627 self.assertEqual(len(result['tasks']), 2)
628 body = result['tasks'][0]['body']
629 self.assertTrue('if green make yellow' in body)
630 self.assertTrue('if green make yellow' in body)
631 self.assertTrue('/p/proj/adminRules' in body)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100632 six.assertCountEqual(
633 self, ['cow@test.com', 'owner1@test.com'], result['notified'])
Copybara854996b2021-09-07 19:36:02 +0000634
635 def testOutboundEmailTask_Normal(self):
636 """We can send an email."""
637 params = {
638 'from_addr': 'requester@example.com',
639 'reply_to': 'user@example.com',
640 'to': 'user@example.com',
641 'subject': 'Test subject'}
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200642 data = json.dumps(params)
643 res = self.app.test_client().post('/_task/outboundEmail.do', data=data)
644 res_string = res.get_data()[5:]
645 res_json = json.loads(res_string)
646 self.assertEqual(params['from_addr'], res_json['sender'])
647 self.assertEqual(params['subject'], res_json['subject'])
Copybara854996b2021-09-07 19:36:02 +0000648
649 def testOutboundEmailTask_MissingTo(self):
650 """We skip emails that don't specify the To-line."""
651 params = {
652 'from_addr': 'requester@example.com',
653 'reply_to': 'user@example.com',
654 'subject': 'Test subject'}
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200655 data = json.dumps(params)
656 res = self.app.test_client().post('/_task/outboundEmail.do', data=data)
657 res_string = res.get_data()[5:]
658 res_json = json.loads(res_string)
659 self.assertEqual(
660 'Skipping because no "to" address found.', res_json['note'])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100661 self.assertNotIn(b'from_addr', res_string)
Copybara854996b2021-09-07 19:36:02 +0000662
663 def testOutboundEmailTask_BannedUser(self):
664 """We don't send emails to banned users.."""
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200665 self.servlet.services.user.TestAddUser(
666 'banned@example.com', 404, banned=True)
Copybara854996b2021-09-07 19:36:02 +0000667 params = {
668 'from_addr': 'requester@example.com',
669 'reply_to': 'user@example.com',
670 'to': 'banned@example.com',
671 'subject': 'Test subject'}
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200672 data = json.dumps(params)
673 res = self.app.test_client().post('/_task/outboundEmail.do', data=data)
674 res_string = res.get_data()[5:]
675 res_json = json.loads(res_string)
676 self.assertEqual('Skipping because user is banned.', res_json['note'])
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100677 self.assertNotIn(b'from_addr', res_string)