Merge branch 'main' into avm99963-monorail

Merged commit 34d8229ae2b51fb1a15bd208e6fe6185c94f6266

GitOrigin-RevId: 7ee0917f93a577e475f8e09526dd144d245593f4
diff --git a/framework/test/alerts_test.py b/framework/test/alerts_test.py
index 0c398c1..122a0fe 100644
--- a/framework/test/alerts_test.py
+++ b/framework/test/alerts_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 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 alert display helpers."""
 from __future__ import print_function
diff --git a/framework/test/authdata_test.py b/framework/test/authdata_test.py
index a0e7313..22a7552 100644
--- a/framework/test/authdata_test.py
+++ b/framework/test/authdata_test.py
@@ -1,7 +1,6 @@
-# Copyright 2017 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 2017 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for the authdata module."""
 from __future__ import print_function
diff --git a/framework/test/banned_test.py b/framework/test/banned_test.py
index 0331cdd..f07b6e8 100644
--- a/framework/test/banned_test.py
+++ b/framework/test/banned_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unittests for monorail.framework.banned."""
 from __future__ import print_function
@@ -10,8 +9,6 @@
 
 import unittest
 
-import webapp2
-
 from framework import banned
 from framework import monorailrequest
 from services import service_manager
diff --git a/framework/test/cloud_tasks_helpers_test.py b/framework/test/cloud_tasks_helpers_test.py
index 09ad2cd..bbc52b0 100644
--- a/framework/test/cloud_tasks_helpers_test.py
+++ b/framework/test/cloud_tasks_helpers_test.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# 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 cloud tasks helper module."""
@@ -34,8 +34,8 @@
     get_client_mock().queue_path.assert_called_with(
         settings.app_id, settings.CLOUD_TASKS_REGION, queue)
     get_client_mock().create_task.assert_called_once()
-    ((_parent, called_task), _kwargs) = get_client_mock().create_task.call_args
-    self.assertEqual(called_task, task)
+    _, kwargs = get_client_mock().create_task.call_args
+    self.assertEqual(kwargs['task'], task)
 
   @mock.patch('framework.cloud_tasks_helpers._get_client')
   def test_create_task_raises(self, get_client_mock):
@@ -53,7 +53,7 @@
 
     cloud_tasks_helpers.create_task(task)
 
-    (_args, kwargs) = get_client_mock().create_task.call_args
+    _, kwargs = get_client_mock().create_task.call_args
     self.assertEqual(kwargs.get('retry'), cloud_tasks_helpers._DEFAULT_RETRY)
 
   def test_generate_simple_task(self):
@@ -66,7 +66,7 @@
         'app_engine_http_request':
             {
                 'relative_uri': '/alphabet/letters',
-                'body': 'a=a&b=b',
+                'body': b'a=a&b=b',
                 'headers': {
                     'Content-type': 'application/x-www-form-urlencoded'
                 }
@@ -79,7 +79,7 @@
         'app_engine_http_request':
             {
                 'relative_uri': '/alphabet/letters',
-                'body': '',
+                'body': b'',
                 'headers': {
                     'Content-type': 'application/x-www-form-urlencoded'
                 }
diff --git a/framework/test/csv_helpers_test.py b/framework/test/csv_helpers_test.py
index 19c89c5..a8726a7 100644
--- a/framework/test/csv_helpers_test.py
+++ b/framework/test/csv_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for csv_helpers functions."""
 from __future__ import print_function
diff --git a/framework/test/deleteusers_test.py b/framework/test/deleteusers_test.py
index 87ed5bc..13c6922 100644
--- a/framework/test/deleteusers_test.py
+++ b/framework/test/deleteusers_test.py
@@ -1,7 +1,6 @@
-# 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
+# Copyright 2019 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for deleteusers classes."""
 from __future__ import print_function
@@ -10,6 +9,7 @@
 
 import logging
 import mock
+import six
 import unittest
 from six.moves import urllib
 
@@ -52,24 +52,24 @@
     self.assertEqual(get_client_mock().create_task.call_count, 3)
 
     expected_task = self.generate_simple_task(
-        urls.SEND_WIPEOUT_USER_LISTS_TASK + '.do', 'limit=2&offset=0')
+        urls.SEND_WIPEOUT_USER_LISTS_TASK + '.do', b'limit=2&offset=0')
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
     expected_task = self.generate_simple_task(
-        urls.SEND_WIPEOUT_USER_LISTS_TASK + '.do', 'limit=2&offset=2')
+        urls.SEND_WIPEOUT_USER_LISTS_TASK + '.do', b'limit=2&offset=2')
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
     expected_task = self.generate_simple_task(
-        urls.DELETE_WIPEOUT_USERS_TASK + '.do', '')
+        urls.DELETE_WIPEOUT_USERS_TASK + '.do', b'')
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
   @mock.patch('framework.cloud_tasks_helpers._get_client')
@@ -79,10 +79,10 @@
 
     expected_task = self.generate_simple_task(
         urls.SEND_WIPEOUT_USER_LISTS_TASK + '.do',
-        'limit={}&offset=0'.format(deleteusers.MAX_BATCH_SIZE))
+        b'limit=%d&offset=0' % deleteusers.MAX_BATCH_SIZE)
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
   @mock.patch('framework.cloud_tasks_helpers._get_client')
@@ -118,13 +118,13 @@
   def testHandleRequest_NoLimit(self):
     mr = testing_helpers.MakeMonorailRequest()
     self.services.user.users_by_id = {}
-    with self.assertRaisesRegexp(AssertionError, 'Missing param limit'):
+    with self.assertRaisesRegex(AssertionError, 'Missing param limit'):
       self.task.HandleRequest(mr)
 
   def testHandleRequest_NoOffset(self):
     mr = testing_helpers.MakeMonorailRequest(path='url/url?limit=3')
     self.services.user.users_by_id = {}
-    with self.assertRaisesRegexp(AssertionError, 'Missing param offset'):
+    with self.assertRaisesRegex(AssertionError, 'Missing param offset'):
       self.task.HandleRequest(mr)
 
   def testHandleRequest_ZeroOffset(self):
@@ -152,7 +152,7 @@
         'app_engine_http_request':
             {
                 'relative_uri': url,
-                'body': body,
+                'body': six.ensure_binary(body),
                 'headers': {
                     'Content-type': 'application/x-www-form-urlencoded'
                 }
@@ -178,8 +178,8 @@
         urls.DELETE_USERS_TASK + '.do', query)
 
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
     query = urllib.parse.urlencode({'emails': 'user4@gmail.com'})
@@ -187,8 +187,8 @@
         urls.DELETE_USERS_TASK + '.do', query)
 
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
 
   @mock.patch('framework.cloud_tasks_helpers._get_client')
@@ -206,6 +206,6 @@
         urls.DELETE_USERS_TASK + '.do', query)
 
     get_client_mock().create_task.assert_any_call(
-        get_client_mock().queue_path(),
-        expected_task,
+        parent=get_client_mock().queue_path(),
+        task=expected_task,
         retry=cloud_tasks_helpers._DEFAULT_RETRY)
