Build API: Implement validate_only calls.

Add validate-only support to all existing endpoints and
tests to enforce the setting is respected.
Add is_in validator to help transition some endpoints
to decorator-only validation.
Some cleanup and standardization in the controller tests.

BUG=chromium:987263
TEST=run_tests

Cq-Depend: chromium:1726252
Change-Id: I108dfc1a221847eae47a18f2f60e12d2575c9ea8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1726253
Reviewed-by: David Burger <dburger@chromium.org>
Commit-Queue: Alex Klein <saklein@chromium.org>
Tested-by: Alex Klein <saklein@chromium.org>
diff --git a/api/controller/sdk_unittest.py b/api/controller/sdk_unittest.py
index 03d924e..1b534f6 100644
--- a/api/controller/sdk_unittest.py
+++ b/api/controller/sdk_unittest.py
@@ -7,21 +7,24 @@
 
 from __future__ import print_function
 
-from chromite.lib import cros_test_lib
+import mock
 
+from chromite.api import api_config
 from chromite.api.controller import sdk as sdk_controller
 from chromite.api.gen.chromite.api import sdk_pb2
 from chromite.lib import cros_build_lib
+from chromite.lib import cros_test_lib
 from chromite.service import sdk as sdk_service
 
 
-class SdkCreateTest(cros_test_lib.MockTestCase):
+class SdkCreateTest(cros_test_lib.MockTestCase, api_config.ApiConfigMixin):
   """Create tests."""
 
   def setUp(self):
     """Setup method."""
     # We need to run the command outside the chroot.
     self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
+    self.response = sdk_pb2.CreateResponse()
 
   def _GetRequest(self, no_replace=False, bootstrap=False, no_use_image=False,
                   cache_path=None, chroot_path=None):
@@ -38,57 +41,64 @@
 
     return request
 
+  def testValidateOnly(self):
+    """Sanity check that a validate only call does not execute any logic."""
+    patch = self.PatchObject(sdk_service, 'Create')
+
+    sdk_controller.Create(self._GetRequest(), self.response,
+                          self.validate_only_config)
+    patch.assert_not_called()
+
   def testSuccess(self):
     """Test the successful call output handling."""
     self.PatchObject(sdk_service, 'Create', return_value=1)
 
     request = self._GetRequest()
-    response = sdk_pb2.CreateResponse()
 
-    sdk_controller.Create(request, response)
+    sdk_controller.Create(request, self.response, self.api_config)
 
-    self.assertEqual(1, response.version.version)
+    self.assertEqual(1, self.response.version.version)
 
-  def testArgumentHandling(self):
+  def testFalseArguments(self):
     """Test the argument handling."""
-    # Get some defaults to return so it's passing around valid objects.
-    paths_obj = sdk_service.ChrootPaths()
-    args_obj = sdk_service.CreateArguments()
-
     # Create the patches.
     self.PatchObject(sdk_service, 'Create', return_value=1)
-    paths_patch = self.PatchObject(sdk_service, 'ChrootPaths',
-                                   return_value=paths_obj)
-    args_patch = self.PatchObject(sdk_service, 'CreateArguments',
-                                  return_value=args_obj)
-
-    # Just need a response to pass through.
-    response = sdk_pb2.CreateResponse()
+    args_patch = self.PatchObject(sdk_service, 'CreateArguments')
 
     # Flag translation tests.
     # Test all false values in the message.
     request = self._GetRequest(no_replace=False, bootstrap=False,
                                no_use_image=False)
-    sdk_controller.Create(request, response)
+    sdk_controller.Create(request, self.response, self.api_config)
     args_patch.assert_called_with(replace=True, bootstrap=False,
-                                  use_image=True, paths=paths_obj)
+                                  use_image=True, paths=mock.ANY)
+
+  def testTrueArguments(self):
+    # Create the patches.
+    self.PatchObject(sdk_service, 'Create', return_value=1)
+    args_patch = self.PatchObject(sdk_service, 'CreateArguments')
 
     # Test all True values in the message.
     request = self._GetRequest(no_replace=True, bootstrap=True,
                                no_use_image=True)
-    sdk_controller.Create(request, response)
+    sdk_controller.Create(request, self.response, self.api_config)
     args_patch.assert_called_with(replace=False, bootstrap=True,
-                                  use_image=False, paths=paths_obj)
+                                  use_image=False, paths=mock.ANY)
+
+  def testPathArguments(self):
+    # Create the patches.
+    self.PatchObject(sdk_service, 'Create', return_value=1)
+    paths_patch = self.PatchObject(sdk_service, 'ChrootPaths')
 
     # Test the path arguments get passed through.
     cache_dir = '/cache/dir'
     chroot_path = '/chroot/path'
     request = self._GetRequest(cache_path=cache_dir, chroot_path=chroot_path)
-    sdk_controller.Create(request, response)
+    sdk_controller.Create(request, self.response, self.api_config)
     paths_patch.assert_called_with(cache_dir=cache_dir, chroot_path=chroot_path)
 
 
-class SdkUpdateTest(cros_test_lib.MockTestCase):
+class SdkUpdateTest(cros_test_lib.MockTestCase, api_config.ApiConfigMixin):
   """Update tests."""
 
   def setUp(self):
@@ -96,6 +106,8 @@
     # We need to run the command inside the chroot.
     self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
 
+    self.response = sdk_pb2.UpdateResponse()
+
   def _GetRequest(self, build_source=False, targets=None):
     """Helper to simplify building a request instance."""
     request = sdk_pb2.UpdateRequest()
@@ -107,20 +119,23 @@
 
     return request
 
-  def _GetResponse(self):
-    """Helper to build an empty response instance."""
-    return sdk_pb2.UpdateResponse()
+  def testValidateOnly(self):
+    """Sanity check that a validate only call does not execute any logic."""
+    patch = self.PatchObject(sdk_service, 'Update')
+
+    sdk_controller.Update(self._GetRequest(), self.response,
+                          self.validate_only_config)
+    patch.assert_not_called()
 
   def testSuccess(self):
     """Successful call output handling test."""
     expected_version = 1
     self.PatchObject(sdk_service, 'Update', return_value=expected_version)
     request = self._GetRequest()
-    response = self._GetResponse()
 
-    sdk_controller.Update(request, response)
+    sdk_controller.Update(request, self.response, self.api_config)
 
-    self.assertEqual(expected_version, response.version.version)
+    self.assertEqual(expected_version, self.response.version.version)
 
   def testArgumentHandling(self):
     """Test the proto argument handling."""
@@ -129,15 +144,13 @@
     args_patch = self.PatchObject(sdk_service, 'UpdateArguments',
                                   return_value=args)
 
-    response = self._GetResponse()
-
     # No boards and flags False.
     request = self._GetRequest(build_source=False)
-    sdk_controller.Update(request, response)
+    sdk_controller.Update(request, self.response, self.api_config)
     args_patch.assert_called_with(build_source=False, toolchain_targets=[])
 
     # Multiple boards and flags True.
     targets = ['board1', 'board2']
     request = self._GetRequest(build_source=True, targets=targets)
-    sdk_controller.Update(request, response)
+    sdk_controller.Update(request, self.response, self.api_config)
     args_patch.assert_called_with(build_source=True, toolchain_targets=targets)