Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/api/v3/test/converters_test.py b/api/v3/test/converters_test.py
index 1bbd12c..088de89 100644
--- a/api/v3/test/converters_test.py
+++ b/api/v3/test/converters_test.py
@@ -1,6 +1,6 @@
-# Copyright 2020 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.
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for converting internal protorpc to external protoc."""
 
 from __future__ import print_function
@@ -32,7 +32,7 @@
 from testing import testing_helpers
 from tracker import field_helpers
 from services import service_manager
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from tracker import tracker_bizobj as tbo
 
 EXPLICIT_DERIVATION = issue_objects_pb2.Derivation.Value('EXPLICIT')
@@ -896,23 +896,24 @@
 
   def testIngestAttachmentUploads(self):
     up_1 = issues_pb2.AttachmentUpload(
-        filename='clown.gif', content='iTs prOUnOuNcED JIF')
-    up_2 = issues_pb2.AttachmentUpload(
-        filename='mowgli', content='cutest dog')
+        filename='clown.gif', content=b'iTs prOUnOuNcED JIF')
+    up_2 = issues_pb2.AttachmentUpload(filename='mowgli', content=b'cutest dog')
 
     ingested = self.converter.IngestAttachmentUploads([up_1, up_2])
-    expected = [framework_helpers.AttachmentUpload(
-        'clown.gif', 'iTs prOUnOuNcED JIF', 'image/gif'),
-                framework_helpers.AttachmentUpload(
-                    'mowgli', 'cutest dog', 'text/plain')]
+    expected = [
+        framework_helpers.AttachmentUpload(
+            'clown.gif', b'iTs prOUnOuNcED JIF', 'image/gif'),
+        framework_helpers.AttachmentUpload(
+            'mowgli', b'cutest dog', 'text/plain')
+    ]
     self.assertEqual(ingested, expected)
 
   def testtIngestAttachmentUploads_Invalid(self):
     up_1 = issues_pb2.AttachmentUpload(filename='clown.gif')
-    up_2 = issues_pb2.AttachmentUpload(content='cutest dog')
+    up_2 = issues_pb2.AttachmentUpload(content=b'cutest dog')
 
-    with self.assertRaisesRegexp(
-        exceptions.InputException, 'Uploaded .+\nUploaded .+'):
+    with self.assertRaisesRegex(exceptions.InputException,
+                                'Uploaded .+\nUploaded .+'):
       self.converter.IngestAttachmentUploads([up_1, up_2])
 
   def testIngestIssueDeltas(self):
@@ -1263,8 +1264,7 @@
     err_msgs.append(
         'Invalid `update_mask` for projects/proj-780/issues/3 delta.')
 
-    with self.assertRaisesRegexp(exceptions.InputException,
-                                 '\n'.join(err_msgs)):
+    with self.assertRaisesRegex(exceptions.InputException, '\n'.join(err_msgs)):
       self.converter.IngestIssueDeltas(api_deltas)
 
   def testIngestIssueDeltas_OutputOnlyIgnored(self):
@@ -1383,7 +1383,7 @@
         r'eldDefs/2\): Could not parse NoDate',
     ]
     error_messages_re = '\n'.join(error_messages)
-    with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
+    with self.assertRaisesRegex(exceptions.InputException, error_messages_re):
       self.converter.IngestIssueDeltas([api_delta])
 
   @mock.patch('time.time', mock.MagicMock(return_value=CURRENT_TIME))
@@ -1450,7 +1450,7 @@
         approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
         update_mask=field_mask_pb2.FieldMask(paths=['chicken']))
     expected_err = 'Invalid `update_mask` for %s delta' % av_name
-    with self.assertRaisesRegexp(exceptions.InputException, expected_err):
+    with self.assertRaisesRegex(exceptions.InputException, expected_err):
       self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
 
   def testIngestApprovalDeltas_FilterFieldValues(self):
@@ -1483,8 +1483,8 @@
         field_vals_remove=[approval_enum_fv, approval_2_fv],
         approvers_remove=['users/222'],
     )
-    with self.assertRaisesRegexp(exceptions.InputException,
-                                 'Field .* does not belong to approval .*'):
+    with self.assertRaisesRegex(exceptions.InputException,
+                                'Field .* does not belong to approval .*'):
       self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
 
   def testIngestApprovalDeltas_InvalidFieldValues(self):
@@ -1511,7 +1511,7 @@
         approval_value=av,
         approvers_remove=['users/222'],
     )
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'Field projects/proj/fieldDefs/404 is not in this project'):
       self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
@@ -1739,7 +1739,7 @@
             name=project1_av_name, field_values=[project1_fv, project2_fv]),
         update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
 
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'Field projects/proj/fieldDefs/%d is not in this project' %
         self.field_def_6):
@@ -2028,7 +2028,7 @@
         r'.+issue:.+[\n\r]+: Issue.+404.+not found'
     ]
     error_messages_re = '\n'.join(error_messages)
-    with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
+    with self.assertRaisesRegex(exceptions.InputException, error_messages_re):
       self.converter.IngestIssue(ingest, self.project_1.project_id)
 
   def testIngestIssuesListColumns(self):
diff --git a/api/v3/test/frontend_servicer_test.py b/api/v3/test/frontend_servicer_test.py
index e58f1ab..5fc9729 100644
--- a/api/v3/test/frontend_servicer_test.py
+++ b/api/v3/test/frontend_servicer_test.py
@@ -1,7 +1,6 @@
-# Copyright 2020 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
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for the hotlists servicer."""
 from __future__ import print_function
 from __future__ import division