diff --git a/framework/test/emailfmt_test.py b/framework/test/emailfmt_test.py
index dd7cca3..5445189 100644
--- a/framework/test/emailfmt_test.py
+++ b/framework/test/emailfmt_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 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 monorail.framework.emailfmt."""
 from __future__ import print_function
@@ -9,6 +8,7 @@
 from __future__ import absolute_import
 
 import mock
+import six
 import unittest
 
 from google.appengine.ext import testbed
@@ -16,7 +16,7 @@
 import settings
 from framework import emailfmt
 from framework import framework_views
-from proto import project_pb2
+from mrproto import project_pb2
 from testing import testing_helpers
 
 from google.appengine.api import apiproxy_stub_map
@@ -79,12 +79,11 @@
         testing_helpers.HEADER_LINES + [references_header], 'awesome!')
     (from_addr, to_addrs, cc_addrs, references, incident_id, subject,
      body) = emailfmt.ParseEmailMessage(msg)
-    self.assertItemsEqual(
-        ['<5678@bar.com>',
-         '<0=969704940193871313=13442892928193434663='
-         'proj@monorail.example.com>',
-         '<1234@foo.com>'],
-        references)
+    six.assertCountEqual(
+        self, [
+            '<5678@bar.com>', '<0=969704940193871313=13442892928193434663='
+            'proj@monorail.example.com>', '<1234@foo.com>'
+        ], references)
 
   def testParseEmailMessage_Bulk(self):
     for precedence in ['Bulk', 'Junk']:
diff --git a/framework/test/exceptions_test.py b/framework/test/exceptions_test.py
index 8fe2295..5013267 100644
--- a/framework/test/exceptions_test.py
+++ b/framework/test/exceptions_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.
 """Unittest for the exceptions module."""
 
 from __future__ import print_function
@@ -21,7 +21,7 @@
 
     err_aggregator.AddErrorMessage('The chickens are missing.')
     err_aggregator.AddErrorMessage('The foxes are free.')
-    with self.assertRaisesRegexp(
+    with self.assertRaisesRegex(
         exceptions.InputException,
         'The chickens are missing.\nThe foxes are free.'):
       err_aggregator.RaiseIfErrors()
@@ -34,16 +34,16 @@
   def testWithinContext_ExceptionPassedIn(self):
     """We do not suppress exceptions raised within wrapped code."""
 
-    with self.assertRaisesRegexp(exceptions.InputException,
-                                 'We should raise this'):
+    with self.assertRaisesRegex(exceptions.InputException,
+                                'We should raise this'):
       with exceptions.ErrorAggregator(exceptions.InputException) as errors:
         errors.AddErrorMessage('We should ignore this error.')
         raise exceptions.InputException('We should raise this')
 
   def testWithinContext_NoExceptionPassedIn(self):
     """We raise an exception for any errors if no exceptions are passed in."""
-    with self.assertRaisesRegexp(exceptions.InputException,
-                                 'We can raise this now.'):
+    with self.assertRaisesRegex(exceptions.InputException,
+                                'We can raise this now.'):
       with exceptions.ErrorAggregator(exceptions.InputException) as errors:
         errors.AddErrorMessage('We can raise this now.')
         return True
diff --git a/framework/test/filecontent_test.py b/framework/test/filecontent_test.py
index 4843b47..f2c8d9e 100644
--- a/framework/test/filecontent_test.py
+++ b/framework/test/filecontent_test.py
@@ -1,13 +1,13 @@
-# Copyright 2016 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 2016 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 filecontent module."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
+import six
 import unittest
 
 from framework import filecontent
@@ -84,10 +84,10 @@
     return is_binary
 
   def testFileIsBinaryEmpty(self):
-    self.assertFalse(self.IsBinary(''))
+    self.assertFalse(self.IsBinary(b''))
 
   def testFileIsBinaryShortText(self):
-    self.assertFalse(self.IsBinary('This is some plain text.'))
+    self.assertFalse(self.IsBinary(b'This is some plain text.'))
 
   def testLineLengthDetection(self):
     unicode_str = (
@@ -101,29 +101,35 @@
     lines.append(long_line)
 
     # High lower ratio - text
-    self.assertFalse(self.IsBinary('\n'.join(lines)))
+    self.assertFalse(self.IsBinary(b'\n'.join(lines)))
 
     lines.extend([long_line] * 99)
 
     # 50/50 lower/upper ratio - binary
-    self.assertTrue(self.IsBinary('\n'.join(lines)))
+    self.assertTrue(self.IsBinary(b'\n'.join(lines)))
 
     # Single line too long - binary
     lines = [short_line] * 100
     lines.append(short_line * 100)  # Very long line
-    self.assertTrue(self.IsBinary('\n'.join(lines)))
+    self.assertTrue(self.IsBinary(b'\n'.join(lines)))
 
   def testFileIsBinaryLongText(self):
-    self.assertFalse(self.IsBinary('This is plain text. \n' * 100))
+    self.assertFalse(self.IsBinary(b'This is plain text. \n' * 100))
     # long utf-8 lines are OK
-    self.assertFalse(self.IsBinary('This one long line. ' * 100))
+    self.assertFalse(self.IsBinary(b'This one long line. ' * 100))
 
   def testFileIsBinaryLongBinary(self):
-    bin_string = ''.join([chr(c) for c in range(122, 252)])
+    if six.PY2:
+      bin_string = ''.join([chr(c) for c in range(122, 252)])
+    else:
+      bin_string = bytes(range(122, 252))
     self.assertTrue(self.IsBinary(bin_string * 100))
 
   def testFileIsTextByPath(self):
-    bin_string = ''.join([chr(c) for c in range(122, 252)] * 100)
+    if six.PY2:
+      bin_string = ''.join([chr(c) for c in range(122, 252)] * 100)
+    else:
+      bin_string = bytes(range(122, 252)) * 100
     unicode_str = (
         u'Some non-ascii chars - '
         u'\xa2\xfa\xb6\xe7\xfc\xea\xd0\xf4\xe6\xf0\xce\xf6\xbe')
@@ -143,7 +149,7 @@
             filecontent.DecodeFileContents(contents, path=path)[1])
 
   def testFileIsBinaryByCommonExtensions(self):
-    contents = 'this is not examined'
+    contents = b'this is not examined'
     self.assertTrue(filecontent.DecodeFileContents(
         contents, path='junk.zip')[1])
     self.assertTrue(filecontent.DecodeFileContents(
@@ -175,13 +181,13 @@
         contents, path='/wiki/PageName.wiki')[1])
 
   def testUnreasonablyLongFile(self):
-    contents = '\n' * (filecontent.SOURCE_FILE_MAX_LINES + 2)
+    contents = b'\n' * (filecontent.SOURCE_FILE_MAX_LINES + 2)
     _contents, is_binary, is_long = filecontent.DecodeFileContents(
         contents)
     self.assertFalse(is_binary)
     self.assertTrue(is_long)
 
-    contents = '\n' * 100
+    contents = b'\n' * 100
     _contents, is_binary, is_long = filecontent.DecodeFileContents(
         contents)
     self.assertFalse(is_binary)
diff --git a/framework/test/flask_servlet_test.py b/framework/test/flask_servlet_test.py
deleted file mode 100644
index 4c47209..0000000
--- a/framework/test/flask_servlet_test.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# Copyright 2016 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
-"""Unit tests for servlet base class module."""
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
-
-import time
-import mock
-import unittest
-import logging
-
-from google.appengine.ext import testbed
-
-from framework import flaskservlet, framework_constants, servlet_helpers
-from framework import xsrf
-from proto import project_pb2
-from proto import tracker_pb2
-from proto import user_pb2
-from services import service_manager
-from testing import fake
-from testing import testing_helpers
-
-
-class TestableFlaskServlet(flaskservlet.FlaskServlet):
-  """A tiny concrete subclass of abstract class Servlet."""
-
-  def __init__(self, services=None, do_post_redirect=True):
-    super(TestableFlaskServlet, self).__init__(services=services)
-    self.do_post_redirect = do_post_redirect
-    self.seen_post_data = None
-
-
-class FlaskServletTest(unittest.TestCase):
-
-  def setUp(self):
-    services = service_manager.Services(
-        project=fake.ProjectService(),
-        project_star=fake.ProjectStarService(),
-        user=fake.UserService(),
-        usergroup=fake.UserGroupService())
-    services.user.TestAddUser('user@example.com', 111)
-    self.page_class = flaskservlet.FlaskServlet(services=services)
-    self.testbed = testbed.Testbed()
-    self.testbed.activate()
-    self.testbed.init_user_stub()
-    self.testbed.init_memcache_stub()
-    self.testbed.init_datastore_v3_stub()
-
-  def tearDown(self):
-    self.testbed.deactivate()
-
-  def testDefaultValues(self):
-    self.assertEqual(None, self.page_class._MAIN_TAB_MODE)
-    self.assertTrue(self.page_class._TEMPLATE_PATH.endswith('/templates/'))
-    self.assertEqual(None, self.page_class._PAGE_TEMPLATE)
-
-  @mock.patch('flask.abort')
-  def testCheckForMovedProject_NoRedirect(self, mock_abort):
-    project = fake.Project(
-        project_name='proj', state=project_pb2.ProjectState.LIVE)
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-    mock_abort.assert_not_called()
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/source/browse/p/adminAdvanced', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-    mock_abort.assert_not_called()
-
-  @mock.patch('flask.redirect')
-  def testCheckForMovedProject_Redirect(self, mock_redirect):
-    project = fake.Project(project_name='proj', moved_to='http://example.com')
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    self.page_class.request_path = '/p/test'
-    self.page_class._CheckForMovedProject(mr, request)
-    mock_redirect.assert_called_once_with(
-        'http://127.0.0.1/hosting/moved?project=proj', code=302)
-
-  def testGatherBaseData(self):
-    project = self.page_class.services.project.TestAddProject(
-        'testproj', state=project_pb2.ProjectState.LIVE)
-    project.cached_content_timestamp = 12345
-
-    (_request, mr) = testing_helpers.GetRequestObjects(
-        path='/p/testproj/feeds', project=project)
-    nonce = '1a2b3c4d5e6f7g'
-
-    base_data = self.page_class.GatherBaseData(mr, nonce)
-
-    self.assertEqual(base_data['nonce'], nonce)
-    self.assertEqual(base_data['projectname'], 'testproj')
-    self.assertEqual(base_data['project'].cached_content_timestamp, 12345)
-    self.assertEqual(base_data['project_alert'], None)
-
-    self.assertTrue(base_data['currentPageURL'].endswith('/p/testproj/feeds'))
-    self.assertTrue(
-        base_data['currentPageURLEncoded'].endswith('%2Fp%2Ftestproj%2Ffeeds'))
-
-  def testGatherHelpData_Normal(self):
-    project = fake.Project(project_name='proj')
-    _request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    help_data = self.page_class.GatherHelpData(mr, {})
-    self.assertEqual(None, help_data['cue'])
-    self.assertEqual(None, help_data['account_cue'])
diff --git a/framework/test/framework_bizobj_test.py b/framework/test/framework_bizobj_test.py
index 131ebb5..5ea05a6 100644
--- a/framework/test/framework_bizobj_test.py
+++ b/framework/test/framework_bizobj_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 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 monorail.framework.framework_bizobj."""
 from __future__ import print_function
@@ -15,9 +14,9 @@
 from framework import authdata
 from framework import framework_bizobj
 from framework import framework_constants
-from proto import project_pb2
-from proto import tracker_pb2
-from proto import user_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
 from services import service_manager
 from services import client_config_svc
 from testing import fake
diff --git a/framework/test/framework_helpers_test.py b/framework/test/framework_helpers_test.py
index fb8810b..fe0a225 100644
--- a/framework/test/framework_helpers_test.py
+++ b/framework/test/framework_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for the framework_helpers module."""
 from __future__ import print_function
@@ -15,14 +14,15 @@
   from mox3 import mox
 except ImportError:
   import mox
+import six
 import time
 
 from businesslogic import work_env
 from framework import framework_helpers
 from framework import framework_views
-from proto import features_pb2
-from proto import project_pb2
-from proto import user_pb2
+from mrproto import features_pb2
+from mrproto import project_pb2
+from mrproto import user_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
@@ -381,8 +381,8 @@
     """Run one call to the target method and check expected results."""
     actual_added, actual_removed = framework_helpers.ComputeListDeltas(
         old, new)
-    self.assertItemsEqual(added, actual_added)
-    self.assertItemsEqual(removed, actual_removed)
+    six.assertCountEqual(self, added, actual_added)
+    six.assertCountEqual(self, removed, actual_removed)
 
   def testEmptyLists(self):
     self.DoOne(old=[], new=[], added=[], removed=[])
@@ -430,7 +430,7 @@
         'preview_on_hover',
         'settings_user_prefs',
         ]
-    self.assertItemsEqual(expected_keys, list(page_data.keys()))
+    six.assertCountEqual(self, expected_keys, list(page_data.keys()))
 
     self.assertEqual('profile/url', page_data['profile_url_fragment'])
     self.assertTrue(page_data['settings_user_prefs'].public_issue_notice)
diff --git a/framework/test/framework_views_test.py b/framework/test/framework_views_test.py
index 57f9fd1..edb05cd 100644
--- a/framework/test/framework_views_test.py
+++ b/framework/test/framework_views_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for framework_views classes."""
 from __future__ import print_function
@@ -14,9 +13,9 @@
 from framework import framework_constants
 from framework import framework_views
 from framework import monorailrequest
-from proto import project_pb2
-from proto import tracker_pb2
-from proto import user_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
 import settings
 from services import service_manager
 from testing import fake
diff --git a/framework/test/gcs_helpers_test.py b/framework/test/gcs_helpers_test.py
index a7c01d0..5eab073 100644
--- a/framework/test/gcs_helpers_test.py
+++ b/framework/test/gcs_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for the framework_helpers module."""
 from __future__ import print_function
@@ -27,8 +26,7 @@
     self.testbed.init_memcache_stub()
     self.testbed.init_app_identity_stub()
 
-    self.test_storage_client = mock.create_autospec(
-        storage.Client, instance=True)
+    self.test_storage_client = mock.MagicMock()
     mock.patch.object(
         storage, 'Client', return_value=self.test_storage_client).start()
 
diff --git a/framework/test/grid_view_helpers_test.py b/framework/test/grid_view_helpers_test.py
index df3ecc6..d54353a 100644
--- a/framework/test/grid_view_helpers_test.py
+++ b/framework/test/grid_view_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for grid_view_helpers classes and functions."""
 from __future__ import print_function
