diff --git a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java index b8db6e7eb5b23571f40ae99be833bdddd935dcc0..2cf01448f6fa1ab4f52d3a72fc06b15d57c5cda7 100644 --- a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java +++ b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java @@ -28,12 +28,10 @@ import org.springframework.hateoas.server.RepresentationModelAssembler; import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; import org.springframework.stereotype.Component; -import io.micrometer.common.lang.NonNullApi; import lombok.RequiredArgsConstructor; @Component @RequiredArgsConstructor -@NonNullApi public class RootModelAssembler implements RepresentationModelAssembler<Root, EntityModel<Root>> { static final String REL_CONFIGURATION = "configuration"; diff --git a/src/main/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandler.java b/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java similarity index 95% rename from src/main/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandler.java rename to src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java index 27f9c626b4586717558355e5a2671676ec0e570d..75a3dda5ce10f4d50ebaaabc31ec840ac9c54d3b 100644 --- a/src/main/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandler.java +++ b/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java @@ -19,7 +19,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.errorhandling; +package de.ozgcloud.admin.common.errorhandling; import java.util.Map; @@ -36,7 +36,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep import de.ozgcloud.common.errorhandling.TechnicalException; @RestControllerAdvice -public class AdminExceptionHandler extends ResponseEntityExceptionHandler { +public class ExceptionController extends ResponseEntityExceptionHandler { static final Map<Class<? extends Exception>, HttpStatus> STATUS_BY_EXCEPTION = Map.of( RuntimeException.class, HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/main/java/de/ozgcloud/admin/errorhandling/FunctionalException.java b/src/main/java/de/ozgcloud/admin/common/errorhandling/FunctionalException.java similarity index 97% rename from src/main/java/de/ozgcloud/admin/errorhandling/FunctionalException.java rename to src/main/java/de/ozgcloud/admin/common/errorhandling/FunctionalException.java index e0e57b3feb65073674c0645aac9fc997af8dbfed..5b2369b2a97772a1607b84ebaa9a46de2fec9643 100644 --- a/src/main/java/de/ozgcloud/admin/errorhandling/FunctionalException.java +++ b/src/main/java/de/ozgcloud/admin/common/errorhandling/FunctionalException.java @@ -19,7 +19,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.errorhandling; +package de.ozgcloud.admin.common.errorhandling; import java.io.Serial; import java.util.UUID; diff --git a/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java b/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java index b61f1d9c358751a1e974be75b9cc33b3f2551536..7f58244eddd973d568eb271fbc59d2b551c849f0 100644 --- a/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java +++ b/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java @@ -14,7 +14,7 @@ import lombok.RequiredArgsConstructor; @RequestMapping(FrontendEnvironmentController.PATH) public class FrontendEnvironmentController { - static final String PATH = "/api/environment"; // NOSONAR + static final String PATH = "/api/frontendEnvironment"; // NOSONAR private final ProductionProperties environmentProperties; diff --git a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java index 568d79a76fc6268d93b7ecc2e172ae9625d40ed1..03de09a7d58f57bd20200d7ea468391b5c3e572f 100644 --- a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java +++ b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java @@ -53,7 +53,7 @@ public class SecurityConfiguration { http.exceptionHandling(eh -> eh.authenticationEntryPoint(authenticationEntryPoint)); http.authorizeHttpRequests(requests -> requests - .requestMatchers(HttpMethod.GET, "/api/environment").permitAll() + .requestMatchers(HttpMethod.GET, "/api/frontendEnvironment").permitAll() .requestMatchers("/api").authenticated() .requestMatchers("/api/**").authenticated() .requestMatchers("/actuator").permitAll() diff --git a/src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerITCase.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java similarity index 63% rename from src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerITCase.java rename to src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java index 740d33c05999c8ee00d6135e636bb20aa2d12493..b4884dd0e4b23e908645251677c76064c9086e8a 100644 --- a/src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerITCase.java +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java @@ -1,12 +1,19 @@ -package de.ozgcloud.admin.errorhandling; +package de.ozgcloud.admin.common.errorhandling; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.Set; import java.util.stream.Stream; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotEmpty; + import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -23,12 +30,14 @@ import org.springframework.test.web.servlet.ResultActions; import de.ozgcloud.admin.RootController; import de.ozgcloud.admin.RootModelAssembler; import de.ozgcloud.common.test.ITCase; +import lombok.Builder; +import lombok.Getter; import lombok.SneakyThrows; @ITCase @AutoConfigureMockMvc @WithMockUser -public class AdminExceptionHandlerITCase { +class ExceptionControllerITCase { @Autowired private MockMvc mockMvc; @@ -46,23 +55,9 @@ public class AdminExceptionHandlerITCase { var result = performGet(); - System.out.println(result.andReturn().getResponse().getContentAsString()); - result.andExpect(status().is(expectedStatus.value())); } - @Test - @SneakyThrows - void shouldHandleRuntimeExceptionExceptionWithStatus() { - when(modelAssembler.toModel(any())).thenThrow(new RuntimeException("Message")); - - var result = performGet(); - - System.out.println(result.andReturn().getResponse().getContentAsString()); - - result.andExpect(status().isInternalServerError()); - } - @ParameterizedTest @MethodSource("exceptionAndExpectedStatus") @SneakyThrows @@ -75,13 +70,42 @@ public class AdminExceptionHandlerITCase { } private static Stream<Arguments> exceptionAndExpectedStatus() { - return AdminExceptionHandler.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); + return ExceptionController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); } + } + + @Nested + class TestConstraintViolationException { + @Test @SneakyThrows - private ResultActions performGet() { - return mockMvc.perform(get(RootController.PATH)); + void shouldHaveValidationMessage() { + when(modelAssembler.toModel(any())).thenAnswer((a) -> { + throw new ConstraintViolationException(getConstraintViolations()); + }); + + var result = performGet(); + + result.andExpect(jsonPath("$.detail").value("string: Empty field")); + } + + private Set<ConstraintViolation<ValidatedClass>> getConstraintViolations() { + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + return validator.validate(ValidatedClass.builder().build()); + } + + @Getter + @Builder + private static class ValidatedClass { + @NotEmpty(message = "Empty field") + private String string; } + + } + + @SneakyThrows + private ResultActions performGet() { + return mockMvc.perform(get(RootController.PATH)); } } diff --git a/src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerTest.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java similarity index 69% rename from src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerTest.java rename to src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java index 8f62290e40d9c31147f0497fc40330392a21dd65..f33a3715b32778a78b725e096ea0bf99e1fd4047 100644 --- a/src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerTest.java +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java @@ -19,9 +19,9 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.errorhandling; +package de.ozgcloud.admin.common.errorhandling; -import static de.ozgcloud.admin.errorhandling.AdminExceptionHandler.*; +import static de.ozgcloud.admin.common.errorhandling.ExceptionController.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -41,7 +41,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import lombok.SneakyThrows; -class AdminExceptionHandlerTest { +class ExceptionControllerTest { private MockMvc mockMvc; @@ -49,7 +49,7 @@ class AdminExceptionHandlerTest { void setup() { mockMvc = MockMvcBuilders .standaloneSetup(new TestErrorController()) - .setControllerAdvice(new AdminExceptionHandler()).build(); + .setControllerAdvice(new ExceptionController()).build(); } @DisplayName("Error handler") @@ -74,13 +74,49 @@ class AdminExceptionHandlerTest { result.andExpect(jsonPath("$.status").value(expectedStatus.value())); } + @ParameterizedTest + @MethodSource("exceptionAndExpectedStatus") + @SneakyThrows + void shouldRespondWithTitler(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) { + var result = doPerformWithError(exceptionClass); + + result.andExpect(jsonPath("$.title").exists()); + } + + @ParameterizedTest + @MethodSource("exceptionAndExpectedStatus") + @SneakyThrows + void shouldRespondWithDetail(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) { + var result = doPerformWithError(exceptionClass); + + result.andExpect(jsonPath("$.detail").exists()); + } + + @ParameterizedTest + @MethodSource("exceptionAndExpectedStatus") + @SneakyThrows + void shouldRespondWithInstance(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) { + var result = doPerformWithError(exceptionClass); + + result.andExpect(jsonPath("$.instance").exists()); + } + + @ParameterizedTest + @MethodSource("exceptionAndExpectedStatus") + @SneakyThrows + void shouldRespondWithType(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) { + var result = doPerformWithError(exceptionClass); + + result.andExpect(jsonPath("$.type").exists()); + } + private static Stream<Arguments> exceptionAndExpectedStatus() { return STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); } @SneakyThrows private ResultActions doPerformWithError(Class<? extends Exception> exceptionClass) { - return mockMvc.perform(get("/test-error").param("errorClassName", exceptionClass.getName())); + return mockMvc.perform(get("/api/test-error").param("errorClassName", exceptionClass.getName())); } } diff --git a/src/test/java/de/ozgcloud/admin/errorhandling/TestErrorController.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java similarity index 92% rename from src/test/java/de/ozgcloud/admin/errorhandling/TestErrorController.java rename to src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java index 4ad218f842cdc6e96a0c3b563e51aad7c9cdbeaf..c10446041aaba51ac761ac4e91ec01d6f9b7bb4b 100644 --- a/src/test/java/de/ozgcloud/admin/errorhandling/TestErrorController.java +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java @@ -19,7 +19,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.errorhandling; +package de.ozgcloud.admin.common.errorhandling; import java.util.Collections; import java.util.Map; @@ -34,11 +34,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import de.ozgcloud.common.errorhandling.TechnicalException; -import io.micrometer.common.lang.NonNullApi; @RestController -@RequestMapping("/test-error") -@NonNullApi +@RequestMapping("/api/test-error") class TestErrorController { @FunctionalInterface @@ -54,13 +52,12 @@ class TestErrorController { ConstraintViolationException.class, () -> new ConstraintViolationException(ERROR_MESSAGE, Collections.emptySet()), ResourceNotFoundException.class, () -> new ResourceNotFoundException(ERROR_MESSAGE), FunctionalException.class, () -> new FunctionalException(() -> ERROR_MESSAGE), - TechnicalException.class, () -> new TechnicalException(ERROR_MESSAGE) - ); + TechnicalException.class, () -> new TechnicalException(ERROR_MESSAGE)); @GetMapping String throwException(@RequestParam String errorClassName) throws Exception { throw EXCEPTION_PRODUCER.get( - Class.forName(errorClassName) - ).produceException(); + Class.forName(errorClassName)).produceException(); } + } diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java index 653c7b6886f24ade6257f26e718f8cc3f61f9bec..d4f087b46c68ba418bd74437e879be459f10db94 100644 --- a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java +++ b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java @@ -67,7 +67,7 @@ class SecurityConfigurationITCase { @SneakyThrows @ParameterizedTest @ValueSource(strings = { - "/api/environment", + "/api/frontendEnvironment", "/configserver/name/profile" }) void shouldAllow(String path) { @@ -145,7 +145,7 @@ class SecurityConfigurationITCase { @SneakyThrows @ParameterizedTest @ValueSource(strings = { - "/api/environment", + "/api/frontendEnvironment", "/configserver/name/profile", "/api", "/api/configuration", "/api/configuration/param", })