@@ -19,7 +18,7 @@
 from api.v3.api_proto import project_objects_pb2
 from framework import exceptions
 from framework import monorailcontext
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from tracker import tracker_constants
@@ -209,9 +208,8 @@
         'owner_proj', project_id=777, owner_ids=[111])
     self.services.project.TestAddProject(
         'committer_proj', project_id=888, committer_ids=[111])
-    contributor_proj = self.services.project.TestAddProject(
-        'contributor_proj', project_id=999)
-    contributor_proj.contributor_ids = [111]
+    self.services.project.TestAddProject(
+        'contributor_proj', project_id=999, contrib_ids=[111])
 
     request = frontend_pb2.GatherProjectMembershipsForUserRequest(
         user=self.user_1_resource_name)
diff --git a/api/v3/test/hotlists_servicer_test.py b/api/v3/test/hotlists_servicer_test.py
index 170cf2e..2708126 100644
--- a/api/v3/test/hotlists_servicer_test.py
+++ b/api/v3/test/hotlists_servicer_test.py
@@ -1,7 +1,6 @@
-# Copyright 2020 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
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Tests for the hotlists servicer."""
 from __future__ import print_function
diff --git a/api/v3/test/issues_servicer_test.py b/api/v3/test/issues_servicer_test.py
index cb01014..bf02855 100644
--- a/api/v3/test/issues_servicer_test.py
+++ b/api/v3/test/issues_servicer_test.py
@@ -1,7 +1,6 @@
-# Copyright 2020 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
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Tests for the issues servicer."""
 from __future__ import print_function
@@ -20,7 +19,7 @@
 from framework import framework_helpers
 from framework import monorailcontext
 from framework import permissions
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from testing import fake
 from services import service_manager
 
@@ -172,7 +171,7 @@
     request = issues_pb2.BatchGetIssuesRequest(
         parent='projects/cow',
         names=['projects/cow/issues/1235', 'projects/chicken/issues/1234'])
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'projects/chicken/issues/1234 is not a child issue of projects/cow.'):
       self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
@@ -180,7 +179,7 @@
     request = issues_pb2.BatchGetIssuesRequest(
         parent='projects/sheep',
         names=['projects/cow/issues/1235', 'projects/chicken/issues/1234'])
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'projects/cow/issues/1235 is not a child issue of projects/sheep.\n' +
         'projects/chicken/issues/1234 is not a child issue of projects/sheep.'):
@@ -189,7 +188,7 @@
     request = issues_pb2.BatchGetIssuesRequest(
         parent='projects/cow',
         names=['projects/cow/badformat/1235', 'projects/chicken/issues/1234'])
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'Invalid resource name: projects/cow/badformat/1235.'):
       self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
@@ -201,10 +200,10 @@
     request = issues_pb2.BatchGetIssuesRequest(
         parent='projects/chicken',
         names=['projects/chicken/issues/1', 'projects/chicken/issues/2'])
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.NoSuchIssueException,
-        "\['projects/chicken/issues/1', 'projects/chicken/issues/2'\] not found"
-    ):
+        r"\['projects/chicken/issues/1', 'projects/chicken/issues/2'\] "
+        'not found'):
       self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
 
   @mock.patch('api.v3.api_constants.MAX_BATCH_ISSUES', 2)