@@ -13,7 +12,7 @@
 from framework import framework_constants
 from framework import framework_views
 from framework import grid_view_helpers
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from testing import fake
 from tracker import tracker_bizobj
 
diff --git a/framework/test/jsonfeed_test.py b/framework/test/jsonfeed_test.py
index 4ca83fa..7c9a6b6 100644
--- a/framework/test/jsonfeed_test.py
+++ b/framework/test/jsonfeed_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for jsonfeed module."""
 from __future__ import print_function
@@ -9,13 +8,12 @@
 from __future__ import absolute_import
 
 from six.moves import http_client
-import logging
 import unittest
 
+import flask
 from google.appengine.api import app_identity
 
 from framework import jsonfeed
-from framework import servlet
 from framework import xsrf
 from services import service_manager
 from testing import testing_helpers
@@ -28,7 +26,7 @@
 
   def testGet(self):
     """Tests handling of GET requests."""
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
 
     # all expected args are present + a bonus arg that should be ignored
     feed.mr = testing_helpers.MakeMonorailRequest(
@@ -41,7 +39,7 @@
 
   def testPost(self):
     """Tests handling of POST requests."""
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.mr = testing_helpers.MakeMonorailRequest(
         path='/foo/bar/wee?sna=foo', method='POST',
         params={'a': '123', 'z': 'zebra'})
@@ -52,7 +50,7 @@
     self.assertEqual(1, len(feed.json_data))
 
   def testSecurityTokenChecked_BadToken(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.mr = testing_helpers.MakeMonorailRequest(
         user_info={'user_id': 555})
     # Note that feed.mr has no token set.
@@ -64,7 +62,7 @@
     self.assertRaises(xsrf.TokenIncorrect, feed.post)
 
   def testSecurityTokenChecked_HandlerDoesNotNeedToken(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.mr = testing_helpers.MakeMonorailRequest(
         user_info={'user_id': 555})
     # Note that feed.mr has no token set.
@@ -73,21 +71,21 @@
     feed.post()
 
   def testSecurityTokenChecked_AnonUserDoesNotNeedToken(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.mr = testing_helpers.MakeMonorailRequest()
     # Note that feed.mr has no token set, but also no auth.user_id.
     feed.get()
     feed.post()
 
   def testSameAppOnly_ExternallyAccessible(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.mr = testing_helpers.MakeMonorailRequest()
     # Note that request has no X-Appengine-Inbound-Appid set.
     feed.get()
     feed.post()
 
   def testSameAppOnly_InternalOnlyCalledFromSameApp(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.CHECK_SAME_APP = True
     feed.mr = testing_helpers.MakeMonorailRequest()
     app_id = app_identity.get_application_id()
@@ -96,36 +94,36 @@
     feed.post()
 
   def testSameAppOnly_InternalOnlyCalledExternally(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.CHECK_SAME_APP = True
     feed.mr = testing_helpers.MakeMonorailRequest()
     # Note that request has no X-Appengine-Inbound-Appid set.
+    feed.response = flask.Response()
     self.assertIsNone(feed.get())
     self.assertFalse(feed.handle_request_called)
-    self.assertEqual(http_client.FORBIDDEN, feed.response.status)
+    self.assertEqual(http_client.FORBIDDEN, feed.response.status_code)
     self.assertIsNone(feed.post())
     self.assertFalse(feed.handle_request_called)
-    self.assertEqual(http_client.FORBIDDEN, feed.response.status)
+    self.assertEqual(http_client.FORBIDDEN, feed.response.status_code)
 
   def testSameAppOnly_InternalOnlyCalledFromWrongApp(self):
-    feed = TestableJsonFeed()
+    feed = _TestableJsonFeed()
     feed.CHECK_SAME_APP = True
     feed.mr = testing_helpers.MakeMonorailRequest()
     feed.mr.request.headers['X-Appengine-Inbound-Appid'] = 'wrong'
+    feed.response = flask.Response()
     self.assertIsNone(feed.get())
     self.assertFalse(feed.handle_request_called)
-    self.assertEqual(http_client.FORBIDDEN, feed.response.status)
+    self.assertEqual(http_client.FORBIDDEN, feed.response.status_code)
     self.assertIsNone(feed.post())
     self.assertFalse(feed.handle_request_called)
-    self.assertEqual(http_client.FORBIDDEN, feed.response.status)
+    self.assertEqual(http_client.FORBIDDEN, feed.response.status_code)
 
 
-class TestableJsonFeed(jsonfeed.JsonFeed):
+class _TestableJsonFeed(jsonfeed.JsonFeed):
 
-  def __init__(self, request=None):
-    response = testing_helpers.Blank()
-    super(TestableJsonFeed, self).__init__(
-        request or 'req', response, services=service_manager.Services())
+  def __init__(self):
+    super(_TestableJsonFeed, self).__init__(services=service_manager.Services())
 
     self.response_data = None
     self.handle_request_called = False
diff --git a/framework/test/monitoring_test.py b/framework/test/monitoring_test.py
index edbd15d..0592336 100644
--- a/framework/test/monitoring_test.py
+++ b/framework/test/monitoring_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.
 
 """Unit tests for the monitoring module."""
 
diff --git a/framework/test/monorailcontext_test.py b/framework/test/monorailcontext_test.py
index 2071c9e..04ef491 100644
--- a/framework/test/monorailcontext_test.py
+++ b/framework/test/monorailcontext_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 MonorailContext."""
 from __future__ import print_function
diff --git a/framework/test/monorailrequest_test.py b/framework/test/monorailrequest_test.py
index ef52f1e..0c81694 100644
--- a/framework/test/monorailrequest_test.py
+++ b/framework/test/monorailrequest_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for the monorailrequest module."""
 from __future__ import print_function
@@ -18,17 +17,16 @@
 except ImportError:
   import mox
 import six
+import werkzeug
 
 from google.appengine.api import oauth
 from google.appengine.api import users
 
-import webapp2
-
 from framework import exceptions
 from framework import monorailrequest
 from framework import permissions
-from proto import project_pb2
-from proto import tracker_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
@@ -170,14 +168,15 @@
 
   def testGetIntListParam_NoParam(self):
     mr = monorailrequest.MonorailRequest(self.services)
-    mr.ParseRequest(webapp2.Request.blank('servlet'), self.services)
+    mr.ParseRequest(testing_helpers.RequestStub('servlet'), self.services)
     self.assertEqual(mr.GetIntListParam('ids'), None)
     self.assertEqual(mr.GetIntListParam('ids', default_value=['test']),
                       ['test'])
 
   def testGetIntListParam_OneValue(self):
     mr = monorailrequest.MonorailRequest(self.services)
-    mr.ParseRequest(webapp2.Request.blank('servlet?ids=11'), self.services)
+    request = testing_helpers.RequestStub('servlet?ids=11')
+    mr.ParseRequest(request, self.services)
     self.assertEqual(mr.GetIntListParam('ids'), [11])
     self.assertEqual(mr.GetIntListParam('ids', default_value=['test']),
                       [11])
@@ -185,7 +184,7 @@
   def testGetIntListParam_MultiValue(self):
     mr = monorailrequest.MonorailRequest(self.services)
     mr.ParseRequest(
-        webapp2.Request.blank('servlet?ids=21,22,23'), self.services)
+        testing_helpers.RequestStub('servlet?ids=21,22,23'), self.services)
     self.assertEqual(mr.GetIntListParam('ids'), [21, 22, 23])
     self.assertEqual(mr.GetIntListParam('ids', default_value=['test']),
                       [21, 22, 23])
@@ -194,18 +193,18 @@
     mr = monorailrequest.MonorailRequest(self.services)
     with self.assertRaises(exceptions.InputException):
       mr.ParseRequest(
-          webapp2.Request.blank('servlet?ids=not_an_int'), self.services)
+          testing_helpers.RequestStub('servlet?ids=not_an_int'), self.services)
 
   def testGetIntListParam_Malformed(self):
     mr = monorailrequest.MonorailRequest(self.services)
     with self.assertRaises(exceptions.InputException):
       mr.ParseRequest(
-          webapp2.Request.blank('servlet?ids=31,32,,'), self.services)
+          testing_helpers.RequestStub('servlet?ids=31,32,,'), self.services)
 
   def testDefaultValuesNoUrl(self):
     """If request has no param, default param values should be used."""
     mr = monorailrequest.MonorailRequest(self.services)
-    mr.ParseRequest(webapp2.Request.blank('servlet'), self.services)
+    mr.ParseRequest(testing_helpers.RequestStub('servlet'), self.services)
     self.assertEqual(mr.GetParam('r', 3), 3)
     self.assertEqual(mr.GetIntParam('r', 3), 3)
     self.assertEqual(mr.GetPositiveIntParam('r', 3), 3)
@@ -213,7 +212,7 @@
 
   def _MRWithMockRequest(
       self, path, headers=None, *mr_args, **mr_kwargs):
-    request = webapp2.Request.blank(path, headers=headers)
+    request = testing_helpers.RequestStub(path, headers=headers)
     mr = monorailrequest.MonorailRequest(self.services, *mr_args, **mr_kwargs)
     mr.ParseRequest(request, self.services)
     return mr
@@ -282,7 +281,7 @@
         mr.viewed_user_auth.user_pb)
 
   def testViewedUser_NoSuchEmail(self):
-    with self.assertRaises(webapp2.HTTPException) as cm:
+    with self.assertRaises(werkzeug.exceptions.HTTPException) as cm:
       self._MRWithMockRequest('/u/unknownuser@example.com/')
     self.assertEqual(404, cm.exception.code)
 
@@ -353,13 +352,13 @@
 
     # project colspec contains hotlist columns
     mr = testing_helpers.MakeMonorailRequest(
-        path='p/proj/issues/detail?id=123&colspec=Rank Adder Adder Owner')
+        path='/p/proj/issues/detail?id=123&colspec=Rank Adder Adder Owner')
     mr.ComputeColSpec(None)
     self.assertEqual(tracker_constants.DEFAULT_COL_SPEC, mr.col_spec)
 
     # hotlist columns are not deleted when page is a hotlist page
     mr = testing_helpers.MakeMonorailRequest(
-        path='u/jrobbins@example.com/hotlists/TestHotlist?colspec=Rank Adder',
+        path='/u/jrobbins@example.com/hotlists/TestHotlist?colspec=Rank Adder',
         hotlist=self.hotlist)
     mr.ComputeColSpec(None)
     self.assertEqual('Rank Adder', mr.col_spec)
@@ -513,10 +512,13 @@
     self.assertEqual(['Foo-Bar', 'Foo-Bar-Baz', 'Release-1.2', 'Hey', 'There'],
                      parse('Foo-Bar Foo-Bar-Baz Release-1.2 Hey!There'))
     self.assertEqual(
-        ['\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9'.decode('utf-8'),
-         '\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')],
-        parse('\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9 '
-              '\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')))
+        [
+            b'\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9'.decode('utf-8'),
+            b'\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')
+        ],
+        parse(
+            b'\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9 '
+            b'\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')))
 
   def testParseColSpec_Dedup(self):
     """An attacker cannot inflate response size by repeating a column."""
@@ -584,7 +586,7 @@
         email=lambda: email))
     self.mox.ReplayAll()
 
-    request = webapp2.Request.blank('/p/' + project_name)
+    request = testing_helpers.RequestStub('/p/' + project_name)
     mr = monorailrequest.MonorailRequest(self.services)
     with mr.profiler.Phase('parse user info'):
       mr.ParseRequest(request, self.services)
@@ -609,7 +611,7 @@
 
   def testExternalUserPermissions_Archived(self):
     mr = self.MakeRequestAsUser('archived', 'user@gmail.com')
-    self.CheckPermissions(mr.perms, False, False, False)
+    self.CheckPermissions(mr.perms, True, False, False)
 
   def testExternalUserPermissions_MembersOnly(self):
     mr = self.MakeRequestAsUser('members-only', 'user@gmail.com')
diff --git a/framework/test/paginate_test.py b/framework/test/paginate_test.py
index 99adaa9..44c8b59 100644
--- a/framework/test/paginate_test.py
+++ b/framework/test/paginate_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for pagination classes."""
 from __future__ import print_function
