blob: 3b1b6d193bba2d4e6cf283961d28923938ba3159 [file] [log] [blame]
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
"""Unittests for monorail.feature.alert2issue."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import email
import unittest
from mock import patch
import mox
from parameterized import parameterized
from features import alert2issue
from framework import authdata
from framework import emailfmt
from proto import tracker_pb2
from services import service_manager
from testing import fake
from testing import testing_helpers
from tracker import tracker_helpers
AlertEmailHeader = emailfmt.AlertEmailHeader
class TestData(object):
"""Contains constants or such objects that are intended to be read-only."""
cnxn = 'fake cnxn'
test_issue_local_id = 100
component_id = 123
trooper_queue = 'my-trooper-bug-queue'
project_name = 'proj'
project_addr = '%s+ALERT+%s@monorail.example.com' % (
project_name, trooper_queue)
project_id = 987
from_addr = 'user@monorail.example.com'
user_id = 111
msg_body = 'this is the body'
msg_subject = 'this is the subject'
msg = testing_helpers.MakeMessage(
testing_helpers.ALERT_EMAIL_HEADER_LINES, msg_body)
incident_id = msg.get(AlertEmailHeader.INCIDENT_ID)
incident_label = alert2issue._GetIncidentLabel(incident_id)
# All the tests in this class use the following alert properties, and
# the generator functions/logic should be tested in a separate class.
alert_props = {
'owner_id': 0,
'cc_ids': [],
'status': 'Available',
'incident_label': incident_label,
'priority': 'Pri-0',
'trooper_queue': trooper_queue,
'field_values': [],
'labels': [
'Restrict-View-Google', 'Pri-0', incident_label, trooper_queue
],
'component_ids': [component_id],
}
class ProcessEmailNotificationTests(unittest.TestCase, TestData):
"""Implements unit tests for alert2issue.ProcessEmailNotification."""
def setUp(self):
# services
self.services = service_manager.Services(
config=fake.ConfigService(),
issue=fake.IssueService(),
user=fake.UserService(),
usergroup=fake.UserGroupService(),
project=fake.ProjectService(),
features=fake.FeaturesService())
# project
self.project = self.services.project.TestAddProject(
self.project_name, project_id=self.project_id,
process_inbound_email=True, contrib_ids=[self.user_id])
# config
proj_config = fake.MakeTestConfig(self.project_id, [], ['Available'])
comp_def_1 = tracker_pb2.ComponentDef(
component_id=123, project_id=987, path='FOO', docstring='foo docstring')
proj_config.component_defs = [comp_def_1]
self.services.config.StoreConfig(self.cnxn, proj_config)
# sender
self.auth = authdata.AuthData(user_id=self.user_id, email=self.from_addr)
# issue
self.issue = tracker_pb2.Issue(
project_id=self.project_id,
local_id=self.test_issue_local_id,
summary=self.msg_subject,
reporter_id=self.user_id,
component_ids=[self.component_id],
status=self.alert_props['status'],
labels=self.alert_props['labels'],
)
self.services.issue.TestAddIssue(self.issue)
# Patch send_notifications functions.
self.notification_patchers = [
patch('features.send_notifications.%s' % func, spec=True)
for func in [
'PrepareAndSendIssueBlockingNotification',
'PrepareAndSendIssueChangeNotification',
]
]
self.blocking_notification = self.notification_patchers[0].start()
self.blocking_notification = self.notification_patchers[1].start()
self.mox = mox.Mox()
def tearDown(self):
self.notification_patchers[0].stop()
self.notification_patchers[1].stop()
self.mox.UnsetStubs()
self.mox.ResetAll()
def testGoogleAddrsAreAllowlistedSender(self):
self.assertTrue(alert2issue.IsAllowlisted('test@google.com'))
self.assertFalse(alert2issue.IsAllowlisted('test@notgoogle.com'))
def testSkipNotification_IfFromNonAllowlistedSender(self):
self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
alert2issue.IsAllowlisted(self.from_addr).AndReturn(False)
# None of them should be called, if the sender has not been allowlisted.
self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
self.mox.StubOutWithMock(self.services.issue, 'CreateIssue')
self.mox.ReplayAll()
alert2issue.ProcessEmailNotification(
self.services, self.cnxn, self.project, self.project_addr,
self.from_addr, self.auth, self.msg_subject, self.msg_body,
self.incident_label, self.msg, self.trooper_queue)
self.mox.VerifyAll()
def testSkipNotification_TooLongComment(self):
self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
self.mox.StubOutWithMock(alert2issue, 'IsCommentSizeReasonable')
alert2issue.IsCommentSizeReasonable(
'Filed by %s on behalf of %s\n\n%s' %
(self.auth.email, self.from_addr, self.msg_body)).AndReturn(False)
# None of them should be called, if the comment is too long.
self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
self.mox.StubOutWithMock(self.services.issue, 'CreateIssue')
self.mox.ReplayAll()
alert2issue.ProcessEmailNotification(
self.services, self.cnxn, self.project, self.project_addr,
self.from_addr, self.auth, self.msg_subject, self.msg_body,
self.incident_label, self.msg, self.trooper_queue)
self.mox.VerifyAll()
def testProcessNotification_IfFromAllowlistedSender(self):
self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
tracker_helpers.LookupComponentIDs(
['Infra'],
mox.IgnoreArg()).AndReturn([1])
self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
self.mox.StubOutWithMock(self.services.issue, 'CreateIssue')
self.mox.ReplayAll()
# Either of the methods should be called, if the sender is allowlisted.
with self.assertRaises(mox.UnexpectedMethodCallError):
alert2issue.ProcessEmailNotification(
self.services, self.cnxn, self.project, self.project_addr,
self.from_addr, self.auth, self.msg_subject, self.msg_body,
self.incident_label, self.msg, self.trooper_queue)
self.mox.VerifyAll()
def testIssueCreated_ForNewIncident(self):
"""Tests if a new issue is created for a new incident."""
self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
# FindAlertIssue() returns None for a new incident.
self.mox.StubOutWithMock(alert2issue, 'FindAlertIssue')
alert2issue.FindAlertIssue(
self.services, self.cnxn, self.project.project_id,
self.incident_label).AndReturn(None)
# Mock GetAlertProperties() to create the issue with the expected
# properties.
self.mox.StubOutWithMock(alert2issue, 'GetAlertProperties')
alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.msg).AndReturn(self.alert_props)
self.mox.ReplayAll()
alert2issue.ProcessEmailNotification(
self.services, self.cnxn, self.project, self.project_addr,
self.from_addr, self.auth, self.msg_subject, self.msg_body,
self.incident_id, self.msg, self.trooper_queue)
# the local ID of the newly created issue should be +1 from the highest ID
# in the existing issues.
comments = self._verifyIssue(self.test_issue_local_id + 1, self.alert_props)
self.assertEqual(comments[0].content,
'Filed by %s on behalf of %s\n\n%s' % (
self.from_addr, self.from_addr, self.msg_body))
self.mox.VerifyAll()
def testProcessEmailNotification_ExistingIssue(self):
"""When an alert for an ongoing incident comes in, add a comment."""
self.mox.StubOutWithMock(alert2issue, 'IsAllowlisted')
alert2issue.IsAllowlisted(self.from_addr).AndReturn(True)
# FindAlertIssue() returns None for a new incident.
self.mox.StubOutWithMock(alert2issue, 'FindAlertIssue')
alert2issue.FindAlertIssue(
self.services, self.cnxn, self.project.project_id,
self.incident_label).AndReturn(self.issue)
# Mock GetAlertProperties() to create the issue with the expected
# properties.
self.mox.StubOutWithMock(alert2issue, 'GetAlertProperties')
alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.msg).AndReturn(self.alert_props)
self.mox.ReplayAll()
# Before processing the notification, ensures that there is only 1 comment
# in the test issue.
comments = self._verifyIssue(self.test_issue_local_id, self.alert_props)
self.assertEqual(len(comments), 1)
# Process
alert2issue.ProcessEmailNotification(
self.services, self.cnxn, self.project, self.project_addr,
self.from_addr, self.auth, self.msg_subject, self.msg_body,
self.incident_id, self.msg, self.trooper_queue)
# Now, it should have a new comment added.
comments = self._verifyIssue(self.test_issue_local_id, self.alert_props)
self.assertEqual(len(comments), 2)
self.assertEqual(comments[1].content,
'Filed by %s on behalf of %s\n\n%s' % (
self.from_addr, self.from_addr, self.msg_body))
self.mox.VerifyAll()
def _verifyIssue(self, local_issue_id, alert_props):
actual_issue = self.services.issue.GetIssueByLocalID(
self.cnxn, self.project.project_id, local_issue_id)
actual_comments = self.services.issue.GetCommentsForIssue(
self.cnxn, actual_issue.issue_id)
self.assertEqual(actual_issue.summary, self.msg_subject)
self.assertEqual(actual_issue.status, alert_props['status'])
self.assertEqual(actual_issue.reporter_id, self.user_id)
self.assertEqual(actual_issue.component_ids, [self.component_id])
if alert_props['owner_id']:
self.assertEqual(actual_issue.owner_id, alert_props['owner_id'])
self.assertEqual(sorted(actual_issue.labels), sorted(alert_props['labels']))
return actual_comments
class GetAlertPropertiesTests(unittest.TestCase, TestData):
"""Implements unit tests for alert2issue.GetAlertProperties."""
def assertSubset(self, lhs, rhs):
if not (lhs <= rhs):
raise AssertionError('%s not a subset of %s' % (lhs, rhs))
def assertCaseInsensitiveEqual(self, lhs, rhs):
self.assertEqual(lhs if lhs is None else lhs.lower(),
rhs if lhs is None else rhs.lower())
def setUp(self):
# services
self.services = service_manager.Services(
config=fake.ConfigService(),
issue=fake.IssueService(),
user=fake.UserService(),
usergroup=fake.UserGroupService(),
project=fake.ProjectService())
# project
self.project = self.services.project.TestAddProject(
self.project_name, project_id=self.project_id,
process_inbound_email=True, contrib_ids=[self.user_id])
proj_config = fake.MakeTestConfig(
self.project_id,
[
# test labels for Pri field
'Pri-0', 'Pri-1', 'Pri-2', 'Pri-3',
# test labels for OS field
'OS-Android', 'OS-Windows',
# test labels for Type field
'Type-Bug', 'Type-Bug-Regression', 'Type-Bug-Security', 'Type-Task',
],
['Assigned', 'Available', 'Unconfirmed']
)
self.services.config.StoreConfig(self.cnxn, proj_config)
# create a test email message, which tests can alternate the header values
# to verify the behaviour of a given parser function.
self.test_msg = email.Message.Message()
for key, value in self.msg.items():
self.test_msg[key] = value
self.mox = mox.Mox()
@parameterized.expand([
(None,),
('',),
(' ',),
])
def testDefaultComponent(self, header_value):
"""Checks if the default component is Infra."""
self.test_msg.replace_header(AlertEmailHeader.COMPONENT, header_value)
self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
tracker_helpers.LookupComponentIDs(
['Infra'],
mox.IgnoreArg()).AndReturn([self.component_id])
self.mox.ReplayAll()
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertEqual(props['component_ids'], [self.component_id])
self.mox.VerifyAll()
@parameterized.expand([
# an existing single component with componentID 1
({'Infra': 1}, [1]),
# 3 of existing components
({'Infra': 1, 'Foo': 2, 'Bar': 3}, [1, 2, 3]),
# a non-existing component
({'Infra': None}, []),
# 3 of non-existing components
({'Infra': None, 'Foo': None, 'Bar': None}, []),
# a mix of existing and non-existing components
({'Infra': 1, 'Foo': None, 'Bar': 2}, [1, 2]),
])
def testGetComponentIDs(self, components, expected_component_ids):
"""Tests _GetComponentIDs."""
self.test_msg.replace_header(
AlertEmailHeader.COMPONENT, ','.join(sorted(components.keys())))
self.mox.StubOutWithMock(tracker_helpers, 'LookupComponentIDs')
tracker_helpers.LookupComponentIDs(
sorted(components.keys()),
mox.IgnoreArg()).AndReturn(
[components[key] for key in sorted(components.keys())
if components[key]]
)
self.mox.ReplayAll()
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertEqual(sorted(props['component_ids']),
sorted(expected_component_ids))
self.mox.VerifyAll()
def testLabelsWithNecessaryValues(self):
"""Checks if the labels contain all the necessary values."""
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
# This test assumes that the test message contains non-empty values for
# all the headers.
self.assertTrue(props['incident_label'])
self.assertTrue(props['priority'])
self.assertTrue(props['issue_type'])
self.assertTrue(props['oses'])
# Here are a list of the labels that props['labels'] should contain
self.assertIn('Restrict-View-Google'.lower(), props['labels'])
self.assertIn(self.trooper_queue, props['labels'])
self.assertIn(props['incident_label'], props['labels'])
self.assertIn(props['priority'], props['labels'])
self.assertIn(props['issue_type'], props['labels'])
for os in props['oses']:
self.assertIn(os, props['labels'])
@parameterized.expand([
(None, 0),
('', 0),
(' ', 0),
])
def testDefaultOwnerID(self, header_value, expected_owner_id):
"""Checks if _GetOwnerID returns None in default."""
self.test_msg.replace_header(AlertEmailHeader.OWNER, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertEqual(props['owner_id'], expected_owner_id)
@parameterized.expand(
[
# an existing user with userID 1.
('owner@example.org', 1),
# a non-existing user.
('owner@example.org', 0),
])
def testGetOwnerID(self, owner, expected_owner_id):
"""Tests _GetOwnerID returns the ID of the owner."""
self.test_msg.replace_header(AlertEmailHeader.CC, '')
self.test_msg.replace_header(AlertEmailHeader.OWNER, owner)
self.mox.StubOutWithMock(self.services.user, 'LookupExistingUserIDs')
self.services.user.LookupExistingUserIDs(self.cnxn, [owner]).AndReturn(
{owner: expected_owner_id})
self.mox.ReplayAll()
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.mox.VerifyAll()
self.assertEqual(props['owner_id'], expected_owner_id)
@parameterized.expand([
(None, []),
('', []),
(' ', []),
])
def testDefaultCCIDs(self, header_value, expected_cc_ids):
"""Checks if _GetCCIDs returns an empty list in default."""
self.test_msg.replace_header(AlertEmailHeader.CC, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertEqual(props['cc_ids'], expected_cc_ids)
@parameterized.expand([
# with one existing user cc-ed.
({'user1@example.org': 1}, [1]),
# with two of existing users.
({'user1@example.org': 1, 'user2@example.org': 2}, [1, 2]),
# with one non-existing user.
({'user1@example.org': None}, []),
# with two of non-existing users.
({'user1@example.org': None, 'user2@example.org': None}, []),
# with a mix of existing and non-existing users.
({'user1@example.org': 1, 'user2@example.org': None}, [1]),
])
def testGetCCIDs(self, ccers, expected_cc_ids):
"""Tests _GetCCIDs returns the IDs of the email addresses to be cc-ed."""
self.test_msg.replace_header(
AlertEmailHeader.CC, ','.join(sorted(ccers.keys())))
self.test_msg.replace_header(AlertEmailHeader.OWNER, '')
self.mox.StubOutWithMock(self.services.user, 'LookupExistingUserIDs')
self.services.user.LookupExistingUserIDs(
self.cnxn, sorted(ccers.keys())).AndReturn(ccers)
self.mox.ReplayAll()
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.mox.VerifyAll()
self.assertEqual(sorted(props['cc_ids']), sorted(expected_cc_ids))
@parameterized.expand([
# None and '' should result in the default priority returned.
(None, 'Pri-2'),
('', 'Pri-2'),
(' ', 'Pri-2'),
# Tests for valid priority values
('0', 'Pri-0'),
('1', 'Pri-1'),
('2', 'Pri-2'),
('3', 'Pri-3'),
# Tests for invalid priority values
('test', 'Pri-2'),
('foo', 'Pri-2'),
('critical', 'Pri-2'),
('4', 'Pri-2'),
('3x', 'Pri-2'),
('00', 'Pri-2'),
('01', 'Pri-2'),
])
def testGetPriority(self, header_value, expected_priority):
"""Tests _GetPriority."""
self.test_msg.replace_header(AlertEmailHeader.PRIORITY, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertCaseInsensitiveEqual(props['priority'], expected_priority)
@parameterized.expand([
(None, 'Available'),
('', 'Available'),
(' ', 'Available'),
])
def testDefaultStatus(self, header_value, expected_status):
"""Checks if _GetStatus return Available in default."""
self.test_msg.replace_header(AlertEmailHeader.STATUS, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertCaseInsensitiveEqual(props['status'], expected_status)
@parameterized.expand([
('random_status', True, 'random_status'),
# If the status is not one of the open statuses, the default status
# should be returned instead.
('random_status', False, 'Available'),
])
def testGetStatusWithoutOwner(self, status, means_open, expected_status):
"""Tests GetStatus without an owner."""
self.test_msg.replace_header(AlertEmailHeader.STATUS, status)
self.mox.StubOutWithMock(tracker_helpers, 'MeansOpenInProject')
tracker_helpers.MeansOpenInProject(status, mox.IgnoreArg()).AndReturn(
means_open)
self.mox.ReplayAll()
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertCaseInsensitiveEqual(props['status'], expected_status)
self.mox.VerifyAll()
@parameterized.expand([
# If there is an owner, the status should always be Assigned.
(None, 'Assigned'),
('', 'Assigned'),
(' ', 'Assigned'),
('random_status', 'Assigned'),
('Available', 'Assigned'),
('Unconfirmed', 'Assigned'),
('Fixed', 'Assigned'),
])
def testGetStatusWithOwner(self, status, expected_status):
"""Tests GetStatus with an owner."""
owner = 'owner@example.org'
self.test_msg.replace_header(AlertEmailHeader.OWNER, owner)
self.test_msg.replace_header(AlertEmailHeader.CC, '')
self.test_msg.replace_header(AlertEmailHeader.STATUS, status)
self.mox.StubOutWithMock(self.services.user, 'LookupExistingUserIDs')
self.services.user.LookupExistingUserIDs(self.cnxn, [owner]).AndReturn(
{owner: 1})
self.mox.ReplayAll()
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertCaseInsensitiveEqual(props['status'], expected_status)
self.mox.VerifyAll()
@parameterized.expand(
[
# None and '' should result in None returned.
(None, None),
('', None),
(' ', None),
# allowlisted issue types
('Bug', 'Type-Bug'),
('Bug-Regression', 'Type-Bug-Regression'),
('Bug-Security', 'Type-Bug-Security'),
('Task', 'Type-Task'),
# non-allowlisted issue types
('foo', None),
('bar', None),
('Bug,Bug-Regression', None),
('Bug,', None),
(',Task', None),
])
def testGetIssueType(self, header_value, expected_issue_type):
"""Tests _GetIssueType."""
self.test_msg.replace_header(AlertEmailHeader.TYPE, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertCaseInsensitiveEqual(props['issue_type'], expected_issue_type)
@parameterized.expand(
[
# None and '' should result in an empty list returned.
(None, []),
('', []),
(' ', []),
# a single, allowlisted os
('Android', ['OS-Android']),
# a single, non-allowlisted OS
('Bendroid', []),
# multiple, allowlisted oses
('Android,Windows', ['OS-Android', 'OS-Windows']),
# multiple, non-allowlisted oses
('Bendroid,Findows', []),
# a mix of allowlisted and non-allowlisted oses
('Android,Findows,Windows,Bendroid', ['OS-Android', 'OS-Windows']),
# a mix of allowlisted and non-allowlisted oses with trailing commas.
('Android,Findows,Windows,Bendroid,,', ['OS-Android', 'OS-Windows']),
# a mix of allowlisted and non-allowlisted oses with commas at the
# beginning.
(
',,Android,Findows,Windows,Bendroid,,',
['OS-Android', 'OS-Windows']),
])
def testGetOSes(self, header_value, expected_oses):
"""Tests _GetOSes."""
self.test_msg.replace_header(AlertEmailHeader.OS, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
self.assertEqual(sorted(os if os is None else os.lower()
for os in props['oses']),
sorted(os if os is None else os.lower()
for os in expected_oses))
@parameterized.expand([
# None and '' should result in an empty list + RSVG returned.
(None, []),
('', []),
(' ', []),
('Label-1', ['label-1']),
('Label-1,Label-2', ['label-1', 'label-2',]),
('Label-1,Label-2,Label-3', ['label-1', 'label-2', 'label-3']),
# Duplicates should be removed.
('Label-1,Label-1', ['label-1']),
('Label-1,label-1', ['label-1']),
(',Label-1,label-1,', ['label-1']),
('Label-1,label-1,', ['label-1']),
(',Label-1,,label-1,,,', ['label-1']),
('Label-1,Label-2,Label-1', ['label-1', 'label-2']),
# Whitespaces should be removed from labels.
('La bel - 1 ', ['label-1']),
('La bel - 1 , Label- 1', ['label-1']),
('La bel- 1 , Label - 2', ['label-1', 'label-2']),
# RSVG should be set always.
('Label-1,Label-1,Restrict-View-Google', ['label-1']),
])
def testGetLabels(self, header_value, expected_labels):
"""Tests _GetLabels."""
self.test_msg.replace_header(AlertEmailHeader.LABEL, header_value)
props = alert2issue.GetAlertProperties(
self.services, self.cnxn, self.project_id, self.incident_id,
self.trooper_queue, self.test_msg)
# Check if there are any duplicates
labels = set(props['labels'])
self.assertEqual(sorted(props['labels']), sorted(list(labels)))
# Check the labels that shouldb always be included
self.assertIn('Restrict-View-Google'.lower(), labels)
self.assertIn(props['trooper_queue'], labels)
self.assertIn(props['incident_label'], labels)
self.assertIn(props['priority'], labels)
self.assertIn(props['issue_type'], labels)
self.assertSubset(set(props['oses']), labels)
# All the custom labels should be present.
self.assertSubset(set(expected_labels), labels)