@@ -491,8 +490,10 @@
         parent='projects/chicken',
         issue=request_issue,
         description='description',
-        uploads=[issues_pb2.AttachmentUpload(
-            filename='mowgli.gif', content='cute dog')],
+        uploads=[
+            issues_pb2.AttachmentUpload(
+                filename='mowgli.gif', content=b'cute dog')
+        ],
     )
     mc = monorailcontext.MonorailContext(
         self.services, cnxn=self.cnxn, requester=self.owner.email)
@@ -512,9 +513,9 @@
         uploads=[issues_pb2.AttachmentUpload(
             filename='mowgli.gif')],
     )
-    with self.assertRaisesRegexp(
-      exceptions.InputException,
-      'Uploaded atachment missing filename or content'):
+    with self.assertRaisesRegex(
+        exceptions.InputException,
+        'Uploaded atachment missing filename or content'):
       self.CallWrapped(self.issues_svcr.MakeIssue, mc, unValid_request)
 
 
@@ -542,12 +543,15 @@
             issues_pb2.IssueDelta(
                 issue=issue_objects_pb2.Issue(
                     name='projects/proj-780/issues/1',
-                    labels=[issue_objects_pb2.Issue.LabelValue(
-                        label='add-me')]),
+                    labels=[issue_objects_pb2.Issue.LabelValue(label='add-me')
+                           ]),
                 update_mask=field_mask_pb2.FieldMask(paths=['labels']),
-                labels_remove=['remove-me'])],
-        uploads=[issues_pb2.AttachmentUpload(
-            filename='mowgli.gif', content='cute dog')],
+                labels_remove=['remove-me'])
+        ],
+        uploads=[
+            issues_pb2.AttachmentUpload(
+                filename='mowgli.gif', content=b'cute dog')
+        ],
         comment_content='Release the chicken.',
         notify_type=issues_pb2.NotifyType.Value('NO_NOTIFICATION'))
 
@@ -555,6 +559,7 @@
         self.issues_svcr.ModifyIssues, mc, request)
     exp_issue.labels = ['keep-me', 'add-me']
     exp_issue.modified_timestamp = 12345
+    exp_issue.migration_modified_timestamp = 12345
     exp_api_issue = self.issues_svcr.converter.ConvertIssue(exp_issue)
     self.assertEqual([iss for iss in response.issues], [exp_api_issue])
 
@@ -566,8 +571,10 @@
     # the ApplyFilterRules path. (see filter_helpers._ComputeDerivedFields)
     exp_issue.derived_owner_id = 0
     exp_issue.derived_status = ''
-    exp_attachments = [framework_helpers.AttachmentUpload(
-        'mowgli.gif', 'cute dog', 'image/gif')]
+    exp_attachments = [
+        framework_helpers.AttachmentUpload(
+            'mowgli.gif', b'cute dog', 'image/gif')
+    ]
     exp_amendments = [tracker_pb2.Amendment(
         field=tracker_pb2.FieldID.LABELS, newvalue='-remove-me add-me')]
     self.services.issue.CreateIssueComment.assert_called_once_with(
@@ -595,7 +602,7 @@
             issues_pb2.IssueDelta(),
             issues_pb2.IssueDelta()
         ])
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'Requesting 3 updates when the allowed maximum is 2 updates.'):
       self.CallWrapped(self.issues_svcr.ModifyIssues, mc, request)
@@ -614,7 +621,7 @@
                     blocking_issue_refs=issue_ref_list),
                 blocking_issues_remove=issue_ref_list)
         ])
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'Updates include 5 impacted issues when the allowed maximum is 4.'):
       self.CallWrapped(self.issues_svcr.ModifyIssues, mc, request)
diff --git a/api/v3/test/monorail_servicer_test.py b/api/v3/test/monorail_servicer_test.py
index 2abcaf9..7d628ba 100644
--- a/api/v3/test/monorail_servicer_test.py
+++ b/api/v3/test/monorail_servicer_test.py
@@ -1,7 +1,6 @@
-# Copyright 2018 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
+# Copyright 2018 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Tests for MonorailServicer."""
 from __future__ import print_function
@@ -75,11 +74,11 @@
   pass
 
 
-class TestableServicer(monorail_servicer.MonorailServicer):
+class _TestableServicer(monorail_servicer.MonorailServicer):
   """Fake servicer class."""
 
   def __init__(self, services):
