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

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


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 7f64eac08d [Cherry-pick to branch-1.3] [#12834] fix(server): reject 
null request bodies in remaining REST operations (#12866) (#12881)
7f64eac08d is described below

commit 7f64eac08dbd5aa9e18250dd9ba78c766b85aed4
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Sep 3 21:13:36 2026 +0800

    [Cherry-pick to branch-1.3] [#12834] fix(server): reject null request 
bodies in remaining REST operations (#12866) (#12881)
    
    **Cherry-pick Information:**
    - Original commit: 364e145c804f6c753ed3b5ad0bd8478e170d99b3
    - Target branch: `branch-1.3`
    - Status: ✅ Conflicts resolved manually
    
    **Resolution:**
    - Kept the null-body guards and regression tests for the 17 affected
    operations available on `branch-1.3`, plus the deprecated tag route.
    - Removed `BulkOperations.java` and `TestBulkOperations.java` because
    the Bulk user/group REST feature is not present on `branch-1.3`.
    - Kept the existing v1 tag-association path and omitted the main-only v2
    tag-value route and tests.
    - Resolved the Catalog, Permission, and Statistic conflicts against the
    `branch-1.3` control flow without bringing in unrelated main-only
    changes.
    
    **Validation:**
    - `./gradlew :server:spotlessApply --no-daemon`
    - `./gradlew :server:compileJava :server:compileTestJava -PskipITs
    --no-daemon`
    - Targeted tests for the 9 affected REST test classes: 96 tests, 0
    failures, 0 errors
    - Conflict-marker scan and `git diff --check`
    
    ---------
    
    Co-authored-by: Shuai Liu <[email protected]>
    Co-authored-by: mchades <[email protected]>
---
 .../server/web/rest/CatalogOperations.java         | 16 ++++-
 .../web/rest/MetadataObjectPolicyOperations.java   |  9 ++-
 .../web/rest/MetadataObjectTagOperations.java      |  8 +++
 .../server/web/rest/MetalakeOperations.java        |  7 ++
 .../gravitino/server/web/rest/OwnerOperations.java |  8 +++
 .../server/web/rest/PermissionOperations.java      | 56 +++++++++++++++
 .../server/web/rest/PolicyOperations.java          |  9 ++-
 .../server/web/rest/StatisticOperations.java       | 37 ++++++++--
 .../server/web/rest/TestCatalogOperations.java     | 28 ++++++++
 .../rest/TestMetadataObjectPolicyOperations.java   | 19 +++++-
 .../web/rest/TestMetadataObjectTagOperations.java  | 16 +++++
 .../server/web/rest/TestMetalakeOperations.java    | 63 +++++++++++++++++
 .../server/web/rest/TestOwnerOperations.java       | 11 +++
 .../server/web/rest/TestPermissionOperations.java  | 77 +++++++++++++++++++++
 .../server/web/rest/TestPolicyOperations.java      | 13 ++++
 .../server/web/rest/TestStatisticOperations.java   | 79 ++++++++++++++++++++--
 .../server/web/rest/TestTagOperations.java         | 16 +++++
 17 files changed, 455 insertions(+), 17 deletions(-)

diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
index 8def70d574..b7818a1a5a 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
@@ -188,6 +188,13 @@ public class CatalogOperations {
       @PathParam("metalake") @AuthorizationMetadata(type = 
Entity.EntityType.METALAKE)
           String metalake,
       CatalogCreateRequest request) {
+    if (request == null) {
+      // Unlike a failed connection test, which 
handleTestConnectionException() reports inside an
+      // HTTP 200 response by design, a missing request body never reaches the 
connection test, so
+      // it is rejected with a regular HTTP 400.
+      return Utils.illegalArguments("Request body cannot be null");
+    }
+
     LOG.info("Received test connection request for catalog: {}.{}", metalake, 
request.getName());
     try {
       return Utils.doAs(
@@ -227,8 +234,15 @@ public class CatalogOperations {
       @PathParam("catalog") @AuthorizationMetadata(type = 
Entity.EntityType.CATALOG)
           String catalogName,
       CatalogSetRequest request) {
-    LOG.info("Received set request for catalog: {}.{}", metalake, catalogName);
+    if (request == null) {
+      return ExceptionHandlers.handleCatalogException(
+          OperationType.SET,
+          catalogName,
+          metalake,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
 
+    LOG.info("Received set request for catalog: {}.{}", metalake, catalogName);
     OperationType op = request.isInUse() ? OperationType.ENABLE : 
OperationType.DISABLE;
 
     try {
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectPolicyOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectPolicyOperations.java
index 78a03570fb..19ea45ab96 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectPolicyOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectPolicyOperations.java
@@ -251,12 +251,19 @@ public class MetadataObjectPolicyOperations {
       @PathParam("fullName") @AuthorizationFullName String fullName,
       @AuthorizationRequest(type = 
AuthorizationRequest.RequestType.ASSOCIATE_POLICY)
           PoliciesAssociateRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handlePolicyException(
+          OperationType.ASSOCIATE,
+          "",
+          fullName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     LOG.info(
         "Received associate policies request for object type: {}, full name: 
{} under metalake: {}",
         type,
         fullName,
         metalake);
-
     try {
       return Utils.doAs(
           httpRequest,
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
index d96e655d30..525fe9943e 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
@@ -260,6 +260,14 @@ public class MetadataObjectTagOperations {
       @PathParam("fullName") @AuthorizationFullName String fullName,
       @AuthorizationRequest(type = 
AuthorizationRequest.RequestType.ASSOCIATE_TAG)
           TagsAssociateRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleTagException(
+          OperationType.ASSOCIATE,
+          "",
+          fullName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     LOG.info(
         "Received associate tags request for object type: {}, full name: {} 
under metalake: {}",
         type,
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetalakeOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetalakeOperations.java
index 7cce216e78..b8596792a1 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetalakeOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetalakeOperations.java
@@ -181,6 +181,13 @@ public class MetalakeOperations {
       @PathParam("name") @AuthorizationMetadata(type = 
Entity.EntityType.METALAKE)
           String metalakeName,
       MetalakeSetRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleMetalakeException(
+          OperationType.SET,
+          metalakeName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     LOG.info("Received set request for metalake: {}", metalakeName);
     try {
       return Utils.doAs(
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
index dbe91956b0..962dfde6a2 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
@@ -119,6 +119,14 @@ public class OwnerOperations {
       @PathParam("metadataObjectType") String metadataObjectType,
       @PathParam("fullName") String fullName,
       OwnerSetRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleOwnerException(
+          OperationType.SET,
+          String.format("metadata object %s", fullName),
+          metalake,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       MetadataObject object =
           MetadataObjects.parse(
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java
index 6ea1ea3390..ae74667c7c 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java
@@ -88,6 +88,14 @@ public class PermissionOperations {
           String metalake,
       @PathParam("user") String user,
       RoleGrantRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleUserPermissionOperationException(
+          OperationType.GRANT,
+          "",
+          user,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       return Utils.doAs(
           httpRequest,
@@ -117,6 +125,14 @@ public class PermissionOperations {
           String metalake,
       @PathParam("group") String group,
       RoleGrantRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleGroupPermissionOperationException(
+          OperationType.GRANT,
+          "",
+          group,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       return Utils.doAs(
           httpRequest,
@@ -146,6 +162,14 @@ public class PermissionOperations {
           String metalake,
       @PathParam("user") String user,
       RoleRevokeRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleUserPermissionOperationException(
+          OperationType.REVOKE,
+          "",
+          user,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       return Utils.doAs(
           httpRequest,
@@ -175,6 +199,14 @@ public class PermissionOperations {
           String metalake,
       @PathParam("group") String group,
       RoleRevokeRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleGroupPermissionOperationException(
+          OperationType.REVOKE,
+          "",
+          group,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       return Utils.doAs(
           httpRequest,
@@ -208,6 +240,14 @@ public class PermissionOperations {
       @PathParam("type") String type,
       @PathParam("fullName") String fullName,
       PrivilegeGrantRequest privilegeGrantRequest) {
+    if (privilegeGrantRequest == null) {
+      return ExceptionHandlers.handleRolePermissionOperationException(
+          OperationType.GRANT,
+          fullName,
+          role,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       MetadataObject object =
           MetadataObjects.parse(
@@ -256,6 +296,14 @@ public class PermissionOperations {
       @PathParam("type") String type,
       @PathParam("fullName") String fullName,
       PrivilegeRevokeRequest privilegeRevokeRequest) {
+    if (privilegeRevokeRequest == null) {
+      return ExceptionHandlers.handleRolePermissionOperationException(
+          OperationType.REVOKE,
+          fullName,
+          role,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       MetadataObject object =
           MetadataObjects.parse(
@@ -300,6 +348,14 @@ public class PermissionOperations {
           String metalake,
       @PathParam("role") String role,
       PrivilegeOverrideRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleRolePermissionOperationException(
+          OperationType.UPDATE,
+          role,
+          metalake,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
 
       return Utils.doAs(
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
index ec336b74ae..1816356524 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/PolicyOperations.java
@@ -253,8 +253,15 @@ public class PolicyOperations {
           String metalake,
       @PathParam("policy") @AuthorizationMetadata(type = 
Entity.EntityType.POLICY) String name,
       PolicySetRequest request) {
-    LOG.info("Received set policy request for policy: {} under metalake: {}", 
name, metalake);
+    if (request == null) {
+      return ExceptionHandlers.handlePolicyException(
+          OperationType.SET,
+          name,
+          metalake,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
 
+    LOG.info("Received set policy request for policy: {} under metalake: {}", 
name, metalake);
     OperationType op = request.isEnable() ? OperationType.ENABLE : 
OperationType.DISABLE;
 
     try {
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
index 420869c020..e86ce980c8 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
@@ -81,9 +81,6 @@ public class StatisticOperations {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(StatisticOperations.class);
 
-  private static final String NULL_STATS_UPDATE_REQUEST_BODY_ERROR =
-      "Statistics update request body cannot be null";
-
   @Context private HttpServletRequest httpRequest;
 
   private final StatisticDispatcher statisticDispatcher;
@@ -154,6 +151,14 @@ public class StatisticOperations {
       @PathParam("type") @AuthorizationObjectType String type,
       @PathParam("fullName") @AuthorizationFullName String fullName,
       StatisticsUpdateRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleStatisticException(
+          OperationType.UPDATE,
+          "",
+          fullName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       LOG.info(
           "Received update statistics request for object full name: {} type: 
{} in the metalake {}",
@@ -163,9 +168,6 @@ public class StatisticOperations {
       return Utils.doAs(
           httpRequest,
           () -> {
-            if (request == null) {
-              throw new 
IllegalArgumentException(NULL_STATS_UPDATE_REQUEST_BODY_ERROR);
-            }
             request.validate();
             MetadataObject object =
                 MetadataObjects.parse(
@@ -214,6 +216,14 @@ public class StatisticOperations {
       @PathParam("type") @AuthorizationObjectType String type,
       @PathParam("fullName") @AuthorizationFullName String fullName,
       StatisticsDropRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handleStatisticException(
+          OperationType.DROP,
+          "",
+          fullName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     try {
       LOG.info(
           "Received drop statistics request for object full name: {} type: {} 
in the metalake {}",
@@ -353,6 +363,14 @@ public class StatisticOperations {
       @PathParam("type") @AuthorizationObjectType String type,
       @PathParam("fullName") @AuthorizationFullName String fullName,
       PartitionStatisticsUpdateRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handlePartitionStatsException(
+          OperationType.UPDATE,
+          "",
+          fullName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
+
     LOG.info("Updating partition statistics for table: {} in the metalake {}", 
fullName, metalake);
     try {
       return Utils.doAs(
@@ -427,6 +445,13 @@ public class StatisticOperations {
       @PathParam("type") @AuthorizationObjectType String type,
       @PathParam("fullName") @AuthorizationFullName String fullName,
       PartitionStatisticsDropRequest request) {
+    if (request == null) {
+      return ExceptionHandlers.handlePartitionStatsException(
+          OperationType.DROP,
+          "",
+          fullName,
+          new IllegalArgumentException("Request body cannot be null"));
+    }
 
     try {
       return Utils.doAs(
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
index dfe09762e6..242bc02149 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
@@ -30,6 +30,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.ImmutableList;
@@ -312,6 +314,20 @@ public class TestCatalogOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse2.getType());
   }
 
+  @Test
+  public void testTestConnectionWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/catalogs/testConnection")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    // A missing request body never reaches the connection test, so it is 
reported as a regular
+    // HTTP 400 rather than through the HTTP 200 envelope used for 
connection-test failures.
+    assertNullRequestBodyRejected(resp);
+    verify(manager, never()).testConnection(any(), any(), any(), any(), any());
+  }
+
   @Test
   public void testConnection() {
     CatalogCreateRequest req =
@@ -610,6 +626,18 @@ public class TestCatalogOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse1.getType());
   }
 
+  @Test
+  public void testSetCatalogWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/catalogs/catalog1")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method("PATCH", Entity.entity("null", 
MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
   private static TestCatalog buildCatalog(String metalake, String catalogName) 
{
     CatalogEntity entity =
         CatalogEntity.builder()
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectPolicyOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectPolicyOperations.java
index e95b06fb88..6ff31e2203 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectPolicyOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectPolicyOperations.java
@@ -57,12 +57,11 @@ import org.apache.gravitino.policy.PolicyManager;
 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.Test;
 
-public class TestMetadataObjectPolicyOperations extends JerseyTest {
+public class TestMetadataObjectPolicyOperations extends BaseOperationsTest {
 
   private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
 
@@ -669,4 +668,20 @@ public class TestMetadataObjectPolicyOperations extends 
JerseyTest {
         .withAuditInfo(testAuditInfo1)
         .build();
   }
+
+  @Test
+  public void testAssociatePoliciesForObjectWithNullRequest() {
+    MetadataObject catalog = MetadataObjects.parse("object1", 
MetadataObject.Type.CATALOG);
+
+    Response response =
+        target(basePath(metalake))
+            .path(catalog.type().toString())
+            .path(catalog.fullName())
+            .path("policies")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(response);
+  }
 }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
index e9e7796521..93eb9cf288 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
@@ -1036,6 +1036,22 @@ public class TestMetadataObjectTagOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse1.getType());
   }
 
+  @Test
+  public void testAssociateTagsForObjectWithNullRequest() {
+    MetadataObject catalog = MetadataObjects.parse("object1", 
MetadataObject.Type.CATALOG);
+
+    Response response =
+        target(basePath(metalake))
+            .path(catalog.type().toString())
+            .path(catalog.fullName())
+            .path("tags")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(response);
+  }
+
   private String basePath(String metalake) {
     return "/metalakes/" + metalake + "/objects";
   }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetalakeOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetalakeOperations.java
index ae7eb39133..a2cbd0b251 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetalakeOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetalakeOperations.java
@@ -51,6 +51,7 @@ import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.dto.MetalakeDTO;
 import org.apache.gravitino.dto.requests.MetalakeCreateRequest;
+import org.apache.gravitino.dto.requests.MetalakeSetRequest;
 import org.apache.gravitino.dto.requests.MetalakeUpdateRequest;
 import org.apache.gravitino.dto.requests.MetalakeUpdatesRequest;
 import org.apache.gravitino.dto.responses.DropResponse;
@@ -70,6 +71,7 @@ import 
org.apache.gravitino.server.web.mapper.JsonMappingExceptionMapper;
 import org.apache.gravitino.server.web.mapper.JsonParseExceptionMapper;
 import org.apache.gravitino.server.web.mapper.JsonProcessingExceptionMapper;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.test.TestProperties;
 import org.junit.jupiter.api.Assertions;
@@ -263,6 +265,67 @@ public class TestMetalakeOperations extends 
BaseOperationsTest {
     assertNullRequestBodyRejected(resp);
   }
 
+  @Test
+  public void testSetMetalake() {
+    Mockito.doNothing().when(metalakeManager).enableMetalake(any());
+    Mockito.doNothing().when(metalakeManager).disableMetalake(any());
+
+    Response enableResp =
+        target("/metalakes/test")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method(
+                "PATCH",
+                Entity.entity(new MetalakeSetRequest(true), 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
enableResp.getStatus());
+    Mockito.verify(metalakeManager).enableMetalake(any());
+
+    Response disableResp =
+        target("/metalakes/test")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method(
+                "PATCH",
+                Entity.entity(new MetalakeSetRequest(false), 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
disableResp.getStatus());
+    Mockito.verify(metalakeManager).disableMetalake(any());
+  }
+
+  @Test
+  public void testSetMetalakeWithNullRequest() {
+    Response resp =
+        target("/metalakes/test")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method("PATCH", Entity.entity("null", 
MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testSetMetalakeWithMalformedJson() {
+    // The null-body guard must not take over malformed JSON, which keeps 
using the registered
+    // Jackson exception mappers.
+    Response resp =
+        target("/metalakes/test")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method("PATCH", Entity.entity("{", 
MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
resp.getStatus());
+    ErrorResponse errorResponse = resp.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE, 
errorResponse.getCode());
+    Assertions.assertEquals(
+        IllegalArgumentException.class.getSimpleName(), 
errorResponse.getType());
+    Assertions.assertTrue(errorResponse.getMessage().contains("Malformed json 
request"));
+  }
+
   @Test
   public void testLoadMetalake() {
     String metalakeName = "test";
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
index 97eb522374..b342f3cfda 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
@@ -316,6 +316,17 @@ class TestOwnerOperations extends BaseOperationsTest {
     Assertions.assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE, 
errorResponse3.getCode());
   }
 
+  @Test
+  public void testSetOwnerForObjectWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
   @Test
   public void testRoleObject() {
     MetadataObject role = MetadataObjects.of(null, "role", 
MetadataObject.Type.ROLE);
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java
index 6a18db9af7..037490a407 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java
@@ -812,4 +812,81 @@ public class TestPermissionOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse2.getType());
   }
+
+  @Test
+  public void testGrantRolesToUserWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/permissions/users/user1/grant")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testGrantRolesToGroupWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/permissions/groups/group1/grant")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testRevokeRolesFromUserWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/permissions/users/user1/revoke")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testRevokeRolesFromGroupWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/permissions/groups/group1/revoke")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testGrantPrivilegeToRoleWithNullRequest() {
+    Response resp =
+        
target("/metalakes/metalake1/permissions/roles/role1/metalake/metalake1/grant")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testRevokePrivilegeFromRoleWithNullRequest() {
+    Response resp =
+        
target("/metalakes/metalake1/permissions/roles/role1/metalake/metalake1/revoke")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testOverrideRolePrivilegesWithNullRequest() {
+    Response resp =
+        target("/metalakes/metalake1/permissions/roles/role1/")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(new byte[0], MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
 }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
index 3095512cac..85e45239fe 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPolicyOperations.java
@@ -671,6 +671,19 @@ public class TestPolicyOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResp.getType());
   }
 
+  @Test
+  public void testSetPolicyWithNullRequest() {
+    Response resp =
+        target(policyPath(metalake))
+            .path("policy1")
+            .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true)
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .method("PATCH", Entity.entity("null", 
MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
   @Test
   public void testDeletePolicy() {
     when(policyManager.deletePolicy(metalake, "policy1")).thenReturn(true);
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
index a970af8214..da591459de 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
@@ -73,14 +73,13 @@ import org.apache.gravitino.stats.StatisticValue;
 import org.apache.gravitino.stats.StatisticValues;
 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 TestStatisticOperations extends JerseyTest {
+public class TestStatisticOperations extends BaseOperationsTest {
 
   private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
     @Override
@@ -392,12 +391,80 @@ public class TestStatisticOperations extends JerseyTest {
             .accept("application/vnd.gravitino.v1+json")
             .put(entity("null", MediaType.APPLICATION_JSON_TYPE));
 
-    Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
resp.getStatus());
     Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE, 
resp.getMediaType());
+    assertNullRequestBodyRejected(resp);
+  }
 
-    ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
-    Assertions.assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE, 
errorResp.getCode());
-    Assertions.assertEquals(IllegalArgumentException.class.getSimpleName(), 
errorResp.getType());
+  @Test
+  public void testDropStatisticsWithNullRequest() {
+    when(tableDispatcher.tableExists(any())).thenReturn(true);
+
+    MetadataObject tableObject =
+        MetadataObjects.parse(
+            String.format("%s.%s.%s", catalog, schema, table), 
MetadataObject.Type.TABLE);
+
+    Response resp =
+        target(
+                "/metalakes/"
+                    + metalake
+                    + "/objects/"
+                    + tableObject.type()
+                    + "/"
+                    + tableObject.fullName()
+                    + "/statistics")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testUpdatePartitionStatisticsWithNullRequest() {
+    when(tableDispatcher.tableExists(any())).thenReturn(true);
+
+    MetadataObject tableObject =
+        MetadataObjects.parse(
+            String.format("%s.%s.%s", catalog, schema, table), 
MetadataObject.Type.TABLE);
+
+    Response resp =
+        target(
+                "/metalakes/"
+                    + metalake
+                    + "/objects/"
+                    + tableObject.type()
+                    + "/"
+                    + tableObject.fullName()
+                    + "/statistics/partitions")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
+  }
+
+  @Test
+  public void testDropPartitionStatisticsWithNullRequest() {
+    when(tableDispatcher.tableExists(any())).thenReturn(true);
+
+    MetadataObject tableObject =
+        MetadataObjects.parse(
+            String.format("%s.%s.%s", catalog, schema, table), 
MetadataObject.Type.TABLE);
+
+    Response resp =
+        target(
+                "/metalakes/"
+                    + metalake
+                    + "/objects/"
+                    + tableObject.type()
+                    + "/"
+                    + tableObject.fullName()
+                    + "/statistics/partitions")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(entity(new byte[0], MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(resp);
   }
 
   @Test
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java
index e5152b8046..ae15334359 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java
@@ -1131,6 +1131,22 @@ public class TestTagOperations extends 
BaseOperationsTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse1.getType());
   }
 
+  @Test
+  public void testAssociateTagsForObjectWithNullRequest() {
+    MetadataObject catalog = MetadataObjects.parse("object1", 
MetadataObject.Type.CATALOG);
+
+    // The deprecated route delegates to MetadataObjectTagOperations, so it 
inherits the same guard.
+    Response response =
+        target(tagPath(metalake))
+            .path(catalog.type().toString())
+            .path(catalog.fullName())
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .post(Entity.entity("null", MediaType.APPLICATION_JSON_TYPE));
+
+    assertNullRequestBodyRejected(response);
+  }
+
   private String tagPath(String metalake) {
     return "/metalakes/" + metalake + "/tags";
   }

Reply via email to