@@ -15,7 +14,7 @@
 from framework import exceptions
 from framework import paginate
 from testing import testing_helpers
-from proto import secrets_pb2
+from mrproto import secrets_pb2
 
 
 class PageTokenTest(unittest.TestCase):
diff --git a/framework/test/permissions_test.py b/framework/test/permissions_test.py
index cd67c6c..6280208 100644
--- a/framework/test/permissions_test.py
+++ b/framework/test/permissions_test.py
@@ -1,13 +1,13 @@
-# Copyright 2016 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 2016 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 permissions.py."""
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
+import six
 import time
 import unittest
 
@@ -21,12 +21,13 @@
 from framework import framework_constants
 from framework import framework_views
 from framework import permissions
-from proto import features_pb2
-from proto import project_pb2
-from proto import site_pb2
-from proto import tracker_pb2
-from proto import user_pb2
-from proto import usergroup_pb2
+from mrproto import features_pb2
+from mrproto import project_pb2
+from mrproto import site_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
+from mrproto import usergroup_pb2
+from services import service_manager
 from testing import fake
 from testing import testing_helpers
 from tracker import tracker_bizobj
@@ -160,10 +161,20 @@
     self.assertEqual('PermissionSet(a, b, cc)', self.perms.DebugString())
 
   def testRepr(self):
-    self.assertEqual('PermissionSet(frozenset([]))',
-                     permissions.PermissionSet([]).__repr__())
-    self.assertEqual('PermissionSet(frozenset([\'a\']))',
-                     permissions.PermissionSet(['A']).__repr__())
+    if six.PY2:
+      self.assertEqual(
+          'PermissionSet(frozenset([]))',
+          permissions.PermissionSet([]).__repr__())
+      self.assertEqual(
+          "PermissionSet(frozenset(['a']))",
+          permissions.PermissionSet(['A']).__repr__())
+    else:
+      self.assertEqual(
+          'PermissionSet(frozenset())',
+          permissions.PermissionSet([]).__repr__())
+      self.assertEqual(
+          "PermissionSet(frozenset({'a'}))",
+          permissions.PermissionSet(['A']).__repr__())
 
 
 class PermissionsTest(unittest.TestCase):
@@ -953,8 +964,9 @@
         None, None, self.live_project))
 
     self.archived_project.delete_time = self.NOW + 1
-    self.assertFalse(permissions.UserCanViewProject(
-        None, None, self.archived_project))
+    # Anonymous users may view an archived project.
+    self.assertTrue(
+        permissions.UserCanViewProject(None, None, self.archived_project))
     self.assertTrue(permissions.UserCanViewProject(
         self.owner, {self.OWNER_USER_ID}, self.archived_project))
     self.assertTrue(permissions.UserCanViewProject(
@@ -1230,6 +1242,14 @@
   ADMIN_PERMS = permissions.ADMIN_PERMISSIONSET
   PERMS = permissions.EMPTY_PERMISSIONSET
 
+  def setUp(self):
+    self.user_svc = fake.UserService()
+    self.services = service_manager.Services(user=self.user_svc)
+
+    self.user_svc.TestAddUser('allowlisteduser@test.com', 567)
+
+    settings.config_freeze_project_ids = {}
+
   def testUpdateIssuePermissions_Normal(self):
     perms = permissions.UpdateIssuePermissions(
         permissions.COMMITTER_ACTIVE_PERMISSIONSET, self.PROJECT,
@@ -1366,6 +1386,21 @@
         self.RESTRICTED_ISSUE3, {OWNER_ID})
     self.assertIn('editissue', perms.perm_names)
 
+  def testUpdateIssuePermissions_DefaultPermsDoNotIncludeEdit(self):
+    # Permissions can be checked from the homepage without a project context.
+    perms = permissions.UpdateIssuePermissions(
+        permissions.COMMITTER_ACTIVE_PERMISSIONSET, None,
+        self.RESTRICTED_ISSUE3, {OWNER_ID})
+    self.assertNotIn('editissue', perms.perm_names)
+
+  def testUpdateIssuePermissions_OwnerRespectsArchivedProject(self):
+    project = project_pb2.Project()
+    project.state = project_pb2.ProjectState.ARCHIVED
+    perms = permissions.UpdateIssuePermissions(
+        permissions.COMMITTER_ACTIVE_PERMISSIONSET, project,
+        self.RESTRICTED_ISSUE3, {OWNER_ID})
+    self.assertNotIn('editissue', perms.perm_names)
+
   def testUpdateIssuePermissions_CustomPermissionGrantsEditPermission(self):
     project = project_pb2.Project()
     project.committer_ids.append(999)
@@ -1650,6 +1685,39 @@
         {111, 222}, permissions.PermissionSet([]), self.PROJECT,
         [333]))
 
+  def testCanEditProjectConfig_Admin(self):
+    mr = testing_helpers.MakeMonorailRequest(
+        project=fake.Project(project_id=789))
+    mr.perms = permissions.ADMIN_PERMISSIONSET
+    self.assertTrue(permissions.CanEditProjectConfig(mr, self.services))
+
+  def testCanEditProjectConfig_NormalUser(self):
+    mr = testing_helpers.MakeMonorailRequest(
+        project=fake.Project(project_id=789))
+    mr.perms = permissions.CONTRIBUTOR_ACTIVE_PERMISSIONSET
+    self.assertFalse(permissions.CanEditProjectConfig(mr, self.services))
+
+  def testCanEditProjectConfig_Admin_FrozenConfig(self):
+    mr = testing_helpers.MakeMonorailRequest(
+        project=fake.Project(project_id=789))
+    mr.perms = permissions.ADMIN_PERMISSIONSET
+    mr.auth.effective_ids = {567}
+
+    settings.config_freeze_override_users = {}
+    settings.config_freeze_project_ids = {789}
+    self.assertFalse(permissions.CanEditProjectConfig(mr, self.services))
+
+  def testCanEditProjectConfig_Admin_FrozenConfig_AllowedUser(self):
+    mr = testing_helpers.MakeMonorailRequest(
+        project=fake.Project(project_id=789))
+    mr.perms = permissions.ADMIN_PERMISSIONSET
+    mr.auth.effective_ids = {567}
+
+    settings.config_freeze_override_users = {789: 'allowlisteduser@test.com'}
+    settings.config_freeze_project_ids = {789}
+
+    self.assertTrue(permissions.CanEditProjectConfig(mr, self.services))
+
   def testCanViewComponentDef_ComponentAdmin(self):
     cd = tracker_pb2.ComponentDef(admin_ids=[111])
     perms = permissions.PermissionSet([])
@@ -1667,36 +1735,51 @@
         {111}, permissions.PermissionSet([]),
         None, cd))
 
-  def testCanEditComponentDef_ComponentAdmin(self):
+  def testCanEditComponentDefLegacy_ComponentAdmin(self):
     cd = tracker_pb2.ComponentDef(admin_ids=[111], path='Whole')
     sub_cd = tracker_pb2.ComponentDef(admin_ids=[222], path='Whole>Part')
     config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
     config.component_defs.append(cd)
     config.component_defs.append(sub_cd)
     perms = permissions.PermissionSet([])
-    self.assertTrue(permissions.CanEditComponentDef(
-        {111}, perms, None, cd, config))
-    self.assertFalse(permissions.CanEditComponentDef(
-        {222}, perms, None, cd, config))
-    self.assertFalse(permissions.CanEditComponentDef(
-        {999}, perms, None, cd, config))
-    self.assertTrue(permissions.CanEditComponentDef(
-        {111}, perms, None, sub_cd, config))
-    self.assertTrue(permissions.CanEditComponentDef(
-        {222}, perms, None, sub_cd, config))
-    self.assertFalse(permissions.CanEditComponentDef(
-        {999}, perms, None, sub_cd, config))
+    self.assertTrue(
+        permissions.CanEditComponentDefLegacy({111}, perms, None, cd, config))
+    self.assertFalse(
+        permissions.CanEditComponentDefLegacy({222}, perms, None, cd, config))
+    self.assertFalse(
+        permissions.CanEditComponentDefLegacy({999}, perms, None, cd, config))
+    self.assertTrue(
+        permissions.CanEditComponentDefLegacy(
+            {111}, perms, None, sub_cd, config))
+    self.assertTrue(
+        permissions.CanEditComponentDefLegacy(
+            {222}, perms, None, sub_cd, config))
+    self.assertFalse(
+        permissions.CanEditComponentDefLegacy(
+            {999}, perms, None, sub_cd, config))
 
-  def testCanEditComponentDef_ProjectOwners(self):
+  def testCanEditComponentDefLegacy_ProjectOwners(self):
     cd = tracker_pb2.ComponentDef(path='Whole')
     config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
     config.component_defs.append(cd)