-    super(TestableServicer, self).__init__(services)
+    super(_TestableServicer, self).__init__(services)
     self.was_called = False
     self.seen_mc = None
     self.seen_request = None
@@ -121,7 +120,7 @@
     self.non_member = self.services.user.TestAddUser(
         'nonmember@example.com', 222)
     self.test_user = self.services.user.TestAddUser('test@example.com', 420)
-    self.svcr = TestableServicer(self.services)
+    self.svcr = _TestableServicer(self.services)
     self.nonmember_token = xsrf.GenerateToken(222, xsrf.XHR_SERVLET_PATH)
     self.request = UpdateSomethingRequest(exc_class=None)
     self.prpc_context = context.ServicerContext()
@@ -336,8 +335,8 @@
         'email': 'bigbadwolf@gserviceaccount.com',
     }
 
-    with self.assertRaisesRegexp(
-        permissions.PermissionException, r'Account .+ is not allowlisted'):
+    with self.assertRaisesRegex(permissions.PermissionException,
+                                r'Account .+ is not allowlisted'):
       self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
 
   @mock.patch('google.oauth2.id_token.verify_oauth2_token')
@@ -355,8 +354,8 @@
         'email': allowlisted_service_account_email.email,
     }
 
-    with self.assertRaisesRegexp(
-        permissions.PermissionException, r'Invalid token audience: .+'):
+    with self.assertRaisesRegex(permissions.PermissionException,
+                                r'Invalid token audience: .+'):
       self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
 
   @mock.patch('google.oauth2.id_token.verify_oauth2_token')
@@ -373,8 +372,8 @@
         'email': 'some-other-site-user@test.com',
     }
 
-    with self.assertRaisesRegexp(
-        permissions.PermissionException, r'Client .+ is not allowlisted'):
+    with self.assertRaisesRegex(permissions.PermissionException,
+                                r'Client .+ is not allowlisted'):
       self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
 
     # Assert some-other-site-user was not auto-created.
diff --git a/api/v3/test/paginator_test.py b/api/v3/test/paginator_test.py
index ca0b713..30a7b82 100644
--- a/api/v3/test/paginator_test.py
+++ b/api/v3/test/paginator_test.py
@@ -1,6 +1,6 @@
-# Copyright 2020 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.
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for the Paginator class."""
 
 from __future__ import print_function
@@ -75,4 +75,4 @@
 
   def testCoercePageSize_Zero(self):
     """Handles zero equivalently to None."""
-    self.assertEqual(5, paginator.CoercePageSize(0, 5))
\ No newline at end of file
+    self.assertEqual(5, paginator.CoercePageSize(0, 5))
diff --git a/api/v3/test/permissions_converter_test.py b/api/v3/test/permissions_converter_test.py
index e679eb6..5e05b4c 100644
--- a/api/v3/test/permissions_converter_test.py
+++ b/api/v3/test/permissions_converter_test.py
@@ -1,6 +1,6 @@
-# Copyright 2020 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.
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for converting permission strings to API permissions enums."""
 
 from __future__ import print_function
diff --git a/api/v3/test/permissions_servicer_test.py b/api/v3/test/permissions_servicer_test.py
index 076bd40..ae6eb94 100644
--- a/api/v3/test/permissions_servicer_test.py
+++ b/api/v3/test/permissions_servicer_test.py
@@ -1,6 +1,6 @@
-# Copyright 2020 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.
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for the permissions servicer."""
 from __future__ import print_function
 from __future__ import division
diff --git a/api/v3/test/projects_servicer_test.py b/api/v3/test/projects_servicer_test.py
index 2a9fbba..364566a 100644
--- a/api/v3/test/projects_servicer_test.py
+++ b/api/v3/test/projects_servicer_test.py
@@ -1,7 +1,6 @@
-# Copyright 2020 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
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for the hotlists servicer."""
 from __future__ import print_function
 from __future__ import division
diff --git a/api/v3/test/users_servicer_test.py b/api/v3/test/users_servicer_test.py
index 8982ec9..efc03bf 100644
--- a/api/v3/test/users_servicer_test.py
+++ b/api/v3/test/users_servicer_test.py
@@ -1,6 +1,6 @@
-# Copyright 2020 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.
+# Copyright 2020 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Tests for the users servicer."""
 from __future__ import print_function
 from __future__ import division