Build API: Add mock-call functionality.

The core functionality for making mock calls to the API.
Support added for success, error, and invalid responses.
Allows consumers to test their implementations against the
potentially branched API without needing to run the full
endpoint.

BUG=chromium:999178
TEST=run_tests

Change-Id: I5de9c37f8a759c175627b6db5e9696533aada031
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1787836
Tested-by: Alex Klein <saklein@chromium.org>
Commit-Queue: Alex Klein <saklein@chromium.org>
Reviewed-by: Evan Hernandez <evanhernandez@chromium.org>
Reviewed-by: David Burger <dburger@chromium.org>
diff --git a/api/validate_unittest.py b/api/validate_unittest.py
index b38488a..c39da65 100644
--- a/api/validate_unittest.py
+++ b/api/validate_unittest.py
@@ -17,7 +17,7 @@
 from chromite.lib import osutils
 
 
-class ExistsTest(cros_test_lib.TempDirTestCase):
+class ExistsTest(cros_test_lib.TempDirTestCase, api_config.ApiConfigMixin):
   """Tests for the exists validator."""
 
   def test_not_exists(self):
@@ -25,11 +25,11 @@
     path = os.path.join(self.tempdir, 'DOES_NOT_EXIST')
 
     @validate.exists('path')
-    def impl(_input_proto):
+    def impl(_input_proto, _output_proto, _config):
       self.fail('Incorrectly allowed method to execute.')
 
     with self.assertRaises(cros_build_lib.DieSystemExit):
-      impl(common_pb2.Chroot(path=path))
+      impl(common_pb2.Chroot(path=path), None, self.api_config)
 
   def test_exists(self):
     """Test the validator fails when given a path that doesn't exist."""
@@ -37,88 +37,116 @@
     osutils.SafeMakedirs(path)
 
     @validate.exists('path')
-    def impl(_input_proto):
+    def impl(_input_proto, _output_proto, _config):
       pass
 
-    impl(common_pb2.Chroot(path=path))
+    impl(common_pb2.Chroot(path=path), None, self.api_config)
+
+  def test_skip_validation(self):
+    """Test skipping validation case."""
+    @validate.exists('path')
+    def impl(_input_proto, _output_proto, _config):
+      pass
+
+    # This would otherwise raise an error for an invalid path.
+    impl(common_pb2.Chroot(), None, self.no_validate_config)
 
 
-class IsInTest(cros_test_lib.TestCase):
+class IsInTest(cros_test_lib.TestCase, api_config.ApiConfigMixin):
   """Tests for the is_in validator."""
 
   def test_in(self):
     """Test a valid value."""
     @validate.is_in('path', ['/chroot/path', '/other/chroot/path'])
-    def impl(*_args):
+    def impl(_input_proto, _output_proto, _config):
       pass
 
     # Make sure all of the values work.
-    impl(common_pb2.Chroot(path='/chroot/path'))
-    impl(common_pb2.Chroot(path='/other/chroot/path'))
+    impl(common_pb2.Chroot(path='/chroot/path'), None, self.api_config)
+    impl(common_pb2.Chroot(path='/other/chroot/path'), None, self.api_config)
 
   def test_not_in(self):
     """Test an invalid value."""
     @validate.is_in('path', ['/chroot/path', '/other/chroot/path'])
-    def impl(*_args):
+    def impl(_input_proto, _output_proto, _config):
       pass
 
     # Should be failing on the invalid value.
     with self.assertRaises(cros_build_lib.DieSystemExit):
-      impl(common_pb2.Chroot(path='/bad/value'))
+      impl(common_pb2.Chroot(path='/bad/value'), None, self.api_config)
 
   def test_not_set(self):
     """Test an unset value."""
     @validate.is_in('path', ['/chroot/path', '/other/chroot/path'])
-    def impl(*_args):
+    def impl(_input_proto, _output_proto, _config):
       pass
 
     # Should be failing without a value set.
     with self.assertRaises(cros_build_lib.DieSystemExit):
-      impl(common_pb2.Chroot())
+      impl(common_pb2.Chroot(), None, self.api_config)
+
+  def test_skip_validation(self):
+    """Test skipping validation case."""
+    @validate.is_in('path', ['/chroot/path', '/other/chroot/path'])
+    def impl(_input_proto, _output_proto, _config):
+      pass
+
+    # This would otherwise raise an error for an invalid path.
+    impl(common_pb2.Chroot(), None, self.no_validate_config)
 
 
-class RequiredTest(cros_test_lib.TestCase):
+class RequiredTest(cros_test_lib.TestCase, api_config.ApiConfigMixin):
   """Tests for the required validator."""
 
   def test_invalid_field(self):
     """Test validator fails when given an unset value."""
 
     @validate.require('does.not.exist')
-    def impl(_input_proto):
+    def impl(_input_proto, _output_proto, _config):
       self.fail('Incorrectly allowed method to execute.')
 
     with self.assertRaises(cros_build_lib.DieSystemExit):
-      impl(common_pb2.Chroot())
+      impl(common_pb2.Chroot(), None, self.api_config)
 
   def test_not_set(self):
     """Test validator fails when given an unset value."""
 
     @validate.require('env.use_flags')
-    def impl(_input_proto):
+    def impl(_input_proto, _output_proto, _config):
       self.fail('Incorrectly allowed method to execute.')
 
     with self.assertRaises(cros_build_lib.DieSystemExit):
-      impl(common_pb2.Chroot())
+      impl(common_pb2.Chroot(), None, self.api_config)
 
   def test_set(self):
     """Test validator passes when given set values."""
 
     @validate.require('path', 'env.use_flags')
-    def impl(_input_proto):
+    def impl(_input_proto, _output_proto, _config):
       pass
 
-    impl(common_pb2.Chroot(path='/chroot/path',
-                           env={'use_flags': [{'flag': 'test'}]}))
+    in_proto = common_pb2.Chroot(path='/chroot/path',
+                                 env={'use_flags': [{'flag': 'test'}]})
+    impl(in_proto, None, self.api_config)
 
   def test_mixed(self):
     """Test validator passes when given a set value."""
 
     @validate.require('path', 'env.use_flags')
-    def impl(_input_proto):
+    def impl(_input_proto, _output_proto, _config):
       pass
 
     with self.assertRaises(cros_build_lib.DieSystemExit):
-      impl(common_pb2.Chroot(path='/chroot/path'))
+      impl(common_pb2.Chroot(path='/chroot/path'), None, self.api_config)
+
+  def test_skip_validation(self):
+    """Test skipping validation case."""
+    @validate.require('path', 'env.use_flags')
+    def impl(_input_proto, _output_proto, _config):
+      pass
+
+    # This would otherwise raise an error for an invalid path.
+    impl(common_pb2.Chroot(), None, self.no_validate_config)
 
 
 class ValidateOnlyTest(cros_test_lib.TestCase, api_config.ApiConfigMixin):
@@ -143,7 +171,7 @@
     """Test no use of validate only."""
     @validate.validation_complete
     def impl(_input_proto, _output_proto, _config):
-      assert False
+      self.fail('Incorrectly allowed method to execute.')
 
     # We will get an assertion error unless validate_only prevents the function
     # from being called.