This is an automated email from the ASF dual-hosted git repository.

jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 5880e6972a [#11114] test(client-python): add integration tests for 
User API and fix UserDTO audit validation (#11115)
5880e6972a is described below

commit 5880e6972ad040a2dbc05959d54b2344723ac482
Author: Sun Yuhan <[email protected]>
AuthorDate: Wed May 20 19:45:36 2026 +0800

    [#11114] test(client-python): add integration tests for User API and fix 
UserDTO audit validation (#11115)
    
    ### What changes were proposed in this pull request?
    
    - Add audit null validation in `UserDTO.Builder.build()` to align with
    the Java side
    - Add integration tests for User API (`add_user`, `get_user`,
    `remove_user`, `list_users`, `list_user_names`)
    - Update existing unit tests to provide audit in Builder calls
    
    ### Why are the changes needed?
    
    The Java `UserDTO.Builder.build()` validates `audit != null`, but the
    Python counterpart does not. Also, PR #11058 only added mocked unit
    tests for the User API. Integration tests are needed to validate against
    a real Gravitino server.
    
    Fix: #11114
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    - 8 new integration tests passed against a real Gravitino server with
    authorization enabled
    - 36 existing unit tests passed
    
    ---------
    
    Co-authored-by: Sun Yuhan <[email protected]>
    Co-authored-by: Jerry Shao <[email protected]>
---
 .../gravitino/dto/authorization/user_dto.py        |   2 +
 .../client-python/tests/integration/test_user.py   | 147 +++++++++++++++++++++
 .../unittests/dto/responses/test_user_response.py  |  21 ++-
 .../tests/unittests/dto/test_user_dto.py           |  43 +++++-
 4 files changed, 206 insertions(+), 7 deletions(-)

diff --git a/clients/client-python/gravitino/dto/authorization/user_dto.py 
b/clients/client-python/gravitino/dto/authorization/user_dto.py
index da32198534..0178f0702d 100644
--- a/clients/client-python/gravitino/dto/authorization/user_dto.py
+++ b/clients/client-python/gravitino/dto/authorization/user_dto.py
@@ -88,4 +88,6 @@ class UserDTO(User):
         def build(self) -> UserDTO:
             if not self._name:
                 raise ValueError("name cannot be null or empty")
+            if self._audit is None:
+                raise ValueError("audit cannot be null")
             return UserDTO(self._name, self._roles, self._audit)
diff --git a/clients/client-python/tests/integration/test_user.py 
b/clients/client-python/tests/integration/test_user.py
new file mode 100644
index 0000000000..9576461214
--- /dev/null
+++ b/clients/client-python/tests/integration/test_user.py
@@ -0,0 +1,147 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+import os
+from random import randint
+
+from gravitino import GravitinoAdminClient, GravitinoClient
+from gravitino.exceptions.base import (
+    GravitinoRuntimeException,
+    NoSuchUserException,
+    UserAlreadyExistsException,
+)
+
+from tests.integration.integration_test_env import IntegrationTestEnv
+
+logger = logging.getLogger(__name__)
+
+
+class TestUser(IntegrationTestEnv):
+    metalake_name: str = "test_user_metalake" + str(randint(1, 10000))
+
+    gravitino_admin_client: GravitinoAdminClient = None
+    gravitino_client: GravitinoClient = None
+
+    @classmethod
+    def setUpClass(cls):
+        cls._get_gravitino_home()
+        conf_path = os.path.join(cls.gravitino_home, "conf", "gravitino.conf")
+        cls._reset_conf({"gravitino.authorization.enable": "true"}, conf_path)
+        cls._append_conf({"gravitino.authorization.enable": "true"}, conf_path)
+        if (
+            os.environ.get("START_EXTERNAL_GRAVITINO") is not None
+            and os.environ.get("START_EXTERNAL_GRAVITINO").lower() == "true"
+        ):
+            cls.restart_server()
+        else:
+            super().setUpClass()
+        cls.gravitino_admin_client = 
GravitinoAdminClient(uri="http://localhost:8090";)
+
+    @classmethod
+    def tearDownClass(cls):
+        conf_path = os.path.join(cls.gravitino_home, "conf", "gravitino.conf")
+        cls._reset_conf({"gravitino.authorization.enable": "false"}, conf_path)
+        if (
+            os.environ.get("START_EXTERNAL_GRAVITINO") is not None
+            and os.environ.get("START_EXTERNAL_GRAVITINO").lower() == "true"
+        ):
+            cls.restart_server()
+        else:
+            super().tearDownClass()
+
+    def setUp(self):
+        self.init_test_env()
+
+    def tearDown(self):
+        self.clean_test_data()
+
+    def init_test_env(self):
+        self.gravitino_admin_client.create_metalake(
+            self.metalake_name, comment="", properties={}
+        )
+        self.gravitino_client = GravitinoClient(
+            uri="http://localhost:8090";, metalake_name=self.metalake_name
+        )
+
+    def clean_test_data(self):
+        self.gravitino_client = GravitinoClient(
+            uri="http://localhost:8090";, metalake_name=self.metalake_name
+        )
+        try:
+            self.gravitino_admin_client.drop_metalake(self.metalake_name, 
force=True)
+        except GravitinoRuntimeException:
+            logger.warning("Failed to drop metalake %s", self.metalake_name)
+
+    def test_add_user(self):
+        user = self.gravitino_client.add_user("test_add_user")
+
+        self.assertIsNotNone(user)
+        self.assertEqual("test_add_user", user.name())
+        self.assertIsNotNone(user.audit_info())
+
+    def test_get_user(self):
+        self.gravitino_client.add_user("test_get_user")
+
+        user = self.gravitino_client.get_user("test_get_user")
+
+        self.assertIsNotNone(user)
+        self.assertEqual("test_get_user", user.name())
+
+    def test_list_users(self):
+        self.gravitino_client.add_user("test_list_user_1")
+        self.gravitino_client.add_user("test_list_user_2")
+
+        users = self.gravitino_client.list_users()
+
+        self.assertIsNotNone(users)
+        user_names = [u.name() for u in users]
+        self.assertIn("test_list_user_1", user_names)
+        self.assertIn("test_list_user_2", user_names)
+
+    def test_list_user_names(self):
+        self.gravitino_client.add_user("test_list_names_1")
+        self.gravitino_client.add_user("test_list_names_2")
+
+        names = self.gravitino_client.list_user_names()
+
+        self.assertIsNotNone(names)
+        self.assertIn("test_list_names_1", names)
+        self.assertIn("test_list_names_2", names)
+
+    def test_remove_user(self):
+        self.gravitino_client.add_user("test_remove_user")
+
+        removed = self.gravitino_client.remove_user("test_remove_user")
+        self.assertTrue(removed)
+
+        with self.assertRaises(NoSuchUserException):
+            self.gravitino_client.get_user("test_remove_user")
+
+    def test_get_nonexistent_user(self):
+        with self.assertRaises(NoSuchUserException):
+            self.gravitino_client.get_user("nonexistent_user")
+
+    def test_add_duplicate_user(self):
+        self.gravitino_client.add_user("test_duplicate_user")
+
+        with self.assertRaises(UserAlreadyExistsException):
+            self.gravitino_client.add_user("test_duplicate_user")
+
+    def test_remove_nonexistent_user(self):
+        removed = self.gravitino_client.remove_user("nonexistent_user")
+        self.assertFalse(removed)
diff --git 
a/clients/client-python/tests/unittests/dto/responses/test_user_response.py 
b/clients/client-python/tests/unittests/dto/responses/test_user_response.py
index e1070c63b7..1f55b544f4 100644
--- a/clients/client-python/tests/unittests/dto/responses/test_user_response.py
+++ b/clients/client-python/tests/unittests/dto/responses/test_user_response.py
@@ -20,6 +20,7 @@ from __future__ import annotations
 import json as _json
 import unittest
 
+from gravitino.dto.audit_dto import AuditDTO
 from gravitino.dto.authorization.user_dto import UserDTO
 from gravitino.dto.responses.user_response import (
     UserListResponse,
@@ -30,7 +31,11 @@ from gravitino.dto.responses.user_response import (
 
 class TestUserResponses(unittest.TestCase):
     def test_user_response(self):
-        user_dto = UserDTO.builder().with_name("user1").build()
+        audit = AuditDTO(
+            _creator="admin",
+            _create_time="2024-01-01T00:00:00Z",
+        )
+        user_dto = 
UserDTO.builder().with_name("user1").with_audit(audit).build()
         resp = UserResponse(0, user_dto)
 
         resp.validate()
@@ -62,8 +67,18 @@ class TestUserResponses(unittest.TestCase):
         self.assertListEqual(names, deser_dict["names"])
 
     def test_user_list_response(self):
-        user1 = UserDTO.builder().with_name("user1").with_roles(["r1"]).build()
-        user2 = UserDTO.builder().with_name("user2").build()
+        audit = AuditDTO(
+            _creator="admin",
+            _create_time="2024-01-01T00:00:00Z",
+        )
+        user1 = (
+            UserDTO.builder()
+            .with_name("user1")
+            .with_roles(["r1"])
+            .with_audit(audit)
+            .build()
+        )
+        user2 = UserDTO.builder().with_name("user2").with_audit(audit).build()
 
         resp = UserListResponse(0, [user1, user2])
 
diff --git a/clients/client-python/tests/unittests/dto/test_user_dto.py 
b/clients/client-python/tests/unittests/dto/test_user_dto.py
index 74bf2b2e10..ef921516c3 100644
--- a/clients/client-python/tests/unittests/dto/test_user_dto.py
+++ b/clients/client-python/tests/unittests/dto/test_user_dto.py
@@ -44,7 +44,13 @@ class TestUserDTO(unittest.TestCase):
         self.assertEqual(deser_dict["audit"]["creator"], "admin")
 
     def test_user_dto_without_roles(self):
-        user_dto = UserDTO.builder().with_name("test_user_no_roles").build()
+        audit = AuditDTO(
+            _creator="admin",
+            _create_time="2024-01-01T00:00:00Z",
+        )
+        user_dto = (
+            
UserDTO.builder().with_name("test_user_no_roles").with_audit(audit).build()
+        )
 
         ser_json = _json.dumps(user_dto.to_dict()).encode("utf-8")
         deser_dict = _json.loads(ser_json)
@@ -52,20 +58,29 @@ class TestUserDTO(unittest.TestCase):
         self.assertEqual(deser_dict["roles"], [])
 
     def test_user_dto_methods(self):
+        audit = AuditDTO(
+            _creator="admin",
+            _create_time="2024-01-01T00:00:00Z",
+        )
         user_dto = (
             UserDTO.builder()
             .with_name("method_user")
             .with_roles(["admin_role"])
+            .with_audit(audit)
             .build()
         )
         self.assertEqual(user_dto.name(), "method_user")
         self.assertEqual(user_dto.roles(), ["admin_role"])
-        self.assertIsNone(user_dto.audit_info())
+        self.assertIsNotNone(user_dto.audit_info())
 
     def test_builder_empty_name_raises(self):
         with self.assertRaises(ValueError):
             UserDTO.builder().build()
 
+    def test_builder_null_audit_raises(self):
+        with self.assertRaises(ValueError):
+            UserDTO.builder().with_name("test_user").build()
+
     def test_equality_and_hash(self):
         audit = AuditDTO(_creator="test", _create_time="2024-01-01T00:00:00Z")
 
@@ -96,11 +111,31 @@ class TestUserDTO(unittest.TestCase):
         self.assertNotEqual(user1, user3)
 
     def test_roles_returns_list_type(self):
-        user_dto = 
UserDTO.builder().with_name("test").with_roles(["r1"]).build()
+        audit = AuditDTO(
+            _creator="admin",
+            _create_time="2024-01-01T00:00:00Z",
+        )
+        user_dto = (
+            UserDTO.builder()
+            .with_name("test")
+            .with_roles(["r1"])
+            .with_audit(audit)
+            .build()
+        )
         self.assertIsInstance(user_dto.roles(), list)
 
     def test_roles_immutability(self):
-        user_dto = UserDTO.builder().with_name("test").with_roles(["r1", 
"r2"]).build()
+        audit = AuditDTO(
+            _creator="admin",
+            _create_time="2024-01-01T00:00:00Z",
+        )
+        user_dto = (
+            UserDTO.builder()
+            .with_name("test")
+            .with_roles(["r1", "r2"])
+            .with_audit(audit)
+            .build()
+        )
         roles = user_dto.roles()
         roles.append("r3")
         self.assertEqual(["r1", "r2"], user_dto.roles())

Reply via email to