diff --git a/Jenkinsfile b/Jenkinsfile index 2660f439ceea5002be7350a683d06ac75c7386b5..38190aad4b2000b439a34a9fcdfb373734aa3222 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -344,4 +344,4 @@ String generateHelmChartVersion() { } return chartVersion.replaceAll("_", "-") -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index 72ac8ad1f43e60eaf4023cc34420b16446d81011..0ffeec2d31faf889eb0d11b07ba567b2e1552b08 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ </parent> <groupId>de.ozgcloud</groupId> <artifactId>administration</artifactId> - <version>0.1.0</version> + <version>0.2.0-SNAPSHOT</version> <name>Administration</name> <description>Administration Backend Project</description> diff --git a/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java b/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java index 75a3dda5ce10f4d50ebaaabc31ec840ac9c54d3b..d5e5803b9ec8364e3298402bda28c84291a957e4 100644 --- a/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java +++ b/src/main/java/de/ozgcloud/admin/common/errorhandling/ExceptionController.java @@ -21,12 +21,20 @@ */ package de.ozgcloud.admin.common.errorhandling; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; +import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.ErrorResponse; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -38,26 +46,54 @@ import de.ozgcloud.common.errorhandling.TechnicalException; @RestControllerAdvice public class ExceptionController extends ResponseEntityExceptionHandler { - static final Map<Class<? extends Exception>, HttpStatus> STATUS_BY_EXCEPTION = Map.of( - RuntimeException.class, HttpStatus.INTERNAL_SERVER_ERROR, - AccessDeniedException.class, HttpStatus.FORBIDDEN, - ConstraintViolationException.class, HttpStatus.UNPROCESSABLE_ENTITY, - ResourceNotFoundException.class, HttpStatus.NOT_FOUND, - FunctionalException.class, HttpStatus.BAD_REQUEST, - TechnicalException.class, HttpStatus.INTERNAL_SERVER_ERROR); + @ExceptionHandler(RuntimeException.class) + public ErrorResponse handleRuntimeException(RuntimeException ex) { + return ErrorResponse.builder(ex, HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()).build(); + } + + @ExceptionHandler(AccessDeniedException.class) + public ErrorResponse handleAccessDeniedException(AccessDeniedException ex) { + return ErrorResponse.builder(ex, HttpStatus.FORBIDDEN, ex.getLocalizedMessage()).build(); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ErrorResponse handleResourceNotFoundException(ResourceNotFoundException ex) { + return ErrorResponse.builder(ex, HttpStatus.NOT_FOUND, ex.getLocalizedMessage()).build(); + } + + @ExceptionHandler(FunctionalException.class) + public ErrorResponse handleFunctionalException(FunctionalException ex) { + return ErrorResponse.builder(ex, HttpStatus.BAD_REQUEST, ex.getLocalizedMessage()).build(); + } - @ExceptionHandler({ - RuntimeException.class, - AccessDeniedException.class, - ConstraintViolationException.class, - ResourceNotFoundException.class, - FunctionalException.class, - TechnicalException.class, - }) - public ErrorResponse handleException(RuntimeException ex) { - var status = STATUS_BY_EXCEPTION.getOrDefault(ex.getClass(), HttpStatus.INTERNAL_SERVER_ERROR); + @ExceptionHandler(TechnicalException.class) + public ErrorResponse handleTechnicalException(TechnicalException ex) { + return ErrorResponse.builder(ex, HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage()).build(); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ErrorResponse handleConstraintViolationException(ConstraintViolationException ex) { + var problemDetail = buildProblemDetail(HttpStatus.UNPROCESSABLE_ENTITY, ex); + return ErrorResponse.builder(ex, problemDetail).build(); + } - return ErrorResponse.builder(ex, status, ex.getLocalizedMessage()).build(); + private ProblemDetail buildProblemDetail(HttpStatus status, ConstraintViolationException ex) { + var problemDetail = ProblemDetail.forStatusAndDetail(status, ex.getLocalizedMessage()); + problemDetail.setProperty("invalid-params", getDetailedviolationList(ex.getConstraintViolations())); + return problemDetail; } + private List<Map<String, String>> getDetailedviolationList(Set<ConstraintViolation<?>> violations) { + List<Map<String, String>> detailedViolations = new ArrayList<>(); + Optional.ofNullable(violations).orElse(Collections.emptySet()).forEach(v -> detailedViolations.add(buildDetailedViolation(v))); + return detailedViolations; + + } + + private Map<String, String> buildDetailedViolation(ConstraintViolation<?> violation) { + var detailedViolation = new LinkedHashMap<String, String>(); + detailedViolation.put("name", violation.getPropertyPath().toString()); + detailedViolation.put("reason", violation.getMessage()); + return detailedViolation; + } } diff --git a/src/main/java/de/ozgcloud/admin/common/errorhandling/ValidationMessageCodes.java b/src/main/java/de/ozgcloud/admin/common/errorhandling/ValidationMessageCodes.java new file mode 100644 index 0000000000000000000000000000000000000000..f0ca75ebd60fc7e432b92de34008f5a2a05fc011 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/common/errorhandling/ValidationMessageCodes.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch das + * Ministerium für Energiewende, Klimaschutz, Umwelt und Natur + * Zentrales IT-Management + * + * 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.common.errorhandling; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ValidationMessageCodes { + + private static final String FIELD_PREFIX = "validation_field_"; + public static final String FIELD_IS_EMPTY = FIELD_PREFIX + "empty"; +} \ No newline at end of file diff --git a/src/main/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepository.java b/src/main/java/de/ozgcloud/admin/settings/AdminEnvironmentRepository.java similarity index 95% rename from src/main/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepository.java rename to src/main/java/de/ozgcloud/admin/settings/AdminEnvironmentRepository.java index e299e4e12bc5e650b83ad8278908000205ec5540..78d539beba3d9ad97e9197f0fc2efd86b5d99934 100644 --- a/src/main/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepository.java +++ b/src/main/java/de/ozgcloud/admin/settings/AdminEnvironmentRepository.java @@ -1,4 +1,4 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import java.util.List; diff --git a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationProperties.java b/src/main/java/de/ozgcloud/admin/settings/ConfigurationProperties.java similarity index 89% rename from src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationProperties.java rename to src/main/java/de/ozgcloud/admin/settings/ConfigurationProperties.java index 750b1f303df5477927f0b7193e6c86be3c026f20..aec9487ddc5e61d46dc64ca82e6df0488981ca15 100644 --- a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationProperties.java +++ b/src/main/java/de/ozgcloud/admin/settings/ConfigurationProperties.java @@ -1,4 +1,4 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import java.util.Map; diff --git a/src/main/java/de/ozgcloud/admin/settings/DataRestConfiguration.java b/src/main/java/de/ozgcloud/admin/settings/DataRestConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..7c5d282338be512a5a0397f0950d30935e513dcf --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/settings/DataRestConfiguration.java @@ -0,0 +1,18 @@ +package de.ozgcloud.admin.settings; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; +import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; + +@Configuration +public class DataRestConfiguration implements RepositoryRestConfigurer { + @Autowired + private SettingsValidator settingsValidator; + + @Override + public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) { + v.addValidator("beforeSave", settingsValidator); + v.addValidator("beforeCreate", settingsValidator); + } +} \ No newline at end of file diff --git a/src/main/java/de/ozgcloud/admin/configurationparameter/PropertyRepository.java b/src/main/java/de/ozgcloud/admin/settings/PropertyRepository.java similarity index 83% rename from src/main/java/de/ozgcloud/admin/configurationparameter/PropertyRepository.java rename to src/main/java/de/ozgcloud/admin/settings/PropertyRepository.java index 05a6fb0faffedb89455ef6ddf6788aaccd66c55a..69b124411a0b340c7f2920705d3698390c46391b 100644 --- a/src/main/java/de/ozgcloud/admin/configurationparameter/PropertyRepository.java +++ b/src/main/java/de/ozgcloud/admin/settings/PropertyRepository.java @@ -1,4 +1,4 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import java.util.List; diff --git a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameter.java b/src/main/java/de/ozgcloud/admin/settings/Settings.java similarity index 65% rename from src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameter.java rename to src/main/java/de/ozgcloud/admin/settings/Settings.java index 1d60c480d267c4eb0a762fe9f617efc8e8a3b84a..85c519591406cba2bd753b283e377aea4d8ab9ca 100644 --- a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameter.java +++ b/src/main/java/de/ozgcloud/admin/settings/Settings.java @@ -19,17 +19,32 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; + +import jakarta.validation.Valid; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; + import lombok.Builder; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; @Builder -@Document -public record ConfigurationParameter( - @Id - String id -) { +@Getter +@Jacksonized +@Document(Settings.COLLECTION_NAME) +public class Settings { + static final String COLLECTION_NAME = "settings"; + + @Id + private String id; + + private String name; + @Valid + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY, property = "name") + private SettingsBody settingsBody; } diff --git a/src/main/java/de/ozgcloud/admin/settings/SettingsBody.java b/src/main/java/de/ozgcloud/admin/settings/SettingsBody.java new file mode 100644 index 0000000000000000000000000000000000000000..f007d95301abb9f33a262299ff864188d1008967 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/settings/SettingsBody.java @@ -0,0 +1,13 @@ +package de.ozgcloud.admin.settings; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; + +import de.ozgcloud.admin.settings.postfach.Postfach; + +@JsonSubTypes({ + @Type(value = Postfach.class, name = "Postfach") +}) +public interface SettingsBody { + +} diff --git a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterConstants.java b/src/main/java/de/ozgcloud/admin/settings/SettingsConstants.java similarity index 86% rename from src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterConstants.java rename to src/main/java/de/ozgcloud/admin/settings/SettingsConstants.java index bf9f82e2aa776369d825982461f3a83c3786f912..d12906c039760748fd9904a2bc9a3ccdf9f66259 100644 --- a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterConstants.java +++ b/src/main/java/de/ozgcloud/admin/settings/SettingsConstants.java @@ -19,15 +19,15 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) -class ConfigurationParameterConstants { +public class SettingsConstants { - static final String REL = "params"; + static final String REL = "settings"; - static final String PATH = "param"; + public static final String PATH = "settings"; } diff --git a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterRepository.java b/src/main/java/de/ozgcloud/admin/settings/SettingsRepository.java similarity index 78% rename from src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterRepository.java rename to src/main/java/de/ozgcloud/admin/settings/SettingsRepository.java index 1915e55dba201369f9b696d363e824e9a19967b6..722039cce0407f83ea7e4edb87943f705dbf4f6f 100644 --- a/src/main/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterRepository.java +++ b/src/main/java/de/ozgcloud/admin/settings/SettingsRepository.java @@ -19,15 +19,12 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource; -@RepositoryRestResource( - collectionResourceRel = ConfigurationParameterConstants.REL, - path = ConfigurationParameterConstants.PATH -) -public interface ConfigurationParameterRepository extends MongoRepository<ConfigurationParameter, String> { +@RepositoryRestResource(collectionResourceRel = SettingsConstants.REL, path = SettingsConstants.PATH) +interface SettingsRepository extends MongoRepository<Settings, String> { } diff --git a/src/main/java/de/ozgcloud/admin/settings/SettingsValidator.java b/src/main/java/de/ozgcloud/admin/settings/SettingsValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..e4b91f801e5d04f32b8d74e4d552d8440d05d957 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/settings/SettingsValidator.java @@ -0,0 +1,27 @@ +package de.ozgcloud.admin.settings; + +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validation; + +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +@Component +class SettingsValidator implements Validator { + + @Override + public boolean supports(Class<?> clazz) { + return Settings.class.equals(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + var validator = Validation.buildDefaultValidatorFactory().getValidator(); + var constraintViolations = validator.validate(target); + if (!constraintViolations.isEmpty()) { + throw new ConstraintViolationException(constraintViolations); + } + } + +} diff --git a/src/main/java/de/ozgcloud/admin/settings/postfach/Absender.java b/src/main/java/de/ozgcloud/admin/settings/postfach/Absender.java new file mode 100644 index 0000000000000000000000000000000000000000..c5be084a5b87420612dcbef48ae3a57359b015e4 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/settings/postfach/Absender.java @@ -0,0 +1,25 @@ +package de.ozgcloud.admin.settings.postfach; + +import static de.ozgcloud.admin.common.errorhandling.ValidationMessageCodes.*; + +import jakarta.validation.constraints.NotEmpty; + +import lombok.Builder; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Builder +@Jacksonized +class Absender { + @NotEmpty(message = FIELD_IS_EMPTY) + private String name; + @NotEmpty(message = FIELD_IS_EMPTY) + private String anschrift; + @NotEmpty(message = FIELD_IS_EMPTY) + private String dienst; + @NotEmpty(message = FIELD_IS_EMPTY) + private String mandant; + @NotEmpty(message = FIELD_IS_EMPTY) + private String gemeindeschluessel; +} diff --git a/src/main/java/de/ozgcloud/admin/settings/postfach/Postfach.java b/src/main/java/de/ozgcloud/admin/settings/postfach/Postfach.java new file mode 100644 index 0000000000000000000000000000000000000000..f764f208307e71d8865e5ebf9bb6dd35c7408ff0 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/settings/postfach/Postfach.java @@ -0,0 +1,17 @@ +package de.ozgcloud.admin.settings.postfach; + +import jakarta.validation.Valid; + +import de.ozgcloud.admin.settings.SettingsBody; +import lombok.Builder; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Jacksonized +@Builder +public class Postfach implements SettingsBody { + @Valid + private Absender absender; + private String signatur; +} diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 1afb53200c2c8d36ff043b985de7ff662b0a9ea8..c4a009f732ce241044969d32e6252ff0ee5e7091 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,7 +1,7 @@ spring: data: mongodb: - uri: mongodb://localhost/config-db + uri: mongodb://localhost:27017/config-db security: user: name: user diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java index b4884dd0e4b23e908645251677c76064c9086e8a..18880e1718ab14fcbddfe5663d8a6968740fce20 100644 --- a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerITCase.java @@ -14,6 +14,8 @@ import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.util.Arrays; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -70,7 +72,7 @@ class ExceptionControllerITCase { } private static Stream<Arguments> exceptionAndExpectedStatus() { - return ExceptionController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); + return TestErrorController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); } } @@ -79,26 +81,52 @@ class ExceptionControllerITCase { class TestConstraintViolationException { @Test @SneakyThrows - void shouldHaveValidationMessage() { + void shouldHaveInvalidFieldNameInResponse() { when(modelAssembler.toModel(any())).thenAnswer((a) -> { - throw new ConstraintViolationException(getConstraintViolations()); + throw new ConstraintViolationException(getConstraintViolations("Not Empty")); }); var result = performGet(); - result.andExpect(jsonPath("$.detail").value("string: Empty field")); + result.andExpect(jsonPath("$.invalid-params[*].name").value("string2")); } - private Set<ConstraintViolation<ValidatedClass>> getConstraintViolations() { + @Test + @SneakyThrows + void shouldHaveTwoInvalidParamsInResponse() { + when(modelAssembler.toModel(any())).thenAnswer((a) -> { + throw new ConstraintViolationException(getConstraintViolations(StringUtils.EMPTY)); + }); + + var result = performGet(); + + result.andExpect(jsonPath("$.invalid-params[*].length()").value(Arrays.asList(new Integer[] { 2, 2 }))); + } + + @Test + @SneakyThrows + void shouldHaveMessageInResponse() { + when(modelAssembler.toModel(any())).thenAnswer((a) -> { + throw new ConstraintViolationException(getConstraintViolations(StringUtils.EMPTY)); + }); + + var result = performGet(); + + result.andExpect(jsonPath("$.invalid-params[0].reason").value("Empty field")); + } + + private Set<ConstraintViolation<ValidatedClass>> getConstraintViolations(String string) { Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); - return validator.validate(ValidatedClass.builder().build()); + return validator.validate(ValidatedClass.builder().string1(string).build()); } @Getter @Builder private static class ValidatedClass { @NotEmpty(message = "Empty field") - private String string; + private String string1; + @NotEmpty(message = "Empty field") + private String string2; } } diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java index f33a3715b32778a78b725e096ea0bf99e1fd4047..41f95cf47993da9c9a390d4e7d62702b2f0c79c7 100644 --- a/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/ExceptionControllerTest.java @@ -21,7 +21,6 @@ */ package de.ozgcloud.admin.common.errorhandling; -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.*; @@ -111,7 +110,7 @@ class ExceptionControllerTest { } private static Stream<Arguments> exceptionAndExpectedStatus() { - return STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); + return TestErrorController.STATUS_BY_EXCEPTION.entrySet().stream().map(kv -> Arguments.of(kv.getKey(), kv.getValue())); } @SneakyThrows diff --git a/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java b/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java index c10446041aaba51ac761ac4e91ec01d6f9b7bb4b..c49345edcbebd221c8b19ab7fd3e08d11177f4ba 100644 --- a/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java +++ b/src/test/java/de/ozgcloud/admin/common/errorhandling/TestErrorController.java @@ -27,6 +27,7 @@ import java.util.Map; import jakarta.validation.ConstraintViolationException; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -44,6 +45,14 @@ class TestErrorController { Exception produceException(); } + static final Map<Class<? extends Exception>, HttpStatus> STATUS_BY_EXCEPTION = Map.of( + RuntimeException.class, HttpStatus.INTERNAL_SERVER_ERROR, + AccessDeniedException.class, HttpStatus.FORBIDDEN, + ConstraintViolationException.class, HttpStatus.UNPROCESSABLE_ENTITY, + ResourceNotFoundException.class, HttpStatus.NOT_FOUND, + FunctionalException.class, HttpStatus.BAD_REQUEST, + TechnicalException.class, HttpStatus.INTERNAL_SERVER_ERROR); + static final String ERROR_MESSAGE = "error message"; static Map<Class<? extends Exception>, ExceptionProducer> EXCEPTION_PRODUCER = Map.of( diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java index 653c7b6886f24ade6257f26e718f8cc3f61f9bec..d8bf3dcae4e6b88a078fa58c860d8365bb77ae60 100644 --- a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java +++ b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java @@ -79,7 +79,7 @@ class SecurityConfigurationITCase { @SneakyThrows @ParameterizedTest @ValueSource(strings = { - "/api", "/api/configuration", "/api/configuration/param", + "/api", "/api/configuration", "/api/configuration/settings", "/unknown", }) void shouldDeny(String path) { @@ -147,7 +147,7 @@ class SecurityConfigurationITCase { @ValueSource(strings = { "/api/environment", "/configserver/name/profile", - "/api", "/api/configuration", "/api/configuration/param", + "/api", "/api/configuration", "/api/configuration/settings", }) @WithJwt(CLAIMS) void shouldAllow(String path) { diff --git a/src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentITCase.java b/src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentITCase.java similarity index 94% rename from src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentITCase.java rename to src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentITCase.java index 0b2e2558c43fc91a6db9027626df11f857217baf..bb562e80385ddcef643285c9ff78f1cacabc3f79 100644 --- a/src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentITCase.java +++ b/src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentITCase.java @@ -1,5 +1,5 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; diff --git a/src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepositoryITCase.java b/src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentRepositoryITCase.java similarity index 97% rename from src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepositoryITCase.java rename to src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentRepositoryITCase.java index 49974b6c846d0652944609f4d157ce75eb2be3a8..a894dec5af5e10e2a510daae05ebba6f508977cc 100644 --- a/src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepositoryITCase.java +++ b/src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentRepositoryITCase.java @@ -1,4 +1,4 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import static org.assertj.core.api.Assertions.*; diff --git a/src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepositoryTest.java b/src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentRepositoryTest.java similarity index 97% rename from src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepositoryTest.java rename to src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentRepositoryTest.java index de8ece0831d8594257e5e2bb7e7135117ef37033..bc10bdbd739bbd215f54f352c9413b77b3186c3b 100644 --- a/src/test/java/de/ozgcloud/admin/configurationparameter/AdminEnvironmentRepositoryTest.java +++ b/src/test/java/de/ozgcloud/admin/settings/AdminEnvironmentRepositoryTest.java @@ -1,4 +1,4 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import static org.assertj.core.api.Assertions.*; diff --git a/src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationPropertiesTestFactory.java b/src/test/java/de/ozgcloud/admin/settings/ConfigurationPropertiesTestFactory.java similarity index 93% rename from src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationPropertiesTestFactory.java rename to src/test/java/de/ozgcloud/admin/settings/ConfigurationPropertiesTestFactory.java index 1deb5483b2a802bb939357f1988e837183bfde82..c1db07dd6f33924272dae792d6cac31eed53dc6e 100644 --- a/src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationPropertiesTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/settings/ConfigurationPropertiesTestFactory.java @@ -1,4 +1,4 @@ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import java.util.Map; diff --git a/src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterITCase.java b/src/test/java/de/ozgcloud/admin/settings/SettingsITCase.java similarity index 87% rename from src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterITCase.java rename to src/test/java/de/ozgcloud/admin/settings/SettingsITCase.java index ba805a1afc84c592b58f048fd5fe2f2bd7a0b768..8b21102c73aaec826c2d6a62d0fd5ab59620556f 100644 --- a/src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterITCase.java +++ b/src/test/java/de/ozgcloud/admin/settings/SettingsITCase.java @@ -19,7 +19,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -42,7 +42,7 @@ import lombok.SneakyThrows; @DataITCase @AutoConfigureMockMvc @WithMockUser -class ConfigurationParameterITCase { +class SettingsITCase { @Autowired private MockMvc mockMvc; @@ -58,14 +58,16 @@ class ConfigurationParameterITCase { @BeforeEach void init() { - mongoOperations.dropCollection(ConfigurationParameter.class); - mongoOperations.save(ConfigurationParameterTestFactory.create()); + mongoOperations.dropCollection(Settings.class); + mongoOperations.save(SettingsTestFactory.create()); } @Test @SneakyThrows void shouldHaveStatusOkForExisting() { - var result = doPerform(ConfigurationParameterTestFactory.ID); + var id = mongoOperations.findAll(Settings.class).get(0).getId(); + + var result = doPerform(id); result.andExpect(status().isOk()); } @@ -80,7 +82,7 @@ class ConfigurationParameterITCase { @SneakyThrows private ResultActions doPerform(String id) { - return mockMvc.perform(get(String.join("/", restProperties.getBasePath(), ConfigurationParameterConstants.PATH, id))); + return mockMvc.perform(get(String.join("/", restProperties.getBasePath(), SettingsConstants.PATH, id))); } } diff --git a/src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterTestFactory.java b/src/test/java/de/ozgcloud/admin/settings/SettingsTestFactory.java similarity index 67% rename from src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterTestFactory.java rename to src/test/java/de/ozgcloud/admin/settings/SettingsTestFactory.java index 843b45af4333290bbd3cc4cfdb3732a22eecc03f..ddf73db41640f52d74bd08155f4415581278cffa 100644 --- a/src/test/java/de/ozgcloud/admin/configurationparameter/ConfigurationParameterTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/settings/SettingsTestFactory.java @@ -19,18 +19,21 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.configurationparameter; +package de.ozgcloud.admin.settings; -import java.util.UUID; +import de.ozgcloud.common.test.TestUtils; -public class ConfigurationParameterTestFactory { - public static final String ID = UUID.randomUUID().toString(); +public class SettingsTestFactory { - public static ConfigurationParameter create() { + public static Settings create() { return createBuilder().build(); } - public static ConfigurationParameter.ConfigurationParameterBuilder createBuilder() { - return ConfigurationParameter.builder().id(ID); + public static Settings.SettingsBuilder createBuilder() { + return Settings.builder(); + } + + public static String buildSettingsJson(Settings settings, String settingBodyString) { + return TestUtils.loadTextFile("jsonTemplates/settings/createSettings.json.tmpl", settings.getName(), settingBodyString); } } diff --git a/src/test/java/de/ozgcloud/admin/settings/postfach/AbsenderTestFactory.java b/src/test/java/de/ozgcloud/admin/settings/postfach/AbsenderTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..d048fb4c1b12ab988c88540ede24d3cd1b21e06a --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/settings/postfach/AbsenderTestFactory.java @@ -0,0 +1,27 @@ +package de.ozgcloud.admin.settings.postfach; + +import java.util.Random; + +import com.thedeanda.lorem.LoremIpsum; + +public class AbsenderTestFactory { + public static final String NAME = LoremIpsum.getInstance().getName(); + public static final String ANSCHRIFT = LoremIpsum.getInstance().getEmail(); + public static final String DIENST = LoremIpsum.getInstance().getName(); + public static final String MANDANT = LoremIpsum.getInstance().getName(); + public static final String GEMEINDESCHLUESSEL = String.valueOf(new Random().nextInt(100000)); + + public static Absender create() { + return createBuilder().build(); + } + + public static Absender.AbsenderBuilder createBuilder() { + return Absender.builder() + .name(NAME) + .anschrift(ANSCHRIFT) + .dienst(DIENST) + .mandant(MANDANT) + .gemeindeschluessel(GEMEINDESCHLUESSEL); + } + +} diff --git a/src/test/java/de/ozgcloud/admin/settings/postfach/PostfachITCase.java b/src/test/java/de/ozgcloud/admin/settings/postfach/PostfachITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..a00766eebadd1f1b62e1fad29bd46b85ab07ccc6 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/settings/postfach/PostfachITCase.java @@ -0,0 +1,291 @@ +package de.ozgcloud.admin.settings.postfach; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.ozgcloud.admin.settings.Settings; +import de.ozgcloud.admin.settings.SettingsBody; +import de.ozgcloud.admin.settings.SettingsConstants; +import de.ozgcloud.admin.settings.SettingsTestFactory; +import de.ozgcloud.common.test.DataITCase; +import lombok.SneakyThrows; + +@DataITCase +@AutoConfigureMockMvc +@WithMockUser +class PostfachITCase { + private static final String POSTFACH_NAME = "Postfach"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private MongoOperations mongoOperations; + + @Autowired + private RepositoryRestProperties restProperties; + + private Settings settingsWithPostfach = SettingsTestFactory.createBuilder() + .name(POSTFACH_NAME) + .settingsBody(PostfachTestFactory.create()) + .build(); + + @BeforeEach + void clear() { + mongoOperations.dropCollection(Settings.class); + } + + @Nested + class TestSave { + + @Test + @SneakyThrows + void shouldHaveResponseStatusCreated() { + var result = performPost(settingsWithPostfach); + + result.andExpect(status().isCreated()); + } + + @Test + @SneakyThrows + void shouldCreateSettingsItem() { + performPost(settingsWithPostfach); + + assertThat(getSettingWithPostfachFromDb()) + .usingRecursiveComparison().ignoringFields("id").isEqualTo(settingsWithPostfach); + } + + @Nested + class TestPostfachSettings { + + @Test + @SneakyThrows + void shouldBeInstanceOfPostfach() { + performPost(settingsWithPostfach); + + assertThat(getSettingWithPostfachFromDb() + .getSettingsBody()).isInstanceOf(Postfach.class); + } + + @Test + @SneakyThrows + void shouldCreateEmptySignatur() { + var postfachWithEmptySignatur = PostfachTestFactory.createBuilder() + .signatur(StringUtils.EMPTY).build(); + var settingsWithPostfachEmptySignatur = createSettingsWithPostfach(postfachWithEmptySignatur); + + performPost(settingsWithPostfachEmptySignatur); + + assertThat(getPostfachFromDb().getSignatur()).isEmpty(); + } + + private Postfach getPostfachFromDb() { + return (Postfach) getSettingWithPostfachFromDb().getSettingsBody(); + } + + @Nested + class TestAbsenderValidation { + @Test + @SneakyThrows + void shouldReturnUnprocessableEntityOnEmptyName() { + var absenderWithEmptyName = AbsenderTestFactory.createBuilder().name(StringUtils.EMPTY).build(); + var settingsWithPostfachEmptyAbsenderName = createSettingsWithPostfachAbsender(absenderWithEmptyName); + + var result = performPost(settingsWithPostfachEmptyAbsenderName); + + result.andExpect(status().isUnprocessableEntity()); + } + + @Test + @SneakyThrows + void shouldReturnUnprocessableEntityOnEmptyAnschrift() { + var absenderWithEmptyAnschrift = AbsenderTestFactory.createBuilder().anschrift(StringUtils.EMPTY).build(); + var settingsWithPostfachEmptyAnschrift = createSettingsWithPostfachAbsender(absenderWithEmptyAnschrift); + + var result = performPost(settingsWithPostfachEmptyAnschrift); + + result.andExpect(status().isUnprocessableEntity()); + } + + @Test + @SneakyThrows + void shouldReturnUnprocessableEntityOnEmptyDienst() { + var absenderWithEmptyDienst = AbsenderTestFactory.createBuilder().dienst(StringUtils.EMPTY).build(); + var settingsWithPostfachEmptyDienst = createSettingsWithPostfachAbsender(absenderWithEmptyDienst); + + var result = performPost(settingsWithPostfachEmptyDienst); + + result.andExpect(status().isUnprocessableEntity()); + } + + @Test + @SneakyThrows + void shouldReturnUnprocessableEntityOnEmptyMandant() { + var absenderWithEmptyMandant = AbsenderTestFactory.createBuilder().mandant(StringUtils.EMPTY).build(); + var setttingsWithPostfachEmptyMandant = createSettingsWithPostfachAbsender(absenderWithEmptyMandant); + + var result = performPost(setttingsWithPostfachEmptyMandant); + + result.andExpect(status().isUnprocessableEntity()); + } + + @Test + @SneakyThrows + void shouldReturnUnprocessableEntityOnEmptyGemeindeSchluessel() { + var absenderWithEmptyGemeindeschluessel = AbsenderTestFactory.createBuilder().gemeindeschluessel(StringUtils.EMPTY).build(); + var settingsWithPostfachEmptyGemeindeschluessel = createSettingsWithPostfachAbsender(absenderWithEmptyGemeindeschluessel); + + var result = performPost(settingsWithPostfachEmptyGemeindeschluessel); + + result.andExpect(status().isUnprocessableEntity()); + } + + private Settings createSettingsWithPostfachAbsender(Absender absender) { + var postfach = PostfachTestFactory.createBuilder().absender(absender).build(); + return createSettingsWithPostfach(postfach); + } + + } + + private Settings createSettingsWithPostfach(SettingsBody postfach) { + return SettingsTestFactory.createBuilder().name(POSTFACH_NAME).settingsBody(postfach).build(); + } + } + + @SneakyThrows + private ResultActions performPost(Settings setting) { + var postBody = convertSettingToString(setting); + return mockMvc.perform(post(String.join("/", restProperties.getBasePath(), + SettingsConstants.PATH)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(postBody)); + } + } + + @Nested + class TestGet { + private String id; + @Autowired + private ObjectMapper mapper; + + @BeforeEach + void init() { + id = mongoOperations.save(settingsWithPostfach).getId(); + } + + @Test + @SneakyThrows + void shouldHaveStatusOkForExisting() { + var result = performGet(); + + result.andExpect(status().isOk()); + } + + @Test + @SneakyThrows + void shouldHaveInstanceOfPostfach() { + + var result = performGet(); + + assertThat(mapper.readValue(result.andReturn().getResponse().getContentAsString(), Settings.class).getSettingsBody()) + .isInstanceOf(Postfach.class); + } + + @SneakyThrows + private ResultActions performGet() { + return mockMvc.perform(get(String.join("/", restProperties.getBasePath(), SettingsConstants.PATH, id))); + } + } + + @Nested + class TestPut { + private String id; + private Postfach updatedPostfach = PostfachTestFactory.createBuilder() + .absender(AbsenderTestFactory.createBuilder() + .name("Neuer Name") + .anschrift("Neue Anschrift") + .build()) + .build(); + private Settings updatedSettings = SettingsTestFactory.createBuilder() + .name(POSTFACH_NAME) + .settingsBody(updatedPostfach) + .build(); + + @BeforeEach + void init() { + id = mongoOperations.save(settingsWithPostfach).getId(); + } + + @Test + @SneakyThrows + void shouldHaveStatusNoContent() { + var result = performPut(); + + result.andExpect(status().isNoContent()); + } + + @Test + @SneakyThrows + void shouldHaveUpdatedSettings() { + performPut(); + + assertThat(getSettingWithPostfachFromDb()) + .usingRecursiveComparison().ignoringFields("id").isEqualTo(updatedSettings); + } + + @Test + @SneakyThrows + void shouldHaveExactlyOnePostfachSetting() { + performPut(); + + List<Settings> postfachSettings = mongoOperations.find(createQueryForPostfach(), Settings.class); + + assertThat(postfachSettings).hasSize(1); + } + + @SneakyThrows + private ResultActions performPut() { + var body = convertSettingToString(updatedSettings); + return mockMvc.perform(put(String.join("/", restProperties.getBasePath(), SettingsConstants.PATH, id)) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(body)); + } + + } + + private Settings getSettingWithPostfachFromDb() { + return mongoOperations.findOne(createQueryForPostfach(), Settings.class); + } + + private Query createQueryForPostfach() { + return new Query().addCriteria(Criteria.where("name").in(POSTFACH_NAME)); + } + + @SneakyThrows + private String convertSettingToString(Settings setting) { + return SettingsTestFactory.buildSettingsJson(setting, PostfachTestFactory.buildPostfachJson((Postfach) setting.getSettingsBody())); + } +} diff --git a/src/test/java/de/ozgcloud/admin/settings/postfach/PostfachTestFactory.java b/src/test/java/de/ozgcloud/admin/settings/postfach/PostfachTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..4f82a2a55e84d76d63b49d54be9ee8100a76b498 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/settings/postfach/PostfachTestFactory.java @@ -0,0 +1,27 @@ +package de.ozgcloud.admin.settings.postfach; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.common.test.TestUtils; + +public class PostfachTestFactory { + public static final Absender ABSENDER = AbsenderTestFactory.create(); + public static final String SIGNATUR = LoremIpsum.getInstance().getHtmlParagraphs(1, 2); + + public static Postfach create() { + return createBuilder().build(); + } + + public static Postfach.PostfachBuilder createBuilder() { + return Postfach.builder() + .absender(ABSENDER) + .signatur(SIGNATUR); + } + + public static String buildPostfachJson(Postfach postfach) { + return TestUtils.loadTextFile("jsonTemplates/settings/createPostfach.json.tmpl", postfach.getAbsender().getName(), + postfach.getAbsender().getAnschrift(), postfach.getAbsender().getDienst(), postfach.getAbsender().getMandant(), + postfach.getAbsender().getGemeindeschluessel(), postfach.getSignatur()); + } + +} diff --git a/src/test/resources/jsonTemplates/settings/createPostfach.json.tmpl b/src/test/resources/jsonTemplates/settings/createPostfach.json.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..6cbce12b094249a9cbbe5b979428c2f5557f904c --- /dev/null +++ b/src/test/resources/jsonTemplates/settings/createPostfach.json.tmpl @@ -0,0 +1,10 @@ + { + "absender" : { + "name" : "%s", + "anschrift" : "%s", + "dienst" : "%s", + "mandant" : "%s", + "gemeindeschluessel" : "%s" + }, + "signatur" : "%s" + } \ No newline at end of file diff --git a/src/test/resources/jsonTemplates/settings/createSettings.json.tmpl b/src/test/resources/jsonTemplates/settings/createSettings.json.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..a0dd5cfd77775267d1f40a54948810e02ac4e766 --- /dev/null +++ b/src/test/resources/jsonTemplates/settings/createSettings.json.tmpl @@ -0,0 +1,4 @@ +{ + "name" : "%s", + "settingsBody" : %s +} \ No newline at end of file