Build API: Enter chroot on INSIDE assertion.

BUG=chromium:953049
TEST=run_tests
TEST=manual - called INSIDE endpoint and verified entry
Cq-Depend: chromium:1569042

Change-Id: I78ad1a249d90b5de112ed6da4db319bcf19ff836
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1570352
Tested-by: Alex Klein <saklein@chromium.org>
Reviewed-by: Evan Hernandez <evanhernandez@chromium.org>
Reviewed-by: Sean Abraham <seanabraham@chromium.org>
diff --git a/scripts/build_api_unittest.py b/scripts/build_api_unittest.py
index efa1669..957e73c 100644
--- a/scripts/build_api_unittest.py
+++ b/scripts/build_api_unittest.py
@@ -16,9 +16,9 @@
 from chromite.scripts import build_api
 
 
-class RouterTest(cros_test_lib.MockTempDirTestCase):
+class RouterTest(cros_test_lib.RunCommandTempDirTestCase):
   """Test Router functionality."""
-  _INPUT_JSON = '{"id":"Input ID"}'
+  _INPUT_JSON_TEMPLATE = '{"id":"Input ID", "chroot":{"path": "%s"}}'
 
   def setUp(self):
     self.router = build_api.Router()
@@ -27,7 +27,13 @@
     self.input_file = os.path.join(self.tempdir, 'input.json')
     self.output_file = os.path.join(self.tempdir, 'output.json')
 
-    osutils.WriteFile(self.input_file, self._INPUT_JSON)
+    self.chroot_dir = os.path.join(self.tempdir, 'chroot')
+    chroot_tmp = os.path.join(self.chroot_dir, 'tmp')
+    # Make the tmp dir for the re-exec inside chroot input/output files.
+    osutils.SafeMakedirs(chroot_tmp)
+
+    osutils.WriteFile(self.input_file,
+                      self._INPUT_JSON_TEMPLATE % self.chroot_dir)
 
   def testInputOutputMethod(self):
     """Test input/output handling."""
@@ -51,83 +57,92 @@
     self.router.Route('chromite.api.TestApiService', 'RenamedMethod',
                       self.input_file, self.output_file)
 
-  def testInsideServiceChrootAsserts(self):
-    """Test the chroot assertion handling with service inside configured."""
-    # Helper variables/functions to make the patches simpler.
-    should_be_called = False
-    is_inside = False
+  def _mock_callable(self, expect_called):
+    """Helper to create the implementation mock to test chroot assertions.
+
+    Args:
+      expect_called (bool): Whether the implementation should be called.
+       When False, an assertion will fail if it is called.
+
+    Returns:
+      callable - The implementation.
+    """
     def impl(_input_msg, _output_msg):
-      self.assertTrue(should_be_called,
+      self.assertTrue(expect_called,
                       'The implementation should not have been called.')
-    def inside():
-      return is_inside
 
-    self.PatchObject(self.router, '_GetMethod', return_value=impl)
-    self.PatchObject(cros_build_lib, 'IsInsideChroot', side_effect=inside)
+    return impl
 
-    # Not inside chroot with inside requirement should raise an error.
-    with self.assertRaises(cros_build_lib.DieSystemExit):
-      self.router.Route('chromite.api.InsideChrootApiService',
-                        'InsideServiceInsideMethod', self.input_file,
-                        self.output_file)
-
-    # Inside chroot with inside requirement.
-    is_inside = should_be_called = True
+  def testInsideServiceInsideMethodInsideChroot(self):
+    """Test inside/inside/inside works correctly."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=True))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
     self.router.Route('chromite.api.InsideChrootApiService',
                       'InsideServiceInsideMethod', self.input_file,
                       self.output_file)
 
-    # Inside chroot with outside override should raise assertion.
-    is_inside = True
-    should_be_called = False
+  def testInsideServiceOutsideMethodOutsideChroot(self):
+    """Test the outside method override works as expected."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=True))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
+    self.router.Route('chromite.api.InsideChrootApiService',
+                      'InsideServiceOutsideMethod', self.input_file,
+                      self.output_file)
+
+  def testInsideServiceInsideMethodOutsideChroot(self):
+    """Test calling an inside method from outside the chroot."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=False))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
+    self.rc.return_value = cros_build_lib.CommandResult(returncode=0)
+
+    service = 'chromite.api.InsideChrootApiService'
+    method = 'InsideServiceInsideMethod'
+    service_method = '%s/%s' % (service, method)
+    self.router.Route(service, method, self.input_file, self.output_file)
+
+    self.assertCommandContains(['build_api', service_method])
+
+  def testInsideServiceOutsideMethodInsideChroot(self):
+    """Test inside chroot for outside method raises an error."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=False))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
     with self.assertRaises(cros_build_lib.DieSystemExit):
       self.router.Route('chromite.api.InsideChrootApiService',
                         'InsideServiceOutsideMethod', self.input_file,
                         self.output_file)
 
-    is_inside = False
-    should_be_called = True
-    self.router.Route('chromite.api.InsideChrootApiService',
-                      'InsideServiceOutsideMethod', self.input_file,
-                      self.output_file)
-
-  def testOutsideServiceChrootAsserts(self):
-    """Test the chroot assertion handling with service outside configured."""
-    # Helper variables/functions to make the patches simpler.
-    should_be_called = False
-    is_inside = False
-    def impl(_input_msg, _output_msg):
-      self.assertTrue(should_be_called,
-                      'The implementation should not have been called.')
-
-    self.PatchObject(self.router, '_GetMethod', return_value=impl)
-    self.PatchObject(cros_build_lib, 'IsInsideChroot',
-                     side_effect=lambda: is_inside)
-
-    # Outside chroot with outside requirement should be fine.
-    is_inside = False
-    should_be_called = True
+  def testOutsideServiceOutsideMethodOutsideChroot(self):
+    """Test outside/outside/outside works correctly."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=True))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
     self.router.Route('chromite.api.OutsideChrootApiService',
                       'OutsideServiceOutsideMethod', self.input_file,
                       self.output_file)
 
-    # Inside chroot with outside requirement should raise error.
-    is_inside = True
-    should_be_called = False
-    with self.assertRaises(cros_build_lib.DieSystemExit):
-      self.router.Route('chromite.api.OutsideChrootApiService',
-                        'OutsideServiceOutsideMethod', self.input_file,
-                        self.output_file)
-
-    # Outside chroot with inside override should raise error.
-    is_inside = should_be_called = False
-    with self.assertRaises(cros_build_lib.DieSystemExit):
-      self.router.Route('chromite.api.OutsideChrootApiService',
-                        'OutsideServiceInsideMethod', self.input_file,
-                        self.output_file)
-
-    # Inside chroot with inside override should be fine.
-    is_inside = should_be_called = True
+  def testOutsideServiceInsideMethodInsideChroot(self):
+    """Test the inside method assertion override works properly."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=True))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
     self.router.Route('chromite.api.OutsideChrootApiService',
                       'OutsideServiceInsideMethod', self.input_file,
                       self.output_file)
+
+  def testOutsideServiceInsideMethodOutsideChroot(self):
+    """Test calling an inside override method from outside the chroot."""
+    self.PatchObject(self.router, '_GetMethod',
+                     return_value=self._mock_callable(expect_called=False))
+    self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
+    self.rc.return_value = cros_build_lib.CommandResult(returncode=0)
+
+    service = 'chromite.api.OutsideChrootApiService'
+    method = 'OutsideServiceInsideMethod'
+    service_method = '%s/%s' % (service, method)
+    self.router.Route(service, method, self.input_file, self.output_file)
+
+    self.assertCommandContains(['build_api', service_method])