api: Add @validate.eq(field, value)

We already have @validate.is_in() for when there are multiple acceptable
values. Introducing @validate.eq(), for when there is only one
acceptable value.

Sample use case: ensuring that a common_pb2.Path is outside the chroot.

BUG=b:259445595
TEST=./run_tests

Change-Id: I424ceeb09827d5cf5e956bc33a85460b1a7d68ae
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4375644
Reviewed-by: Alex Klein <saklein@chromium.org>
Tested-by: Greg Edelston <gredelston@google.com>
Commit-Queue: Greg Edelston <gredelston@google.com>
diff --git a/api/validate_unittest.py b/api/validate_unittest.py
index cd7bc03..09a61a1 100644
--- a/api/validate_unittest.py
+++ b/api/validate_unittest.py
@@ -58,6 +58,63 @@
         impl(common_pb2.Chroot(), None, self.no_validate_config)
 
 
+class EqTest(cros_test_lib.TestCase, api_config.ApiConfigMixin):
+    """Tests for the eq validator."""
+
+    def test_eq(self):
+        """Test a valid value."""
+
+        @validate.eq("location", common_pb2.Path.Location.OUTSIDE)
+        def impl(_input_proto, _output_proto, _config):
+            pass
+
+        impl(
+            common_pb2.Path(
+                path="/", location=common_pb2.Path.Location.OUTSIDE
+            ),
+            None,
+            self.api_config,
+        )
+
+    def test_not_eq(self):
+        """Test an invalid value."""
+
+        @validate.eq("location", common_pb2.Path.Location.OUTSIDE)
+        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.Path(
+                    path="/", location=common_pb2.Path.Location.INSIDE
+                ),
+                None,
+                self.api_config,
+            )
+
+    def test_not_set(self):
+        """Test an unset value."""
+
+        @validate.eq("location", common_pb2.Path.Location.OUTSIDE)
+        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.Path(path="/"), None, self.api_config)
+
+    def test_skip_validation(self):
+        """Test skipping validation case."""
+
+        @validate.eq("location", common_pb2.Path.Location.OUTSIDE)
+        def impl(_input_proto, _output_proto, _config):
+            pass
+
+        # This would otherwise raise an error for an invalid path.
+        impl(common_pb2.Path(path="/"), None, self.no_validate_config)
+
+
 class IsInTest(cros_test_lib.TestCase, api_config.ApiConfigMixin):
     """Tests for the is_in validator."""