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

apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 8f4cc43563 IGNITE-23195 AssertionError is not handled in REST API 
(#4383)
8f4cc43563 is described below

commit 8f4cc435631221a116270a1c45f54612687e0cde
Author: Vadim Pakhnushev <[email protected]>
AuthorDate: Mon Sep 16 17:38:57 2024 +0300

    IGNITE-23195 AssertionError is not handled in REST API (#4383)
---
 .../handler/IgniteCliApiExceptionHandler.java      |   1 +
 modules/rest-api/build.gradle                      |   5 +
 .../internal/rest/api/GeneralErrorsController.java |   2 +-
 .../exception/handler/JavaExceptionHandler.java    |   8 +-
 .../rest/exception/handler/ErrorHandlingTest.java  | 256 ++++++++-------------
 .../rest/matcher/MicronautHttpResponseMatcher.java |  61 ++++-
 .../internal/rest/matcher/ProblemMatcher.java      |   4 +-
 .../internal/rest/matcher/RestJobStateMatcher.java |   0
 modules/rest/build.gradle                          |   8 +-
 ...terStateHttpServerFilterNotInitializedTest.java |  27 +--
 .../rest/compute/ItComputeControllerTest.java      |  29 +--
 11 files changed, 191 insertions(+), 210 deletions(-)

diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
index 8fd9befcec..f400f50a65 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
@@ -115,6 +115,7 @@ public class IgniteCliApiExceptionHandler implements 
ExceptionHandler<IgniteCliA
         try {
             return objectMapper.readValue(responseBody, Problem.class);
         } catch (JsonProcessingException ex) {
+            LOG.error("Failed to extract problem from body {}", ex, 
responseBody);
             throw new RuntimeException(ex);
         }
     }
diff --git a/modules/rest-api/build.gradle b/modules/rest-api/build.gradle
index ac99e8020b..d3b12fa70d 100644
--- a/modules/rest-api/build.gradle
+++ b/modules/rest-api/build.gradle
@@ -18,6 +18,7 @@
 apply from: "$rootDir/buildscripts/java-core.gradle"
 apply from: "$rootDir/buildscripts/publishing.gradle"
 apply from: "$rootDir/buildscripts/java-junit5.gradle"
+apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
 
 description = 'ignite-rest-api'
 
@@ -50,6 +51,10 @@ dependencies {
     testImplementation libs.micronaut.http.server.netty
     testImplementation libs.hamcrest.core
     testImplementation libs.hamcrest.optional
+
+    testFixturesImplementation testFixtures(project(":ignite-core"))
+    testFixturesImplementation libs.hamcrest.core
+    testFixturesImplementation libs.micronaut.http.core
 }
 
 ext {
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/GeneralErrorsController.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/GeneralErrorsController.java
index 56c5a1e457..f03e735a25 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/GeneralErrorsController.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/GeneralErrorsController.java
@@ -60,7 +60,7 @@ public class GeneralErrorsController {
     public HttpResponse<? extends Problem> unsupportedMediaType(HttpRequest<?> 
request) {
         return HttpProblemResponse.from(
                 Problem.fromHttpCode(HttpCode.UNSUPPORTED_MEDIA_TYPE)
-                        .detail("Unsupported media type: " + 
request.getContentType().map(MediaType::getType).orElse(null))
+                        .detail("Unsupported media type: " + 
request.getContentType().map(MediaType::getName).orElse(null))
         );
     }
 
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/JavaExceptionHandler.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/JavaExceptionHandler.java
index e60ee1858b..563524d09e 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/JavaExceptionHandler.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/JavaExceptionHandler.java
@@ -29,16 +29,16 @@ import org.apache.ignite.internal.rest.constants.HttpCode;
 import org.apache.ignite.internal.rest.problem.HttpProblemResponse;
 
 /**
- * Handles {@link Exception} and represents it as an application/problem+json 
response. This will catch all unhandled exceptions since all
+ * Handles {@link Throwable} and represents it as an application/problem+json 
response. This will catch all unhandled exceptions since all
  * REST endpoints are marked as producing problem json.
  */
 @Singleton
-@Requires(classes = {Exception.class, ExceptionHandler.class})
-public class JavaExceptionHandler implements ExceptionHandler<Exception, 
HttpResponse<? extends Problem>> {
+@Requires(classes = {Throwable.class, ExceptionHandler.class})
+public class JavaExceptionHandler implements ExceptionHandler<Throwable, 
HttpResponse<? extends Problem>> {
     private static final IgniteLogger LOG = 
Loggers.forClass(JavaExceptionHandler.class);
 
     @Override
-    public HttpResponse<? extends Problem> handle(HttpRequest request, 
Exception exception) {
+    public HttpResponse<? extends Problem> handle(HttpRequest request, 
Throwable exception) {
         LOG.error("Unhandled exception", exception);
         return HttpProblemResponse.from(
                 Problem.fromHttpCode(HttpCode.INTERNAL_SERVER_ERROR)
diff --git 
a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
 
b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
index cec54b7532..730dc1ee79 100644
--- 
a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
+++ 
b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/ErrorHandlingTest.java
@@ -17,16 +17,20 @@
 
 package org.apache.ignite.internal.rest.exception.handler;
 
-import static org.apache.ignite.internal.rest.constants.HttpCode.BAD_REQUEST;
-import static 
org.apache.ignite.internal.rest.constants.HttpCode.METHOD_NOT_ALLOWED;
-import static org.apache.ignite.internal.rest.constants.HttpCode.NOT_FOUND;
-import static 
org.apache.ignite.internal.rest.constants.HttpCode.UNSUPPORTED_MEDIA_TYPE;
-import static 
org.apache.ignite.internal.rest.problem.ProblemJsonMediaType.APPLICATION_JSON_PROBLEM_TYPE;
+import static io.micronaut.http.HttpStatus.BAD_REQUEST;
+import static io.micronaut.http.HttpStatus.INTERNAL_SERVER_ERROR;
+import static io.micronaut.http.HttpStatus.METHOD_NOT_ALLOWED;
+import static io.micronaut.http.HttpStatus.NOT_FOUND;
+import static io.micronaut.http.HttpStatus.UNAUTHORIZED;
+import static io.micronaut.http.HttpStatus.UNSUPPORTED_MEDIA_TYPE;
+import static 
org.apache.ignite.internal.rest.matcher.MicronautHttpResponseMatcher.isProblemResponse;
+import static org.apache.ignite.internal.rest.matcher.ProblemMatcher.isProblem;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 import io.micronaut.context.annotation.Bean;
 import io.micronaut.context.annotation.Factory;
@@ -34,7 +38,7 @@ import io.micronaut.context.annotation.Property;
 import io.micronaut.core.bind.exceptions.UnsatisfiedArgumentException;
 import io.micronaut.core.type.Argument;
 import io.micronaut.http.HttpRequest;
-import io.micronaut.http.HttpResponse;
+import io.micronaut.http.HttpStatus;
 import io.micronaut.http.MutableHttpRequest;
 import io.micronaut.http.client.HttpClient;
 import io.micronaut.http.client.annotation.Client;
@@ -52,9 +56,11 @@ import 
org.apache.ignite.internal.lang.IgniteInternalException;
 import org.apache.ignite.internal.rest.api.InvalidParam;
 import org.apache.ignite.internal.rest.api.Problem;
 import org.apache.ignite.internal.rest.constants.MediaType;
+import org.apache.ignite.internal.rest.matcher.ProblemMatcher;
 import org.apache.ignite.lang.IgniteException;
-import org.junit.jupiter.api.Assertions;
+import org.hamcrest.Matcher;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -70,143 +76,99 @@ public class ErrorHandlingTest {
     @Client("/test")
     HttpClient client;
 
-    private final AtomicReference<Throwable> throwable = new 
AtomicReference<>(new RuntimeException());
+    private static final AtomicReference<Throwable> throwable = new 
AtomicReference<>(new RuntimeException());
 
     private static Stream<Arguments> testExceptions() {
         return Stream.of(
                 // couldn't find a case when exception is thrown
-                Arguments.of(new 
UnsatisfiedArgumentException(Argument.DOUBLE)),
+                arguments(new UnsatisfiedArgumentException(Argument.DOUBLE), 
BAD_REQUEST,
+                        "Bad Request", "Required argument [double] not 
specified"),
                 // thrown when request uri is invalid, but it's not possible 
to create such request with HttpClient (it validates uri)
-                Arguments.of(new URISyntaxException("uri", "reason")),
-                Arguments.of(new 
AuthenticationException("authentication-exception")),
-                Arguments.of(new AuthorizationException(null)),
-                Arguments.of(new IgniteException("ignite-exception")),
-                Arguments.of(new 
IgniteInternalCheckedException("ignite-internal-exception")),
-                Arguments.of(new 
IgniteInternalException("ignite-internal-exception")),
-                Arguments.of(new RuntimeException("runtime-exception")),
-                Arguments.of(new Exception("exception"))
+                arguments(new URISyntaxException("uri", "reason"), 
BAD_REQUEST, "Malformed URI", "reason: uri"),
+                arguments(new 
AuthenticationException("authentication-exception"), UNAUTHORIZED,
+                        "Unauthorized", "authentication-exception"),
+                arguments(new AuthorizationException(null), UNAUTHORIZED, 
"Unauthorized", null),
+                arguments(new IgniteException("ignite-exception"), 
INTERNAL_SERVER_ERROR, "Internal Server Error", "ignite-exception"),
+                arguments(new 
IgniteInternalCheckedException("ignite-internal-exception"), 
INTERNAL_SERVER_ERROR,
+                        "Internal Server Error", "ignite-internal-exception"),
+                arguments(new 
IgniteInternalException("ignite-internal-exception"), INTERNAL_SERVER_ERROR,
+                        "Internal Server Error", "ignite-internal-exception"),
+                arguments(new RuntimeException("runtime-exception"), 
INTERNAL_SERVER_ERROR, "Internal Server Error", "runtime-exception"),
+                arguments(new Exception("exception"), INTERNAL_SERVER_ERROR, 
"Internal Server Error", "exception"),
+                // Can't test for the AssertionError because TC will fail if 
this text is logged. We log unhandled exception in the
+                // JavaExceptionHandler
+                // arguments(new AssertionError("assert"), 
INTERNAL_SERVER_ERROR, "Internal Server Error", "assert"),
+                arguments(new Throwable("assert"), INTERNAL_SERVER_ERROR, 
"Internal Server Error", "assert")
         );
     }
 
     @ParameterizedTest
     @MethodSource("testExceptions")
-    public void testExceptions(Throwable throwable) {
-        this.throwable.set(throwable);
+    public void testExceptions(Throwable throwable, HttpStatus status, String 
title, String detail) {
+        ErrorHandlingTest.throwable.set(throwable);
 
-        // Invoke endpoint with not allowed method
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange("/test/throw-exception")
-        );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-        assertEquals(response.code(), problem.status());
-        assertNotNull(problem.title());
+        assertThrowsProblem(() -> 
client.toBlocking().exchange("/throw-exception"), status, title, detail);
     }
 
     @Test
     public void endpoint404() {
         // Invoke non-existing endpoint
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange("/endpoint404")
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange("/endpoint404"),
+                NOT_FOUND,
+                "Not Found",
+                "Requested resource not found: /test/endpoint404"
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(NOT_FOUND.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(NOT_FOUND.code(), problem.status());
-        assertEquals("Not Found", problem.title());
-        assertEquals("Requested resource not found: /test/endpoint404", 
problem.detail());
     }
 
     @Test
     public void invalidDataTypePathVariable() {
         // Invoke endpoint with wrong path variable data type
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange("/list/abc")
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange("/list/abc"),
+                BAD_REQUEST,
+                "Invalid parameter",
+                "Failed to convert argument [id] for value [abc] due to: For 
input string: \"abc\""
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(BAD_REQUEST.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(BAD_REQUEST.code(), problem.status());
-        assertEquals("Invalid parameter", problem.title());
-        assertEquals("Failed to convert argument [id] for value [abc] due to: 
For input string: \"abc\"", problem.detail());
     }
 
     @Test
     public void requiredQueryValueNotSpecified() {
         // Invoke endpoint without required query value
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange("/list")
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange("/list"),
+                BAD_REQUEST,
+                "Bad Request",
+                "Required QueryValue [greatThan] not specified"
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(BAD_REQUEST.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(BAD_REQUEST.code(), problem.status());
-        assertEquals("Bad Request", problem.title());
-        assertEquals("Required QueryValue [greatThan] not specified", 
problem.detail());
     }
 
     @Test
     public void invalidTypeQueryValue() {
         // Invoke endpoint with wrong data type of request argument
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange("/list?greatThan=abc")
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange("/list?greatThan=abc"),
+                BAD_REQUEST,
+                "Invalid parameter",
+                "Failed to convert argument [greatThan] for value [abc] due 
to: For input string: \"abc\""
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(BAD_REQUEST.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(BAD_REQUEST.code(), problem.status());
-        assertEquals("Invalid parameter", problem.title());
-        assertEquals("Failed to convert argument [greatThan] for value [abc] 
due to: For input string: \"abc\"", problem.detail());
     }
 
     @Test
     public void invalidTypeQueryValue1() {
         // Invoke endpoint with wrong request argument values
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> 
client.toBlocking().exchange("/list?greatThan=-1&lessThan=11")
+        assertThrowsProblem(
+                () -> 
client.toBlocking().exchange("/list?greatThan=-1&lessThan=11"),
+                BAD_REQUEST,
+                isProblem()
+                        .withStatus(BAD_REQUEST.getCode())
+                        .withTitle("Bad Request")
+                        .withDetail("Validation failed")
+                        .withInvalidParams(containsInAnyOrder(
+                                new InvalidParam("list.greatThan", "greatThan: 
must be greater than or equal to 0"),
+                                new InvalidParam("list.lessThan", "lessThan: 
must be less than or equal to 10")
+                        ))
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(BAD_REQUEST.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(BAD_REQUEST.code(), problem.status());
-        assertEquals("Bad Request", problem.title());
-        assertEquals("Validation failed", problem.detail());
-
-        assertEquals(2, problem.invalidParams().size());
-
-        assertThat(problem.invalidParams(), containsInAnyOrder(
-                new InvalidParam("list.greatThan", "greatThan: must be greater 
than or equal to 0"),
-                new InvalidParam("list.lessThan", "lessThan: must be less than 
or equal to 10")
-        ));
     }
 
     @Test
@@ -216,20 +178,12 @@ public class ErrorHandlingTest {
                 .contentType(MediaType.TEXT_PLAIN)
                 .body("text='qwe'");
 
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class))
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class)),
+                UNSUPPORTED_MEDIA_TYPE,
+                "Unsupported Media Type",
+                "Unsupported media type: text/plain"
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(UNSUPPORTED_MEDIA_TYPE.code(), 
response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(UNSUPPORTED_MEDIA_TYPE.code(), problem.status());
-        assertEquals("Unsupported Media Type", problem.title());
-        assertEquals("Unsupported media type: text", problem.detail());
     }
 
     @Test
@@ -239,20 +193,12 @@ public class ErrorHandlingTest {
                 .contentType(MediaType.APPLICATION_JSON)
                 .body("{text='qwe'");
 
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class))
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class)),
+                BAD_REQUEST,
+                "Invalid JSON",
+                containsString("Unexpected character")
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(BAD_REQUEST.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(BAD_REQUEST.code(), problem.status());
-        assertEquals("Invalid JSON", problem.title());
-        assertThat(problem.detail(), containsString("Unexpected character"));
     }
 
     @Test
@@ -262,20 +208,12 @@ public class ErrorHandlingTest {
                 .contentType(MediaType.APPLICATION_JSON)
                 .body("");
 
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class))
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class)),
+                BAD_REQUEST,
+                "Bad Request",
+                containsString("Required Body [dto] not specified")
         );
-
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
-
-        assertEquals(BAD_REQUEST.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
-
-        assertEquals(BAD_REQUEST.code(), problem.status());
-        assertEquals("Bad Request", problem.title());
-        assertThat(problem.detail(), containsString("Required Body [dto] not 
specified"));
     }
 
     @Test
@@ -283,20 +221,30 @@ public class ErrorHandlingTest {
         // Invoke endpoint with not allowed method
         MutableHttpRequest<String> request = 
HttpRequest.GET(UriBuilder.of("/echo").build());
 
-        HttpClientResponseException thrown = Assertions.assertThrows(
-                HttpClientResponseException.class,
-                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class))
+        assertThrowsProblem(
+                () -> client.toBlocking().exchange(request, 
Argument.of(EchoMessage.class), Argument.of(Problem.class)),
+                METHOD_NOT_ALLOWED,
+                "Method Not Allowed",
+                "Method not allowed: GET"
         );
+    }
 
-        HttpResponse<?> response = thrown.getResponse();
-        Problem problem = response.getBody(Problem.class).get();
+    private static void assertThrowsProblem(Executable executable, HttpStatus 
status, String title, String detail) {
+        assertThrowsProblem(executable, status, title, equalTo(detail));
+    }
+
+    private static void assertThrowsProblem(Executable executable, HttpStatus 
status, String title, Matcher<String> detailMatcher) {
+        assertThrowsProblem(executable, status, isProblem()
+                .withStatus(status.getCode())
+                .withTitle(title)
+                .withDetail(detailMatcher)
+        );
+    }
 
-        assertEquals(METHOD_NOT_ALLOWED.code(), response.status().getCode());
-        assertEquals(APPLICATION_JSON_PROBLEM_TYPE.getType(), 
response.getContentType().get().getType());
+    private static void assertThrowsProblem(Executable executable, HttpStatus 
status, ProblemMatcher problemMatcher) {
+        HttpClientResponseException thrown = 
assertThrows(HttpClientResponseException.class, executable);
 
-        assertEquals(METHOD_NOT_ALLOWED.code(), problem.status());
-        assertEquals("Method Not Allowed", problem.title());
-        assertEquals("Method not allowed: GET", problem.detail());
+        assertThat(thrown.getResponse(), isProblemResponse(status, 
problemMatcher));
     }
 
     @Bean
diff --git 
a/modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/MicronautHttpResponseMatcher.java
 
b/modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/MicronautHttpResponseMatcher.java
similarity index 62%
rename from 
modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/MicronautHttpResponseMatcher.java
rename to 
modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/MicronautHttpResponseMatcher.java
index d1163b55b7..e6825e4140 100644
--- 
a/modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/MicronautHttpResponseMatcher.java
+++ 
b/modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/MicronautHttpResponseMatcher.java
@@ -17,11 +17,15 @@
 
 package org.apache.ignite.internal.rest.matcher;
 
+import static 
org.apache.ignite.internal.rest.problem.ProblemJsonMediaType.APPLICATION_JSON_PROBLEM_TYPE;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 
 import io.micronaut.http.HttpResponse;
 import io.micronaut.http.HttpStatus;
+import io.micronaut.http.MediaType;
+import java.util.Optional;
+import org.apache.ignite.internal.rest.api.Problem;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -34,7 +38,9 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
 
     private Matcher<T> bodyMatcher;
 
-    private Class<T> body;
+    private Class<T> bodyClass;
+
+    private Matcher<String> mediaTypeMatcher;
 
     private MicronautHttpResponseMatcher(Matcher<Integer> statusCodeMatcher) {
         this.statusCodeMatcher = statusCodeMatcher;
@@ -60,6 +66,18 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
         return new MicronautHttpResponseMatcher<>(is(statusCode));
     }
 
+    /**
+     * Creates a matcher that matches when the examined {@link HttpResponse} 
is a problem json that matches the specified matcher.
+     *
+     * @param problemMatcher Expected problem.
+     * @return Matcher.
+     */
+    public static MicronautHttpResponseMatcher<Problem> 
isProblemResponse(HttpStatus status, ProblemMatcher problemMatcher) {
+        return MicronautHttpResponseMatcher.<Problem>hasStatus(status)
+                .withMediaType(APPLICATION_JSON_PROBLEM_TYPE)
+                .withBody(problemMatcher.withStatus(status.getCode()), 
Problem.class);
+    }
+
     /**
      * Sets the expected body.
      *
@@ -68,7 +86,7 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
      */
     public MicronautHttpResponseMatcher<T> withBody(T body) {
         this.bodyMatcher = equalTo(body);
-        this.body = (Class<T>) body.getClass();
+        this.bodyClass = (Class<T>) body.getClass();
         return this;
     }
 
@@ -76,12 +94,23 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
      * Sets the body matcher.
      *
      * @param bodyMatcher Body matcher.
-     * @param body Body class.
+     * @param bodyClass Body class.
      * @return Matcher.
      */
-    public MicronautHttpResponseMatcher<T> withBody(Matcher<T> bodyMatcher, 
Class<T> body) {
+    public MicronautHttpResponseMatcher<T> withBody(Matcher<T> bodyMatcher, 
Class<T> bodyClass) {
         this.bodyMatcher = bodyMatcher;
-        this.body = body;
+        this.bodyClass = bodyClass;
+        return this;
+    }
+
+    /**
+     * Sets the media type.
+     *
+     * @param mediaType Media type.
+     * @return Matcher.
+     */
+    public MicronautHttpResponseMatcher<T> withMediaType(MediaType mediaType) {
+        this.mediaTypeMatcher = equalTo(mediaType.getName());
         return this;
     }
 
@@ -91,8 +120,18 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
             return false;
         }
 
-        if (bodyMatcher != null && 
!bodyMatcher.matches(httpResponse.getBody(body).get())) {
-            return false;
+        if (bodyMatcher != null) {
+            Optional<T> body = httpResponse.getBody(bodyClass);
+            if (body.isEmpty() || !bodyMatcher.matches(body.get())) {
+                return false;
+            }
+        }
+
+        if (mediaTypeMatcher != null) {
+            Optional<MediaType> contentType = httpResponse.getContentType();
+            if (contentType.isEmpty() || 
!mediaTypeMatcher.matches(contentType.get().getName())) {
+                return false;
+            }
         }
 
         return true;
@@ -107,6 +146,10 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
         if (bodyMatcher != null) {
             description.appendText(" and body 
").appendDescriptionOf(bodyMatcher);
         }
+
+        if (mediaTypeMatcher != null) {
+            description.appendText(" and content type 
").appendDescriptionOf(mediaTypeMatcher);
+        }
     }
 
     @Override
@@ -114,6 +157,8 @@ public class MicronautHttpResponseMatcher<T> extends 
TypeSafeMatcher<HttpRespons
         mismatchDescription.appendText("status code was ")
                 .appendValue(item.code())
                 .appendText(" and body was ")
-                .appendValue(item.getBody(String.class));
+                .appendValue(item.getBody(String.class))
+                .appendText(" and content type was ")
+                
.appendValue(item.getContentType().map(MediaType::getName).orElse(null));
     }
 }
diff --git 
a/modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/ProblemMatcher.java
 
b/modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/ProblemMatcher.java
similarity index 96%
rename from 
modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/ProblemMatcher.java
rename to 
modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/ProblemMatcher.java
index 4faf8040db..9be1f99f20 100644
--- 
a/modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/ProblemMatcher.java
+++ 
b/modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/ProblemMatcher.java
@@ -46,7 +46,7 @@ public class ProblemMatcher extends TypeSafeMatcher<Problem> {
 
     private Matcher<UUID> traceIdMatcher = AnythingMatcher.anything();
 
-    private Matcher<Collection<InvalidParam>> invalidParamsMatcher = 
AnythingMatcher.anything();
+    private Matcher<Iterable<? extends InvalidParam>> invalidParamsMatcher = 
AnythingMatcher.anything();
 
     /**
      * Creates a matcher for {@link Problem}.
@@ -124,7 +124,7 @@ public class ProblemMatcher extends 
TypeSafeMatcher<Problem> {
         return withInvalidParams(equalTo(invalidParams));
     }
 
-    public ProblemMatcher withInvalidParams(Matcher<Collection<InvalidParam>> 
matcher) {
+    public ProblemMatcher withInvalidParams(Matcher<Iterable<? extends 
InvalidParam>> matcher) {
         this.invalidParamsMatcher = matcher;
         return this;
     }
diff --git 
a/modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/RestJobStateMatcher.java
 
b/modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/RestJobStateMatcher.java
similarity index 100%
rename from 
modules/rest/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/RestJobStateMatcher.java
rename to 
modules/rest-api/src/testFixtures/java/org/apache/ignite/internal/rest/matcher/RestJobStateMatcher.java
diff --git a/modules/rest/build.gradle b/modules/rest/build.gradle
index 626317f38a..e8f8f1f9cd 100644
--- a/modules/rest/build.gradle
+++ b/modules/rest/build.gradle
@@ -18,7 +18,6 @@
 apply from: "$rootDir/buildscripts/java-core.gradle"
 apply from: "$rootDir/buildscripts/publishing.gradle"
 apply from: "$rootDir/buildscripts/java-junit5.gradle"
-apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
 apply from: "$rootDir/buildscripts/java-integration-test.gradle"
 
 description = 'ignite-rest'
@@ -85,7 +84,7 @@ dependencies {
     integrationTestImplementation 
testFixtures(project(':ignite-cluster-management'))
     integrationTestImplementation 
testFixtures(project(':ignite-configuration'))
     integrationTestImplementation testFixtures(project(":ignite-api"))
-    integrationTestImplementation testFixtures(project(":ignite-rest"))
+    integrationTestImplementation testFixtures(project(":ignite-rest-api"))
     integrationTestImplementation libs.awaitility
     integrationTestImplementation libs.micronaut.junit5
     integrationTestImplementation libs.micronaut.test
@@ -98,9 +97,4 @@ dependencies {
         //So, exclude asm-core transitive dependency to protect of jar-hell.
         exclude group: 'org.ow2.asm', module: 'asm'
     }
-
-    testFixturesImplementation(project(":ignite-rest-api"))
-    testFixturesImplementation testFixtures(project(":ignite-core"))
-    testFixturesImplementation libs.hamcrest.core
-    testFixturesImplementation libs.micronaut.http.core
 }
diff --git 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItClusterStateHttpServerFilterNotInitializedTest.java
 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItClusterStateHttpServerFilterNotInitializedTest.java
index e95d02d88b..1d8c5d54fe 100644
--- 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItClusterStateHttpServerFilterNotInitializedTest.java
+++ 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/ItClusterStateHttpServerFilterNotInitializedTest.java
@@ -20,13 +20,12 @@ package org.apache.ignite.internal.rest;
 import static io.micronaut.http.HttpRequest.GET;
 import static io.micronaut.http.HttpRequest.PATCH;
 import static io.micronaut.http.HttpStatus.CONFLICT;
-import static 
org.apache.ignite.internal.rest.problem.ProblemJsonMediaType.APPLICATION_JSON_PROBLEM_TYPE;
+import static 
org.apache.ignite.internal.rest.matcher.MicronautHttpResponseMatcher.isProblemResponse;
+import static org.apache.ignite.internal.rest.matcher.ProblemMatcher.isProblem;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.client.HttpClient;
@@ -37,7 +36,6 @@ import jakarta.inject.Inject;
 import java.util.stream.Stream;
 import org.apache.ignite.internal.Cluster;
 import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
-import org.apache.ignite.internal.rest.api.Problem;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -92,24 +90,19 @@ public class 
ItClusterStateHttpServerFilterNotInitializedTest extends ClusterPer
 
     @ParameterizedTest
     @MethodSource("disabledEndpoints")
-    void clusterEndpointsDisabledWhenNotInitialized(HttpRequest<String> 
request) throws JsonProcessingException {
+    void clusterEndpointsDisabledWhenNotInitialized(HttpRequest<String> 
request) {
         HttpClientResponseException ex = assertThrows(
                 HttpClientResponseException.class,
                 () -> client.toBlocking().exchange(request)
         );
 
-        assertThat(ex.getStatus(), is(CONFLICT));
-
-        assertThat(ex.getResponse().getContentType().orElseThrow().getName(), 
is(APPLICATION_JSON_PROBLEM_TYPE.getName()));
-        Problem problem = readProblem(ex);
-
-        assertThat(problem.status(), is(CONFLICT.getCode()));
-        assertThat(problem.title(), is("Cluster is not initialized"));
-        assertThat(problem.detail(), is("Cluster is not initialized. Call 
/management/v1/cluster/init in order to initialize cluster."));
-    }
-
-    private Problem readProblem(HttpClientResponseException ex) throws 
JsonProcessingException {
-        return mapper.readValue(ex.getResponse().getBody(String.class).get(), 
Problem.class);
+        assertThat(
+                ex.getResponse(),
+                isProblemResponse(CONFLICT, isProblem()
+                        .withTitle("Cluster is not initialized")
+                        .withDetail("Cluster is not initialized. Call 
/management/v1/cluster/init in order to initialize cluster.")
+                )
+        );
     }
 
     @ParameterizedTest
diff --git 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/compute/ItComputeControllerTest.java
 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/compute/ItComputeControllerTest.java
index 5f63384c40..e00dab44f7 100644
--- 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/compute/ItComputeControllerTest.java
+++ 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/compute/ItComputeControllerTest.java
@@ -19,7 +19,10 @@ package org.apache.ignite.internal.rest.compute;
 
 import static io.micronaut.http.HttpRequest.DELETE;
 import static io.micronaut.http.HttpRequest.PUT;
+import static io.micronaut.http.HttpStatus.CONFLICT;
+import static io.micronaut.http.HttpStatus.NOT_FOUND;
 import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static 
org.apache.ignite.internal.rest.matcher.MicronautHttpResponseMatcher.isProblemResponse;
 import static org.apache.ignite.internal.rest.matcher.ProblemMatcher.isProblem;
 import static 
org.apache.ignite.internal.rest.matcher.RestJobStateMatcher.canceled;
 import static 
org.apache.ignite.internal.rest.matcher.RestJobStateMatcher.completed;
@@ -50,10 +53,8 @@ import org.apache.ignite.compute.JobExecution;
 import org.apache.ignite.compute.JobExecutionContext;
 import org.apache.ignite.compute.JobTarget;
 import org.apache.ignite.internal.ClusterPerClassIntegrationTest;
-import org.apache.ignite.internal.rest.api.Problem;
 import org.apache.ignite.internal.rest.api.compute.JobState;
 import org.apache.ignite.internal.rest.api.compute.UpdateJobPriorityBody;
-import org.apache.ignite.internal.rest.matcher.MicronautHttpResponseMatcher;
 import org.apache.ignite.network.ClusterNode;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
@@ -180,8 +181,7 @@ public class ItComputeControllerTest extends 
ClusterPerClassIntegrationTest {
 
         assertThat(
                 httpClientResponseException.getResponse(),
-                MicronautHttpResponseMatcher.<Problem>hasStatusCode(404)
-                        
.withBody(isProblem().withStatus(404).withDetail("Compute job not found 
[jobId=" + jobId + "]"), Problem.class)
+                isProblemResponse(NOT_FOUND, isProblem().withDetail("Compute 
job not found [jobId=" + jobId + "]"))
         );
     }
 
@@ -226,8 +226,7 @@ public class ItComputeControllerTest extends 
ClusterPerClassIntegrationTest {
 
         assertThat(
                 httpClientResponseException.getResponse(),
-                MicronautHttpResponseMatcher.<Problem>hasStatusCode(404)
-                        
.withBody(isProblem().withStatus(404).withDetail("Compute job not found 
[jobId=" + jobId + "]"), Problem.class)
+                isProblemResponse(NOT_FOUND, isProblem().withDetail("Compute 
job not found [jobId=" + jobId + "]"))
         );
     }
 
@@ -252,9 +251,8 @@ public class ItComputeControllerTest extends 
ClusterPerClassIntegrationTest {
 
         assertThat(
                 httpClientResponseException.getResponse(),
-                MicronautHttpResponseMatcher.<Problem>hasStatusCode(409)
-                        .withBody(isProblem().withStatus(409)
-                                .withDetail("Compute job has an illegal status 
[jobId=" + jobId + ", status=COMPLETED]"), Problem.class)
+                isProblemResponse(CONFLICT, isProblem()
+                        .withDetail("Compute job has an illegal status 
[jobId=" + jobId + ", status=COMPLETED]"))
         );
     }
 
@@ -311,8 +309,7 @@ public class ItComputeControllerTest extends 
ClusterPerClassIntegrationTest {
 
         assertThat(
                 httpClientResponseException.getResponse(),
-                MicronautHttpResponseMatcher.<Problem>hasStatusCode(404)
-                        
.withBody(isProblem().withStatus(404).withDetail("Compute job not found 
[jobId=" + jobId + "]"), Problem.class)
+                isProblemResponse(NOT_FOUND, isProblem().withDetail("Compute 
job not found [jobId=" + jobId + "]"))
         );
     }
 
@@ -335,9 +332,8 @@ public class ItComputeControllerTest extends 
ClusterPerClassIntegrationTest {
 
         assertThat(
                 httpClientResponseException.getResponse(),
-                MicronautHttpResponseMatcher.<Problem>hasStatusCode(409)
-                        .withBody(isProblem().withStatus(409)
-                                .withDetail("Compute job has an illegal status 
[jobId=" + jobId + ", status=EXECUTING]"), Problem.class)
+                isProblemResponse(CONFLICT, isProblem()
+                        .withDetail("Compute job has an illegal status 
[jobId=" + jobId + ", status=EXECUTING]"))
         );
     }
 
@@ -364,9 +360,8 @@ public class ItComputeControllerTest extends 
ClusterPerClassIntegrationTest {
 
         assertThat(
                 httpClientResponseException.getResponse(),
-                MicronautHttpResponseMatcher.<Problem>hasStatusCode(409)
-                        .withBody(isProblem().withStatus(409)
-                                .withDetail("Compute job has an illegal status 
[jobId=" + jobId + ", status=COMPLETED]"), Problem.class)
+                isProblemResponse(CONFLICT, isProblem()
+                        .withDetail("Compute job has an illegal status 
[jobId=" + jobId + ", status=COMPLETED]"))
         );
     }
 


Reply via email to