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

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


The following commit(s) were added to refs/heads/main by this push:
     new deb1e2d21f [#12783] fix(server): return JSON errors for the whole 
pre-resource-method WebApplicationException family (#12878)
deb1e2d21f is described below

commit deb1e2d21f007535e21d67fd7e5f8e8f09240f3c
Author: Jerry Shao <[email protected]>
AuthorDate: Fri Sep 4 14:16:21 2026 +0800

    [#12783] fix(server): return JSON errors for the whole pre-resource-method 
WebApplicationException family (#12878)
    
    ### What changes were proposed in this pull request?
    
    Follow-up to #12784. That PR registered `ParamExceptionMapper` (typed
    parameter conversion failures) and `NotFoundExceptionMapper` (unmatched
    routes), but those only cover two specific cases. Any other error
    resolved
    before reaching a resource method was still unmapped and fell through to
    Jetty's default HTML error page.
    
    This PR adds two fixes:
    
    - `WebApplicationExceptionMapper`, registered on the common
    `javax.ws.rs.WebApplicationException` base class as a catch-all for the
    rest of the JAX-RS family — a wrong HTTP method (`NotAllowedException`,
    405), an unsupported `Content-Type` (`NotSupportedException`, 415), etc.
    JAX-RS always selects the mapper for the nearest type in an exception's
    class hierarchy, so `ParamExceptionMapper` and `NotFoundExceptionMapper`
    still take precedence for the cases they already handle. It preserves
    the
      original response's headers (e.g. `Allow` on 405) via
    `Response.fromResponse(...)`, and intentionally never preserves any
    entity
    the original response might carry, so every error under `/api/*` stays
    the
      same `ErrorResponse` JSON shape.
    - `VersioningFilter` now writes the JSON `ErrorResponse` directly
    instead of
      calling `sendError` when it rejects an unsupported API version. This
    filter runs *before* Jersey ever sees the request, so no
    `ExceptionMapper`
    — including the one above — can ever intercept it; this was the actual
    406
    case users hit. (Jersey's own `NotAcceptableException` is not reachable
    through this API today, since `VersioningFilter` normalizes or rejects
    the
    version before Jersey runs; `WebApplicationExceptionMapper` still
    handles
      it generically as defense-in-depth.)
    
    Confirmed no existing code throws a raw `WebApplicationException`
    subtype
    expecting to reach the catch-all mapper with custom headers/entity: the
    two
    direct throws in `server/` (`NotSupportedException` in
    `MetadataObjectSecretOperations`/`MetadataObjectCredentialOperations`)
    and
    two more in `core/` (`CredentialOperationDispatcher`,
    `SecretPropertyOperationDispatcher`) are all already caught by a local
    `try`/`catch` before ever reaching Jersey's provider chain.
    
    Fix: #12783
    
    ### Why are the changes needed?
    
    Stated generally (per review discussion on #12784 and this PR): any
    error
    resolved before reaching a resource method should return the same
    structured JSON body as the rest of the API.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, under `/api/*`:
    - A wrong HTTP method (e.g. `POST /api/version`) now returns a
    structured
    JSON `ErrorResponse` — HTTP 405, type `UnsupportedOperationException` —
      instead of Jetty's default HTML error page.
    - An unrecognized `Accept` API version (e.g.
      `application/vnd.gravitino.v99+json`) now returns a structured JSON
    `ErrorResponse` — HTTP 406, type `IllegalArgumentException` — instead of
      an empty body with no `Content-Type`.
    
    No other endpoint's behavior changes.
    
    ### How was this patch tested?
    
    - `TestWebApplicationExceptionMapper` (unit test) covers
    `NotAllowedException`
      (405, including the preserved `Allow` header), `NotSupportedException`
      (415), and `NotAcceptableException` (406).
    - `TestVersioningFilter` updated to assert the new JSON `ErrorResponse`
    body
    (status, content type, and parsed `ErrorResponse` fields) instead of the
      old `sendError` call.
    - `JsonErrorHandlerIT` (integration test against a real running server)
      covers a malformed model version, a malformed model version URI, an
      unmatched `/api/*` route, a wrong HTTP method, and an unsupported API
      version — all 5 pass.
    - Ran the full `server` and `server-common` unit test suites; no
      regressions.
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../integration/test/JsonErrorHandlerIT.java       | 64 +++++++++++++-----
 .../apache/gravitino/server/GravitinoServer.java   |  2 +
 .../gravitino/server/web/VersioningFilter.java     | 13 +++-
 .../web/mapper/WebApplicationExceptionMapper.java  | 77 ++++++++++++++++++++++
 .../gravitino/server/web/TestVersioningFilter.java | 29 +++++++-
 .../mapper/TestWebApplicationExceptionMapper.java  | 67 +++++++++++++++++++
 6 files changed, 231 insertions(+), 21 deletions(-)

diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/JsonErrorHandlerIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/JsonErrorHandlerIT.java
index 797f9eda3c..c3841ae975 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/JsonErrorHandlerIT.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/JsonErrorHandlerIT.java
@@ -30,13 +30,16 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 /**
- * Integration test verifying that a malformed typed path parameter on the 
metadata API (e.g. a
- * non-numeric model version) returns the same structured JSON {@link 
ErrorResponse} used by every
- * other error on the API, instead of Jetty's default HTML error page.
+ * Integration test verifying that requests rejected before reaching a 
resource method (malformed
+ * typed parameters, unmatched routes, wrong HTTP methods, an unsupported API 
version) return the
+ * same structured JSON {@link ErrorResponse} used by every other error on the 
API, instead of
+ * Jetty's default HTML error page.
  *
- * <p>Jersey fails to convert the path segment into the resource method's 
typed {@code @PathParam}
- * before any resource method runs, so the metalake/catalog/schema/model in 
the URL need not
- * actually exist for this failure to occur.
+ * <p>Most of these cases are handled by Jersey itself — converting a typed
+ * {@code @PathParam}/{@code @QueryParam}, matching a route, matching an HTTP 
method to a resource —
+ * before any resource method runs. The unsupported-API-version case is 
rejected earlier still, by
+ * {@code VersioningFilter} before the request ever reaches Jersey. Either 
way, the
+ * metalake/catalog/schema/model in the URL need not actually exist for these 
failures to occur.
  */
 public class JsonErrorHandlerIT extends BaseIT {
 
@@ -45,42 +48,69 @@ public class JsonErrorHandlerIT extends BaseIT {
   @Test
   public void testMalformedModelVersionReturnsJsonErrorBody() throws Exception 
{
     HttpResponse<String> response =
-        
sendGet("/api/metalakes/m/catalogs/c/schemas/s/models/mo/versions/abc");
+        sendRequest("GET", 
"/api/metalakes/m/catalogs/c/schemas/s/models/mo/versions/abc");
 
     Assertions.assertEquals(404, response.statusCode());
-    assertJsonNotFoundBody(response, "PathParamException");
+    assertJsonErrorBody(response, ErrorConstants.NOT_FOUND_CODE, 
"PathParamException");
   }
 
   @Test
   public void testMalformedModelVersionUriReturnsJsonErrorBody() throws 
Exception {
     HttpResponse<String> response =
-        
sendGet("/api/metalakes/m/catalogs/c/schemas/s/models/mo/versions/abc/uri");
+        sendRequest("GET", 
"/api/metalakes/m/catalogs/c/schemas/s/models/mo/versions/abc/uri");
 
     Assertions.assertEquals(404, response.statusCode());
-    assertJsonNotFoundBody(response, "PathParamException");
+    assertJsonErrorBody(response, ErrorConstants.NOT_FOUND_CODE, 
"PathParamException");
   }
 
   @Test
   public void testUnknownApiRouteStillReturnsJsonErrorBody() throws Exception {
     // A route that Jersey cannot match at all is a different failure (no 
@PathParam conversion
     // is even attempted), but it must be covered by the same fix.
-    HttpResponse<String> response = sendGet("/api/v99/nonexistent/route");
+    HttpResponse<String> response = sendRequest("GET", 
"/api/v99/nonexistent/route");
 
     Assertions.assertEquals(404, response.statusCode());
-    assertJsonNotFoundBody(response, "NotFoundException");
+    assertJsonErrorBody(response, ErrorConstants.NOT_FOUND_CODE, 
"NotFoundException");
   }
 
-  private HttpResponse<String> sendGet(String path) throws Exception {
+  @Test
+  public void testWrongHttpMethodReturnsJsonErrorBody() throws Exception {
+    // /api/version only supports GET; POSTing to it never reaches a resource 
method either.
+    HttpResponse<String> response = sendRequest("POST", "/api/version");
+
+    Assertions.assertEquals(405, response.statusCode());
+    assertJsonErrorBody(
+        response, ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
"UnsupportedOperationException");
+  }
+
+  @Test
+  public void testUnsupportedApiVersionReturnsJsonErrorBody() throws Exception 
{
+    // VersioningFilter rejects an unrecognized API version before the request 
ever reaches
+    // Jersey, so this is not covered by any JAX-RS ExceptionMapper.
     HttpRequest request =
         HttpRequest.newBuilder()
-            .uri(new URI("http://localhost:"; + getGravitinoServerPort() + 
path))
+            .uri(new URI("http://localhost:"; + getGravitinoServerPort() + 
"/api/version"))
+            .header("Accept", "application/vnd.gravitino.v99+json")
             .GET()
             .build();
+    HttpResponse<String> response = httpClient.send(request, 
HttpResponse.BodyHandlers.ofString());
+
+    Assertions.assertEquals(406, response.statusCode());
+    assertJsonErrorBody(
+        response, ErrorConstants.ILLEGAL_ARGUMENTS_CODE, 
"IllegalArgumentException");
+  }
+
+  private HttpResponse<String> sendRequest(String method, String path) throws 
Exception {
+    HttpRequest request =
+        HttpRequest.newBuilder()
+            .uri(new URI("http://localhost:"; + getGravitinoServerPort() + 
path))
+            .method(method, HttpRequest.BodyPublishers.noBody())
+            .build();
     return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
   }
 
-  private void assertJsonNotFoundBody(HttpResponse<String> response, String 
expectedType)
-      throws Exception {
+  private void assertJsonErrorBody(
+      HttpResponse<String> response, int expectedCode, String expectedType) 
throws Exception {
     String contentType = 
response.headers().firstValue("Content-Type").orElse("");
     Assertions.assertTrue(
         contentType.contains("application/json"),
@@ -90,7 +120,7 @@ public class JsonErrorHandlerIT extends BaseIT {
 
     ErrorResponse errorResponse =
         ObjectMapperProvider.objectMapper().readValue(response.body(), 
ErrorResponse.class);
-    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResponse.getCode());
+    Assertions.assertEquals(expectedCode, errorResponse.getCode());
     Assertions.assertEquals(expectedType, errorResponse.getType());
     Assertions.assertFalse(errorResponse.getMessage().isEmpty());
   }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java 
b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
index 1450124335..880639e318 100644
--- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
+++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
@@ -66,6 +66,7 @@ import 
org.apache.gravitino.server.web.mapper.JsonParseExceptionMapper;
 import org.apache.gravitino.server.web.mapper.JsonProcessingExceptionMapper;
 import org.apache.gravitino.server.web.mapper.NotFoundExceptionMapper;
 import org.apache.gravitino.server.web.mapper.ParamExceptionMapper;
+import org.apache.gravitino.server.web.mapper.WebApplicationExceptionMapper;
 import org.apache.gravitino.server.web.ui.WebUIFilter;
 import org.apache.gravitino.stats.StatisticDispatcher;
 import org.apache.gravitino.tag.TagDispatcher;
@@ -174,6 +175,7 @@ public class GravitinoServer extends ResourceConfig {
     register(JsonMappingExceptionMapper.class);
     register(ParamExceptionMapper.class);
     register(NotFoundExceptionMapper.class);
+    register(WebApplicationExceptionMapper.class);
     register(ObjectMapperProvider.class).register(JacksonFeature.class);
     property(CommonProperties.JSON_JACKSON_DISABLED_MODULES, 
"DefaultScalaModule");
 
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/VersioningFilter.java 
b/server/src/main/java/org/apache/gravitino/server/web/VersioningFilter.java
index 98e132f89c..26fc3271c0 100644
--- a/server/src/main/java/org/apache/gravitino/server/web/VersioningFilter.java
+++ b/server/src/main/java/org/apache/gravitino/server/web/VersioningFilter.java
@@ -19,6 +19,7 @@
 package org.apache.gravitino.server.web;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -36,6 +37,7 @@ import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -156,8 +158,17 @@ public class VersioningFilter implements Filter {
     }
 
     LOG.error("Unsupported version v{} in request header.", version);
+    String message = String.format("Unsupported version v%d in request 
header", version);
+    ErrorResponse errorResponse = ErrorResponse.illegalArguments(message);
+
+    // Write the JSON ErrorResponse directly instead of calling 
HttpServletResponse#sendError, so
+    // this filter -- which runs before Jersey ever sees the request -- 
doesn't fall through to
+    // Jetty's default HTML error page.
     HttpServletResponse resp = (HttpServletResponse) response;
-    resp.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "Unsupported 
version");
+    resp.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+    resp.setContentType("application/json");
+    resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
+    ObjectMapperProvider.objectMapper().writeValue(resp.getWriter(), 
errorResponse);
     return true;
   }
 
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/mapper/WebApplicationExceptionMapper.java
 
b/server/src/main/java/org/apache/gravitino/server/web/mapper/WebApplicationExceptionMapper.java
new file mode 100644
index 0000000000..94ed1b48c4
--- /dev/null
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/mapper/WebApplicationExceptionMapper.java
@@ -0,0 +1,77 @@
+/*
+ * 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.server.web.mapper;
+
+import javax.annotation.Priority;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.eclipse.jetty.http.HttpStatus;
+
+/**
+ * WebApplicationExceptionMapper returns a structured JSON error body for any 
{@link
+ * WebApplicationException} that JAX-RS/Jersey raises itself before a resource 
method runs (e.g. a
+ * wrong HTTP method or an unacceptable {@code Accept}/{@code Content-Type} 
header), instead of
+ * letting the servlet container fall back to Jetty's default HTML error page.
+ *
+ * <p>{@link org.glassfish.jersey.server.ParamException} and {@link 
javax.ws.rs.NotFoundException}
+ * are subtypes of {@link WebApplicationException} with their own, more 
specific mappers ({@link
+ * ParamExceptionMapper}, {@link NotFoundExceptionMapper}); JAX-RS always 
prefers the mapper
+ * registered for the nearest type in the exception's class hierarchy, so this 
mapper only applies
+ * to every other case in the family.
+ *
+ * <p>Whatever entity {@code exception.getResponse()} may already carry is 
deliberately not
+ * preserved: every error under {@code /api/*} must be the same {@link 
ErrorResponse} JSON shape, so
+ * an exception that needs to convey more than a status code should do so 
through {@link
+ * WebApplicationException#getMessage()}, which this mapper does read.
+ */
+@Priority(1)
+public class WebApplicationExceptionMapper implements 
ExceptionMapper<WebApplicationException> {
+
+  @Override
+  public Response toResponse(WebApplicationException exception) {
+    int status = exception.getResponse().getStatus();
+    String message =
+        StringUtils.isBlank(exception.getMessage())
+            ? HttpStatus.getMessage(status)
+            : exception.getMessage();
+
+    return Response.fromResponse(exception.getResponse())
+        .entity(toErrorResponse(status, message))
+        .type(MediaType.APPLICATION_JSON)
+        .build();
+  }
+
+  private static ErrorResponse toErrorResponse(int status, String message) {
+    switch (status) {
+      case HttpServletResponse.SC_BAD_REQUEST:
+        return ErrorResponse.illegalArguments(message);
+      case HttpServletResponse.SC_FORBIDDEN:
+        return ErrorResponse.forbidden(message, null);
+      case HttpServletResponse.SC_METHOD_NOT_ALLOWED:
+        return ErrorResponse.unsupportedOperation(message);
+      default:
+        return ErrorResponse.restError(message);
+    }
+  }
+}
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/TestVersioningFilter.java
 
b/server/src/test/java/org/apache/gravitino/server/web/TestVersioningFilter.java
index 2d5f3dc1f0..91db0adeaa 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/TestVersioningFilter.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/TestVersioningFilter.java
@@ -31,6 +31,8 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -40,12 +42,30 @@ import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
 import 
org.apache.gravitino.server.web.VersioningFilter.MutableHttpServletRequest;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 
 public class TestVersioningFilter {
 
+  private static StringWriter stubWriter(HttpServletResponse mockResponse) 
throws IOException {
+    StringWriter stringWriter = new StringWriter();
+    when(mockResponse.getWriter()).thenReturn(new PrintWriter(stringWriter));
+    return stringWriter;
+  }
+
+  private static void assertUnsupportedVersionResponse(
+      HttpServletResponse mockResponse, StringWriter writer) throws 
IOException {
+    verify(mockResponse).setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
+    verify(mockResponse).setContentType("application/json");
+    ErrorResponse errorResponse =
+        ObjectMapperProvider.objectMapper().readValue(writer.toString(), 
ErrorResponse.class);
+    assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE, 
errorResponse.getCode());
+    assertTrue(errorResponse.getMessage().contains("Unsupported version"));
+  }
+
   @Test
   public void testDoFilterWithSupportedVersion() throws ServletException, 
IOException {
     VersioningFilter filter = new VersioningFilter();
@@ -75,11 +95,12 @@ public class TestVersioningFilter {
         .thenReturn(
             new 
Vector<>(Collections.singletonList("application/vnd.gravitino.v3+json"))
                 .elements());
+    StringWriter writer = stubWriter(mockResponse);
 
     filter.doFilter(mockRequest, mockResponse, mockChain);
 
     verify(mockChain, never()).doFilter(any(), any());
-    verify(mockResponse).sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, 
"Unsupported version");
+    assertUnsupportedVersionResponse(mockResponse, writer);
   }
 
   @Test
@@ -278,10 +299,11 @@ public class TestVersioningFilter {
     verify(mockResponse, never()).sendError(anyInt(), anyString());
 
     reset(mockChain, mockResponse);
+    StringWriter writer = stubWriter(mockResponse);
 
     filter.doFilter(mockRequest, mockResponse, mockChain);
     verify(mockChain, never()).doFilter(any(), any());
-    verify(mockResponse).sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, 
"Unsupported version");
+    assertUnsupportedVersionResponse(mockResponse, writer);
   }
 
   @Test
@@ -359,8 +381,9 @@ public class TestVersioningFilter {
                     Collections.singletonList(
                         "application/vnd.gravitino.v3+json; q=0.9, 
application/json"))
                 .elements());
+    StringWriter writer = stubWriter(mockResponse);
     filter.doFilter(mockRequest, mockResponse, mockChain);
     verify(mockChain, never()).doFilter(any(), any());
-    verify(mockResponse).sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, 
"Unsupported version");
+    assertUnsupportedVersionResponse(mockResponse, writer);
   }
 }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/mapper/TestWebApplicationExceptionMapper.java
 
b/server/src/test/java/org/apache/gravitino/server/web/mapper/TestWebApplicationExceptionMapper.java
new file mode 100644
index 0000000000..5aa2dc7d9c
--- /dev/null
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/mapper/TestWebApplicationExceptionMapper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.web.mapper;
+
+import javax.ws.rs.NotAcceptableException;
+import javax.ws.rs.NotAllowedException;
+import javax.ws.rs.NotSupportedException;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestWebApplicationExceptionMapper {
+
+  private final WebApplicationExceptionMapper mapper = new 
WebApplicationExceptionMapper();
+
+  @Test
+  public void testWrongHttpMethodReturnsJsonBody() {
+    Response response = mapper.toResponse(new NotAllowedException("GET"));
+    ErrorResponse entity = (ErrorResponse) response.getEntity();
+
+    Assertions.assertEquals(
+        Response.Status.METHOD_NOT_ALLOWED.getStatusCode(), 
response.getStatus());
+    Assertions.assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE, 
entity.getCode());
+    Assertions.assertFalse(entity.getMessage().isEmpty());
+    Assertions.assertTrue(
+        response.getHeaderString("Allow") != null
+            && response.getHeaderString("Allow").contains("GET"));
+  }
+
+  @Test
+  public void testUnsupportedMediaTypeReturnsJsonBody() {
+    Response response = mapper.toResponse(new 
NotSupportedException("unsupported content type"));
+    ErrorResponse entity = (ErrorResponse) response.getEntity();
+
+    Assertions.assertEquals(
+        Response.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode(), 
response.getStatus());
+    Assertions.assertEquals(ErrorConstants.REST_ERROR_CODE, entity.getCode());
+    Assertions.assertEquals("unsupported content type", entity.getMessage());
+  }
+
+  @Test
+  public void testNotAcceptableReturnsJsonBody() {
+    Response response = mapper.toResponse(new NotAcceptableException());
+    ErrorResponse entity = (ErrorResponse) response.getEntity();
+
+    Assertions.assertEquals(Response.Status.NOT_ACCEPTABLE.getStatusCode(), 
response.getStatus());
+    Assertions.assertFalse(entity.getMessage().isEmpty());
+  }
+}

Reply via email to