-    self.assertTrue(permissions.CanEditComponentDef(
-        {111}, permissions.PermissionSet([permissions.EDIT_PROJECT]),
-        None, cd, config))
-    self.assertFalse(permissions.CanEditComponentDef(
-        {111}, permissions.PermissionSet([]),
-        None, cd, config))
+    self.assertTrue(
+        permissions.CanEditComponentDefLegacy(
+            {111}, permissions.PermissionSet([permissions.EDIT_PROJECT]), None,
+            cd, config))
+    self.assertFalse(
+        permissions.CanEditComponentDefLegacy(
+            {111}, permissions.PermissionSet([]), None, cd, config))
+
+  def testCanEditComponentDefLegacy_FrozenProject(self):
+    cd = tracker_pb2.ComponentDef(path='Whole')
+    config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
+    config.component_defs.append(cd)
+    project = project_pb2.Project(project_id=789)
+    settings.config_freeze_project_ids = {789}
+    self.assertFalse(
+        permissions.CanEditComponentDefLegacy(
+            {111}, permissions.PermissionSet([permissions.EDIT_PROJECT]),
+            project, cd, config))
 
   def testCanViewFieldDef_FieldAdmin(self):
     fd = tracker_pb2.FieldDef(admin_ids=[111])
diff --git a/framework/test/profiler_test.py b/framework/test/profiler_test.py
index 3cc7e85..3694e93 100644
--- a/framework/test/profiler_test.py
+++ b/framework/test/profiler_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Test for monorail.framework.profiler."""
 from __future__ import print_function
@@ -73,7 +72,7 @@
         with prof.Phase('baz'):
           raise Exception('whoops')
     except Exception as e:
-      self.assertEqual(e.message, 'whoops')
+      self.assertEqual(str(e), 'whoops')
     finally:
       self.assertEqual(prof.current_phase.name, 'overall profile')
       self.assertEqual(prof.top_phase.subphases[0].subphases[1].name, 'baz')
diff --git a/framework/test/ratelimiter_test.py b/framework/test/ratelimiter_test.py
index 84230e8..bfc8de7 100644
--- a/framework/test/ratelimiter_test.py
+++ b/framework/test/ratelimiter_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for RateLimiter.
 """
diff --git a/framework/test/reap_test.py b/framework/test/reap_test.py
index 92d17fb..e01273f 100644
--- a/framework/test/reap_test.py
+++ b/framework/test/reap_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 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 reap module."""
 from __future__ import print_function
@@ -20,7 +19,7 @@
 
 from framework import reap
 from framework import sql
-from proto import project_pb2
+from mrproto import project_pb2
 from services import service_manager
 from services import template_svc
 from testing import fake
diff --git a/framework/test/registerpages_helpers_test.py b/framework/test/registerpages_helpers_test.py
deleted file mode 100644
index 61c489e..0000000
--- a/framework/test/registerpages_helpers_test.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2016 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
-
-"""Tests for URL handler registration helper functions."""
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
-
-import unittest
-
-import webapp2
-
-from framework import registerpages_helpers
-
-
-class SendRedirectInScopeTest(unittest.TestCase):
-
-  def testMakeRedirectInScope_Error(self):
-    self.assertRaises(
-        AssertionError,
-        registerpages_helpers.MakeRedirectInScope, 'no/initial/slash', 'p')
-    self.assertRaises(
-        AssertionError,
-        registerpages_helpers.MakeRedirectInScope, '', 'p')
-
-  def testMakeRedirectInScope_Normal(self):
-    factory = registerpages_helpers.MakeRedirectInScope('/', 'p')
-    # Non-dasher, normal case
-    request = webapp2.Request.blank(
-        path='/p/foo', headers={'Host': 'example.com'})
-    response = webapp2.Response()
-    redirector = factory(request, response)
-    redirector.get()
-    self.assertEqual(response.location, '//example.com/p/foo/')
-    self.assertEqual(response.status, '301 Moved Permanently')
-
-  def testMakeRedirectInScope_Temporary(self):
-    factory = registerpages_helpers.MakeRedirectInScope(
-        '/', 'p', permanent=False)
-    request = webapp2.Request.blank(
-        path='/p/foo', headers={'Host': 'example.com'})
-    response = webapp2.Response()
-    redirector = factory(request, response)
-    redirector.get()
-    self.assertEqual(response.location, '//example.com/p/foo/')
-    self.assertEqual(response.status, '302 Moved Temporarily')
-
-  def testMakeRedirectInScope_KeepQueryString(self):
-    factory = registerpages_helpers.MakeRedirectInScope(
-        '/', 'p', keep_qs=True)
-    request = webapp2.Request.blank(
-        path='/p/foo?q=1', headers={'Host': 'example.com'})
-    response = webapp2.Response()
-    redirector = factory(request, response)
-    redirector.get()
-    self.assertEqual(response.location, '//example.com/p/foo/?q=1')
-    self.assertEqual(response.status, '302 Moved Temporarily')
diff --git a/framework/test/servlet_helpers_test.py b/framework/test/servlet_helpers_test.py
index 870de40..4700e12 100644
--- a/framework/test/servlet_helpers_test.py
+++ b/framework/test/servlet_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for servlet base class helper functions."""
 from __future__ import print_function
@@ -12,12 +11,12 @@
 import settings
 
 from google.appengine.ext import testbed
-
+from six.moves import urllib
 
 from framework import permissions
 from framework import servlet_helpers
-from proto import project_pb2
-from proto import tracker_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
 from testing import testing_helpers
 
 
@@ -217,30 +216,21 @@
   def tearDown(self):
     self.testbed.deactivate()
 
-  def testCreateLoginUrl(self):
-    _, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/issues/detail?id=123&q=term', project=self.project)
-    url = servlet_helpers.SafeCreateLoginURL(mr, 'current.url.to.return.to')
-    # Ensure that users can pick their account to use with Monorail.
-    self.assertIn('/AccountChooser', url)
-    self.assertIn('current.url.to.return.to', url)
-
-  def testCreateLoginUrl(self):
-    _, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/issues/detail?id=123&q=term', project=self.project)
-    url = servlet_helpers.SafeCreateLoginURL(mr, 'current.url.to.return.to')
-    # Ensure that users can pick their account to use with Monorail.
-    self.assertIn('/AccountChooser', url)
-    self.assertIn('current.url.to.return.to', url)
-
   def testCreateEscapedLoginUrlFromMR(self):
     _, mr = testing_helpers.GetRequestObjects(
         path='/p/proj/issues/detail?id=123&q=term', project=self.project)
     mr.current_page_url_encoded = (
         'https%3A%2F%2Fbugs.chromium.org'
-        '%2Fp%2Fchromium%2Fissues%2Fentry')
+        '%2Fp%2Fchromium%2Fissues%2Fentry%3F'
+        'template%3DBuild%2520Infrastructure%26'
+        'labels%3DRestrict-View-Google%2CInfra-Troopers')
     url = servlet_helpers.SafeCreateLoginURL(mr)
-    self.assertIn('https%3A%2F%2Fbugs.chromium.org%2Fp', url)
+    double_encoded_query = (
+        'https%253A%252F%252Fbugs.chromium.org'
+        '%252Fp%252Fchromium%252Fissues%252Fentry%253F'
+        'template%253DBuild%252520Infrastructure%2526'
+        'labels%253DRestrict-View-Google%252CInfra-Troopers')
+    self.assertIn(double_encoded_query, url)
 
   def testCreateLogoutUrl(self):
     _, mr = testing_helpers.GetRequestObjects(
diff --git a/framework/test/servlet_test.py b/framework/test/servlet_test.py
index 694e493..33f3644 100644
--- a/framework/test/servlet_test.py
+++ b/framework/test/servlet_test.py
@@ -1,8 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 """Unit tests for servlet base class module."""
 from __future__ import print_function
 from __future__ import division
@@ -11,38 +9,30 @@
 import time
 import mock
 import unittest
+import logging
 
-from google.appengine.api import app_identity
 from google.appengine.ext import testbed
 
-import webapp2
-
-from framework import framework_constants, servlet_helpers
+from framework import framework_constants
 from framework import servlet
+from framework import servlet_helpers
 from framework import xsrf
-from proto import project_pb2
-from proto import tracker_pb2
-from proto import user_pb2
+from mrproto import project_pb2
+from mrproto import tracker_pb2
+from mrproto import user_pb2
 from services import service_manager
 from testing import fake
 from testing import testing_helpers
 
 
-class TestableServlet(servlet.Servlet):
+class _TestableServlet(servlet.Servlet):
   """A tiny concrete subclass of abstract class Servlet."""
 
-  def __init__(self, request, response, services=None, do_post_redirect=True):
-    super(TestableServlet, self).__init__(request, response, services=services)
+  def __init__(self, services=None, do_post_redirect=True):
+    super(_TestableServlet, self).__init__(services=services)
     self.do_post_redirect = do_post_redirect
     self.seen_post_data = None
 
-  def ProcessFormData(self, _mr, post_data):
-    self.seen_post_data = post_data
-    if self.do_post_redirect:
-      return '/This/Is?The=Next#Page'
-    else:
-      self.response.write('sending raw data to browser')
-
 
 class ServletTest(unittest.TestCase):
 
@@ -53,8 +43,7 @@
         user=fake.UserService(),
         usergroup=fake.UserGroupService())
     services.user.TestAddUser('user@example.com', 111)
-    self.page_class = TestableServlet(
-        webapp2.Request.blank('/'), webapp2.Response(), services=services)
+    self.page_class = servlet.Servlet(services=services)
     self.testbed = testbed.Testbed()
     self.testbed.activate()
     self.testbed.init_user_stub()
@@ -69,6 +58,30 @@
     self.assertTrue(self.page_class._TEMPLATE_PATH.endswith('/templates/'))
     self.assertEqual(None, self.page_class._PAGE_TEMPLATE)
 
+  @mock.patch('flask.abort')
+  def testCheckForMovedProject_NoRedirect(self, mock_abort):
+    project = fake.Project(
+        project_name='proj', state=project_pb2.ProjectState.LIVE)
+    request, mr = testing_helpers.GetRequestObjects(
+        path='/p/proj', project=project)
+    self.page_class._CheckForMovedProject(mr, request)
+    mock_abort.assert_not_called()
+
+    request, mr = testing_helpers.GetRequestObjects(
+        path='/p/proj/source/browse/p/adminAdvanced', project=project)
+    self.page_class._CheckForMovedProject(mr, request)
+    mock_abort.assert_not_called()
+
+  @mock.patch('flask.redirect')
+  def testCheckForMovedProject_Redirect(self, mock_redirect):
+    project = fake.Project(project_name='proj', moved_to='http://example.com')
+    request, mr = testing_helpers.GetRequestObjects(
+        path='/p/proj', project=project)
+    self.page_class.request_path = '/p/test'
+    self.page_class._CheckForMovedProject(mr, request)
+    mock_redirect.assert_called_once_with(
+        'http://127.0.0.1/hosting/moved?project=proj', code=302)
+
   def testGatherBaseData(self):
     project = self.page_class.services.project.TestAddProject(
         'testproj', state=project_pb2.ProjectState.LIVE)
