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

jshao pushed a commit to branch branch-lance-namepspace-dev
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-lance-namepspace-dev by 
this push:
     new fc0f6179d6 [#9009]improvement(lance-rest-server): add ITs for LRS 
namespace operations (#9008)
fc0f6179d6 is described below

commit fc0f6179d62e3536c2964f2f2bd877fbe1c982b1
Author: mchades <[email protected]>
AuthorDate: Tue Nov 4 18:09:25 2025 +0800

    [#9009]improvement(lance-rest-server): add ITs for LRS namespace operations 
(#9008)
    
    ### What changes were proposed in this pull request?
    
    add ITs for LRS namespace operations
    
    ### Why are the changes needed?
    
    Fix: #9009
    
    ### Does this PR introduce _any_ user-facing change?
    
    no
    
    ### How was this patch tested?
    
    CI pass
---
 .../integration/test/CatalogIcebergBaseIT.java     |   2 +-
 .../test/CatalogIcebergKerberosHiveIT.java         |   2 +-
 .../connector/integration/test/FlinkEnvIT.java     |   2 +-
 integration-test-common/build.gradle.kts           |   3 +
 .../gravitino/integration/test/MiniGravitino.java  |  80 ++++-
 .../integration/test/MiniGravitinoContext.java     |  11 +-
 .../gravitino/integration/test/util/BaseIT.java    |  25 +-
 .../gravitino/GravitinoLanceNamespaceWrapper.java  |  19 +-
 lance/lance-rest-server/build.gradle.kts           |  17 +
 .../lance/integration/test/LanceRESTServiceIT.java | 391 +++++++++++++++++++++
 .../service/rest/ServletRequestFactoryBase.java    |  21 +-
 .../service/rest/TestLanceNamespaceOperations.java | 326 +++++++++++++++++
 .../connector/integration/test/SparkEnvIT.java     |   2 +-
 13 files changed, 865 insertions(+), 36 deletions(-)

diff --git 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
index b0d4f3b5eb..82aedaff7d 100644
--- 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
+++ 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
@@ -124,7 +124,7 @@ public abstract class CatalogIcebergBaseIT extends BaseIT {
 
   @BeforeAll
   public void startup() throws Exception {
-    super.ignoreAuxRestService = false;
+    super.ignoreIcebergAuxRestService = false;
     super.startIntegrationTest();
     containerSuite.startHiveContainer();
     initIcebergCatalogProperties();
diff --git 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
index cce787ec85..5591a4f26f 100644
--- 
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
+++ 
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
@@ -127,7 +127,7 @@ public class CatalogIcebergKerberosHiveIT extends BaseIT {
       // Config kerberos configuration for Gravitino server
       addKerberosConfig();
 
-      super.ignoreAuxRestService = false;
+      super.ignoreIcebergAuxRestService = false;
       // Start Gravitino server
       super.startIntegrationTest();
     } catch (Exception e) {
diff --git 
a/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
 
b/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
index ca79144f5b..db04715a4c 100644
--- 
a/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
+++ 
b/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
@@ -112,7 +112,7 @@ public abstract class FlinkEnvIT extends BaseIT {
   protected abstract String getProvider();
 
   private void initIcebergRestServiceEnv() {
-    super.ignoreAuxRestService = false;
+    super.ignoreIcebergAuxRestService = false;
     Map<String, String> icebergRestServiceConfigs = new HashMap<>();
     icebergRestServiceConfigs.put(
         "gravitino."
diff --git a/integration-test-common/build.gradle.kts 
b/integration-test-common/build.gradle.kts
index bd15dc2a34..8aac2cafad 100644
--- a/integration-test-common/build.gradle.kts
+++ b/integration-test-common/build.gradle.kts
@@ -30,6 +30,9 @@ dependencies {
   testImplementation(project(":clients:client-java"))
   testImplementation(project(":common"))
   testImplementation(project(":core"))
+  testImplementation(project(":lance:lance-common")) {
+    exclude("*")
+  }
   testImplementation(project(":server"))
   testImplementation(project(":server-common"))
   testImplementation(libs.bundles.jetty)
diff --git 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
 
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
index 3cff6940c6..eb26fb37e4 100644
--- 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
+++ 
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
@@ -19,6 +19,10 @@
 package org.apache.gravitino.integration.test;
 
 import static 
com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
+import static 
org.apache.gravitino.lance.common.config.LanceConfig.GRAVITINO_NAMESPACE_BACKEND;
+import static 
org.apache.gravitino.lance.common.config.LanceConfig.LANCE_CONFIG_PREFIX;
+import static 
org.apache.gravitino.lance.common.config.LanceConfig.NAMESPACE_BACKEND;
+import static 
org.apache.gravitino.lance.common.config.LanceConfig.NAMESPACE_BACKEND_URI;
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableMap;
@@ -27,6 +31,7 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -50,6 +55,7 @@ import org.apache.gravitino.rest.RESTUtils;
 import org.apache.gravitino.server.GravitinoServer;
 import org.apache.gravitino.server.ServerConfig;
 import org.apache.gravitino.server.web.JettyServerConfig;
+import org.junit.platform.commons.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -78,11 +84,32 @@ public class MiniGravitino {
     mockConfDir.mkdirs();
   }
 
-  private void removeAuxRestConfiguration(Properties properties) {
-    // Disable Iceberg REST service
-    properties.remove(
-        AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
-            + AuxiliaryServiceManager.AUX_SERVICE_NAMES);
+  private void removeAuxRestConfiguration(Properties properties, String 
serviceToRemove) {
+    String value =
+        properties.getProperty(
+            AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
+                + AuxiliaryServiceManager.AUX_SERVICE_NAMES);
+    if (StringUtils.isNotBlank(value) && 
StringUtils.isNotBlank(serviceToRemove)) {
+      List<String> serviceNames = COMMA.splitToList(value);
+      List<String> updatedServiceNames = new ArrayList<>();
+      for (String serviceName : serviceNames) {
+        if (!serviceName.equalsIgnoreCase(serviceToRemove)) {
+          updatedServiceNames.add(serviceName);
+        }
+      }
+
+      String updatedValue = String.join(",", updatedServiceNames);
+      if (StringUtils.isBlank(updatedValue)) {
+        properties.remove(
+            AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
+                + AuxiliaryServiceManager.AUX_SERVICE_NAMES);
+      } else {
+        properties.setProperty(
+            AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
+                + AuxiliaryServiceManager.AUX_SERVICE_NAMES,
+            updatedValue);
+      }
+    }
   }
 
   public void start() throws Exception {
@@ -103,9 +130,18 @@ public class MiniGravitino {
         serverConfig.loadPropertiesFromFile(
             new File(ITUtils.joinPath(mockConfDir.getAbsolutePath(), 
"gravitino.conf")));
 
-    // Disable auxiliary rest service.
-    if (context.ignoreAuxRestService) {
-      removeAuxRestConfiguration(properties);
+    if (context.ignoreIcebergAuxRestService) {
+      // Disable auxiliary rest service.
+      removeAuxRestConfiguration(properties, "iceberg-rest");
+      LOG.info("Iceberg auxiliary REST service is disabled for 
MiniGravitino.");
+      ITUtils.overwriteConfigFile(
+          ITUtils.joinPath(mockConfDir.getAbsolutePath(), "gravitino.conf"), 
properties);
+    }
+
+    if (context.ignoreLanceAuxRestService) {
+      // Disable auxiliary rest service.
+      removeAuxRestConfiguration(properties, "lance-rest");
+      LOG.info("Lance auxiliary REST service is disabled for MiniGravitino.");
       ITUtils.overwriteConfigFile(
           ITUtils.joinPath(mockConfDir.getAbsolutePath(), "gravitino.conf"), 
properties);
     }
@@ -230,20 +266,38 @@ public class MiniGravitino {
     return customConfigs;
   }
 
-  private Map<String, String> getLanceRestServiceConfigs() throws IOException {
+  private Map<String, String> getLanceRestServiceConfigs(Map<String, String> 
configMap)
+      throws IOException {
+    if (context.ignoreLanceAuxRestService) {
+      return Collections.emptyMap();
+    }
+
     Map<String, String> customConfigs = new HashMap<>();
 
     String lanceJarPath = Paths.get("lance", "lance-rest-server", "build", 
"libs").toString();
     String lanceConfigPath =
         Paths.get("lance", "lance-rest-server", "src", "main", 
"resources").toString();
     customConfigs.put(
-        "gravitino.lance-rest." + 
AuxiliaryServiceManager.AUX_SERVICE_CLASSPATH,
+        LANCE_CONFIG_PREFIX + AuxiliaryServiceManager.AUX_SERVICE_CLASSPATH,
         String.join(",", lanceJarPath, lanceConfigPath));
 
     customConfigs.put(
-        "gravitino.lance-rest." + 
JettyServerConfig.WEBSERVER_HTTP_PORT.getKey(),
+        LANCE_CONFIG_PREFIX + JettyServerConfig.WEBSERVER_HTTP_PORT.getKey(),
         String.valueOf(RESTUtils.findAvailablePort(4000, 5000)));
-    return customConfigs;
+
+    if (GRAVITINO_NAMESPACE_BACKEND.equals(
+        configMap.getOrDefault(NAMESPACE_BACKEND.getKey(), 
NAMESPACE_BACKEND.getDefaultValue()))) {
+      // Set the Lance REST service to use the Gravitino server URI
+      String gravitinoUri =
+          String.format(
+              "http://%s:%s";,
+              "localhost",
+              configMap.get(
+                  GravitinoServer.WEBSERVER_CONF_PREFIX
+                      + JettyServerConfig.WEBSERVER_HTTP_PORT.getKey()));
+      customConfigs.put(LANCE_CONFIG_PREFIX + NAMESPACE_BACKEND_URI.getKey(), 
gravitinoUri);
+    }
+    return ImmutableMap.copyOf(customConfigs);
   }
 
   // Customize the config file
@@ -255,7 +309,7 @@ public class MiniGravitino {
         String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
 
     configMap.putAll(getIcebergRestServiceConfigs());
-    configMap.putAll(getLanceRestServiceConfigs());
+    configMap.putAll(getLanceRestServiceConfigs(configMap));
     configMap.putAll(context.customConfig);
 
     ITUtils.rewriteConfigFile(configTempFileName, configFileName, configMap);
diff --git 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
 
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
index 601138ecfd..cf02bb3f3c 100644
--- 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
+++ 
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
@@ -23,10 +23,15 @@ import java.util.Map;
 
 public class MiniGravitinoContext {
   Map<String, String> customConfig;
-  final boolean ignoreAuxRestService;
+  final boolean ignoreIcebergAuxRestService;
+  final boolean ignoreLanceAuxRestService;
 
-  public MiniGravitinoContext(Map<String, String> customConfig, boolean 
ignoreAuxRestService) {
+  public MiniGravitinoContext(
+      Map<String, String> customConfig,
+      boolean ignoreIcebergAuxRestService,
+      boolean ignoreLanceAuxRestService) {
     this.customConfig = customConfig;
-    this.ignoreAuxRestService = ignoreAuxRestService;
+    this.ignoreIcebergAuxRestService = ignoreIcebergAuxRestService;
+    this.ignoreLanceAuxRestService = ignoreLanceAuxRestService;
   }
 }
diff --git 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
 
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
index 1ae60a0fec..9497d5bd23 100644
--- 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
+++ 
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
@@ -21,6 +21,8 @@ package org.apache.gravitino.integration.test.util;
 import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PATH;
 import static 
org.apache.gravitino.integration.test.util.TestDatabaseName.PG_CATALOG_POSTGRESQL_IT;
 import static 
org.apache.gravitino.integration.test.util.TestDatabaseName.PG_JDBC_BACKEND;
+import static 
org.apache.gravitino.lance.common.config.LanceConfig.LANCE_CONFIG_PREFIX;
+import static 
org.apache.gravitino.lance.common.config.LanceConfig.METALAKE_NAME;
 import static 
org.apache.gravitino.server.GravitinoServer.WEBSERVER_CONF_PREFIX;
 
 import com.google.common.base.Splitter;
@@ -102,7 +104,9 @@ public class BaseIT {
 
   protected Map<String, String> customConfigs = new HashMap<>();
 
-  protected boolean ignoreAuxRestService = true;
+  protected boolean ignoreIcebergAuxRestService = true;
+
+  protected boolean ignoreLanceAuxRestService = true;
 
   public String DOWNLOAD_MYSQL_JDBC_DRIVER_URL =
       
"https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.26/mysql-connector-java-8.0.26.jar";;
@@ -133,6 +137,16 @@ public class BaseIT {
     customConfigs.putAll(configs);
   }
 
+  protected int getLanceRESTServerPort() {
+    JettyServerConfig lanceServerConfig =
+        JettyServerConfig.fromConfig(serverConfig, LANCE_CONFIG_PREFIX);
+    return lanceServerConfig.getHttpPort();
+  }
+
+  protected String getLanceRESTServerMetalakeName() {
+    return serverConfig.getRawString(LANCE_CONFIG_PREFIX + 
METALAKE_NAME.getKey());
+  }
+
   private void rewriteGravitinoServerConfig() throws IOException {
     String gravitinoHome = System.getenv("GRAVITINO_HOME");
     Path configPath = Paths.get(gravitinoHome, "conf", 
GravitinoServer.CONF_FILE);
@@ -329,8 +343,15 @@ public class BaseIT {
 
     serverConfig = new ServerConfig();
     customConfigs.put(ENTITY_RELATIONAL_JDBC_BACKEND_PATH.getKey(), 
file.getAbsolutePath());
+    if (!ignoreLanceAuxRestService) {
+      customConfigs.put(
+          LANCE_CONFIG_PREFIX + METALAKE_NAME.getKey(),
+          GravitinoITUtils.genRandomName("LanceRESTService_metalake"));
+    }
     if (testMode != null && testMode.equals(ITUtils.EMBEDDED_TEST_MODE)) {
-      MiniGravitinoContext context = new MiniGravitinoContext(customConfigs, 
ignoreAuxRestService);
+      MiniGravitinoContext context =
+          new MiniGravitinoContext(
+              customConfigs, ignoreIcebergAuxRestService, 
ignoreLanceAuxRestService);
       miniGravitino = new MiniGravitino(context);
       miniGravitino.start();
       serverConfig = miniGravitino.getServerConfig();
diff --git 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
index 865313b31c..dd2c4629b4 100644
--- 
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
+++ 
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
@@ -74,6 +74,7 @@ import org.apache.gravitino.Schema;
 import org.apache.gravitino.SchemaChange;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
+import org.apache.gravitino.exceptions.CatalogInUseException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NonEmptyCatalogException;
@@ -332,7 +333,8 @@ public class GravitinoLanceNamespaceWrapper extends 
NamespaceWrapper
     // Catalog exists, handle based on mode
     switch (mode) {
       case EXIST_OK:
-        response.setProperties(Maps.newHashMap());
+        response.setProperties(
+            
Optional.ofNullable(catalog.properties()).orElse(Collections.emptyMap()));
         return response;
       case CREATE:
         throw LanceNamespaceException.conflict(
@@ -344,7 +346,7 @@ public class GravitinoLanceNamespaceWrapper extends 
NamespaceWrapper
         CatalogChange[] changes =
             buildChanges(
                 properties,
-                catalog.properties(),
+                removeInUseProperty(catalog.properties()),
                 CatalogChange::setProperty,
                 CatalogChange::removeProperty,
                 CatalogChange[]::new);
@@ -356,6 +358,12 @@ public class GravitinoLanceNamespaceWrapper extends 
NamespaceWrapper
     }
   }
 
+  private Map<String, String> removeInUseProperty(Map<String, String> 
properties) {
+    return properties.entrySet().stream()
+        .filter(e -> !e.getKey().equalsIgnoreCase(Catalog.PROPERTY_IN_USE))
+        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
+
   private CreateNamespaceResponse createOrUpdateSchema(
       String catalogName,
       String schemaName,
@@ -378,7 +386,8 @@ public class GravitinoLanceNamespaceWrapper extends 
NamespaceWrapper
     // Schema exists, handle based on mode
     switch (mode) {
       case EXIST_OK:
-        response.setProperties(Maps.newHashMap());
+        response.setProperties(
+            
Optional.ofNullable(schema.properties()).orElse(Collections.emptyMap()));
         return response;
       case CREATE:
         throw LanceNamespaceException.conflict(
@@ -422,9 +431,9 @@ public class GravitinoLanceNamespaceWrapper extends 
NamespaceWrapper
         }
         return new DropNamespaceResponse(); // SKIP mode
       }
-    } catch (NonEmptyCatalogException e) {
+    } catch (NonEmptyCatalogException | CatalogInUseException e) {
       throw LanceNamespaceException.badRequest(
-          String.format("Catalog %s is not empty.", catalogName),
+          String.format("Catalog %s is not empty or in used", catalogName),
           NonEmptyCatalogException.class.getSimpleName(),
           catalogName,
           CommonUtil.formatCurrentStackTrace());
diff --git a/lance/lance-rest-server/build.gradle.kts 
b/lance/lance-rest-server/build.gradle.kts
index 7befc28b35..b3e06f6707 100644
--- a/lance/lance-rest-server/build.gradle.kts
+++ b/lance/lance-rest-server/build.gradle.kts
@@ -53,7 +53,24 @@ dependencies {
   implementation(libs.jackson.datatype.jdk8)
   implementation(libs.jackson.datatype.jsr310)
 
+  testImplementation(project(":clients:client-java"))
+  testImplementation(project(":server"))
+  testImplementation(project(":integration-test-common", "testArtifacts"))
+
+  testImplementation(libs.commons.io)
+  testImplementation(libs.jersey.test.framework.core) {
+    exclude(group = "org.junit.jupiter")
+  }
+  testImplementation(libs.jersey.test.framework.provider.jetty) {
+    exclude(group = "org.junit.jupiter")
+  }
+
   testImplementation(libs.junit.jupiter.api)
+  testImplementation(libs.mockito.inline)
+  testImplementation(libs.mysql.driver)
+  testImplementation(libs.postgresql.driver)
+  testImplementation(libs.testcontainers)
+
   testRuntimeOnly(libs.junit.jupiter.engine)
 }
 
diff --git 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
new file mode 100644
index 0000000000..f1fe008782
--- /dev/null
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
@@ -0,0 +1,391 @@
+/*
+ * 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.lance.integration.test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.lancedb.lance.namespace.LanceNamespace;
+import com.lancedb.lance.namespace.LanceNamespaceException;
+import com.lancedb.lance.namespace.LanceNamespaces;
+import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
+import com.lancedb.lance.namespace.model.CreateNamespaceResponse;
+import com.lancedb.lance.namespace.model.DescribeNamespaceRequest;
+import com.lancedb.lance.namespace.model.DescribeNamespaceResponse;
+import com.lancedb.lance.namespace.model.DropNamespaceRequest;
+import com.lancedb.lance.namespace.model.DropNamespaceResponse;
+import com.lancedb.lance.namespace.model.ListNamespacesRequest;
+import com.lancedb.lance.namespace.model.ListNamespacesResponse;
+import com.lancedb.lance.namespace.model.NamespaceExistsRequest;
+import com.lancedb.lance.namespace.rest.RestNamespaceConfig;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.integration.test.util.BaseIT;
+import org.apache.gravitino.integration.test.util.GravitinoITUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class LanceRESTServiceIT extends BaseIT {
+
+  private GravitinoMetalake metalake;
+  private Map<String, String> properties =
+      new HashMap<>() {
+        {
+          put("key1", "value1");
+        }
+      };
+  private final BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);
+  private LanceNamespace ns;
+  private Path tempDir;
+
+  @BeforeAll
+  public void startIntegrationTest() throws Exception {
+    super.ignoreLanceAuxRestService = false;
+    super.startIntegrationTest();
+    this.metalake = createMetalake(getLanceRESTServerMetalakeName());
+
+    HashMap<String, String> props = Maps.newHashMap();
+    props.put(RestNamespaceConfig.URI, getLanceRestServiceUrl());
+    props.put(RestNamespaceConfig.DELIMITER, 
RestNamespaceConfig.DELIMITER_DEFAULT);
+    this.ns = LanceNamespaces.connect("rest", props, null, allocator);
+
+    this.tempDir = Files.createTempDirectory("test_lance_rest_service_it_");
+  }
+
+  @AfterAll
+  public void clean() {
+    client.dropMetalake(getLanceRESTServerMetalakeName(), true);
+    tempDir.toFile().deleteOnExit();
+  }
+
+  @AfterEach
+  public void clearMetalake() {
+    Arrays.stream(metalake.listCatalogs()).forEach(c -> 
metalake.dropCatalog(c, true));
+  }
+
+  @Test
+  public void testListNamespaces() {
+    Catalog catalog1 = 
createCatalog(GravitinoITUtils.genRandomName("lance_catalog_1"));
+    Catalog catalog2 = 
createCatalog(GravitinoITUtils.genRandomName("lance_catalog_2"));
+    Schema schema1 =
+        catalog1
+            .asSchemas()
+            .createSchema("lance_schema_1", "schema for lance rest service 
tests", null);
+
+    // test list catalogs via lance rest namespace client
+    ListNamespacesRequest listNamespacesReq = new ListNamespacesRequest();
+    ListNamespacesResponse listNamespacesResp = 
ns.listNamespaces(listNamespacesReq);
+
+    Assertions.assertEquals(
+        Sets.newHashSet(catalog1.name(), catalog2.name()), 
listNamespacesResp.getNamespaces());
+
+    // test list schemas via lance rest namespace client
+    listNamespacesReq.addIdItem(catalog1.name());
+    listNamespacesResp = ns.listNamespaces(listNamespacesReq);
+
+    Assertions.assertEquals(Sets.newHashSet(schema1.name()), 
listNamespacesResp.getNamespaces());
+  }
+
+  @Test
+  public void testDescribeNamespace() {
+    Catalog catalog = 
createCatalog(GravitinoITUtils.genRandomName("lance_catalog"));
+    Map<String, String> schemaProps =
+        new HashMap<>() {
+          {
+            put("schema_key1", "schema_value1");
+          }
+        };
+    Schema schema = catalog.asSchemas().createSchema("lance_schema", null, 
schemaProps);
+
+    // test describe catalog via lance rest namespace client
+    DescribeNamespaceRequest describeNamespaceReq = new 
DescribeNamespaceRequest();
+    describeNamespaceReq.addIdItem(catalog.name());
+    DescribeNamespaceResponse describeNamespaceResp = 
ns.describeNamespace(describeNamespaceReq);
+
+    Assertions.assertEquals(catalog.properties(), 
describeNamespaceResp.getProperties());
+
+    // test describe schema via lance rest namespace client
+    describeNamespaceReq.addIdItem(schema.name());
+    describeNamespaceResp = ns.describeNamespace(describeNamespaceReq);
+
+    Assertions.assertEquals(schema.properties(), 
describeNamespaceResp.getProperties());
+
+    // test describe the root namespace
+    DescribeNamespaceRequest rootDescNamespaceReq = new 
DescribeNamespaceRequest();
+    LanceNamespaceException exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.describeNamespace(rootDescNamespaceReq));
+
+    Assertions.assertEquals(400, exception.getCode());
+    Assertions.assertTrue(exception.getErrorResponse().isPresent());
+    Assertions.assertTrue(
+        exception
+            .getErrorResponse()
+            .get()
+            .getError()
+            .contains("Expected at most 2-level and at least 1-level 
namespace"));
+    Assertions.assertEquals(
+        IllegalArgumentException.class.getSimpleName(),
+        exception.getErrorResponse().get().getType());
+
+    // test describe a non-existent catalog namespace
+    DescribeNamespaceRequest nonExistentCatalogReq = new 
DescribeNamespaceRequest();
+    nonExistentCatalogReq.addIdItem("non_existent_catalog");
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.describeNamespace(nonExistentCatalogReq));
+    Assertions.assertEquals(404, exception.getCode());
+
+    // test describe a non-existent schema namespace
+    DescribeNamespaceRequest nonExistentSchemaReq = new 
DescribeNamespaceRequest();
+    nonExistentSchemaReq.addIdItem(catalog.name());
+    nonExistentSchemaReq.addIdItem("non_existent_schema");
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.describeNamespace(nonExistentSchemaReq));
+    Assertions.assertEquals(404, exception.getCode());
+  }
+
+  @Test
+  public void testCreateNamespace() {
+    String catalogName = GravitinoITUtils.genRandomName("lance_catalog");
+    Map<String, String> catalogProps =
+        new HashMap<>() {
+          {
+            put("catalog_key1", "catalog_value1");
+          }
+        };
+
+    // test create catalog via lance rest namespace client
+    CreateNamespaceRequest createNamespaceReq = new CreateNamespaceRequest();
+    createNamespaceReq.addIdItem(catalogName);
+    createNamespaceReq.setProperties(catalogProps);
+    CreateNamespaceResponse createNamespaceResp = 
ns.createNamespace(createNamespaceReq);
+
+    Catalog catalog = metalake.loadCatalog(catalogName);
+    Assertions.assertEquals(catalog.properties(), 
createNamespaceResp.getProperties());
+
+    // create catalog again with default mode (create) should fail
+    LanceNamespaceException exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.createNamespace(createNamespaceReq));
+    Assertions.assertEquals(409, exception.getCode());
+
+    // create catalog again with exist_ok mode should succeed
+    createNamespaceReq.setMode(CreateNamespaceRequest.ModeEnum.EXIST_OK);
+    createNamespaceResp = ns.createNamespace(createNamespaceReq);
+    Assertions.assertEquals(catalog.properties(), 
createNamespaceResp.getProperties());
+
+    // create catalog again with overwrite mode should succeed and update 
properties
+    Map<String, String> newProps =
+        new HashMap<>(catalogProps) {
+          {
+            put("catalog_key2", "catalog_value2");
+          }
+        };
+    createNamespaceReq.setMode(CreateNamespaceRequest.ModeEnum.OVERWRITE);
+    createNamespaceReq.setProperties(newProps);
+    createNamespaceResp = ns.createNamespace(createNamespaceReq);
+
+    catalog = metalake.loadCatalog(catalogName);
+    Assertions.assertEquals(catalog.properties(), 
createNamespaceResp.getProperties());
+    Assertions.assertEquals(catalog.properties(), 
createNamespaceResp.getProperties());
+
+    // test create schema via lance rest namespace client
+    CreateNamespaceRequest createSchemaReq = new CreateNamespaceRequest();
+    String schemaName = "lance_schema";
+    Map<String, String> schemaProps =
+        new HashMap<>() {
+          {
+            put("schema_key1", "schema_value1");
+          }
+        };
+    createSchemaReq.addIdItem(catalogName);
+    createSchemaReq.addIdItem(schemaName);
+    createSchemaReq.setProperties(schemaProps);
+    createNamespaceResp = ns.createNamespace(createSchemaReq);
+
+    Schema schema = catalog.asSchemas().loadSchema(schemaName);
+    Assertions.assertEquals(schema.properties(), 
createNamespaceResp.getProperties());
+
+    // create schema again with default mode (create) should fail
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.createNamespace(createSchemaReq));
+    Assertions.assertEquals(409, exception.getCode());
+
+    // create schema again with exist_ok mode should succeed
+    createSchemaReq.setMode(CreateNamespaceRequest.ModeEnum.EXIST_OK);
+    createNamespaceResp = ns.createNamespace(createSchemaReq);
+    Assertions.assertEquals(schema.properties(), 
createNamespaceResp.getProperties());
+
+    // create schema again with overwrite mode should succeed and update 
properties
+    Map<String, String> newSchemaProps =
+        new HashMap<>(schemaProps) {
+          {
+            put("schema_key2", "schema_value2");
+          }
+        };
+    createSchemaReq.setMode(CreateNamespaceRequest.ModeEnum.OVERWRITE);
+    createSchemaReq.setProperties(newSchemaProps);
+    createNamespaceResp = ns.createNamespace(createSchemaReq);
+
+    schema = catalog.asSchemas().loadSchema(schemaName);
+    Assertions.assertEquals(schema.properties(), 
createNamespaceResp.getProperties());
+  }
+
+  @Test
+  public void testDropNamespace() {
+    Catalog catalog = 
createCatalog(GravitinoITUtils.genRandomName("lance_catalog"));
+    Schema schema = catalog.asSchemas().createSchema("lance_schema", null, 
null);
+
+    // test drop a non-existent namespace (catalog) with default mode (FAIL) 
should fail
+    DropNamespaceRequest dropNamespaceReq = new DropNamespaceRequest();
+    dropNamespaceReq.addIdItem("non_existent_catalog");
+    LanceNamespaceException exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.dropNamespace(dropNamespaceReq));
+    Assertions.assertEquals(404, exception.getCode());
+
+    // test drop a non-existent namespace (catalog) with SKIP mode should 
succeed
+    dropNamespaceReq.setMode(DropNamespaceRequest.ModeEnum.SKIP);
+    DropNamespaceResponse dropNamespaceResp = 
ns.dropNamespace(dropNamespaceReq);
+    Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+
+    // test drop a non-existent namespace (schema) with default mode (FAIL) 
should fail
+    DropNamespaceRequest dropSchemaReq = new DropNamespaceRequest();
+    dropSchemaReq.addIdItem(catalog.name());
+    dropSchemaReq.addIdItem("non_existent_schema");
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.dropNamespace(dropSchemaReq));
+    Assertions.assertEquals(404, exception.getCode());
+
+    // test drop a non-existent namespace (schema) with SKIP mode should 
succeed
+    dropSchemaReq.setMode(DropNamespaceRequest.ModeEnum.SKIP);
+    dropNamespaceResp = ns.dropNamespace(dropSchemaReq);
+    Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+
+    // test drop a non-empty namespace (catalog) with default behavior 
(RESTRICT) should fail
+    DropNamespaceRequest dropNonEmptyCatalogReq = new DropNamespaceRequest();
+    dropNonEmptyCatalogReq.addIdItem(catalog.name());
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.dropNamespace(dropNonEmptyCatalogReq));
+    Assertions.assertEquals(400, exception.getCode());
+
+    // test drop a non-empty namespace (catalog) with CASCADE behavior should 
succeed
+    
dropNonEmptyCatalogReq.setBehavior(DropNamespaceRequest.BehaviorEnum.CASCADE);
+    dropNamespaceResp = ns.dropNamespace(dropNonEmptyCatalogReq);
+    Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+    Assertions.assertFalse(metalake.catalogExists(catalog.name()));
+
+    // recreate catalog, schema, and table for next test
+    catalog = createCatalog(catalog.name());
+    schema = catalog.asSchemas().createSchema(schema.name(), null, null);
+    String tableName = GravitinoITUtils.genRandomName("test_lance_table");
+    String tableLocation =
+        Path.of(tempDir.toString(), catalog.name(), schema.name(), 
tableName).toString();
+    catalog
+        .asTableCatalog()
+        .createTable(
+            NameIdentifier.of(schema.name(), tableName),
+            null,
+            null,
+            ImmutableMap.of("location", tableLocation, "format", "lance"));
+    // test drop a non-empty namespace (schema) with default behavior 
(RESTRICT) should fail
+    DropNamespaceRequest dropNonEmptySchemaReq = new DropNamespaceRequest();
+    dropNonEmptySchemaReq.addIdItem(catalog.name());
+    dropNonEmptySchemaReq.addIdItem(schema.name());
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.dropNamespace(dropNonEmptySchemaReq));
+    Assertions.assertEquals(400, exception.getCode());
+    Assertions.assertTrue(catalog.asSchemas().schemaExists(schema.name()));
+
+    // test drop a non-empty namespace (schema) with CASCADE behavior should 
succeed
+    
dropNonEmptySchemaReq.setBehavior(DropNamespaceRequest.BehaviorEnum.CASCADE);
+    dropNamespaceResp = ns.dropNamespace(dropNonEmptySchemaReq);
+    Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+    Assertions.assertFalse(catalog.asSchemas().schemaExists(schema.name()));
+  }
+
+  @Test
+  public void testNamespaceExists() {
+    Catalog catalog = 
createCatalog(GravitinoITUtils.genRandomName("lance_catalog"));
+    Schema schema = catalog.asSchemas().createSchema("lance_schema", null, 
null);
+
+    // test existing catalog
+    NamespaceExistsRequest catalogExistsReq = new NamespaceExistsRequest();
+    catalogExistsReq.addIdItem(catalog.name());
+    Assertions.assertDoesNotThrow(() -> ns.namespaceExists(catalogExistsReq));
+
+    // test non-existing catalog
+    NamespaceExistsRequest nonExistentCatalogReq = new 
NamespaceExistsRequest();
+    nonExistentCatalogReq.addIdItem("non_existent_catalog");
+    LanceNamespaceException exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.namespaceExists(nonExistentCatalogReq));
+    Assertions.assertEquals(404, exception.getCode());
+
+    // test existing schema
+    NamespaceExistsRequest schemaExistsReq = new NamespaceExistsRequest();
+    schemaExistsReq.addIdItem(catalog.name());
+    schemaExistsReq.addIdItem(schema.name());
+    Assertions.assertDoesNotThrow(() -> ns.namespaceExists(schemaExistsReq));
+
+    // test non-existing schema
+    NamespaceExistsRequest nonExistentSchemaReq = new NamespaceExistsRequest();
+    nonExistentSchemaReq.addIdItem(catalog.name());
+    nonExistentSchemaReq.addIdItem("non_existent_schema");
+    exception =
+        Assertions.assertThrows(
+            LanceNamespaceException.class, () -> 
ns.namespaceExists(nonExistentSchemaReq));
+    Assertions.assertEquals(404, exception.getCode());
+  }
+
+  private GravitinoMetalake createMetalake(String metalakeName) {
+    return client.createMetalake(metalakeName, "metalake for lance rest 
service tests", null);
+  }
+
+  private Catalog createCatalog(String catalogName) {
+    return metalake.createCatalog(
+        catalogName,
+        Catalog.Type.RELATIONAL,
+        "generic-lakehouse",
+        "catalog for lance rest service tests",
+        properties);
+  }
+
+  private String getLanceRestServiceUrl() {
+    return String.format("http://%s:%d/lance";, "localhost", 
getLanceRESTServerPort());
+  }
+}
diff --git 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/ServletRequestFactoryBase.java
similarity index 65%
copy from 
integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
copy to 
lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/ServletRequestFactoryBase.java
index 601138ecfd..68b557955c 100644
--- 
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/ServletRequestFactoryBase.java
@@ -16,17 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.gravitino.lance.service.rest;
 
-package org.apache.gravitino.integration.test;
+import java.util.function.Supplier;
+import javax.servlet.http.HttpServletRequest;
+import org.glassfish.hk2.api.Factory;
 
-import java.util.Map;
+abstract class ServletRequestFactoryBase
+    implements Factory<HttpServletRequest>, Supplier<HttpServletRequest> {
 
-public class MiniGravitinoContext {
-  Map<String, String> customConfig;
-  final boolean ignoreAuxRestService;
-
-  public MiniGravitinoContext(Map<String, String> customConfig, boolean 
ignoreAuxRestService) {
-    this.customConfig = customConfig;
-    this.ignoreAuxRestService = ignoreAuxRestService;
+  @Override
+  public HttpServletRequest provide() {
+    return get();
   }
+
+  @Override
+  public void dispose(HttpServletRequest instance) {}
 }
diff --git 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
new file mode 100644
index 0000000000..0ba8bf79b7
--- /dev/null
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
@@ -0,0 +1,326 @@
+/*
+ * 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.lance.service.rest;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
+import com.lancedb.lance.namespace.model.CreateNamespaceResponse;
+import com.lancedb.lance.namespace.model.DescribeNamespaceResponse;
+import com.lancedb.lance.namespace.model.DropNamespaceRequest;
+import com.lancedb.lance.namespace.model.DropNamespaceResponse;
+import com.lancedb.lance.namespace.model.ErrorResponse;
+import com.lancedb.lance.namespace.model.ListNamespacesResponse;
+import java.io.IOException;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestLanceNamespaceOperations extends JerseyTest {
+  private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
+    @Override
+    public HttpServletRequest get() {
+      HttpServletRequest request = mock(HttpServletRequest.class);
+      when(request.getRemoteUser()).thenReturn(null);
+      return request;
+    }
+  }
+
+  private static NamespaceWrapper namespaceWrapper = 
mock(NamespaceWrapper.class);
+  private static 
org.apache.gravitino.lance.common.ops.LanceNamespaceOperations namespaceOps =
+      
mock(org.apache.gravitino.lance.common.ops.LanceNamespaceOperations.class);
+
+  @Override
+  protected Application configure() {
+    try {
+      forceSet(
+          TestProperties.CONTAINER_PORT, 
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
+    ResourceConfig resourceConfig = new ResourceConfig();
+    resourceConfig.register(LanceNamespaceOperations.class);
+    resourceConfig.register(
+        new AbstractBinder() {
+          @Override
+          protected void configure() {
+            bind(namespaceWrapper).to(NamespaceWrapper.class).ranked(2);
+            
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
+          }
+        });
+
+    return resourceConfig;
+  }
+
+  @BeforeAll
+  public static void setup() {
+    when(namespaceWrapper.asNamespaceOps()).thenReturn(namespaceOps);
+  }
+
+  @Test
+  public void testListNamespaces() {
+    String namespaceId = "ns1.ns2";
+    String delimiter = ".";
+    ListNamespacesResponse listNamespacesResp = new ListNamespacesResponse();
+    
listNamespacesResp.setNamespaces(Sets.newHashSet(namespaceId.split(delimiter)));
+
+    when(namespaceOps.listNamespaces(any(), any(), any(), 
any())).thenReturn(listNamespacesResp);
+
+    Response resp =
+        target("/v1/namespace/ns1.ns2/list")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .get();
+
+    Mockito.verify(namespaceOps)
+        .listNamespaces(eq(namespaceId), eq(Pattern.quote(delimiter)), any(), 
any());
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    ListNamespacesResponse respEntity = 
resp.readEntity(ListNamespacesResponse.class);
+    Assertions.assertEquals(listNamespacesResp.getNamespaces(), 
respEntity.getNamespaces());
+    Assertions.assertEquals(listNamespacesResp.getPageToken(), 
respEntity.getPageToken());
+
+    // list namespaces under root
+    resp =
+        target("/v1/namespace/./list")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .get();
+
+    Mockito.verify(namespaceOps)
+        .listNamespaces(eq("."), eq(Pattern.quote(delimiter)), any(), any());
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+    respEntity = resp.readEntity(ListNamespacesResponse.class);
+    Assertions.assertEquals(listNamespacesResp.getNamespaces(), 
respEntity.getNamespaces());
+    Assertions.assertEquals(listNamespacesResp.getPageToken(), 
respEntity.getPageToken());
+
+    // test throw exception
+    when(namespaceOps.listNamespaces(any(), any(), any(), any()))
+        .thenThrow(new RuntimeException("Test exception"));
+    resp =
+        target("/v1/namespace/ns1.ns2/list")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .get();
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(500, errorResp.getCode());
+    Assertions.assertEquals("Test exception", errorResp.getError());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp.getType());
+    Assertions.assertEquals("ns1.ns2", errorResp.getInstance());
+    Assertions.assertNotNull(errorResp.getDetail());
+    Assertions.assertTrue(errorResp.getDetail().contains("Test exception"));
+  }
+
+  @Test
+  public void testDescribeNamespace() {
+    String namespaceId = "ns1.ns2";
+    String delimiter = ".";
+    DescribeNamespaceResponse describeNamespaceResp = new 
DescribeNamespaceResponse();
+    describeNamespaceResp.setProperties(ImmutableMap.of("key", "value"));
+
+    when(namespaceOps.describeNamespace(any(), 
any())).thenReturn(describeNamespaceResp);
+
+    Response resp =
+        target("/v1/namespace/ns1.ns2/describe")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(null);
+
+    Mockito.verify(namespaceOps).describeNamespace(eq(namespaceId), 
eq(Pattern.quote(delimiter)));
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    DescribeNamespaceResponse respEntity = 
resp.readEntity(DescribeNamespaceResponse.class);
+    Assertions.assertEquals(describeNamespaceResp.getProperties(), 
respEntity.getProperties());
+
+    // test throw exception
+    when(namespaceOps.describeNamespace(any(), any()))
+        .thenThrow(new RuntimeException("Test exception"));
+    resp =
+        target("/v1/namespace/ns1.ns2/describe")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(null);
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(500, errorResp.getCode());
+    Assertions.assertEquals("Test exception", errorResp.getError());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp.getType());
+  }
+
+  @Test
+  public void testCreateNamespace() {
+    String namespaceId = "ns1.ns2";
+    String delimiter = ".";
+    CreateNamespaceRequest createNamespaceReq = new CreateNamespaceRequest();
+    createNamespaceReq.setProperties(ImmutableMap.of("key", "value"));
+
+    CreateNamespaceResponse createNamespaceResp = new 
CreateNamespaceResponse();
+    createNamespaceResp.setProperties(ImmutableMap.of("key", "value"));
+
+    when(namespaceOps.createNamespace(any(), any(), any(), 
any())).thenReturn(createNamespaceResp);
+
+    Response resp =
+        target("/v1/namespace/ns1.ns2/create")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(createNamespaceReq, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Mockito.verify(namespaceOps)
+        .createNamespace(
+            eq(namespaceId),
+            eq(Pattern.quote(delimiter)),
+            eq(CreateNamespaceRequest.ModeEnum.CREATE),
+            eq(createNamespaceReq.getProperties()));
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    CreateNamespaceResponse respEntity = 
resp.readEntity(CreateNamespaceResponse.class);
+    Assertions.assertEquals(createNamespaceResp.getProperties(), 
respEntity.getProperties());
+
+    // test throw exception
+    when(namespaceOps.createNamespace(any(), any(), any(), any()))
+        .thenThrow(new RuntimeException("Test exception"));
+    resp =
+        target("/v1/namespace/ns1.ns2/create")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(createNamespaceReq, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(500, errorResp.getCode());
+    Assertions.assertEquals("Test exception", errorResp.getError());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp.getType());
+  }
+
+  @Test
+  public void testNamespaceExists() {
+    String namespaceId = "ns1.ns2";
+    String delimiter = ".";
+
+    doNothing().when(namespaceOps).namespaceExists(any(), any());
+
+    Response resp =
+        target("/v1/namespace/ns1.ns2/exists")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(null);
+
+    Mockito.verify(namespaceOps).namespaceExists(eq(namespaceId), 
eq(Pattern.quote(delimiter)));
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+
+    // test throw exception
+    doThrow(new NoSuchCatalogException("Not found"))
+        .when(namespaceOps)
+        .namespaceExists(any(), any());
+    resp =
+        target("/v1/namespace/ns1.ns2/exists")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(null);
+
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(404, errorResp.getCode());
+    Assertions.assertEquals("Not found", errorResp.getError());
+    Assertions.assertEquals(NoSuchCatalogException.class.getSimpleName(), 
errorResp.getType());
+  }
+
+  @Test
+  public void testDropNamespace() {
+    String namespaceId = "ns1.ns2";
+    String delimiter = ".";
+    DropNamespaceRequest dropNamespaceReq = new DropNamespaceRequest();
+
+    DropNamespaceResponse dropNamespaceResp = new DropNamespaceResponse();
+    when(namespaceOps.dropNamespace(any(), any(), any(), 
any())).thenReturn(dropNamespaceResp);
+
+    Response resp =
+        target("/v1/namespace/ns1.ns2/drop")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(dropNamespaceReq, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Mockito.verify(namespaceOps)
+        .dropNamespace(
+            eq(namespaceId),
+            eq(Pattern.quote(delimiter)),
+            eq(DropNamespaceRequest.ModeEnum.FAIL),
+            eq(DropNamespaceRequest.BehaviorEnum.RESTRICT));
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    // test throw exception
+    when(namespaceOps.dropNamespace(any(), any(), any(), any()))
+        .thenThrow(new RuntimeException("Test exception"));
+    resp =
+        target("/v1/namespace/ns1.ns2/drop")
+            .queryParam("delimiter", delimiter)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .post(Entity.entity(dropNamespaceReq, 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp.getStatus());
+    Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+
+    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(500, errorResp.getCode());
+    Assertions.assertEquals("Test exception", errorResp.getError());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp.getType());
+  }
+}
diff --git 
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
 
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
index 9c2d2d5110..f3d891ad39 100644
--- 
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
+++ 
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
@@ -155,7 +155,7 @@ public abstract class SparkEnvIT extends SparkUtilIT {
   protected void initCatalogEnv() throws Exception {}
 
   private void initIcebergRestServiceEnv() {
-    super.ignoreAuxRestService = false;
+    super.ignoreIcebergAuxRestService = false;
     Map<String, String> icebergRestServiceConfigs = new HashMap<>();
     icebergRestServiceConfigs.put(
         "gravitino."

Reply via email to