blob: 2046b5bad96cf83dbbf8820be939f64153fb0a44 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2019 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"""Unittests for monorail.feature.alert2issue."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import email
12import unittest
13from mock import patch
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020014try:
15 from mox3 import mox
16except ImportError:
17 import mox
Copybara854996b2021-09-07 19:36:02 +000018from parameterized import parameterized
19
20from features import alert2issue
21from framework import authdata
22from framework import emailfmt
23from proto import tracker_pb2
24from services import service_manager
25from testing import fake
26from testing import testing_helpers
27from tracker import tracker_helpers
28
29AlertEmailHeader = emailfmt.AlertEmailHeader
30
31
32class TestData(object):
33 """Contains constants or such objects that are intended to be read-only."""
34 cnxn = 'fake cnxn'
35 test_issue_local_id = 100
36 component_id = 123
37 trooper_queue = 'my-trooper-bug-queue'
38
39 project_name = 'proj'
40 project_addr = '%s+ALERT+%s@monorail.example.com' % (
41 project_name, trooper_queue)
42 project_id = 987
43
44 from_addr = 'user@monorail.example.com'
45 user_id = 111
46
47 msg_body = 'this is the body'
48 msg_subject = 'this is the subject'
49 msg = testing_helpers.MakeMessage(
50 testing_helpers.ALERT_EMAIL_HEADER_LINES, msg_body)
51
52 incident_id = msg.get(AlertEmailHeader.INCIDENT_ID)
53 incident_label = alert2issue._GetIncidentLabel(incident_id)
54
55 # All the tests in this class use the following alert properties, and
56 # the generator functions/logic should be tested in a separate class.
57 alert_props = {
58 'owner_id': 0,
59 'cc_ids': [],
60 'status': 'Available',
61 'incident_label': incident_label,
62 'priority': 'Pri-0',
63 'trooper_queue': trooper_queue,
64 'field_values': [],
65 'labels': [
66 'Restrict-View-Google', 'Pri-0', incident_label, trooper_queue
67 ],
68 'component_ids': [component_id],
69 }
70
71
72class ProcessEmailNotificationTests(unittest.TestCase, TestData):
73 """Implements unit tests for alert2issue.ProcessEmailNotification."""
74 def setUp(self):
75 # services
76 self.services = service_manager.Services(
77 config=fake.ConfigService(),
78 issue=fake.IssueService(),
79 user=fake.UserService(),
80 usergroup=fake.UserGroupService(),
81 project=fake.ProjectService(),
82 features=fake.FeaturesService())
83
84 # project
85 self.project = self.services.project.TestAddProject(
86 self.project_name, project_id=self.project_id,
87 process_inbound_email=True, contrib_ids=[self.user_id])
88
89 # config
90 proj_config = fake.MakeTestConfig(self.project_id, [], ['Available'])
91 comp_def_1 = tracker_pb2.ComponentDef(
92 component_id=123, project_id=987, path='FOO', docstring='foo docstring')
93 proj_config.component_defs = [comp_def_1]
94 self.services.config.StoreConfig(self.cnxn, proj_config)
95
96 # sender
97 self.auth = authdata.AuthData(user_id=self.user_id, email=self.from_addr)
98
99 # issue
100 self.issue = tracker_pb2.Issue(
101 project_id=self.project_id,
102 local_id=self.test_issue_local_id,
103 summary=self.msg_subject,
104 reporter_id=self.user_id,
105 component_ids=[self.component_id],
106 status=self.alert_props['status'],
107 labels=self.alert_props['labels'],
108 )
109 self.services.issue.TestAddIssue(self.issue)
110
111 # Patch send_notifications functions.
112 self.notification_patchers = [
113 patch('features.send_notifications.%s' % func, spec=True)
114 for func in [
115 'PrepareAndSendIssueBlockingNotification',
116 'PrepareAndSendIssueChangeNotification',
117 ]
118 ]
119 self.blocking_notification = self.notification_patchers[0].start()
120 self.blocking_notification = self.notification_patchers[1].start()
121
122 self.mox = mox.Mox()
123
124 def tearDown(self):
125 self.notification_patchers[0].stop()
126 self.notification_patchers[1].stop()
127
128 self.mox.UnsetStubs()
129 self.mox.ResetAll()
130
131 def testGoogleAddrsAreAllowlistedSender(self):
132 self.assertTrue(alert2issue.IsAllowlisted('test@google.com'))
133 self.assertFalse(alert2issue.IsAllowlisted('test@notgoogle.com'))
134
135 def testSkipNotification_IfFromNonAllowlistedSender(self):
136 self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
137 alert2issue.IsAllowlisted(self.from_addr).AndReturn(False)
138
139 # None of them should be called, if the sender has not been allowlisted.
140 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
141 self.mox.StubOutWithMock(self.services.issue, 'CreateIssue')
142 self.mox.ReplayAll()
143
144 alert2issue.ProcessEmailNotification(
145 self.services, self.cnxn, self.project, self.project_addr,
146 self.from_addr, self.auth, self.msg_subject, self.msg_body,
147 self.incident_label, self.msg, self.trooper_queue)
148 self.mox.VerifyAll()
149
150 def testSkipNotification_TooLongComment(self):
151 self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
152 alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
153 self.mox.StubOutWithMock(alert2issue, 'IsCommentSizeReasonable')
154 alert2issue.IsCommentSizeReasonable(
155 'Filed by %s on behalf of %s\n\n%s' %
156 (self.auth.email, self.from_addr, self.msg_body)).AndReturn(False)
157
158 # None of them should be called, if the comment is too long.
159 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
160 self.mox.StubOutWithMock(self.services.issue, 'CreateIssue')
161 self.mox.ReplayAll()
162
163 alert2issue.ProcessEmailNotification(
164 self.services, self.cnxn, self.project, self.project_addr,
165 self.from_addr, self.auth, self.msg_subject, self.msg_body,
166 self.incident_label, self.msg, self.trooper_queue)
167 self.mox.VerifyAll()
168
169 def testProcessNotification_IfFromAllowlistedSender(self):
170 self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
171 alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
172
173 self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
174 tracker_helpers.LookupComponentIDs(
175 ['Infra'],
176 mox.IgnoreArg()).AndReturn([1])
177 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
178 self.mox.StubOutWithMock(self.services.issue, 'CreateIssue')
179 self.mox.ReplayAll()
180
181 # Either of the methods should be called, if the sender is allowlisted.
182 with self.assertRaises(mox.UnexpectedMethodCallError):
183 alert2issue.ProcessEmailNotification(
184 self.services, self.cnxn, self.project, self.project_addr,
185 self.from_addr, self.auth, self.msg_subject, self.msg_body,
186 self.incident_label, self.msg, self.trooper_queue)
187
188 self.mox.VerifyAll()
189
190 def testIssueCreated_ForNewIncident(self):
191 """Tests if a new issue is created for a new incident."""
192 self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
193 alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
194
195 # FindAlertIssue() returns None for a new incident.
196 self.mox.StubOutWithMock(alert2issue, 'FindAlertIssue')
197 alert2issue.FindAlertIssue(
198 self.services, self.cnxn, self.project.project_id,
199 self.incident_label).AndReturn(None)
200
201 # Mock GetAlertProperties() to create the issue with the expected
202 # properties.
203 self.mox.StubOutWithMock(alert2issue, 'GetAlertProperties')
204 alert2issue.GetAlertProperties(
205 self.services, self.cnxn, self.project_id, self.incident_id,
206 self.trooper_queue, self.msg).AndReturn(self.alert_props)
207
208 self.mox.ReplayAll()
209 alert2issue.ProcessEmailNotification(
210 self.services, self.cnxn, self.project, self.project_addr,
211 self.from_addr, self.auth, self.msg_subject, self.msg_body,
212 self.incident_id, self.msg, self.trooper_queue)
213
214 # the local ID of the newly created issue should be +1 from the highest ID
215 # in the existing issues.
216 comments = self._verifyIssue(self.test_issue_local_id + 1, self.alert_props)
217 self.assertEqual(comments[0].content,
218 'Filed by %s on behalf of %s\n\n%s' % (
219 self.from_addr, self.from_addr, self.msg_body))
220
221 self.mox.VerifyAll()
222
223 def testProcessEmailNotification_ExistingIssue(self):
224 """When an alert for an ongoing incident comes in, add a comment."""
225 self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
226 alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
227
228 # FindAlertIssue() returns None for a new incident.
229 self.mox.StubOutWithMock(alert2issue, 'FindAlertIssue')
230 alert2issue.FindAlertIssue(
231 self.services, self.cnxn, self.project.project_id,
232 self.incident_label).AndReturn(self.issue)
233
234 # Mock GetAlertProperties() to create the issue with the expected
235 # properties.
236 self.mox.StubOutWithMock(alert2issue, 'GetAlertProperties')
237 alert2issue.GetAlertProperties(
238 self.services, self.cnxn, self.project_id, self.incident_id,
239 self.trooper_queue, self.msg).AndReturn(self.alert_props)
240
241 self.mox.ReplayAll()
242
243 # Before processing the notification, ensures that there is only 1 comment
244 # in the test issue.
245 comments = self._verifyIssue(self.test_issue_local_id, self.alert_props)
246 self.assertEqual(len(comments), 1)
247
248 # Process
249 alert2issue.ProcessEmailNotification(
250 self.services, self.cnxn, self.project, self.project_addr,
251 self.from_addr, self.auth, self.msg_subject, self.msg_body,
252 self.incident_id, self.msg, self.trooper_queue)
253
254 # Now, it should have a new comment added.
255 comments = self._verifyIssue(self.test_issue_local_id, self.alert_props)
256 self.assertEqual(len(comments), 2)
257 self.assertEqual(comments[1].content,
258 'Filed by %s on behalf of %s\n\n%s' % (
259 self.from_addr, self.from_addr, self.msg_body))
260
261 self.mox.VerifyAll()
262
263 def _verifyIssue(self, local_issue_id, alert_props):
264 actual_issue = self.services.issue.GetIssueByLocalID(
265 self.cnxn, self.project.project_id, local_issue_id)
266 actual_comments = self.services.issue.GetCommentsForIssue(
267 self.cnxn, actual_issue.issue_id)
268
269 self.assertEqual(actual_issue.summary, self.msg_subject)
270 self.assertEqual(actual_issue.status, alert_props['status'])
271 self.assertEqual(actual_issue.reporter_id, self.user_id)
272 self.assertEqual(actual_issue.component_ids, [self.component_id])
273 if alert_props['owner_id']:
274 self.assertEqual(actual_issue.owner_id, alert_props['owner_id'])
275 self.assertEqual(sorted(actual_issue.labels), sorted(alert_props['labels']))
276 return actual_comments
277
278
279class GetAlertPropertiesTests(unittest.TestCase, TestData):
280 """Implements unit tests for alert2issue.GetAlertProperties."""
281 def assertSubset(self, lhs, rhs):
282 if not (lhs <= rhs):
283 raise AssertionError('%s not a subset of %s' % (lhs, rhs))
284
285 def assertCaseInsensitiveEqual(self, lhs, rhs):
286 self.assertEqual(lhs if lhs is None else lhs.lower(),
287 rhs if lhs is None else rhs.lower())
288
289 def setUp(self):
290 # services
291 self.services = service_manager.Services(
292 config=fake.ConfigService(),
293 issue=fake.IssueService(),
294 user=fake.UserService(),
295 usergroup=fake.UserGroupService(),
296 project=fake.ProjectService())
297
298 # project
299 self.project = self.services.project.TestAddProject(
300 self.project_name, project_id=self.project_id,
301 process_inbound_email=True, contrib_ids=[self.user_id])
302
303 proj_config = fake.MakeTestConfig(
304 self.project_id,
305 [
306 # test labels for Pri field
307 'Pri-0', 'Pri-1', 'Pri-2', 'Pri-3',
308 # test labels for OS field
309 'OS-Android', 'OS-Windows',
310 # test labels for Type field
311 'Type-Bug', 'Type-Bug-Regression', 'Type-Bug-Security', 'Type-Task',
312 ],
313 ['Assigned', 'Available', 'Unconfirmed']
314 )
315 self.services.config.StoreConfig(self.cnxn, proj_config)
316
317 # create a test email message, which tests can alternate the header values
318 # to verify the behaviour of a given parser function.
319 self.test_msg = email.Message.Message()
320 for key, value in self.msg.items():
321 self.test_msg[key] = value
322
323 self.mox = mox.Mox()
324
325 @parameterized.expand([
326 (None,),
327 ('',),
328 (' ',),
329 ])
330 def testDefaultComponent(self, header_value):
331 """Checks if the default component is Infra."""
332 self.test_msg.replace_header(AlertEmailHeader.COMPONENT, header_value)
333 self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
334 tracker_helpers.LookupComponentIDs(
335 ['Infra'],
336 mox.IgnoreArg()).AndReturn([self.component_id])
337
338 self.mox.ReplayAll()
339 props = alert2issue.GetAlertProperties(
340 self.services, self.cnxn, self.project_id, self.incident_id,
341 self.trooper_queue, self.test_msg)
342 self.assertEqual(props['component_ids'], [self.component_id])
343 self.mox.VerifyAll()
344
345 @parameterized.expand([
346 # an existing single component with componentID 1
347 ({'Infra': 1}, [1]),
348 # 3 of existing components
349 ({'Infra': 1, 'Foo': 2, 'Bar': 3}, [1, 2, 3]),
350 # a non-existing component
351 ({'Infra': None}, []),
352 # 3 of non-existing components
353 ({'Infra': None, 'Foo': None, 'Bar': None}, []),
354 # a mix of existing and non-existing components
355 ({'Infra': 1, 'Foo': None, 'Bar': 2}, [1, 2]),
356 ])
357 def testGetComponentIDs(self, components, expected_component_ids):
358 """Tests _GetComponentIDs."""
359 self.test_msg.replace_header(
360 AlertEmailHeader.COMPONENT, ','.join(sorted(components.keys())))
361
362 self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
363 tracker_helpers.LookupComponentIDs(
364 sorted(components.keys()),
365 mox.IgnoreArg()).AndReturn(
366 [components[key] for key in sorted(components.keys())
367 if components[key]]
368 )
369
370 self.mox.ReplayAll()
371 props = alert2issue.GetAlertProperties(
372 self.services, self.cnxn, self.project_id, self.incident_id,
373 self.trooper_queue, self.test_msg)
374 self.assertEqual(sorted(props['component_ids']),
375 sorted(expected_component_ids))
376 self.mox.VerifyAll()
377
378
379 def testLabelsWithNecessaryValues(self):
380 """Checks if the labels contain all the necessary values."""
381 props = alert2issue.GetAlertProperties(
382 self.services, self.cnxn, self.project_id, self.incident_id,
383 self.trooper_queue, self.test_msg)
384
385 # This test assumes that the test message contains non-empty values for
386 # all the headers.
387 self.assertTrue(props['incident_label'])
388 self.assertTrue(props['priority'])
389 self.assertTrue(props['issue_type'])
390 self.assertTrue(props['oses'])
391
392 # Here are a list of the labels that props['labels'] should contain
393 self.assertIn('Restrict-View-Google'.lower(), props['labels'])
394 self.assertIn(self.trooper_queue, props['labels'])
395 self.assertIn(props['incident_label'], props['labels'])
396 self.assertIn(props['priority'], props['labels'])
397 self.assertIn(props['issue_type'], props['labels'])
398 for os in props['oses']:
399 self.assertIn(os, props['labels'])
400
401 @parameterized.expand([
402 (None, 0),
403 ('', 0),
404 (' ', 0),
405 ])
406 def testDefaultOwnerID(self, header_value, expected_owner_id):
407 """Checks if _GetOwnerID returns None in default."""
408 self.test_msg.replace_header(AlertEmailHeader.OWNER, header_value)
409 props = alert2issue.GetAlertProperties(
410 self.services, self.cnxn, self.project_id, self.incident_id,
411 self.trooper_queue, self.test_msg)
412 self.assertEqual(props['owner_id'], expected_owner_id)
413
414 @parameterized.expand(
415 [
416 # an existing user with userID 1.
417 ('owner@example.org', 1),
418 # a non-existing user.
419 ('owner@example.org', 0),
420 ])
421 def testGetOwnerID(self, owner, expected_owner_id):
422 """Tests _GetOwnerID returns the ID of the owner."""
423 self.test_msg.replace_header(AlertEmailHeader.CC, '')
424 self.test_msg.replace_header(AlertEmailHeader.OWNER, owner)
425
426 self.mox.StubOutWithMock(self.services.user, 'LookupExistingUserIDs')
427 self.services.user.LookupExistingUserIDs(self.cnxn, [owner]).AndReturn(
428 {owner: expected_owner_id})
429
430 self.mox.ReplayAll()
431 props = alert2issue.GetAlertProperties(
432 self.services, self.cnxn, self.project_id, self.incident_id,
433 self.trooper_queue, self.test_msg)
434 self.mox.VerifyAll()
435 self.assertEqual(props['owner_id'], expected_owner_id)
436
437 @parameterized.expand([
438 (None, []),
439 ('', []),
440 (' ', []),
441 ])
442 def testDefaultCCIDs(self, header_value, expected_cc_ids):
443 """Checks if _GetCCIDs returns an empty list in default."""
444 self.test_msg.replace_header(AlertEmailHeader.CC, header_value)
445 props = alert2issue.GetAlertProperties(
446 self.services, self.cnxn, self.project_id, self.incident_id,
447 self.trooper_queue, self.test_msg)
448 self.assertEqual(props['cc_ids'], expected_cc_ids)
449
450 @parameterized.expand([
451 # with one existing user cc-ed.
452 ({'user1@example.org': 1}, [1]),
453 # with two of existing users.
454 ({'user1@example.org': 1, 'user2@example.org': 2}, [1, 2]),
455 # with one non-existing user.
456 ({'user1@example.org': None}, []),
457 # with two of non-existing users.
458 ({'user1@example.org': None, 'user2@example.org': None}, []),
459 # with a mix of existing and non-existing users.
460 ({'user1@example.org': 1, 'user2@example.org': None}, [1]),
461 ])
462 def testGetCCIDs(self, ccers, expected_cc_ids):
463 """Tests _GetCCIDs returns the IDs of the email addresses to be cc-ed."""
464 self.test_msg.replace_header(
465 AlertEmailHeader.CC, ','.join(sorted(ccers.keys())))
466 self.test_msg.replace_header(AlertEmailHeader.OWNER, '')
467
468 self.mox.StubOutWithMock(self.services.user, 'LookupExistingUserIDs')
469 self.services.user.LookupExistingUserIDs(
470 self.cnxn, sorted(ccers.keys())).AndReturn(ccers)
471
472 self.mox.ReplayAll()
473 props = alert2issue.GetAlertProperties(
474 self.services, self.cnxn, self.project_id, self.incident_id,
475 self.trooper_queue, self.test_msg)
476 self.mox.VerifyAll()
477 self.assertEqual(sorted(props['cc_ids']), sorted(expected_cc_ids))
478
479 @parameterized.expand([
480 # None and '' should result in the default priority returned.
481 (None, 'Pri-2'),
482 ('', 'Pri-2'),
483 (' ', 'Pri-2'),
484
485 # Tests for valid priority values
486 ('0', 'Pri-0'),
487 ('1', 'Pri-1'),
488 ('2', 'Pri-2'),
489 ('3', 'Pri-3'),
490
491 # Tests for invalid priority values
492 ('test', 'Pri-2'),
493 ('foo', 'Pri-2'),
494 ('critical', 'Pri-2'),
495 ('4', 'Pri-2'),
496 ('3x', 'Pri-2'),
497 ('00', 'Pri-2'),
498 ('01', 'Pri-2'),
499 ])
500 def testGetPriority(self, header_value, expected_priority):
501 """Tests _GetPriority."""
502 self.test_msg.replace_header(AlertEmailHeader.PRIORITY, header_value)
503 props = alert2issue.GetAlertProperties(
504 self.services, self.cnxn, self.project_id, self.incident_id,
505 self.trooper_queue, self.test_msg)
506 self.assertCaseInsensitiveEqual(props['priority'], expected_priority)
507
508 @parameterized.expand([
509 (None, 'Available'),
510 ('', 'Available'),
511 (' ', 'Available'),
512 ])
513 def testDefaultStatus(self, header_value, expected_status):
514 """Checks if _GetStatus return Available in default."""
515 self.test_msg.replace_header(AlertEmailHeader.STATUS, header_value)
516 props = alert2issue.GetAlertProperties(
517 self.services, self.cnxn, self.project_id, self.incident_id,
518 self.trooper_queue, self.test_msg)
519 self.assertCaseInsensitiveEqual(props['status'], expected_status)
520
521 @parameterized.expand([
522 ('random_status', True, 'random_status'),
523 # If the status is not one of the open statuses, the default status
524 # should be returned instead.
525 ('random_status', False, 'Available'),
526 ])
527 def testGetStatusWithoutOwner(self, status, means_open, expected_status):
528 """Tests GetStatus without an owner."""
529 self.test_msg.replace_header(AlertEmailHeader.STATUS, status)
530 self.mox.StubOutWithMock(tracker_helpers, 'MeansOpenInProject')
531 tracker_helpers.MeansOpenInProject(status, mox.IgnoreArg()).AndReturn(
532 means_open)
533
534 self.mox.ReplayAll()
535 props = alert2issue.GetAlertProperties(
536 self.services, self.cnxn, self.project_id, self.incident_id,
537 self.trooper_queue, self.test_msg)
538 self.assertCaseInsensitiveEqual(props['status'], expected_status)
539 self.mox.VerifyAll()
540
541 @parameterized.expand([
542 # If there is an owner, the status should always be Assigned.
543 (None, 'Assigned'),
544 ('', 'Assigned'),
545 (' ', 'Assigned'),
546
547 ('random_status', 'Assigned'),
548 ('Available', 'Assigned'),
549 ('Unconfirmed', 'Assigned'),
550 ('Fixed', 'Assigned'),
551 ])
552 def testGetStatusWithOwner(self, status, expected_status):
553 """Tests GetStatus with an owner."""
554 owner = 'owner@example.org'
555 self.test_msg.replace_header(AlertEmailHeader.OWNER, owner)
556 self.test_msg.replace_header(AlertEmailHeader.CC, '')
557 self.test_msg.replace_header(AlertEmailHeader.STATUS, status)
558
559 self.mox.StubOutWithMock(self.services.user, 'LookupExistingUserIDs')
560 self.services.user.LookupExistingUserIDs(self.cnxn, [owner]).AndReturn(
561 {owner: 1})
562
563 self.mox.ReplayAll()
564 props = alert2issue.GetAlertProperties(
565 self.services, self.cnxn, self.project_id, self.incident_id,
566 self.trooper_queue, self.test_msg)
567 self.assertCaseInsensitiveEqual(props['status'], expected_status)
568 self.mox.VerifyAll()
569
570 @parameterized.expand(
571 [
572 # None and '' should result in None returned.
573 (None, None),
574 ('', None),
575 (' ', None),
576
577 # allowlisted issue types
578 ('Bug', 'Type-Bug'),
579 ('Bug-Regression', 'Type-Bug-Regression'),
580 ('Bug-Security', 'Type-Bug-Security'),
581 ('Task', 'Type-Task'),
582
583 # non-allowlisted issue types
584 ('foo', None),
585 ('bar', None),
586 ('Bug,Bug-Regression', None),
587 ('Bug,', None),
588 (',Task', None),
589 ])
590 def testGetIssueType(self, header_value, expected_issue_type):
591 """Tests _GetIssueType."""
592 self.test_msg.replace_header(AlertEmailHeader.TYPE, header_value)
593 props = alert2issue.GetAlertProperties(
594 self.services, self.cnxn, self.project_id, self.incident_id,
595 self.trooper_queue, self.test_msg)
596 self.assertCaseInsensitiveEqual(props['issue_type'], expected_issue_type)
597
598 @parameterized.expand(
599 [
600 # None and '' should result in an empty list returned.
601 (None, []),
602 ('', []),
603 (' ', []),
604
605 # a single, allowlisted os
606 ('Android', ['OS-Android']),
607 # a single, non-allowlisted OS
608 ('Bendroid', []),
609 # multiple, allowlisted oses
610 ('Android,Windows', ['OS-Android', 'OS-Windows']),
611 # multiple, non-allowlisted oses
612 ('Bendroid,Findows', []),
613 # a mix of allowlisted and non-allowlisted oses
614 ('Android,Findows,Windows,Bendroid', ['OS-Android', 'OS-Windows']),
615 # a mix of allowlisted and non-allowlisted oses with trailing commas.
616 ('Android,Findows,Windows,Bendroid,,', ['OS-Android', 'OS-Windows']),
617 # a mix of allowlisted and non-allowlisted oses with commas at the
618 # beginning.
619 (
620 ',,Android,Findows,Windows,Bendroid,,',
621 ['OS-Android', 'OS-Windows']),
622 ])
623 def testGetOSes(self, header_value, expected_oses):
624 """Tests _GetOSes."""
625 self.test_msg.replace_header(AlertEmailHeader.OS, header_value)
626 props = alert2issue.GetAlertProperties(
627 self.services, self.cnxn, self.project_id, self.incident_id,
628 self.trooper_queue, self.test_msg)
629 self.assertEqual(sorted(os if os is None else os.lower()
630 for os in props['oses']),
631 sorted(os if os is None else os.lower()
632 for os in expected_oses))
633
634 @parameterized.expand([
635 # None and '' should result in an empty list + RSVG returned.
636 (None, []),
637 ('', []),
638 (' ', []),
639
640 ('Label-1', ['label-1']),
641 ('Label-1,Label-2', ['label-1', 'label-2',]),
642 ('Label-1,Label-2,Label-3', ['label-1', 'label-2', 'label-3']),
643
644 # Duplicates should be removed.
645 ('Label-1,Label-1', ['label-1']),
646 ('Label-1,label-1', ['label-1']),
647 (',Label-1,label-1,', ['label-1']),
648 ('Label-1,label-1,', ['label-1']),
649 (',Label-1,,label-1,,,', ['label-1']),
650 ('Label-1,Label-2,Label-1', ['label-1', 'label-2']),
651
652 # Whitespaces should be removed from labels.
653 ('La bel - 1 ', ['label-1']),
654 ('La bel - 1 , Label- 1', ['label-1']),
655 ('La bel- 1 , Label - 2', ['label-1', 'label-2']),
656
657 # RSVG should be set always.
658 ('Label-1,Label-1,Restrict-View-Google', ['label-1']),
659 ])
660 def testGetLabels(self, header_value, expected_labels):
661 """Tests _GetLabels."""
662 self.test_msg.replace_header(AlertEmailHeader.LABEL, header_value)
663 props = alert2issue.GetAlertProperties(
664 self.services, self.cnxn, self.project_id, self.incident_id,
665 self.trooper_queue, self.test_msg)
666
667 # Check if there are any duplicates
668 labels = set(props['labels'])
669 self.assertEqual(sorted(props['labels']), sorted(list(labels)))
670
671 # Check the labels that shouldb always be included
672 self.assertIn('Restrict-View-Google'.lower(), labels)
673 self.assertIn(props['trooper_queue'], labels)
674 self.assertIn(props['incident_label'], labels)
675 self.assertIn(props['priority'], labels)
676 self.assertIn(props['issue_type'], labels)
677 self.assertSubset(set(props['oses']), labels)
678
679 # All the custom labels should be present.
680 self.assertSubset(set(expected_labels), labels)