diff --git a/Jenkinsfile b/Jenkinsfile index 29d72c6ab83a5b3d97f1bd01d6ada52ad2dc0f2a..8028deca350abe05a476de6816dc32272fc6419a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -48,7 +48,7 @@ pipeline { } configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { - sh "mvn -s $MAVEN_SETTINGS clean install -Dmaven.wagon.http.retryHandler.count=3 -DelasticTests.disabled=true -Dbuild.number=$BUILD_NUMBER" + sh 'mvn -s $MAVEN_SETTINGS clean install -Dmaven.wagon.http.retryHandler.count=3 -DelasticTests.disabled=true -Dbuild.number=$BUILD_NUMBER' } } } @@ -116,7 +116,7 @@ pipeline { configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USER', passwordVariable: 'PASSWORD')]) { - sh 'mvn -s $MAVEN_SETTINGS spring-boot:build-image -DskipTests -Dmaven.wagon.http.retryHandler.count=3 $BUILD_PROFILE -Ddocker.publishRegistry.username=${USER} -Ddocker.publishRegistry.password=${PASSWORD}' + sh 'mvn -s $MAVEN_SETTINGS spring-boot:build-image -DskipTests -Dmaven.wagon.http.retryHandler.count=3 $BUILD_PROFILE -Ddocker.publishRegistry.username=${USER} -Ddocker.publishRegistry.password=${PASSWORD} -Dbuild.number=$BUILD_NUMBER -DimageTag=$IMAGE_TAG -DpublishImage=true' } } } diff --git a/pom.xml b/pom.xml index e8dbfcfe93f84bc818e739441f93e92cc5ee60d8..60a6b290c85d56bb4ee6e133e9e59d788b3eb9df 100644 --- a/pom.xml +++ b/pom.xml @@ -17,10 +17,13 @@ <properties> <imageName>docker.ozg-sh.de/administration</imageName> + <imageTag>build-latest</imageTag> + <publishImage>false</publishImage> <build.number>SET_BY_JENKINS</build.number> <spring-cloud-config-server.version>4.1.0</spring-cloud-config-server.version> <testcontainers-keycloak.version>3.2.0</testcontainers-keycloak.version> <keycloak-admin-client.version>23.0.6</keycloak-admin-client.version> + <mongock.version>5.4.0</mongock.version> </properties> <dependencies> @@ -67,6 +70,17 @@ <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> + <!-- mongock --> + <dependency> + <groupId>io.mongock</groupId> + <artifactId>mongock-springboot-v3</artifactId> + <version>${mongock.version}</version> + </dependency> + <dependency> + <groupId>io.mongock</groupId> + <artifactId>mongodb-springdata-v4-driver</artifactId> + <version>${mongock.version}</version> + </dependency> <!-- Dev --> <dependency> @@ -178,8 +192,8 @@ <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> - <name>${imageName}:${env.BRANCH_NAME}-${project.version}</name> - <publish>true</publish> + <name>${imageName}:${imageTag}</name> + <publish>${publishImage}</publish> </image> <docker> <publishRegistry> diff --git a/src/main/java/de/ozgcloud/admin/AdministrationApplication.java b/src/main/java/de/ozgcloud/admin/AdministrationApplication.java index c9535837fdbb37dcb5a4344bff54816679d33223..563eea5b36d486c99187d35a08c61e6fb2e34bf1 100644 --- a/src/main/java/de/ozgcloud/admin/AdministrationApplication.java +++ b/src/main/java/de/ozgcloud/admin/AdministrationApplication.java @@ -21,6 +21,7 @@ */ package de.ozgcloud.admin; +import io.mongock.runner.springboot.EnableMongock; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @@ -28,6 +29,7 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie @SpringBootApplication @EnableConfigServer +@EnableMongock @EnableMongoRepositories public class AdministrationApplication { diff --git a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java index cd66647d10b09a38b229b1931cf2e65bbef5733a..2cf01448f6fa1ab4f52d3a72fc06b15d57c5cda7 100644 --- a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java +++ b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java @@ -28,13 +28,11 @@ 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 -class RootModelAssembler implements RepresentationModelAssembler<Root, EntityModel<Root>> { +public class RootModelAssembler implements RepresentationModelAssembler<Root, EntityModel<Root>> { static final String REL_CONFIGURATION = "configuration"; private final RepositoryRestProperties restProperties; @@ -46,7 +44,6 @@ class RootModelAssembler implements RepresentationModelAssembler<Root, EntityMod return EntityModel.of( root, Link.of(configLink.toUriString(), REL_CONFIGURATION), - rootLink.withSelfRel() - ); + rootLink.withSelfRel()); } } 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/FrontendEnvironment.java b/src/main/java/de/ozgcloud/admin/environment/Environment.java similarity index 87% rename from src/main/java/de/ozgcloud/admin/environment/FrontendEnvironment.java rename to src/main/java/de/ozgcloud/admin/environment/Environment.java index 0f7ca897f9e77c5956cdcab164977ea35d694476..e2bfb54b4aeb2d33da4461d3eb6ae95d89eab93e 100644 --- a/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironment.java +++ b/src/main/java/de/ozgcloud/admin/environment/Environment.java @@ -5,7 +5,7 @@ import lombok.Getter; @Getter @Builder -public class FrontendEnvironment { +public class Environment { private boolean production; private String remoteHost; private String authServer; diff --git a/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java b/src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java similarity index 81% rename from src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java rename to src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java index b61f1d9c358751a1e974be75b9cc33b3f2551536..71cf34ce3180c793235693f7ec8582a06aa27c63 100644 --- a/src/main/java/de/ozgcloud/admin/environment/FrontendEnvironmentController.java +++ b/src/main/java/de/ozgcloud/admin/environment/EnvironmentController.java @@ -9,10 +9,10 @@ import org.springframework.web.bind.annotation.RestController; import de.ozgcloud.admin.RootController; import lombok.RequiredArgsConstructor; -@RestController +@RestController("ozgCloudEnvironmentController") @RequiredArgsConstructor -@RequestMapping(FrontendEnvironmentController.PATH) -public class FrontendEnvironmentController { +@RequestMapping(EnvironmentController.PATH) +public class EnvironmentController { static final String PATH = "/api/environment"; // NOSONAR @@ -21,8 +21,8 @@ public class FrontendEnvironmentController { private final OAuth2Properties oAuthProperties; @GetMapping - public FrontendEnvironment getEnvironment() { - return FrontendEnvironment.builder() + public Environment getEnvironment() { + return Environment.builder() .production(environmentProperties.isProduction()) .remoteHost(linkTo(RootController.class).toUri().toString()) .authServer(oAuthProperties.getAuthServerUrl()) diff --git a/src/main/java/de/ozgcloud/admin/migration/M001_CreateEmptyPostfachIfMissing.java b/src/main/java/de/ozgcloud/admin/migration/M001_CreateEmptyPostfachIfMissing.java new file mode 100644 index 0000000000000000000000000000000000000000..b871728d899c5b7eb209501167555d9325d17e49 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/migration/M001_CreateEmptyPostfachIfMissing.java @@ -0,0 +1,38 @@ +package de.ozgcloud.admin.migration; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +@ChangeUnit(id = "2024-02-20 17:00:00 OZG-4948-OZG-5058", order = "M001", author = "lmonnerjahn", runAlways = true) +public class M001_CreateEmptyPostfachIfMissing { // NOSONAR + static final String SETTINGS_COLLECTION = "settings"; + + static final String TYPE_NAME_KEY = "name"; + static final String TYPE_NAME_POSTFACH_VALUE = "Postfach"; + + @Execution + public void doMigration(MongoTemplate template) { + template.upsert(buildQuery(), buildUpdate(), SETTINGS_COLLECTION); + } + + private Query buildQuery() { + var criteria = Criteria.where(TYPE_NAME_KEY).is(TYPE_NAME_POSTFACH_VALUE); + return Query.query(criteria); + } + + private Update buildUpdate() { + var update = new Update(); + update.setOnInsert(TYPE_NAME_KEY, TYPE_NAME_POSTFACH_VALUE); + return update; + } + + @RollbackExecution + public void rollback() { + // kein rollback implementiert + } +} diff --git a/src/main/java/de/ozgcloud/admin/migration/MongockFailedEventListener.java b/src/main/java/de/ozgcloud/admin/migration/MongockFailedEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..84e860cb2cbdee0312df576f903dd285e89e1bd9 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/migration/MongockFailedEventListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.admin.migration; + +import io.mongock.runner.spring.base.events.SpringMigrationFailureEvent; +import lombok.extern.log4j.Log4j2; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +public class MongockFailedEventListener implements ApplicationListener<SpringMigrationFailureEvent> { + + @Override + public void onApplicationEvent(SpringMigrationFailureEvent event) { + log.error("Mongock migration failed", event.getMigrationResult()); + } +} \ No newline at end of file diff --git a/src/main/java/de/ozgcloud/admin/migration/MongockStartEventListener.java b/src/main/java/de/ozgcloud/admin/migration/MongockStartEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..ad1558351ae353658dbe653bfe2630e627373489 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/migration/MongockStartEventListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.admin.migration; + +import io.mongock.runner.spring.base.events.SpringMigrationStartedEvent; +import lombok.extern.log4j.Log4j2; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +public class MongockStartEventListener implements ApplicationListener<SpringMigrationStartedEvent> { + + @Override + public void onApplicationEvent(SpringMigrationStartedEvent event) { + log.info("Mongock start migration..."); + } +} \ No newline at end of file diff --git a/src/main/java/de/ozgcloud/admin/migration/MongockSuccessEventListener.java b/src/main/java/de/ozgcloud/admin/migration/MongockSuccessEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..95a7f76ee988dc8f1e72b8db6341da77514e5b54 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/migration/MongockSuccessEventListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.admin.migration; + +import io.mongock.runner.spring.base.events.SpringMigrationSuccessEvent; +import lombok.extern.log4j.Log4j2; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +public class MongockSuccessEventListener implements ApplicationListener<SpringMigrationSuccessEvent> { + + @Override + public void onApplicationEvent(SpringMigrationSuccessEvent event) { + log.info("Mongock migration successfull", event.getMigrationResult()); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 29082f7d8446f187cd3f16f077b94f28d41bf807..315e37b9756df692953d1c5761747bfc1e97f4ac 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -49,6 +49,12 @@ management: exposure: include: health,prometheus +mongock: + runner-type: initializingbean + migration-scan-package: + - de.ozgcloud.admin.migration + enabled: true + spring: application: name: OzgCloud_Administration diff --git a/src/test/java/de/ozgcloud/admin/AdministrationApplicationTest.java b/src/test/java/de/ozgcloud/admin/AdministrationApplicationTest.java index 7d88d4237191cb0b07aaad9235443c689c1efe59..7b7fb5056c8fd67bc11be9a1cc016610e1181146 100644 --- a/src/test/java/de/ozgcloud/admin/AdministrationApplicationTest.java +++ b/src/test/java/de/ozgcloud/admin/AdministrationApplicationTest.java @@ -21,10 +21,10 @@ */ package de.ozgcloud.admin; +import de.ozgcloud.common.test.ITCase; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest +@ITCase class AdministrationApplicationTest { @Test diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..b4884dd0e4b23e908645251677c76064c9086e8a --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java @@ -0,0 +1,111 @@ +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; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +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 +class ExceptionControllerITCase { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private RootModelAssembler modelAssembler; + + @Nested + class TestExceptions { + @ParameterizedTest + @MethodSource("exceptionAndExpectedStatus") + @SneakyThrows + void shouldHandleExceptionWithStatus(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) { + when(modelAssembler.toModel(any())).thenThrow(TestErrorController.EXCEPTION_PRODUCER.get(exceptionClass).produceException()); + + var result = performGet(); + + result.andExpect(status().is(expectedStatus.value())); + } + + @ParameterizedTest + @MethodSource("exceptionAndExpectedStatus") + @SneakyThrows + void shouldRespondWithStatusInBody(Class<? extends Exception> exceptionClass, HttpStatus expectedStatus) { + when(modelAssembler.toModel(any())).thenThrow(exceptionClass); + + var result = performGet(); + + result.andExpect(jsonPath("$.status").value(expectedStatus.value())); + } + + private static Stream<Arguments> exceptionAndExpectedStatus() { + return ExceptionController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); + } + + } + + @Nested + class TestConstraintViolationException { + @Test + @SneakyThrows + 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/AdminExceptionHandlerITCase.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java similarity index 69% rename from src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerITCase.java rename to src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java index 646d2c0b24579a6d7cfd502fbbdf58cfc530806a..f33a3715b32778a78b725e096ea0bf99e1fd4047 100644 --- a/src/test/java/de/ozgcloud/admin/errorhandling/AdminExceptionHandlerITCase.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 AdminExceptionHandlerITCase { +class ExceptionControllerTest { private MockMvc mockMvc; @@ -49,7 +49,7 @@ class AdminExceptionHandlerITCase { void setup() { mockMvc = MockMvcBuilders .standaloneSetup(new TestErrorController()) - .setControllerAdvice(new AdminExceptionHandler()).build(); + .setControllerAdvice(new ExceptionController()).build(); } @DisplayName("Error handler") @@ -74,13 +74,49 @@ class AdminExceptionHandlerITCase { 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/environment/FrontendEnvironmentControllerTest.java b/src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java similarity index 94% rename from src/test/java/de/ozgcloud/admin/environment/FrontendEnvironmentControllerTest.java rename to src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java index 67d81f084a8a4bea8e747713cbb205c9d9433ca3..bdeaf83b00de2410112833f1447e940fc088e9b5 100644 --- a/src/test/java/de/ozgcloud/admin/environment/FrontendEnvironmentControllerTest.java +++ b/src/test/java/de/ozgcloud/admin/environment/EnvironmentControllerTest.java @@ -22,11 +22,11 @@ import de.ozgcloud.admin.RootController; import lombok.SneakyThrows; @ExtendWith(MockitoExtension.class) -class FrontendEnvironmentControllerTest { +class EnvironmentControllerTest { @Spy @InjectMocks - private FrontendEnvironmentController controller; + private EnvironmentController controller; @Mock private ProductionProperties environmentProperties; @@ -120,7 +120,7 @@ class FrontendEnvironmentControllerTest { @SneakyThrows private ResultActions doRequest() { - return mockMvc.perform(get(FrontendEnvironmentController.PATH)); + return mockMvc.perform(get(EnvironmentController.PATH)); } } diff --git a/src/test/java/de/ozgcloud/admin/migration/M001_CreateEmptyPostfachIfMissingITCase.java b/src/test/java/de/ozgcloud/admin/migration/M001_CreateEmptyPostfachIfMissingITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..8eb5819980340878289af90178eea9b1b67b6fae --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/migration/M001_CreateEmptyPostfachIfMissingITCase.java @@ -0,0 +1,103 @@ +package de.ozgcloud.admin.migration; + +import static de.ozgcloud.admin.migration.M001_CreateEmptyPostfachIfMissing.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; + +import de.ozgcloud.common.test.DataITCase; + +@DataITCase +class M001_CreateEmptyPostfachIfMissingITCase { + private final M001_CreateEmptyPostfachIfMissing changeUnit = new M001_CreateEmptyPostfachIfMissing(); + + private static final Document POSTFACH = MigrationTestFactory.createDummyPostfach(); + + @Autowired + private MongoTemplate template; + + @DisplayName("Do migration") + @Nested + class TestDoMigration { + @BeforeEach + public void init() { + template.dropCollection(SETTINGS_COLLECTION); + } + + @Test + void shouldAddPostfachIfEmpty() { + changeUnit.doMigration(template); + + List<Document> settings = findAllSettings(); + + assertThat(settings).usingRecursiveFieldByFieldElementComparatorIgnoringFields("_id") + .containsExactly(MigrationTestFactory.createEmptyPostfach()); + } + + @Test + void shouldAddPostfachIfMissingInCollection() { + var settingItem = MigrationTestFactory.createSettingsItem("SomeType", new Document()); + template.save(settingItem, SETTINGS_COLLECTION); + + changeUnit.doMigration(template); + + List<Document> settings = findAllSettings(); + assertThat(settings) + .extracting(TYPE_NAME_KEY) + .containsExactly("SomeType", MigrationTestFactory.ITEM_TYPE_VALUE_POSTFACH); + } + + @Test + void shouldKeepExistingPostfach() { + template.save(POSTFACH, SETTINGS_COLLECTION); + + changeUnit.doMigration(template); + + List<Document> settings = findAllSettings(); + assertThat(settings).containsExactly(POSTFACH); + } + + private List<Document> findAllSettings() { + return template.findAll(Document.class, SETTINGS_COLLECTION); + } + + } + + static class MigrationTestFactory { + + public static final String ITEM_TYPE_KEY = "name"; + public static final String ITEM_SETTINGS_KEY = "settings"; + public static final String ITEM_ID = "_id"; + public static final String ITEM_TYPE_VALUE_POSTFACH = "Postfach"; + public static final String POSTFACH_ABSENDER_KEY = "absender"; + + static Document createDummyPostfach() { + var postfach = new Document(); + postfach.put(POSTFACH_ABSENDER_KEY, "Some Value"); + return createSettingsItem(ITEM_TYPE_VALUE_POSTFACH, postfach); + } + + static Document createEmptyPostfach() { + var postfach = new Document(); + postfach.put(ITEM_TYPE_KEY, ITEM_TYPE_VALUE_POSTFACH); + postfach.put(ITEM_ID, null); + return postfach; + } + + static Document createSettingsItem(String itemType, Document itemValue) { + var settingsItem = new Document(); + settingsItem.put(ITEM_TYPE_KEY, itemType); + settingsItem.put(ITEM_SETTINGS_KEY, itemValue); + return settingsItem; + } + + } +} diff --git a/src/test/resources/application-itcase.yaml b/src/test/resources/application-itcase.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fc717c37b9cfb97360022930cb07c950b16d1983 --- /dev/null +++ b/src/test/resources/application-itcase.yaml @@ -0,0 +1,2 @@ +mongock: + enabled: false \ No newline at end of file