@@ -89,284 +102,6 @@
     self.assertTrue(
         base_data['currentPageURLEncoded'].endswith('%2Fp%2Ftestproj%2Ffeeds'))
 
-  def testFormHandlerURL(self):
-    self.assertEqual('/edit.do', self.page_class._FormHandlerURL('/'))
-    self.assertEqual(
-      '/something/edit.do',
-      self.page_class._FormHandlerURL('/something/'))
-    self.assertEqual(
-      '/something/edit.do',
-      self.page_class._FormHandlerURL('/something/edit.do'))
-    self.assertEqual(
-      '/something/detail_ezt.do',
-      self.page_class._FormHandlerURL('/something/detail_ezt'))
-
-  def testProcessForm_BadToken(self):
-    user_id = 111
-    token = 'no soup for you'
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/we/we/we?so=excited',
-        params={
-            'yesterday': 'thursday',
-            'today': 'friday',
-            'token': token
-        },
-        user_info={'user_id': user_id},
-        method='POST',
-    )
-    self.assertRaises(
-        xsrf.TokenIncorrect, self.page_class._DoFormProcessing, request, mr)
-    self.assertEqual(None, self.page_class.seen_post_data)
-
-  def testProcessForm_XhrAllowed_BadToken(self):
-    user_id = 111
-    token = 'no soup for you'
-
-    self.page_class.ALLOW_XHR = True
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/we/we/we?so=excited',
-        params={
-            'yesterday': 'thursday',
-            'today': 'friday',
-            'token': token
-        },
-        user_info={'user_id': user_id},
-        method='POST',
-    )
-    self.assertRaises(
-        xsrf.TokenIncorrect, self.page_class._DoFormProcessing, request, mr)
-    self.assertEqual(None, self.page_class.seen_post_data)
-
-  def testProcessForm_XhrAllowed_AcceptsPathToken(self):
-    user_id = 111
-    token = xsrf.GenerateToken(user_id, '/we/we/we')
-
-    self.page_class.ALLOW_XHR = True
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/we/we/we?so=excited',
-        params={
-            'yesterday': 'thursday',
-            'today': 'friday',
-            'token': token
-        },
-        user_info={'user_id': user_id},
-        method='POST',
-    )
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._DoFormProcessing(request, mr)
-    self.assertEqual(302, cm.exception.code)  # forms redirect on success
-
-    self.assertDictEqual(
-        {
-            'yesterday': 'thursday',
-            'today': 'friday',
-            'token': token
-        }, dict(self.page_class.seen_post_data))
-
-  def testProcessForm_XhrAllowed_AcceptsXhrToken(self):
-    user_id = 111
-    token = xsrf.GenerateToken(user_id, 'xhr')
-
-    self.page_class.ALLOW_XHR = True
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/we/we/we?so=excited',
-        params={'yesterday': 'thursday', 'today': 'friday', 'token': token},
-        user_info={'user_id': user_id},
-        method='POST',
-    )
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._DoFormProcessing(request, mr)
-    self.assertEqual(302, cm.exception.code)  # forms redirect on success
-
-    self.assertDictEqual(
-        {
-            'yesterday': 'thursday',
-            'today': 'friday',
-            'token': token
-        }, dict(self.page_class.seen_post_data))
-
-  def testProcessForm_RawResponse(self):
-    user_id = 111
-    token = xsrf.GenerateToken(user_id, '/we/we/we')
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/we/we/we?so=excited',
-        params={'yesterday': 'thursday', 'today': 'friday', 'token': token},
-        user_info={'user_id': user_id},
-        method='POST',
-    )
-    self.page_class.do_post_redirect = False
-    self.page_class._DoFormProcessing(request, mr)
-    self.assertEqual(
-        'sending raw data to browser',
-        self.page_class.response.body)
-
-  def testProcessForm_Normal(self):
-    user_id = 111
-    token = xsrf.GenerateToken(user_id, '/we/we/we')
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/we/we/we?so=excited',
-        params={'yesterday': 'thursday', 'today': 'friday', 'token': token},
-        user_info={'user_id': user_id},
-        method='POST',
-    )
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._DoFormProcessing(request, mr)
-    self.assertEqual(302, cm.exception.code)  # forms redirect on success
-
-    self.assertDictEqual(
-        {'yesterday': 'thursday', 'today': 'friday', 'token': token},
-        dict(self.page_class.seen_post_data))
-
-  def testCalcProjectAlert(self):
-    project = fake.Project(
-        project_name='alerttest', state=project_pb2.ProjectState.LIVE)
-
-    project_alert = servlet_helpers.CalcProjectAlert(project)
-    self.assertEqual(project_alert, None)
-
-    project.state = project_pb2.ProjectState.ARCHIVED
-    project_alert = servlet_helpers.CalcProjectAlert(project)
-    self.assertEqual(
-        project_alert,
-        'Project is archived: read-only by members only.')
-
-    delete_time = int(time.time() + framework_constants.SECS_PER_DAY * 1.5)
-    project.delete_time = delete_time
-    project_alert = servlet_helpers.CalcProjectAlert(project)
-    self.assertEqual(project_alert, 'Scheduled for deletion in 1 day.')
-
-    delete_time = int(time.time() + framework_constants.SECS_PER_DAY * 2.5)
-    project.delete_time = delete_time
-    project_alert = servlet_helpers.CalcProjectAlert(project)
-    self.assertEqual(project_alert, 'Scheduled for deletion in 2 days.')
-
-  def testCheckForMovedProject_NoRedirect(self):
-    project = fake.Project(
-        project_name='proj', state=project_pb2.ProjectState.LIVE)
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/source/browse/p/adminAdvanced', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-
-  def testCheckForMovedProject_Redirect(self):
-    project = fake.Project(project_name='proj', moved_to='http://example.com')
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._CheckForMovedProject(mr, request)
-    self.assertEqual(302, cm.exception.code)  # redirect because project moved
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/source/browse/p/adminAdvanced', project=project)
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._CheckForMovedProject(mr, request)
-    self.assertEqual(302, cm.exception.code)  # redirect because project moved
-
-  def testCheckForMovedProject_AdminAdvanced(self):
-    """We do not redirect away from the page that edits project state."""
-    project = fake.Project(project_name='proj', moved_to='http://example.com')
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/adminAdvanced', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/adminAdvanced?ts=123234', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-
-    request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/adminAdvanced.do', project=project)
-    self.page_class._CheckForMovedProject(mr, request)
-
-  @mock.patch('settings.branded_domains',
-              {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
-  def testMaybeRedirectToBrandedDomain_RedirBrandedProject(self):
-    """We redirect for a branded project if the user typed a different host."""
-    project = fake.Project(project_name='proj')
-    request, _mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/path', project=project)
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
-    self.assertEqual(302, cm.exception.code)  # forms redirect on success
-    self.assertEqual('https://branded.example.com/p/proj/path?redir=1',
-                     cm.exception.location)
-
-    request, _mr = testing_helpers.GetRequestObjects(
-      path='/p/proj/path?query', project=project)
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
-    self.assertEqual(302, cm.exception.code)  # forms redirect on success
-    self.assertEqual('https://branded.example.com/p/proj/path?query&redir=1',
-                     cm.exception.location)
-
-  @mock.patch('settings.branded_domains',
-              {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
-  def testMaybeRedirectToBrandedDomain_AvoidRedirLoops(self):
-    """Don't redirect for a branded project if already redirected."""
-    project = fake.Project(project_name='proj')
-    request, _mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/path?redir=1', project=project)
-    # No redirect happens.
-    self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
-
-  @mock.patch('settings.branded_domains',
-              {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
-  def testMaybeRedirectToBrandedDomain_NonProjectPage(self):
-    """Don't redirect for a branded project if not in any project."""
-    request, _mr = testing_helpers.GetRequestObjects(
-        path='/u/user@example.com')
-    # No redirect happens.
-    self.page_class._MaybeRedirectToBrandedDomain(request, None)
-
-  @mock.patch('settings.branded_domains',
-              {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
-  def testMaybeRedirectToBrandedDomain_AlreadyOnBrandedHost(self):
-    """Don't redirect for a branded project if already on branded domain."""
-    project = fake.Project(project_name='proj')
-    request, _mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/path', project=project)
-    request.host = 'branded.example.com'
-    # No redirect happens.
-    self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
-
-  @mock.patch('settings.branded_domains',
-              {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
-  def testMaybeRedirectToBrandedDomain_Localhost(self):
-    """Don't redirect for a branded project on localhost."""
-    project = fake.Project(project_name='proj')
-    request, _mr = testing_helpers.GetRequestObjects(
-        path='/p/proj/path', project=project)
-    request.host = 'localhost:8080'
-    # No redirect happens.
-    self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
-
-    request.host = '0.0.0.0:8080'
-    # No redirect happens.
-    self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
-
-  @mock.patch('settings.branded_domains',
-              {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
-  def testMaybeRedirectToBrandedDomain_NotBranded(self):
-    """Don't redirect for a non-branded project."""
-    project = fake.Project(project_name='other')
-    request, _mr = testing_helpers.GetRequestObjects(
-        path='/p/other/path?query', project=project)
-    request.host = 'branded.example.com'  # But other project is unbranded.
-
-    with self.assertRaises(webapp2.HTTPException) as cm:
-      self.page_class._MaybeRedirectToBrandedDomain(request, 'other')
-    self.assertEqual(302, cm.exception.code)  # forms redirect on success
-    self.assertEqual('https://bugs.chromium.org/p/other/path?query&redir=1',
-                     cm.exception.location)
-
   def testGatherHelpData_Normal(self):
     project = fake.Project(project_name='proj')
     _request, mr = testing_helpers.GetRequestObjects(
@@ -374,102 +109,3 @@
     help_data = self.page_class.GatherHelpData(mr, {})
     self.assertEqual(None, help_data['cue'])
     self.assertEqual(None, help_data['account_cue'])
-
-  def testGatherHelpData_VacationReminder(self):
-    project = fake.Project(project_name='proj')
-    _request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    mr.auth.user_id = 111
-    mr.auth.user_pb.vacation_message = 'Gone skiing'
-    help_data = self.page_class.GatherHelpData(mr, {})
-    self.assertEqual('you_are_on_vacation', help_data['cue'])
-
-    self.page_class.services.user.SetUserPrefs(
-        'cnxn', 111,
-        [user_pb2.UserPrefValue(name='you_are_on_vacation', value='true')])
-    help_data = self.page_class.GatherHelpData(mr, {})
-    self.assertEqual(None, help_data['cue'])
-    self.assertEqual(None, help_data['account_cue'])
-
-  def testGatherHelpData_YouAreBouncing(self):
-    project = fake.Project(project_name='proj')
-    _request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    mr.auth.user_id = 111
-    mr.auth.user_pb.email_bounce_timestamp = 1497647529
-    help_data = self.page_class.GatherHelpData(mr, {})
-    self.assertEqual('your_email_bounced', help_data['cue'])
-
-    self.page_class.services.user.SetUserPrefs(
-        'cnxn', 111,
-        [user_pb2.UserPrefValue(name='your_email_bounced', value='true')])
-    help_data = self.page_class.GatherHelpData(mr, {})
-    self.assertEqual(None, help_data['cue'])
-    self.assertEqual(None, help_data['account_cue'])
-
-  def testGatherHelpData_ChildAccount(self):
-    """Display a warning when user is signed in to a child account."""
-    project = fake.Project(project_name='proj')
-    _request, mr = testing_helpers.GetRequestObjects(
-        path='/p/proj', project=project)
-    mr.auth.user_pb.linked_parent_id = 111
-    help_data = self.page_class.GatherHelpData(mr, {})
-    self.assertEqual(None, help_data['cue'])
-    self.assertEqual('switch_to_parent_account', help_data['account_cue'])
-    self.assertEqual('user@example.com', help_data['parent_email'])
-
-  def testGatherDebugData_Visibility(self):
-    project = fake.Project(
-        project_name='testtest', state=project_pb2.ProjectState.LIVE)
-    _request, mr = testing_helpers.GetRequestObjects(
-        path='/p/foo/servlet_path', project=project)
-    debug_data = self.page_class.GatherDebugData(mr, {})
-    self.assertEqual('off', debug_data['dbg'])
-
-    _request, mr = testing_helpers.GetRequestObjects(
-        path='/p/foo/servlet_path?debug=1', project=project)
-    debug_data = self.page_class.GatherDebugData(mr, {})
-    self.assertEqual('on', debug_data['dbg'])
-
-
-class ProjectIsRestrictedTest(unittest.TestCase):
-
-  def testNonRestrictedProject(self):
-    proj = project_pb2.Project()
-    mr = testing_helpers.MakeMonorailRequest()
-    mr.project = proj
-
-    proj.access = project_pb2.ProjectAccess.ANYONE
-    proj.state = project_pb2.ProjectState.LIVE
-    self.assertFalse(servlet_helpers.ProjectIsRestricted(mr))
-
-    proj.state = project_pb2.ProjectState.ARCHIVED
-    self.assertFalse(servlet_helpers.ProjectIsRestricted(mr))
-
-  def testRestrictedProject(self):
-    proj = project_pb2.Project()
-    mr = testing_helpers.MakeMonorailRequest()
-    mr.project = proj
-
-    proj.state = project_pb2.ProjectState.LIVE
-    proj.access = project_pb2.ProjectAccess.MEMBERS_ONLY
-    self.assertTrue(servlet_helpers.ProjectIsRestricted(mr))
-
-
-class VersionBaseTest(unittest.TestCase):
-
-  @mock.patch('settings.local_mode', True)
-  def testLocalhost(self):
-    request = webapp2.Request.blank('/', base_url='http://localhost:8080')
-    actual = servlet_helpers.VersionBaseURL(request)
-    expected = 'http://localhost:8080'
-    self.assertEqual(expected, actual)
-
-  @mock.patch('settings.local_mode', False)
-  @mock.patch('google.appengine.api.app_identity.get_default_version_hostname')
-  def testProd(self, mock_gdvh):
-    mock_gdvh.return_value = 'monorail-prod.appspot.com'
-    request = webapp2.Request.blank('/', base_url='https://bugs.chromium.org')
-    actual = servlet_helpers.VersionBaseURL(request)
-    expected = 'https://test-dot-monorail-prod.appspot.com'
-    self.assertEqual(expected, actual)
diff --git a/framework/test/sorting_test.py b/framework/test/sorting_test.py
index 4251308..44aa6f7 100644
--- a/framework/test/sorting_test.py
+++ b/framework/test/sorting_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for sorting.py functions."""
 from __future__ import print_function
@@ -19,7 +18,7 @@
 
 from framework import sorting
 from framework import framework_views
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from testing import fake
 from testing import testing_helpers
 from tracker import tracker_bizobj
@@ -45,18 +44,18 @@
     """The point of DescendingValue is to reverse the sort order."""
     anti_a = sorting.DescendingValue.MakeDescendingValue('a')
     anti_b = sorting.DescendingValue.MakeDescendingValue('b')
-    self.assertTrue(anti_a > anti_b)
+    self.assertGreater(anti_a, anti_b)
 
   def testMaybeMakeDescending(self):
     """It returns an accessor that makes DescendingValue iff arg is True."""
     asc_accessor = sorting._MaybeMakeDescending(lambda issue: 'a', False)
     asc_value = asc_accessor('fake issue')
-    self.assertTrue(asc_value is 'a')
+    self.assertEqual(asc_value, 'a')
 
     desc_accessor = sorting._MaybeMakeDescending(lambda issue: 'a', True)
     print(desc_accessor)
     desc_value = desc_accessor('fake issue')
-    self.assertTrue(isinstance(desc_value, sorting.DescendingValue))
+    self.assertIsInstance(desc_value, sorting.DescendingValue)
 
 
 class SortingTest(unittest.TestCase):
@@ -161,9 +160,9 @@
     accessor = sorting._IndexListAccessor(well_known_values, base_accessor)
 
     # Case 1: accessor generates no values.
-    self.assertEqual(sorting.MAX_STRING, accessor(art))
+    self.assertEqual([sorting.MAX_STRING], accessor(art))
     neg_accessor = MakeDescending(accessor)
-    self.assertEqual(sorting.MAX_STRING, neg_accessor(art))
+    self.assertEqual([sorting.MAX_STRING], neg_accessor(art))
 
     # Case 2: A single well-known value
     art.component_ids = [33]
@@ -186,9 +185,9 @@
     accessor = sorting._IndexListAccessor(well_known_values, base_accessor)
 
     # Case 1: accessor generates no values.
-    self.assertEqual(sorting.MAX_STRING, accessor(art))
+    self.assertEqual([sorting.MAX_STRING], accessor(art))
     neg_accessor = MakeDescending(accessor)
-    self.assertEqual(sorting.MAX_STRING, neg_accessor(art))
+    self.assertEqual([sorting.MAX_STRING], neg_accessor(art))
 
     # Case 2: A single oddball value
     art.component_ids = [33]
@@ -212,9 +211,9 @@
 
     # Case 1: accessor generates no values.
     accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {})
-    self.assertEqual(sorting.MAX_STRING, accessor(art))
+    self.assertEqual([sorting.MAX_STRING], accessor(art))
     neg_accessor = MakeDescending(accessor)
-    self.assertEqual(sorting.MAX_STRING, neg_accessor(art))
+    self.assertEqual([sorting.MAX_STRING], neg_accessor(art))
 
     # Case 2: A single well-known value
     art.labels = ['Pri-Med']
diff --git a/framework/test/sql_test.py b/framework/test/sql_test.py
index f073e24..e8408cc 100644
--- a/framework/test/sql_test.py
+++ b/framework/test/sql_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for the sql module."""
 from __future__ import print_function
diff --git a/framework/test/table_view_helpers_test.py b/framework/test/table_view_helpers_test.py
index 0260308..f0c6643 100644
--- a/framework/test/table_view_helpers_test.py
+++ b/framework/test/table_view_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for table_view_helpers classes and functions."""
 from __future__ import print_function
@@ -9,12 +8,12 @@
 from __future__ import absolute_import
 
 import collections
+import six
 import unittest
-import logging
 
 from framework import framework_views
 from framework import table_view_helpers
-from proto import tracker_pb2
+from mrproto import tracker_pb2
 from testing import fake
 from testing import testing_helpers
 from tracker import tracker_bizobj
@@ -147,8 +146,9 @@
         users_by_id=self.users_by_id)
     self.assertEqual(cell.type, table_view_helpers.CELL_TYPE_ATTR)
     self.assertEqual(len(cell.values), 2)
-    self.assertItemsEqual([cell.values[0].item, cell.values[1].item],
-                          ['foo@example.com', 'f...@example.com'])
+    six.assertCountEqual(
+        self, [cell.values[0].item, cell.values[1].item],
+        ['foo@example.com', 'f...@example.com'])
 
   # TODO(jrobbins): TableCellProject, TableCellStars
 
diff --git a/framework/test/template_helpers_test.py b/framework/test/template_helpers_test.py
index 85296fa..5f018e4 100644
--- a/framework/test/template_helpers_test.py
+++ b/framework/test/template_helpers_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unit tests for template_helpers module."""
 
@@ -82,7 +81,6 @@
     # pylint: disable=anomalous-unicode-escape-in-string
     test_data = (
         u'This is a short string.',
-
         u'This is a much longer string. '
         u'This is a much longer string. '
         u'This is a much longer string. '
@@ -95,52 +93,52 @@
         u'This is a much longer string. ',
 
         # This is a short escaped i18n string
-        '\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab'.decode('utf-8'),
+        b'\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab'.decode('utf-8'),
 
         # This is a longer i18n string
-        '\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
-        '\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '
-        '\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
-        '\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '
-        '\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
-        '\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '
-        '\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
-        '\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '.decode('utf-8'),
+        b'\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
+        b'\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '
+        b'\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
+        b'\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '
+        b'\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
+        b'\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '
+        b'\xd5\xa1\xd5\xba\xd5\xa1\xd5\xaf\xd5\xab '
+        b'\xe6\x88\x91\xe8\x83\xbd\xe5\x90\x9e '.decode('utf-8'),
 
         # This is a longer i18n string that was causing trouble.
-        '\u041d\u0430 \u0431\u0435\u0440\u0435\u0433\u0443'
-        ' \u043f\u0443\u0441\u0442\u044b\u043d\u043d\u044b\u0445'
-        ' \u0432\u043e\u043b\u043d \u0421\u0442\u043e\u044f\u043b'
-        ' \u043e\u043d, \u0434\u0443\u043c'
-        ' \u0432\u0435\u043b\u0438\u043a\u0438\u0445'
-        ' \u043f\u043e\u043b\u043d, \u0418'
-        ' \u0432\u0434\u0430\u043b\u044c'
-        ' \u0433\u043b\u044f\u0434\u0435\u043b.'
-        ' \u041f\u0440\u0435\u0434 \u043d\u0438\u043c'
-        ' \u0448\u0438\u0440\u043e\u043a\u043e'
-        ' \u0420\u0435\u043a\u0430'
-        ' \u043d\u0435\u0441\u043b\u0430\u0441\u044f;'
-        ' \u0431\u0435\u0434\u043d\u044b\u0439'
-        ' \u0447\u0451\u043b\u043d \u041f\u043e'
-        ' \u043d\u0435\u0439'
-        ' \u0441\u0442\u0440\u0435\u043c\u0438\u043b\u0441\u044f'
-        ' \u043e\u0434\u0438\u043d\u043e\u043a\u043e.'
-        ' \u041f\u043e \u043c\u0448\u0438\u0441\u0442\u044b\u043c,'
-        ' \u0442\u043e\u043f\u043a\u0438\u043c'
-        ' \u0431\u0435\u0440\u0435\u0433\u0430\u043c'
-        ' \u0427\u0435\u0440\u043d\u0435\u043b\u0438'
-        ' \u0438\u0437\u0431\u044b \u0437\u0434\u0435\u0441\u044c'
-        ' \u0438 \u0442\u0430\u043c, \u041f\u0440\u0438\u044e\u0442'
-        ' \u0443\u0431\u043e\u0433\u043e\u0433\u043e'
-        ' \u0447\u0443\u0445\u043e\u043d\u0446\u0430;'
-        ' \u0418 \u043b\u0435\u0441,'
-        ' \u043d\u0435\u0432\u0435\u0434\u043e\u043c\u044b\u0439'
-        ' \u043b\u0443\u0447\u0430\u043c \u0412'
-        ' \u0442\u0443\u043c\u0430\u043d\u0435'
-        ' \u0441\u043f\u0440\u044f\u0442\u0430\u043d\u043d\u043e'
-        '\u0433\u043e \u0441\u043e\u043b\u043d\u0446\u0430,'
-        ' \u041a\u0440\u0443\u0433\u043e\u043c'
-        ' \u0448\u0443\u043c\u0435\u043b.'.decode('utf-8'))
+        u'\u041d\u0430 \u0431\u0435\u0440\u0435\u0433\u0443'
+        u' \u043f\u0443\u0441\u0442\u044b\u043d\u043d\u044b\u0445'
+        u' \u0432\u043e\u043b\u043d \u0421\u0442\u043e\u044f\u043b'
+        u' \u043e\u043d, \u0434\u0443\u043c'
+        u' \u0432\u0435\u043b\u0438\u043a\u0438\u0445'
+        u' \u043f\u043e\u043b\u043d, \u0418'
+        u' \u0432\u0434\u0430\u043b\u044c'
+        u' \u0433\u043b\u044f\u0434\u0435\u043b.'
+        u' \u041f\u0440\u0435\u0434 \u043d\u0438\u043c'
+        u' \u0448\u0438\u0440\u043e\u043a\u043e'
+        u' \u0420\u0435\u043a\u0430'
+        u' \u043d\u0435\u0441\u043b\u0430\u0441\u044f;'
+        u' \u0431\u0435\u0434\u043d\u044b\u0439'
+        u' \u0447\u0451\u043b\u043d \u041f\u043e'
+        u' \u043d\u0435\u0439'
+        u' \u0441\u0442\u0440\u0435\u043c\u0438\u043b\u0441\u044f'
+        u' \u043e\u0434\u0438\u043d\u043e\u043a\u043e.'
+        u' \u041f\u043e \u043c\u0448\u0438\u0441\u0442\u044b\u043c,'
+        u' \u0442\u043e\u043f\u043a\u0438\u043c'
+        u' \u0431\u0435\u0440\u0435\u0433\u0430\u043c'
+        u' \u0427\u0435\u0440\u043d\u0435\u043b\u0438'
+        u' \u0438\u0437\u0431\u044b \u0437\u0434\u0435\u0441\u044c'
+        u' \u0438 \u0442\u0430\u043c, \u041f\u0440\u0438\u044e\u0442'
+        u' \u0443\u0431\u043e\u0433\u043e\u0433\u043e'
+        u' \u0447\u0443\u0445\u043e\u043d\u0446\u0430;'
+        u' \u0418 \u043b\u0435\u0441,'
+        u' \u043d\u0435\u0432\u0435\u0434\u043e\u043c\u044b\u0439'
+        u' \u043b\u0443\u0447\u0430\u043c \u0412'
+        u' \u0442\u0443\u043c\u0430\u043d\u0435'
+        u' \u0441\u043f\u0440\u044f\u0442\u0430\u043d\u043d\u043e'
+        u'\u0433\u043e \u0441\u043e\u043b\u043d\u0446\u0430,'
+        u' \u041a\u0440\u0443\u0433\u043e\u043c'
+        u' \u0448\u0443\u043c\u0435\u043b.')
 
     for unicode_s in test_data:
       # Get the length in characters, not bytes.
diff --git a/framework/test/timestr_test.py b/framework/test/timestr_test.py
index ad11249..e4dd907 100644
--- a/framework/test/timestr_test.py
+++ b/framework/test/timestr_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """Unittest for timestr module."""
 from __future__ import print_function
diff --git a/framework/test/ts_mon_js_test.py b/framework/test/ts_mon_js_test.py
index 4231b76..da48378 100644
--- a/framework/test/ts_mon_js_test.py
+++ b/framework/test/ts_mon_js_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 MonorailTSMonJSHandler."""
 from __future__ import print_function
@@ -13,10 +12,8 @@
 from mock import patch
 
 import flask
-import webapp2
 from google.appengine.ext import testbed
 
-from framework.ts_mon_js import FlaskMonorailTSMonJSHandler
 from framework.ts_mon_js import MonorailTSMonJSHandler
 from services import service_manager
 
@@ -27,66 +24,12 @@
     self.testbed = testbed.Testbed()
     self.testbed.activate()
     self.testbed.init_user_stub()
-
-  def tearDown(self):
-    self.testbed.deactivate()
-
-  @patch('framework.xsrf.ValidateToken')
-  @patch('time.time')
-  def testSubmitMetrics(self, _mockTime, _mockValidateToken):
-    """Test normal case POSTing metrics."""
-    _mockTime.return_value = 1537821859
-    req = webapp2.Request.blank('/_/ts_mon_js')
-    req.body = json.dumps({
-      'metrics': [{
-        'MetricInfo': {
-          'Name': 'monorail/frontend/issue_update_latency',
-          'ValueType': 2,
-        },
-        'Cells': [{
-          'value': {
-            'sum': 1234,
-            'count': 4321,
-            'buckets': {
-              0: 123,
-              1: 321,
-              2: 213,
-            },
-          },
-          'fields': {
-            'client_id': '789',
-            'host_name': 'rutabaga',
-            'document_visible': True,
-          },
-          'start_time': 1537821859 - 60,
-        }],
-      }],
-    })
-    res = webapp2.Response()
-    ts_mon_handler = MonorailTSMonJSHandler(request=req, response=res)
-    class MockApp(object):
-      def __init__(self):
-        self.config = {'services': service_manager.Services()}
-    ts_mon_handler.app = MockApp()
-
-    ts_mon_handler.post()
-
-    self.assertEqual(res.status_int, 201)
-    self.assertEqual(res.body, 'Ok.')
-
-
-class FlaskMonorailTSMonJSHandlerTest(unittest.TestCase):
-
-  def setUp(self):
-    self.testbed = testbed.Testbed()
-    self.testbed.activate()
-    self.testbed.init_user_stub()
     self.services = service_manager.Services()
     self.app = flask.Flask('test_app')
     self.app.config['TESTING'] = True
     self.app.add_url_rule(
         '/_/ts_mon_js.do',
-        view_func=FlaskMonorailTSMonJSHandler(
+        view_func=MonorailTSMonJSHandler(
             services=self.services).PostMonorailTSMonJSHandler,
         methods=['POST'])
 
@@ -95,7 +38,7 @@
 
   @patch('framework.xsrf.ValidateToken')
   @patch('time.time')
-  def testFlaskSubmitMetrics(self, _mockTime, _mockValidateToken):
+  def testSubmitMetrics(self, _mockTime, _mockValidateToken):
     """Test normal case POSTing metrics."""
     _mockTime.return_value = 1537821859
     res = self.app.test_client().post(
diff --git a/framework/test/validate_test.py b/framework/test/validate_test.py
index 9ea17fe..9716122 100644
--- a/framework/test/validate_test.py
+++ b/framework/test/validate_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 """This file provides unit tests for Validate functions."""
 from __future__ import print_function
@@ -31,6 +30,11 @@
       'user@e-x-a-m-p-l-e.com',
       'user@e-x.am-ple.com',
       'user@e--xample.com',
+      'user@example.c',
+      'user@example.comcomcomc',
+      'user@example.co-m',
+      'user@example.c0m',
+      'very-long-email-address@very-long-domain.iam.abcdefghijklmnopqrstuvwxyz',
   ]
 
   BAD_EMAIL_ADDRESSES = [
@@ -52,12 +56,8 @@
       'user@example-.com',
       'user@example',
       'user@example.',
-      'user@example.c',
-      'user@example.comcomcomc',
-      'user@example.co-m',
       'user@exa_mple.com',
       'user@exa-_mple.com',
-      'user@example.c0m',
   ]
 
   def testIsValidEmail(self):
diff --git a/framework/test/warmup_test.py b/framework/test/warmup_test.py
index 13223f1..b3eab65 100644
--- a/framework/test/warmup_test.py
+++ b/framework/test/warmup_test.py
@@ -1,7 +1,6 @@
-# Copyright 2017 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 2017 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 warmup servlet."""
 from __future__ import print_function
@@ -25,7 +24,7 @@
       response = client.get('/')
 
     self.assertEqual(response.status_code, 200)
-    self.assertEqual(response.data, '')
+    self.assertEqual(response.data, b'')
 
   def testHandleStart(self):
     app = flask.Flask(__name__)
@@ -35,7 +34,7 @@
       response = client.get('/')
 
     self.assertEqual(response.status_code, 200)
-    self.assertEqual(response.data, '')
+    self.assertEqual(response.data, b'')
 
   def testHandleStop(self):
     app = flask.Flask(__name__)
@@ -45,4 +44,4 @@
       response = client.get('/')
 
     self.assertEqual(response.status_code, 200)
-    self.assertEqual(response.data, '')
+    self.assertEqual(response.data, b'')
diff --git a/framework/test/xsrf_test.py b/framework/test/xsrf_test.py
index aa04570..1039ab6 100644
--- a/framework/test/xsrf_test.py
+++ b/framework/test/xsrf_test.py
@@ -1,7 +1,6 @@
-# Copyright 2016 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 2016 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 XSRF utility functions."""
 from __future__ import print_function