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

jshao 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 67d316d04 [#5637] Add CLI unit tests to check if the right command has 
been called. (#5638)
67d316d04 is described below

commit 67d316d04fa08181d1f6242d5a7c13ac332398f2
Author: Justin Mclean <[email protected]>
AuthorDate: Fri Nov 22 18:20:33 2024 +1100

    [#5637] Add CLI unit tests to check if the right command has been called. 
(#5638)
    
    ### What changes were proposed in this pull request?
    
    Add CLI unit tests to check if the right command has been called.
    
    ### Why are the changes needed?
    
    To increate unit test coverage.
    
    Fix: #5637
    
    ### Does this PR introduce _any_ user-facing change?
    
    N/A
    
    ### How was this patch tested?
    
    Tested locally.
---
 .../apache/gravitino/cli/GravitinoCommandLine.java | 206 ++++------
 .../apache/gravitino/cli/TestableCommandLine.java  | 427 +++++++++++++++++++++
 .../apache/gravitino/cli/TestCatalogCommands.java  | 293 ++++++++++++++
 .../apache/gravitino/cli/TestColumnCommands.java   |  63 +++
 .../apache/gravitino/cli/TestGroupCommands.java    | 179 +++++++++
 .../apache/gravitino/cli/TestMetalakeCommands.java | 281 ++++++++++++++
 .../org/apache/gravitino/cli/TestRoleCommands.java | 135 +++++++
 .../apache/gravitino/cli/TestSchemaCommands.java   | 248 ++++++++++++
 .../apache/gravitino/cli/TestTableCommands.java    | 220 +++++++++++
 .../org/apache/gravitino/cli/TestTagCommands.java  | 339 ++++++++++++++++
 .../org/apache/gravitino/cli/TestUserCommands.java | 176 +++++++++
 11 files changed, 2429 insertions(+), 138 deletions(-)

diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
index b52aec133..c25b2f7e7 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
@@ -23,75 +23,9 @@ import java.util.Map;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Options;
-import org.apache.gravitino.cli.commands.AddRoleToGroup;
-import org.apache.gravitino.cli.commands.AddRoleToUser;
-import org.apache.gravitino.cli.commands.CatalogAudit;
-import org.apache.gravitino.cli.commands.CatalogDetails;
-import org.apache.gravitino.cli.commands.ClientVersion;
-import org.apache.gravitino.cli.commands.CreateCatalog;
-import org.apache.gravitino.cli.commands.CreateGroup;
-import org.apache.gravitino.cli.commands.CreateMetalake;
-import org.apache.gravitino.cli.commands.CreateRole;
-import org.apache.gravitino.cli.commands.CreateSchema;
-import org.apache.gravitino.cli.commands.CreateTag;
-import org.apache.gravitino.cli.commands.CreateUser;
-import org.apache.gravitino.cli.commands.DeleteCatalog;
-import org.apache.gravitino.cli.commands.DeleteGroup;
-import org.apache.gravitino.cli.commands.DeleteMetalake;
-import org.apache.gravitino.cli.commands.DeleteRole;
-import org.apache.gravitino.cli.commands.DeleteSchema;
-import org.apache.gravitino.cli.commands.DeleteTable;
-import org.apache.gravitino.cli.commands.DeleteTag;
-import org.apache.gravitino.cli.commands.DeleteUser;
-import org.apache.gravitino.cli.commands.GroupDetails;
-import org.apache.gravitino.cli.commands.ListAllTags;
-import org.apache.gravitino.cli.commands.ListCatalogProperties;
-import org.apache.gravitino.cli.commands.ListCatalogs;
-import org.apache.gravitino.cli.commands.ListColumns;
-import org.apache.gravitino.cli.commands.ListEntityTags;
-import org.apache.gravitino.cli.commands.ListGroups;
-import org.apache.gravitino.cli.commands.ListIndexes;
-import org.apache.gravitino.cli.commands.ListMetalakeProperties;
-import org.apache.gravitino.cli.commands.ListMetalakes;
-import org.apache.gravitino.cli.commands.ListRoles;
-import org.apache.gravitino.cli.commands.ListSchema;
-import org.apache.gravitino.cli.commands.ListSchemaProperties;
-import org.apache.gravitino.cli.commands.ListTables;
-import org.apache.gravitino.cli.commands.ListTagProperties;
-import org.apache.gravitino.cli.commands.ListUsers;
-import org.apache.gravitino.cli.commands.MetalakeAudit;
-import org.apache.gravitino.cli.commands.MetalakeDetails;
-import org.apache.gravitino.cli.commands.RemoveCatalogProperty;
-import org.apache.gravitino.cli.commands.RemoveMetalakeProperty;
-import org.apache.gravitino.cli.commands.RemoveRoleFromGroup;
-import org.apache.gravitino.cli.commands.RemoveRoleFromUser;
-import org.apache.gravitino.cli.commands.RemoveSchemaProperty;
-import org.apache.gravitino.cli.commands.RemoveTagProperty;
-import org.apache.gravitino.cli.commands.RoleDetails;
-import org.apache.gravitino.cli.commands.SchemaAudit;
-import org.apache.gravitino.cli.commands.SchemaDetails;
-import org.apache.gravitino.cli.commands.ServerVersion;
-import org.apache.gravitino.cli.commands.SetCatalogProperty;
-import org.apache.gravitino.cli.commands.SetMetalakeProperty;
-import org.apache.gravitino.cli.commands.SetSchemaProperty;
-import org.apache.gravitino.cli.commands.SetTagProperty;
-import org.apache.gravitino.cli.commands.TableAudit;
-import org.apache.gravitino.cli.commands.TableDetails;
-import org.apache.gravitino.cli.commands.TableDistribution;
-import org.apache.gravitino.cli.commands.TablePartition;
-import org.apache.gravitino.cli.commands.TagDetails;
-import org.apache.gravitino.cli.commands.TagEntity;
-import org.apache.gravitino.cli.commands.UntagEntity;
-import org.apache.gravitino.cli.commands.UpdateCatalogComment;
-import org.apache.gravitino.cli.commands.UpdateCatalogName;
-import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
-import org.apache.gravitino.cli.commands.UpdateMetalakeName;
-import org.apache.gravitino.cli.commands.UpdateTagComment;
-import org.apache.gravitino.cli.commands.UpdateTagName;
-import org.apache.gravitino.cli.commands.UserDetails;
 
 /* Gravitino Command line */
-public class GravitinoCommandLine {
+public class GravitinoCommandLine extends TestableCommandLine {
 
   private final CommandLine line;
   private final Options options;
@@ -156,11 +90,11 @@ public class GravitinoCommandLine {
     }
     /* Display Gravitino client version. */
     else if (line.hasOption(GravitinoOptions.VERSION)) {
-      new ClientVersion(getUrl(), ignore).handle();
+      newClientVersion(getUrl(), ignore).handle();
     }
     /* Display Gravitino server version. */
     else if (line.hasOption(GravitinoOptions.SERVER)) {
-      new ServerVersion(getUrl(), ignore).handle();
+      newServerVersion(getUrl(), ignore).handle();
     }
   }
 
@@ -207,36 +141,36 @@ public class GravitinoCommandLine {
 
     if (CommandActions.DETAILS.equals(command)) {
       if (line.hasOption(GravitinoOptions.AUDIT)) {
-        new MetalakeAudit(url, ignore, metalake).handle();
+        newMetalakeAudit(url, ignore, metalake).handle();
       } else {
-        new MetalakeDetails(url, ignore, metalake).handle();
+        newMetalakeDetails(url, ignore, metalake).handle();
       }
     } else if (CommandActions.LIST.equals(command)) {
-      new ListMetalakes(url, ignore).handle();
+      newListMetalakes(url, ignore).handle();
     } else if (CommandActions.CREATE.equals(command)) {
       String comment = line.getOptionValue(GravitinoOptions.COMMENT);
-      new CreateMetalake(url, ignore, metalake, comment).handle();
+      newCreateMetalake(url, ignore, metalake, comment).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteMetalake(url, ignore, force, metalake).handle();
+      newDeleteMetalake(url, ignore, force, metalake).handle();
     } else if (CommandActions.SET.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
       String value = line.getOptionValue(GravitinoOptions.VALUE);
-      new SetMetalakeProperty(url, ignore, metalake, property, value).handle();
+      newSetMetalakeProperty(url, ignore, metalake, property, value).handle();
     } else if (CommandActions.REMOVE.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
-      new RemoveMetalakeProperty(url, ignore, metalake, property).handle();
+      newRemoveMetalakeProperty(url, ignore, metalake, property).handle();
     } else if (CommandActions.PROPERTIES.equals(command)) {
-      new ListMetalakeProperties(url, ignore, metalake).handle();
+      newListMetalakeProperties(url, ignore, metalake).handle();
     } else if (CommandActions.UPDATE.equals(command)) {
       if (line.hasOption(GravitinoOptions.COMMENT)) {
         String comment = line.getOptionValue(GravitinoOptions.COMMENT);
-        new UpdateMetalakeComment(url, ignore, metalake, comment).handle();
+        newUpdateMetalakeComment(url, ignore, metalake, comment).handle();
       }
       if (line.hasOption(GravitinoOptions.RENAME)) {
         String newName = line.getOptionValue(GravitinoOptions.RENAME);
         boolean force = line.hasOption(GravitinoOptions.FORCE);
-        new UpdateMetalakeName(url, ignore, force, metalake, newName).handle();
+        newUpdateMetalakeName(url, ignore, force, metalake, newName).handle();
       }
     }
   }
@@ -250,7 +184,7 @@ public class GravitinoCommandLine {
     String metalake = name.getMetalakeName();
 
     if (CommandActions.LIST.equals(command)) {
-      new ListCatalogs(url, ignore, metalake).handle();
+      newListCatalogs(url, ignore, metalake).handle();
       return;
     }
 
@@ -258,36 +192,36 @@ public class GravitinoCommandLine {
 
     if (CommandActions.DETAILS.equals(command)) {
       if (line.hasOption(GravitinoOptions.AUDIT)) {
-        new CatalogAudit(url, ignore, metalake, catalog).handle();
+        newCatalogAudit(url, ignore, metalake, catalog).handle();
       } else {
-        new CatalogDetails(url, ignore, metalake, catalog).handle();
+        newCatalogDetails(url, ignore, metalake, catalog).handle();
       }
     } else if (CommandActions.CREATE.equals(command)) {
       String comment = line.getOptionValue(GravitinoOptions.COMMENT);
       String provider = line.getOptionValue(GravitinoOptions.PROVIDER);
       String[] properties = line.getOptionValues(GravitinoOptions.PROPERTIES);
       Map<String, String> propertyMap = new Properties().parse(properties);
-      new CreateCatalog(url, ignore, metalake, catalog, provider, comment, 
propertyMap).handle();
+      newCreateCatalog(url, ignore, metalake, catalog, provider, comment, 
propertyMap).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteCatalog(url, ignore, force, metalake, catalog).handle();
+      newDeleteCatalog(url, ignore, force, metalake, catalog).handle();
     } else if (CommandActions.SET.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
       String value = line.getOptionValue(GravitinoOptions.VALUE);
-      new SetCatalogProperty(url, ignore, metalake, catalog, property, 
value).handle();
+      newSetCatalogProperty(url, ignore, metalake, catalog, property, 
value).handle();
     } else if (CommandActions.REMOVE.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
-      new RemoveCatalogProperty(url, ignore, metalake, catalog, 
property).handle();
+      newRemoveCatalogProperty(url, ignore, metalake, catalog, 
property).handle();
     } else if (CommandActions.PROPERTIES.equals(command)) {
-      new ListCatalogProperties(url, ignore, metalake, catalog).handle();
+      newListCatalogProperties(url, ignore, metalake, catalog).handle();
     } else if (CommandActions.UPDATE.equals(command)) {
       if (line.hasOption(GravitinoOptions.COMMENT)) {
         String comment = line.getOptionValue(GravitinoOptions.COMMENT);
-        new UpdateCatalogComment(url, ignore, metalake, catalog, 
comment).handle();
+        newUpdateCatalogComment(url, ignore, metalake, catalog, 
comment).handle();
       }
       if (line.hasOption(GravitinoOptions.RENAME)) {
         String newName = line.getOptionValue(GravitinoOptions.RENAME);
-        new UpdateCatalogName(url, ignore, metalake, catalog, 
newName).handle();
+        newUpdateCatalogName(url, ignore, metalake, catalog, newName).handle();
       }
     }
   }
@@ -302,7 +236,7 @@ public class GravitinoCommandLine {
     String catalog = name.getCatalogName();
 
     if (CommandActions.LIST.equals(command)) {
-      new ListSchema(url, ignore, metalake, catalog).handle();
+      newListSchema(url, ignore, metalake, catalog).handle();
       return;
     }
 
@@ -310,25 +244,25 @@ public class GravitinoCommandLine {
 
     if (CommandActions.DETAILS.equals(command)) {
       if (line.hasOption(GravitinoOptions.AUDIT)) {
-        new SchemaAudit(url, ignore, metalake, catalog, schema).handle();
+        newSchemaAudit(url, ignore, metalake, catalog, schema).handle();
       } else {
-        new SchemaDetails(url, ignore, metalake, catalog, schema).handle();
+        newSchemaDetails(url, ignore, metalake, catalog, schema).handle();
       }
     } else if (CommandActions.CREATE.equals(command)) {
       String comment = line.getOptionValue(GravitinoOptions.COMMENT);
-      new CreateSchema(url, ignore, metalake, catalog, schema, 
comment).handle();
+      newCreateSchema(url, ignore, metalake, catalog, schema, 
comment).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteSchema(url, ignore, force, metalake, catalog, schema).handle();
+      newDeleteSchema(url, ignore, force, metalake, catalog, schema).handle();
     } else if (CommandActions.SET.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
       String value = line.getOptionValue(GravitinoOptions.VALUE);
-      new SetSchemaProperty(url, ignore, metalake, catalog, schema, property, 
value).handle();
+      newSetSchemaProperty(url, ignore, metalake, catalog, schema, property, 
value).handle();
     } else if (CommandActions.REMOVE.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
-      new RemoveSchemaProperty(url, ignore, metalake, catalog, schema, 
property).handle();
+      newRemoveSchemaProperty(url, ignore, metalake, catalog, schema, 
property).handle();
     } else if (CommandActions.PROPERTIES.equals(command)) {
-      new ListSchemaProperties(url, ignore, metalake, catalog, 
schema).handle();
+      newListSchemaProperties(url, ignore, metalake, catalog, schema).handle();
     }
   }
 
@@ -343,7 +277,7 @@ public class GravitinoCommandLine {
     String schema = name.getSchemaName();
 
     if (CommandActions.LIST.equals(command)) {
-      new ListTables(url, ignore, metalake, catalog, schema).handle();
+      newListTables(url, ignore, metalake, catalog, schema).handle();
       return;
     }
 
@@ -351,21 +285,21 @@ public class GravitinoCommandLine {
 
     if (CommandActions.DETAILS.equals(command)) {
       if (line.hasOption(GravitinoOptions.AUDIT)) {
-        new TableAudit(url, ignore, metalake, catalog, schema, table).handle();
+        newTableAudit(url, ignore, metalake, catalog, schema, table).handle();
       } else if (line.hasOption(GravitinoOptions.INDEX)) {
-        new ListIndexes(url, ignore, metalake, catalog, schema, 
table).handle();
+        newListIndexes(url, ignore, metalake, catalog, schema, table).handle();
       } else if (line.hasOption(GravitinoOptions.DISTRIBUTION)) {
-        new TableDistribution(url, ignore, metalake, catalog, schema, 
table).handle();
+        newTableDistribution(url, ignore, metalake, catalog, schema, 
table).handle();
       } else if (line.hasOption(GravitinoOptions.PARTITION)) {
-        new TablePartition(url, ignore, metalake, catalog, schema, 
table).handle();
+        newTablePartition(url, ignore, metalake, catalog, schema, 
table).handle();
       } else {
-        new TableDetails(url, ignore, metalake, catalog, schema, 
table).handle();
+        newTableDetails(url, ignore, metalake, catalog, schema, 
table).handle();
       }
     } else if (CommandActions.CREATE.equals(command)) {
       // TODO
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteTable(url, ignore, force, metalake, catalog, schema, 
table).handle();
+      newDeleteTable(url, ignore, force, metalake, catalog, schema, 
table).handle();
     }
   }
 
@@ -377,23 +311,23 @@ public class GravitinoCommandLine {
     String user = line.getOptionValue(GravitinoOptions.USER);
 
     if (CommandActions.DETAILS.equals(command)) {
-      new UserDetails(url, ignore, metalake, user).handle();
+      newUserDetails(url, ignore, metalake, user).handle();
     } else if (CommandActions.LIST.equals(command)) {
-      new ListUsers(url, ignore, metalake).handle();
+      newListUsers(url, ignore, metalake).handle();
     } else if (CommandActions.CREATE.equals(command)) {
-      new CreateUser(url, ignore, metalake, user).handle();
+      newCreateUser(url, ignore, metalake, user).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteUser(url, ignore, force, metalake, user).handle();
+      newDeleteUser(url, ignore, force, metalake, user).handle();
     } else if (CommandActions.REVOKE.equals(command)) {
       String role = line.getOptionValue(GravitinoOptions.ROLE);
       if (role != null) {
-        new RemoveRoleFromUser(url, ignore, metalake, user, role).handle();
+        newRemoveRoleFromUser(url, ignore, metalake, user, role).handle();
       }
     } else if (CommandActions.GRANT.equals(command)) {
       String role = line.getOptionValue(GravitinoOptions.ROLE);
       if (role != null) {
-        new AddRoleToUser(url, ignore, metalake, user, role).handle();
+        newAddRoleToUser(url, ignore, metalake, user, role).handle();
       }
     }
   }
@@ -406,23 +340,23 @@ public class GravitinoCommandLine {
     String group = line.getOptionValue(GravitinoOptions.GROUP);
 
     if (CommandActions.DETAILS.equals(command)) {
-      new GroupDetails(url, ignore, metalake, group).handle();
+      newGroupDetails(url, ignore, metalake, group).handle();
     } else if (CommandActions.LIST.equals(command)) {
-      new ListGroups(url, ignore, metalake).handle();
+      newListGroups(url, ignore, metalake).handle();
     } else if (CommandActions.CREATE.equals(command)) {
-      new CreateGroup(url, ignore, metalake, group).handle();
+      newCreateGroup(url, ignore, metalake, group).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteGroup(url, ignore, force, metalake, group).handle();
+      newDeleteGroup(url, ignore, force, metalake, group).handle();
     } else if (CommandActions.REVOKE.equals(command)) {
       String role = line.getOptionValue(GravitinoOptions.ROLE);
       if (role != null) {
-        new RemoveRoleFromGroup(url, ignore, metalake, group, role).handle();
+        newRemoveRoleFromGroup(url, ignore, metalake, group, role).handle();
       }
     } else if (CommandActions.GRANT.equals(command)) {
       String role = line.getOptionValue(GravitinoOptions.ROLE);
       if (role != null) {
-        new AddRoleToGroup(url, ignore, metalake, group, role).handle();
+        newAddRoleToGroup(url, ignore, metalake, group, role).handle();
       }
     }
   }
@@ -435,49 +369,45 @@ public class GravitinoCommandLine {
     String tag = line.getOptionValue(GravitinoOptions.TAG);
 
     if (CommandActions.DETAILS.equals(command)) {
-      new TagDetails(url, ignore, metalake, tag).handle();
+      newTagDetails(url, ignore, metalake, tag).handle();
     } else if (CommandActions.LIST.equals(command)) {
       if (!name.hasCatalogName()) {
-        new ListAllTags(url, ignore, metalake).handle();
+        newListTags(url, ignore, metalake).handle();
       } else {
-        new ListEntityTags(url, ignore, metalake, name).handle();
+        newListEntityTags(url, ignore, metalake, name).handle();
       }
     } else if (CommandActions.CREATE.equals(command)) {
       String comment = line.getOptionValue(GravitinoOptions.COMMENT);
-      new CreateTag(url, ignore, metalake, tag, comment).handle();
+      newCreateTag(url, ignore, metalake, tag, comment).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteTag(url, ignore, force, metalake, tag).handle();
+      newDeleteTag(url, ignore, force, metalake, tag).handle();
     } else if (CommandActions.SET.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
       String value = line.getOptionValue(GravitinoOptions.VALUE);
 
-      if (name == null && property != null && value != null) {
-        new SetTagProperty(url, ignore, metalake, tag, property, 
value).handle();
+      if (property != null && value != null) {
+        newSetTagProperty(url, ignore, metalake, tag, property, 
value).handle();
       } else if (name != null && property == null && value == null) {
-        new TagEntity(url, ignore, metalake, name, tag).handle();
-      } else {
-        System.err.println(ErrorMessages.INVALID_SET_COMMAND);
+        newTagEntity(url, ignore, metalake, name, tag).handle();
       }
     } else if (CommandActions.REMOVE.equals(command)) {
       String property = line.getOptionValue(GravitinoOptions.PROPERTY);
       if (property != null) {
-        new RemoveTagProperty(url, ignore, metalake, tag, property).handle();
-      } else if (name != null) {
-        new UntagEntity(url, ignore, metalake, name, tag).handle();
+        newRemoveTagProperty(url, ignore, metalake, tag, property).handle();
       } else {
-        System.err.println(ErrorMessages.INVALID_REMOVE_COMMAND);
+        newUntagEntity(url, ignore, metalake, name, tag).handle();
       }
     } else if (CommandActions.PROPERTIES.equals(command)) {
-      new ListTagProperties(url, ignore, metalake, tag).handle();
+      newListTagProperties(url, ignore, metalake, tag).handle();
     } else if (CommandActions.UPDATE.equals(command)) {
       if (line.hasOption(GravitinoOptions.COMMENT)) {
         String comment = line.getOptionValue(GravitinoOptions.COMMENT);
-        new UpdateTagComment(url, ignore, metalake, tag, comment).handle();
+        newUpdateTagComment(url, ignore, metalake, tag, comment).handle();
       }
       if (line.hasOption(GravitinoOptions.RENAME)) {
         String newName = line.getOptionValue(GravitinoOptions.RENAME);
-        new UpdateTagName(url, ignore, metalake, tag, newName).handle();
+        newUpdateTagName(url, ignore, metalake, tag, newName).handle();
       }
     }
   }
@@ -490,14 +420,14 @@ public class GravitinoCommandLine {
     String role = line.getOptionValue(GravitinoOptions.ROLE);
 
     if (CommandActions.DETAILS.equals(command)) {
-      new RoleDetails(url, ignore, metalake, role).handle();
+      newRoleDetails(url, ignore, metalake, role).handle();
     } else if (CommandActions.LIST.equals(command)) {
-      new ListRoles(url, ignore, metalake).handle();
+      newListRoles(url, ignore, metalake).handle();
     } else if (CommandActions.CREATE.equals(command)) {
-      new CreateRole(url, ignore, metalake, role).handle();
+      newCreateRole(url, ignore, metalake, role).handle();
     } else if (CommandActions.DELETE.equals(command)) {
       boolean force = line.hasOption(GravitinoOptions.FORCE);
-      new DeleteRole(url, ignore, force, metalake, role).handle();
+      newDeleteRole(url, ignore, force, metalake, role).handle();
     }
   }
 
@@ -513,7 +443,7 @@ public class GravitinoCommandLine {
     String table = name.getTableName();
 
     if (CommandActions.LIST.equals(command)) {
-      new ListColumns(url, ignore, metalake, catalog, schema, table).handle();
+      newListColumns(url, ignore, metalake, catalog, schema, table).handle();
     }
   }
 
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
new file mode 100644
index 000000000..85bfa3203
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
@@ -0,0 +1,427 @@
+/* Methods used for testing - basically a seam where we can see what it passed 
and use mocks to see if the correct command is created. */
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import java.util.Map;
+import org.apache.gravitino.cli.commands.AddRoleToGroup;
+import org.apache.gravitino.cli.commands.AddRoleToUser;
+import org.apache.gravitino.cli.commands.CatalogAudit;
+import org.apache.gravitino.cli.commands.CatalogDetails;
+import org.apache.gravitino.cli.commands.ClientVersion;
+import org.apache.gravitino.cli.commands.CreateCatalog;
+import org.apache.gravitino.cli.commands.CreateGroup;
+import org.apache.gravitino.cli.commands.CreateMetalake;
+import org.apache.gravitino.cli.commands.CreateRole;
+import org.apache.gravitino.cli.commands.CreateSchema;
+import org.apache.gravitino.cli.commands.CreateTag;
+import org.apache.gravitino.cli.commands.CreateUser;
+import org.apache.gravitino.cli.commands.DeleteCatalog;
+import org.apache.gravitino.cli.commands.DeleteGroup;
+import org.apache.gravitino.cli.commands.DeleteMetalake;
+import org.apache.gravitino.cli.commands.DeleteRole;
+import org.apache.gravitino.cli.commands.DeleteSchema;
+import org.apache.gravitino.cli.commands.DeleteTable;
+import org.apache.gravitino.cli.commands.DeleteTag;
+import org.apache.gravitino.cli.commands.DeleteUser;
+import org.apache.gravitino.cli.commands.GroupDetails;
+import org.apache.gravitino.cli.commands.ListAllTags;
+import org.apache.gravitino.cli.commands.ListCatalogProperties;
+import org.apache.gravitino.cli.commands.ListCatalogs;
+import org.apache.gravitino.cli.commands.ListColumns;
+import org.apache.gravitino.cli.commands.ListEntityTags;
+import org.apache.gravitino.cli.commands.ListGroups;
+import org.apache.gravitino.cli.commands.ListIndexes;
+import org.apache.gravitino.cli.commands.ListMetalakeProperties;
+import org.apache.gravitino.cli.commands.ListMetalakes;
+import org.apache.gravitino.cli.commands.ListRoles;
+import org.apache.gravitino.cli.commands.ListSchema;
+import org.apache.gravitino.cli.commands.ListSchemaProperties;
+import org.apache.gravitino.cli.commands.ListTables;
+import org.apache.gravitino.cli.commands.ListTagProperties;
+import org.apache.gravitino.cli.commands.ListUsers;
+import org.apache.gravitino.cli.commands.MetalakeAudit;
+import org.apache.gravitino.cli.commands.MetalakeDetails;
+import org.apache.gravitino.cli.commands.RemoveCatalogProperty;
+import org.apache.gravitino.cli.commands.RemoveMetalakeProperty;
+import org.apache.gravitino.cli.commands.RemoveRoleFromGroup;
+import org.apache.gravitino.cli.commands.RemoveRoleFromUser;
+import org.apache.gravitino.cli.commands.RemoveSchemaProperty;
+import org.apache.gravitino.cli.commands.RemoveTagProperty;
+import org.apache.gravitino.cli.commands.RoleDetails;
+import org.apache.gravitino.cli.commands.SchemaAudit;
+import org.apache.gravitino.cli.commands.SchemaDetails;
+import org.apache.gravitino.cli.commands.ServerVersion;
+import org.apache.gravitino.cli.commands.SetCatalogProperty;
+import org.apache.gravitino.cli.commands.SetMetalakeProperty;
+import org.apache.gravitino.cli.commands.SetSchemaProperty;
+import org.apache.gravitino.cli.commands.SetTagProperty;
+import org.apache.gravitino.cli.commands.TableAudit;
+import org.apache.gravitino.cli.commands.TableDetails;
+import org.apache.gravitino.cli.commands.TableDistribution;
+import org.apache.gravitino.cli.commands.TablePartition;
+import org.apache.gravitino.cli.commands.TagDetails;
+import org.apache.gravitino.cli.commands.TagEntity;
+import org.apache.gravitino.cli.commands.UntagEntity;
+import org.apache.gravitino.cli.commands.UpdateCatalogComment;
+import org.apache.gravitino.cli.commands.UpdateCatalogName;
+import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
+import org.apache.gravitino.cli.commands.UpdateMetalakeName;
+import org.apache.gravitino.cli.commands.UpdateTagComment;
+import org.apache.gravitino.cli.commands.UpdateTagName;
+import org.apache.gravitino.cli.commands.UserDetails;
+
+/*
+ * Methods used for testing
+ *
+ * Basically a seam where we can see what is passed and use mocks to see if 
the correct command is created.
+ */
+public class TestableCommandLine {
+
+  protected ClientVersion newClientVersion(String url, boolean ignore) {
+    return new ClientVersion(url, ignore);
+  }
+
+  protected ServerVersion newServerVersion(String url, boolean ignore) {
+    return new ServerVersion(url, ignore);
+  }
+
+  protected MetalakeAudit newMetalakeAudit(String url, boolean ignore, String 
metalake) {
+    return new MetalakeAudit(url, ignore, metalake);
+  }
+
+  protected MetalakeDetails newMetalakeDetails(String url, boolean ignore, 
String metalake) {
+    return new MetalakeDetails(url, ignore, metalake);
+  }
+
+  protected ListMetalakes newListMetalakes(String url, boolean ignore) {
+    return new ListMetalakes(url, ignore);
+  }
+
+  protected CreateMetalake newCreateMetalake(
+      String url, boolean ignore, String metalake, String comment) {
+    return new CreateMetalake(url, ignore, metalake, comment);
+  }
+
+  protected DeleteMetalake newDeleteMetalake(
+      String url, boolean ignore, boolean force, String metalake) {
+    return new DeleteMetalake(url, ignore, force, metalake);
+  }
+
+  protected SetMetalakeProperty newSetMetalakeProperty(
+      String url, boolean ignore, String metalake, String property, String 
value) {
+    return new SetMetalakeProperty(url, ignore, metalake, property, value);
+  }
+
+  protected RemoveMetalakeProperty newRemoveMetalakeProperty(
+      String url, boolean ignore, String metalake, String property) {
+    return new RemoveMetalakeProperty(url, ignore, metalake, property);
+  }
+
+  protected ListMetalakeProperties newListMetalakeProperties(
+      String url, boolean ignore, String metalake) {
+    return new ListMetalakeProperties(url, ignore, metalake);
+  }
+
+  protected UpdateMetalakeComment newUpdateMetalakeComment(
+      String url, boolean ignore, String metalake, String comment) {
+    return new UpdateMetalakeComment(url, ignore, metalake, comment);
+  }
+
+  protected UpdateMetalakeName newUpdateMetalakeName(
+      String url, boolean ignore, boolean force, String metalake, String 
newName) {
+    return new UpdateMetalakeName(url, ignore, force, metalake, newName);
+  }
+
+  protected CatalogAudit newCatalogAudit(
+      String url, boolean ignore, String metalake, String catalog) {
+    return new CatalogAudit(url, ignore, metalake, catalog);
+  }
+
+  protected CatalogDetails newCatalogDetails(
+      String url, boolean ignore, String metalake, String catalog) {
+    return new CatalogDetails(url, ignore, metalake, catalog);
+  }
+
+  protected ListCatalogs newListCatalogs(String url, boolean ignore, String 
metalake) {
+    return new ListCatalogs(url, ignore, metalake);
+  }
+
+  protected CreateCatalog newCreateCatalog(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
+    return new CreateCatalog(url, ignore, metalake, catalog, provider, 
comment, properties);
+  }
+
+  protected DeleteCatalog newDeleteCatalog(
+      String url, boolean ignore, boolean force, String metalake, String 
catalog) {
+    return new DeleteCatalog(url, ignore, force, metalake, catalog);
+  }
+
+  protected SetCatalogProperty newSetCatalogProperty(
+      String url, boolean ignore, String metalake, String catalog, String 
property, String value) {
+    return new SetCatalogProperty(url, ignore, metalake, catalog, property, 
value);
+  }
+
+  protected RemoveCatalogProperty newRemoveCatalogProperty(
+      String url, boolean ignore, String metalake, String catalog, String 
property) {
+    return new RemoveCatalogProperty(url, ignore, metalake, catalog, property);
+  }
+
+  protected ListCatalogProperties newListCatalogProperties(
+      String url, boolean ignore, String metalake, String catalog) {
+    return new ListCatalogProperties(url, ignore, metalake, catalog);
+  }
+
+  protected UpdateCatalogComment newUpdateCatalogComment(
+      String url, boolean ignore, String metalake, String catalog, String 
comment) {
+    return new UpdateCatalogComment(url, ignore, metalake, catalog, comment);
+  }
+
+  protected UpdateCatalogName newUpdateCatalogName(
+      String url, boolean ignore, String metalake, String catalog, String 
newName) {
+    return new UpdateCatalogName(url, ignore, metalake, catalog, newName);
+  }
+
+  protected SchemaAudit newSchemaAudit(
+      String url, boolean ignore, String metalake, String catalog, String 
schema) {
+    return new SchemaAudit(url, ignore, metalake, catalog, schema);
+  }
+
+  protected SchemaDetails newSchemaDetails(
+      String url, boolean ignore, String metalake, String catalog, String 
schema) {
+    return new SchemaDetails(url, ignore, metalake, catalog, schema);
+  }
+
+  protected ListSchema newListSchema(String url, boolean ignore, String 
metalake, String catalog) {
+    return new ListSchema(url, ignore, metalake, catalog);
+  }
+
+  protected CreateSchema newCreateSchema(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String comment) {
+    return new CreateSchema(url, ignore, metalake, catalog, schema, comment);
+  }
+
+  protected DeleteSchema newDeleteSchema(
+      String url, boolean ignore, boolean force, String metalake, String 
catalog, String schema) {
+    return new DeleteSchema(url, ignore, force, metalake, catalog, schema);
+  }
+
+  protected SetSchemaProperty newSetSchemaProperty(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String property,
+      String value) {
+    return new SetSchemaProperty(url, ignore, metalake, catalog, schema, 
property, value);
+  }
+
+  protected RemoveSchemaProperty newRemoveSchemaProperty(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String property) {
+    return new RemoveSchemaProperty(url, ignore, metalake, catalog, schema, 
property);
+  }
+
+  protected ListSchemaProperties newListSchemaProperties(
+      String url, boolean ignore, String metalake, String catalog, String 
schema) {
+    return new ListSchemaProperties(url, ignore, metalake, catalog, schema);
+  }
+
+  protected TableAudit newTableAudit(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
+    return new TableAudit(url, ignore, metalake, catalog, schema, table);
+  }
+
+  protected TableDetails newTableDetails(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
+    return new TableDetails(url, ignore, metalake, catalog, schema, table);
+  }
+
+  protected ListTables newListTables(
+      String url, boolean ignore, String metalake, String catalog, String 
table) {
+    return new ListTables(url, ignore, metalake, catalog, table);
+  }
+
+  protected DeleteTable newDeleteTable(
+      String url,
+      boolean ignore,
+      boolean force,
+      String metalake,
+      String catalog,
+      String schema,
+      String table) {
+    return new DeleteTable(url, ignore, force, metalake, catalog, schema, 
table);
+  }
+
+  protected ListIndexes newListIndexes(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
+    return new ListIndexes(url, ignore, metalake, catalog, schema, table);
+  }
+
+  protected TablePartition newTablePartition(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
+    return new TablePartition(url, ignore, metalake, catalog, schema, table);
+  }
+
+  protected TableDistribution newTableDistribution(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
+    return new TableDistribution(url, ignore, metalake, catalog, schema, 
table);
+  }
+
+  protected UserDetails newUserDetails(String url, boolean ignore, String 
metalake, String user) {
+    return new UserDetails(url, ignore, metalake, user);
+  }
+
+  protected ListUsers newListUsers(String url, boolean ignore, String 
metalake) {
+    return new ListUsers(url, ignore, metalake);
+  }
+
+  protected CreateUser newCreateUser(String url, boolean ignore, String 
metalake, String user) {
+    return new CreateUser(url, ignore, metalake, user);
+  }
+
+  protected DeleteUser newDeleteUser(
+      String url, boolean ignore, boolean force, String metalake, String user) 
{
+    return new DeleteUser(url, ignore, force, metalake, user);
+  }
+
+  protected RemoveRoleFromUser newRemoveRoleFromUser(
+      String url, boolean ignore, String metalake, String user, String role) {
+    return new RemoveRoleFromUser(url, ignore, metalake, user, role);
+  }
+
+  protected AddRoleToUser newAddRoleToUser(
+      String url, boolean ignore, String metalake, String user, String role) {
+    return new AddRoleToUser(url, ignore, metalake, user, role);
+  }
+
+  protected GroupDetails newGroupDetails(String url, boolean ignore, String 
metalake, String user) {
+    return new GroupDetails(url, ignore, metalake, user);
+  }
+
+  protected ListGroups newListGroups(String url, boolean ignore, String 
metalake) {
+    return new ListGroups(url, ignore, metalake);
+  }
+
+  protected CreateGroup newCreateGroup(String url, boolean ignore, String 
metalake, String user) {
+    return new CreateGroup(url, ignore, metalake, user);
+  }
+
+  protected DeleteGroup newDeleteGroup(
+      String url, boolean ignore, boolean force, String metalake, String user) 
{
+    return new DeleteGroup(url, ignore, force, metalake, user);
+  }
+
+  protected RemoveRoleFromGroup newRemoveRoleFromGroup(
+      String url, boolean ignore, String metalake, String user, String role) {
+    return new RemoveRoleFromGroup(url, ignore, metalake, user, role);
+  }
+
+  protected AddRoleToGroup newAddRoleToGroup(
+      String url, boolean ignore, String metalake, String user, String role) {
+    return new AddRoleToGroup(url, ignore, metalake, user, role);
+  }
+
+  protected RoleDetails newRoleDetails(String url, boolean ignore, String 
metalake, String role) {
+    return new RoleDetails(url, ignore, metalake, role);
+  }
+
+  protected ListRoles newListRoles(String url, boolean ignore, String 
metalake) {
+    return new ListRoles(url, ignore, metalake);
+  }
+
+  protected CreateRole newCreateRole(String url, boolean ignore, String 
metalake, String role) {
+    return new CreateRole(url, ignore, metalake, role);
+  }
+
+  protected DeleteRole newDeleteRole(
+      String url, boolean ignore, boolean force, String metalake, String role) 
{
+    return new DeleteRole(url, ignore, force, metalake, role);
+  }
+
+  protected TagDetails newTagDetails(String url, boolean ignore, String 
metalake, String tag) {
+    return new TagDetails(url, ignore, metalake, tag);
+  }
+
+  protected ListAllTags newListTags(String url, boolean ignore, String 
metalake) {
+    return new ListAllTags(url, ignore, metalake);
+  }
+
+  protected CreateTag newCreateTag(
+      String url, boolean ignore, String metalake, String tag, String comment) 
{
+    return new CreateTag(url, ignore, metalake, tag, comment);
+  }
+
+  protected DeleteTag newDeleteTag(
+      String url, boolean ignore, boolean force, String metalake, String tag) {
+    return new DeleteTag(url, ignore, force, metalake, tag);
+  }
+
+  protected SetTagProperty newSetTagProperty(
+      String url, boolean ignore, String metalake, String tag, String 
property, String value) {
+    return new SetTagProperty(url, ignore, metalake, tag, property, value);
+  }
+
+  protected RemoveTagProperty newRemoveTagProperty(
+      String url, boolean ignore, String metalake, String tag, String 
property) {
+    return new RemoveTagProperty(url, ignore, metalake, tag, property);
+  }
+
+  protected ListTagProperties newListTagProperties(
+      String url, boolean ignore, String metalake, String tag) {
+    return new ListTagProperties(url, ignore, metalake, tag);
+  }
+
+  protected UpdateTagComment newUpdateTagComment(
+      String url, boolean ignore, String metalake, String tag, String comment) 
{
+    return new UpdateTagComment(url, ignore, metalake, tag, comment);
+  }
+
+  protected UpdateTagName newUpdateTagName(
+      String url, boolean ignore, String metalake, String tag, String newName) 
{
+    return new UpdateTagName(url, ignore, metalake, tag, newName);
+  }
+
+  protected ListEntityTags newListEntityTags(
+      String url, boolean ignore, String metalake, FullName name) {
+    return new ListEntityTags(url, ignore, metalake, name);
+  }
+
+  protected TagEntity newTagEntity(
+      String url, boolean ignore, String metalake, FullName name, String tag) {
+    return new TagEntity(url, ignore, metalake, name, tag);
+  }
+
+  protected UntagEntity newUntagEntity(
+      String url, boolean ignore, String metalake, FullName name, String tag) {
+    return new UntagEntity(url, ignore, metalake, name, tag);
+  }
+
+  protected ListColumns newListColumns(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String table) {
+    return new ListColumns(url, ignore, metalake, catalog, schema, table);
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCatalogCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCatalogCommands.java
new file mode 100644
index 000000000..c7a24b7a3
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCatalogCommands.java
@@ -0,0 +1,293 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CatalogAudit;
+import org.apache.gravitino.cli.commands.CatalogDetails;
+import org.apache.gravitino.cli.commands.CreateCatalog;
+import org.apache.gravitino.cli.commands.DeleteCatalog;
+import org.apache.gravitino.cli.commands.ListCatalogProperties;
+import org.apache.gravitino.cli.commands.ListCatalogs;
+import org.apache.gravitino.cli.commands.RemoveCatalogProperty;
+import org.apache.gravitino.cli.commands.SetCatalogProperty;
+import org.apache.gravitino.cli.commands.UpdateCatalogComment;
+import org.apache.gravitino.cli.commands.UpdateCatalogName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestCatalogCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListCatalogsCommand() {
+    ListCatalogs mockList = mock(ListCatalogs.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListCatalogs(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testCatalogDetailsCommand() {
+    CatalogDetails mockDetails = mock(CatalogDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(false);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newCatalogDetails(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "catalog");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testCatalogAuditCommand() {
+    CatalogAudit mockAudit = mock(CatalogAudit.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.DETAILS));
+    doReturn(mockAudit)
+        .when(commandLine)
+        .newCatalogAudit(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "catalog");
+    commandLine.handleCommandLine();
+    verify(mockAudit).handle();
+  }
+
+  @Test
+  void testCreateCatalogCommand() {
+    HashMap<String, String> map = new HashMap<>();
+    CreateCatalog mockCreate = mock(CreateCatalog.class);
+    String[] props = {"key1=value1", "key2=value2"};
+    map.put("key1", "value1");
+    map.put("key2", "value2");
+
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROVIDER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROVIDER)).thenReturn("postgres");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTIES)).thenReturn(true);
+    
when(mockCommandLine.getOptionValues(GravitinoOptions.PROPERTIES)).thenReturn(props);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateCatalog(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "postgres",
+            "comment",
+            map);
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteCatalogCommand() {
+    DeleteCatalog mockDelete = mock(DeleteCatalog.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteCatalog(
+            GravitinoCommandLine.DEFAULT_URL, false, false, "metalake_demo", 
"catalog");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteCatalogForceCommand() {
+    DeleteCatalog mockDelete = mock(DeleteCatalog.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteCatalog(
+            GravitinoCommandLine.DEFAULT_URL, false, true, "metalake_demo", 
"catalog");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testSetCatalogPropertyCommand() {
+    SetCatalogProperty mockSetProperty = mock(SetCatalogProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    when(mockCommandLine.hasOption(GravitinoOptions.VALUE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.VALUE)).thenReturn("value");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.SET));
+    doReturn(mockSetProperty)
+        .when(commandLine)
+        .newSetCatalogProperty(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "property",
+            "value");
+    commandLine.handleCommandLine();
+    verify(mockSetProperty).handle();
+  }
+
+  @Test
+  void testRemoveCatalogPropertyCommand() {
+    RemoveCatalogProperty mockRemoveProperty = 
mock(RemoveCatalogProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.REMOVE));
+    doReturn(mockRemoveProperty)
+        .when(commandLine)
+        .newRemoveCatalogProperty(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "property");
+    commandLine.handleCommandLine();
+    verify(mockRemoveProperty).handle();
+  }
+
+  @Test
+  void testListCatalogPropertiesCommand() {
+    ListCatalogProperties mockListProperties = 
mock(ListCatalogProperties.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.PROPERTIES));
+    doReturn(mockListProperties)
+        .when(commandLine)
+        .newListCatalogProperties(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog");
+    commandLine.handleCommandLine();
+    verify(mockListProperties).handle();
+  }
+
+  @Test
+  void testUpdateCatalogCommentCommand() {
+    UpdateCatalogComment mockUpdateComment = mock(UpdateCatalogComment.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("new 
comment");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.UPDATE));
+    doReturn(mockUpdateComment)
+        .when(commandLine)
+        .newUpdateCatalogComment(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "new comment");
+    commandLine.handleCommandLine();
+    verify(mockUpdateComment).handle();
+  }
+
+  @Test
+  void testUpdateCatalogNameCommand() {
+    UpdateCatalogName mockUpdateName = mock(UpdateCatalogName.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    when(mockCommandLine.hasOption(GravitinoOptions.RENAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.RENAME)).thenReturn("new_name");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.CATALOG, 
CommandActions.UPDATE));
+    doReturn(mockUpdateName)
+        .when(commandLine)
+        .newUpdateCatalogName(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "new_name");
+    commandLine.handleCommandLine();
+    verify(mockUpdateName).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java
new file mode 100644
index 000000000..b7e091ebd
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.ListColumns;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestColumnCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListColumnsCommand() {
+    ListColumns mockList = mock(ListColumns.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListColumns(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema", "users");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestGroupCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGroupCommands.java
new file mode 100644
index 000000000..59af42803
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGroupCommands.java
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.AddRoleToGroup;
+import org.apache.gravitino.cli.commands.CreateGroup;
+import org.apache.gravitino.cli.commands.DeleteGroup;
+import org.apache.gravitino.cli.commands.GroupDetails;
+import org.apache.gravitino.cli.commands.ListGroups;
+import org.apache.gravitino.cli.commands.RemoveRoleFromGroup;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestGroupCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListGroupsCommand() {
+    ListGroups mockList = mock(ListGroups.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListGroups(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testGroupDetailsCommand() {
+    GroupDetails mockDetails = mock(GroupDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("groupA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newGroupDetails(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "groupA");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testCreateGroupCommand() {
+    CreateGroup mockCreate = mock(CreateGroup.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("groupA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateGroup(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "groupA");
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteGroupCommand() {
+    DeleteGroup mockDelete = mock(DeleteGroup.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("groupA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteGroup(GravitinoCommandLine.DEFAULT_URL, false, false, 
"metalake_demo", "groupA");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteGroupForceCommand() {
+    DeleteGroup mockDelete = mock(DeleteGroup.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("groupA");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteGroup(GravitinoCommandLine.DEFAULT_URL, false, true, 
"metalake_demo", "groupA");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testRemoveRoleFromGroupCommand() {
+    RemoveRoleFromGroup mockRemove = mock(RemoveRoleFromGroup.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("groupA");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.REVOKE));
+    doReturn(mockRemove)
+        .when(commandLine)
+        .newRemoveRoleFromGroup(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"groupA", "admin");
+    commandLine.handleCommandLine();
+    verify(mockRemove).handle();
+  }
+
+  @Test
+  void testAddRoleToGroupCommand() {
+    AddRoleToGroup mockAdd = mock(AddRoleToGroup.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.GROUP)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.GROUP)).thenReturn("groupA");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.GROUP, 
CommandActions.GRANT));
+    doReturn(mockAdd)
+        .when(commandLine)
+        .newAddRoleToGroup(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"groupA", "admin");
+    commandLine.handleCommandLine();
+    verify(mockAdd).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestMetalakeCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestMetalakeCommands.java
new file mode 100644
index 000000000..d602d56d9
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestMetalakeCommands.java
@@ -0,0 +1,281 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CreateMetalake;
+import org.apache.gravitino.cli.commands.DeleteMetalake;
+import org.apache.gravitino.cli.commands.ListMetalakeProperties;
+import org.apache.gravitino.cli.commands.ListMetalakes;
+import org.apache.gravitino.cli.commands.MetalakeAudit;
+import org.apache.gravitino.cli.commands.MetalakeDetails;
+import org.apache.gravitino.cli.commands.RemoveMetalakeProperty;
+import org.apache.gravitino.cli.commands.SetMetalakeProperty;
+import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
+import org.apache.gravitino.cli.commands.UpdateMetalakeName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestMetalakeCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListMetalakesCommand() {
+    ListMetalakes mockList = mock(ListMetalakes.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.LIST));
+    
doReturn(mockList).when(commandLine).newListMetalakes(GravitinoCommandLine.DEFAULT_URL,
 false);
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testMetalakeDetailsCommand() {
+    MetalakeDetails mockDetails = mock(MetalakeDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(false);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newMetalakeDetails(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testMetalakeAuditCommand() {
+    MetalakeAudit mockAudit = mock(MetalakeAudit.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.DETAILS));
+    doReturn(mockAudit)
+        .when(commandLine)
+        .newMetalakeAudit(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockAudit).handle();
+  }
+
+  @Test
+  void testCreateMetalakeCommand() {
+    CreateMetalake mockCreate = mock(CreateMetalake.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateMetalake(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "comment");
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testCreateMetalakeCommandNoComment() {
+    CreateMetalake mockCreate = mock(CreateMetalake.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateMetalake(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", null);
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteMetalakeCommand() {
+    DeleteMetalake mockDelete = mock(DeleteMetalake.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteMetalake(GravitinoCommandLine.DEFAULT_URL, false, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteMetalakeForceCommand() {
+    DeleteMetalake mockDelete = mock(DeleteMetalake.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteMetalake(GravitinoCommandLine.DEFAULT_URL, false, true, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testSetMetalakePropertyCommand() {
+    SetMetalakeProperty mockSetProperty = mock(SetMetalakeProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    when(mockCommandLine.hasOption(GravitinoOptions.VALUE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.VALUE)).thenReturn("value");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.SET));
+    doReturn(mockSetProperty)
+        .when(commandLine)
+        .newSetMetalakeProperty(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"property", "value");
+    commandLine.handleCommandLine();
+    verify(mockSetProperty).handle();
+  }
+
+  @Test
+  void testRemoveMetalakePropertyCommand() {
+    RemoveMetalakeProperty mockRemoveProperty = 
mock(RemoveMetalakeProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.REMOVE));
+    doReturn(mockRemoveProperty)
+        .when(commandLine)
+        .newRemoveMetalakeProperty(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"property");
+    commandLine.handleCommandLine();
+    verify(mockRemoveProperty).handle();
+  }
+
+  @Test
+  void testListMetalakePropertiesCommand() {
+    ListMetalakeProperties mockListProperties = 
mock(ListMetalakeProperties.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.PROPERTIES));
+    doReturn(mockListProperties)
+        .when(commandLine)
+        .newListMetalakeProperties(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockListProperties).handle();
+  }
+
+  @Test
+  void testUpdateMetalakeCommentCommand() {
+    UpdateMetalakeComment mockUpdateComment = 
mock(UpdateMetalakeComment.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("new 
comment");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.UPDATE));
+    doReturn(mockUpdateComment)
+        .when(commandLine)
+        .newUpdateMetalakeComment(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "new 
comment");
+    commandLine.handleCommandLine();
+    verify(mockUpdateComment).handle();
+  }
+
+  @Test
+  void testUpdateMetalakeNameCommand() {
+    UpdateMetalakeName mockUpdateName = mock(UpdateMetalakeName.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.RENAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.RENAME)).thenReturn("new_name");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.UPDATE));
+    doReturn(mockUpdateName)
+        .when(commandLine)
+        .newUpdateMetalakeName(
+            GravitinoCommandLine.DEFAULT_URL, false, false, "metalake_demo", 
"new_name");
+    commandLine.handleCommandLine();
+    verify(mockUpdateName).handle();
+  }
+
+  @Test
+  void testUpdateMetalakeNameForceCommand() {
+    UpdateMetalakeName mockUpdateName = mock(UpdateMetalakeName.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    when(mockCommandLine.hasOption(GravitinoOptions.RENAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.RENAME)).thenReturn("new_name");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.METALAKE, 
CommandActions.UPDATE));
+    doReturn(mockUpdateName)
+        .when(commandLine)
+        .newUpdateMetalakeName(
+            GravitinoCommandLine.DEFAULT_URL, false, true, "metalake_demo", 
"new_name");
+    commandLine.handleCommandLine();
+    verify(mockUpdateName).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java
new file mode 100644
index 000000000..47b4bb1b1
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestRoleCommands.java
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CreateRole;
+import org.apache.gravitino.cli.commands.DeleteRole;
+import org.apache.gravitino.cli.commands.ListRoles;
+import org.apache.gravitino.cli.commands.RoleDetails;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestRoleCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListRolesCommand() {
+    ListRoles mockList = mock(ListRoles.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.ROLE, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListRoles(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testRoleDetailsCommand() {
+    RoleDetails mockDetails = mock(RoleDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.ROLE, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newRoleDetails(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "admin");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testCreateRoleCommand() {
+    CreateRole mockCreate = mock(CreateRole.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.ROLE, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateRole(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "admin");
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteRoleCommand() {
+    DeleteRole mockDelete = mock(DeleteRole.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.ROLE, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteRole(GravitinoCommandLine.DEFAULT_URL, false, false, 
"metalake_demo", "admin");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteRoleForceCommand() {
+    DeleteRole mockDelete = mock(DeleteRole.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.ROLE, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteRole(GravitinoCommandLine.DEFAULT_URL, false, true, 
"metalake_demo", "admin");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestSchemaCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestSchemaCommands.java
new file mode 100644
index 000000000..89cc72bcd
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestSchemaCommands.java
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CreateSchema;
+import org.apache.gravitino.cli.commands.DeleteSchema;
+import org.apache.gravitino.cli.commands.ListSchema;
+import org.apache.gravitino.cli.commands.ListSchemaProperties;
+import org.apache.gravitino.cli.commands.RemoveSchemaProperty;
+import org.apache.gravitino.cli.commands.SchemaAudit;
+import org.apache.gravitino.cli.commands.SchemaDetails;
+import org.apache.gravitino.cli.commands.SetSchemaProperty;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestSchemaCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListSchemasCommand() {
+    ListSchema mockList = mock(ListSchema.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListSchema(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "catalog");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testSchemaDetailsCommand() {
+    SchemaDetails mockDetails = mock(SchemaDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(false);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newSchemaDetails(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testSchemaAuditCommand() {
+    SchemaAudit mockAudit = mock(SchemaAudit.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.DETAILS));
+    doReturn(mockAudit)
+        .when(commandLine)
+        .newSchemaAudit(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockAudit).handle();
+  }
+
+  @Test
+  void testCreateSchemaCommand() {
+    CreateSchema mockCreate = mock(CreateSchema.class);
+
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateSchema(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "comment");
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteSchemaCommand() {
+    DeleteSchema mockDelete = mock(DeleteSchema.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteSchema(
+            GravitinoCommandLine.DEFAULT_URL, false, false, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteSchemaForceCommand() {
+    DeleteSchema mockDelete = mock(DeleteSchema.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteSchema(
+            GravitinoCommandLine.DEFAULT_URL, false, true, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testSetSchemaPropertyCommand() {
+    SetSchemaProperty mockSetProperty = mock(SetSchemaProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    when(mockCommandLine.hasOption(GravitinoOptions.VALUE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.VALUE)).thenReturn("value");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.SET));
+    doReturn(mockSetProperty)
+        .when(commandLine)
+        .newSetSchemaProperty(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "property",
+            "value");
+    commandLine.handleCommandLine();
+    verify(mockSetProperty).handle();
+  }
+
+  @Test
+  void testRemoveSchemaPropertyCommand() {
+    RemoveSchemaProperty mockRemoveProperty = mock(RemoveSchemaProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.REMOVE));
+    doReturn(mockRemoveProperty)
+        .when(commandLine)
+        .newRemoveSchemaProperty(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "property");
+    commandLine.handleCommandLine();
+    verify(mockRemoveProperty).handle();
+  }
+
+  @Test
+  void testListSchemaPropertiesCommand() {
+    ListSchemaProperties mockListProperties = mock(ListSchemaProperties.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.SCHEMA, 
CommandActions.PROPERTIES));
+    doReturn(mockListProperties)
+        .when(commandLine)
+        .newListSchemaProperties(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockListProperties).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java
new file mode 100644
index 000000000..e2ccca7ed
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTableCommands.java
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.DeleteTable;
+import org.apache.gravitino.cli.commands.ListIndexes;
+import org.apache.gravitino.cli.commands.ListTables;
+import org.apache.gravitino.cli.commands.TableAudit;
+import org.apache.gravitino.cli.commands.TableDetails;
+import org.apache.gravitino.cli.commands.TableDistribution;
+import org.apache.gravitino.cli.commands.TablePartition;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestTableCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListTablesCommand() {
+    ListTables mockList = mock(ListTables.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListTables(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testTableDetailsCommand() {
+    TableDetails mockDetails = mock(TableDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(false);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newTableDetails(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema", "users");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testTableIndexCommand() {
+    ListIndexes mockIndex = mock(ListIndexes.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    when(mockCommandLine.hasOption(GravitinoOptions.INDEX)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DETAILS));
+    doReturn(mockIndex)
+        .when(commandLine)
+        .newListIndexes(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema", "users");
+    commandLine.handleCommandLine();
+    verify(mockIndex).handle();
+  }
+
+  @Test
+  void testTablePartitionCommand() {
+    TablePartition mockPartition = mock(TablePartition.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PARTITION)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DETAILS));
+    doReturn(mockPartition)
+        .when(commandLine)
+        .newTablePartition(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema", "users");
+    commandLine.handleCommandLine();
+    verify(mockPartition).handle();
+  }
+
+  @Test
+  void testTableDistributionCommand() {
+    TableDistribution mockDistribution = mock(TableDistribution.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    
when(mockCommandLine.hasOption(GravitinoOptions.DISTRIBUTION)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DETAILS));
+    doReturn(mockDistribution)
+        .when(commandLine)
+        .newTableDistribution(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema", "users");
+    commandLine.handleCommandLine();
+    verify(mockDistribution).handle();
+  }
+
+  @Test
+  void testTableAuditCommand() {
+    TableAudit mockAudit = mock(TableAudit.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUDIT)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DETAILS));
+    doReturn(mockAudit)
+        .when(commandLine)
+        .newTableAudit(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema", "users");
+    commandLine.handleCommandLine();
+    verify(mockAudit).handle();
+  }
+
+  @Test
+  void testDeleteTableCommand() {
+    DeleteTable mockDelete = mock(DeleteTable.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteTable(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteTableForceCommand() {
+    DeleteTable mockDelete = mock(DeleteTable.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.users");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TABLE, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteTable(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            true,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java
new file mode 100644
index 000000000..3dfd0392c
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTagCommands.java
@@ -0,0 +1,339 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CreateTag;
+import org.apache.gravitino.cli.commands.DeleteTag;
+import org.apache.gravitino.cli.commands.ListAllTags;
+import org.apache.gravitino.cli.commands.ListEntityTags;
+import org.apache.gravitino.cli.commands.ListTagProperties;
+import org.apache.gravitino.cli.commands.RemoveTagProperty;
+import org.apache.gravitino.cli.commands.SetTagProperty;
+import org.apache.gravitino.cli.commands.TagDetails;
+import org.apache.gravitino.cli.commands.TagEntity;
+import org.apache.gravitino.cli.commands.UntagEntity;
+import org.apache.gravitino.cli.commands.UpdateTagComment;
+import org.apache.gravitino.cli.commands.UpdateTagName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestTagCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListTagsCommand() {
+    ListAllTags mockList = mock(ListAllTags.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListTags(GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testMetalakeDetailsCommand() {
+    TagDetails mockDetails = mock(TagDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newTagDetails(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "tagA");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testCreateTagCommand() {
+    CreateTag mockCreate = mock(CreateTag.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateTag(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "tagA", "comment");
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testCreateTagCommandNoComment() {
+    CreateTag mockCreate = mock(CreateTag.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateTag(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "tagA", null);
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteTagCommand() {
+    DeleteTag mockDelete = mock(DeleteTag.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteTag(GravitinoCommandLine.DEFAULT_URL, false, false, 
"metalake_demo", "tagA");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteTagForceCommand() {
+    DeleteTag mockDelete = mock(DeleteTag.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteTag(GravitinoCommandLine.DEFAULT_URL, false, true, 
"metalake_demo", "tagA");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testSetTagPropertyCommand() {
+    SetTagProperty mockSetProperty = mock(SetTagProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    when(mockCommandLine.hasOption(GravitinoOptions.VALUE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.VALUE)).thenReturn("value");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.SET));
+    doReturn(mockSetProperty)
+        .when(commandLine)
+        .newSetTagProperty(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "tagA", 
"property", "value");
+    commandLine.handleCommandLine();
+    verify(mockSetProperty).handle();
+  }
+
+  @Test
+  void testRemoveTagPropertyCommand() {
+    RemoveTagProperty mockRemoveProperty = mock(RemoveTagProperty.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn("property");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.REMOVE));
+    doReturn(mockRemoveProperty)
+        .when(commandLine)
+        .newRemoveTagProperty(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "tagA", 
"property");
+    commandLine.handleCommandLine();
+    verify(mockRemoveProperty).handle();
+  }
+
+  @Test
+  void testListTagPropertiesCommand() {
+    ListTagProperties mockListProperties = mock(ListTagProperties.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.PROPERTIES));
+    doReturn(mockListProperties)
+        .when(commandLine)
+        .newListTagProperties(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "tagA");
+    commandLine.handleCommandLine();
+    verify(mockListProperties).handle();
+  }
+
+  @Test
+  void testUpdateTagCommentCommand() {
+    UpdateTagComment mockUpdateComment = mock(UpdateTagComment.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("new 
comment");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.UPDATE));
+    doReturn(mockUpdateComment)
+        .when(commandLine)
+        .newUpdateTagComment(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "tagA", 
"new comment");
+    commandLine.handleCommandLine();
+    verify(mockUpdateComment).handle();
+  }
+
+  @Test
+  void testUpdateTagNameCommand() {
+    UpdateTagName mockUpdateName = mock(UpdateTagName.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    when(mockCommandLine.hasOption(GravitinoOptions.RENAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.RENAME)).thenReturn("tagB");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.UPDATE));
+    doReturn(mockUpdateName)
+        .when(commandLine)
+        .newUpdateTagName(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "tagA", "tagB");
+    commandLine.handleCommandLine();
+    verify(mockUpdateName).handle();
+  }
+
+  @Test
+  void testListEntityTagsCommand() {
+    ListEntityTags mockListTags = mock(ListEntityTags.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.LIST));
+    doReturn(mockListTags)
+        .when(commandLine)
+        .newListEntityTags(
+            eq(GravitinoCommandLine.DEFAULT_URL), eq(false), 
eq("metalake_demo"), any());
+    commandLine.handleCommandLine();
+    verify(mockListTags).handle();
+  }
+
+  @Test
+  void testTagEntityCommand() {
+    TagEntity mockTagEntity = mock(TagEntity.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.SET));
+    doReturn(mockTagEntity)
+        .when(commandLine)
+        .newTagEntity(
+            eq(GravitinoCommandLine.DEFAULT_URL),
+            eq(false),
+            eq("metalake_demo"),
+            any(),
+            eq("tagA"));
+    commandLine.handleCommandLine();
+    verify(mockTagEntity).handle();
+  }
+
+  @Test
+  void testUntagEntityCommand() {
+    UntagEntity mockUntagEntity = mock(UntagEntity.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.table");
+    when(mockCommandLine.hasOption(GravitinoOptions.TAG)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.TAG)).thenReturn("tagA");
+    
when(mockCommandLine.hasOption(GravitinoOptions.PROPERTY)).thenReturn(false);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.PROPERTY)).thenReturn(null);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.TAG, 
CommandActions.REMOVE));
+    doReturn(mockUntagEntity)
+        .when(commandLine)
+        .newUntagEntity(
+            eq(GravitinoCommandLine.DEFAULT_URL),
+            eq(false),
+            eq("metalake_demo"),
+            any(),
+            eq("tagA"));
+    commandLine.handleCommandLine();
+    verify(mockUntagEntity).handle();
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestUserCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestUserCommands.java
new file mode 100644
index 000000000..ab6ff3a7c
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestUserCommands.java
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CreateUser;
+import org.apache.gravitino.cli.commands.DeleteUser;
+import org.apache.gravitino.cli.commands.ListUsers;
+import org.apache.gravitino.cli.commands.RemoveRoleFromUser;
+import org.apache.gravitino.cli.commands.UserDetails;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestUserCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListUsersCommand() {
+    ListUsers mockList = mock(ListUsers.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListUsers(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testUserDetailsCommand() {
+    UserDetails mockDetails = mock(UserDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("user");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newUserDetails(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "user");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testCreateUserCommand() {
+    CreateUser mockCreate = mock(CreateUser.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("user");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateUser(GravitinoCommandLine.DEFAULT_URL, false, 
"metalake_demo", "user");
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteUserCommand() {
+    DeleteUser mockDelete = mock(DeleteUser.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("user");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteUser(GravitinoCommandLine.DEFAULT_URL, false, false, 
"metalake_demo", "user");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteUserForceCommand() {
+    DeleteUser mockDelete = mock(DeleteUser.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("user");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteUser(GravitinoCommandLine.DEFAULT_URL, false, true, 
"metalake_demo", "user");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  void testRemoveRoleFromUserCommand() {
+    RemoveRoleFromUser mockRemove = mock(RemoveRoleFromUser.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("user");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.REVOKE));
+    doReturn(mockRemove)
+        .when(commandLine)
+        .newRemoveRoleFromUser(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "user", 
"admin");
+    commandLine.handleCommandLine();
+    verify(mockRemove).handle();
+  }
+
+  void testAddRoleToUserCommand() {
+    RemoveRoleFromUser mockAdd = mock(RemoveRoleFromUser.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.USER)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.USER)).thenReturn("user");
+    when(mockCommandLine.hasOption(GravitinoOptions.ROLE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.ROLE)).thenReturn("admin");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.USER, 
CommandActions.GRANT));
+    doReturn(mockAdd)
+        .when(commandLine)
+        .newRemoveRoleFromUser(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", "user", 
"admin");
+    commandLine.handleCommandLine();
+    verify(mockAdd).handle();
+  }
+}

Reply via email to