diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java index 179a2d37..163aeae1 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository; +import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry; import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetsApi; import net.hostsharing.hsadminng.context.Context; @@ -23,7 +24,6 @@ import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; -import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.cleanup; import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.validated; @RestController @@ -79,7 +79,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { .path("/api/hs/hosting/assets/{id}") .buildAndExpand(saved.getUuid()) .toUri(); - final var mapped = mapper.map(saved, HsHostingAssetResource.class); + final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.created(uri).body(mapped); } @@ -95,7 +95,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { final var result = assetRepo.findByUuid(assetUuid); return result .map(assetEntity -> ResponseEntity.ok( - mapper.map(assetEntity, HsHostingAssetResource.class))) + mapper.map(assetEntity, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER))) .orElseGet(() -> ResponseEntity.notFound().build()); } @@ -127,6 +127,13 @@ public class HsHostingAssetController implements HsHostingAssetsApi { new HsHostingAssetEntityPatcher(em, current).apply(body); +// validate(current)/ / self-validation, hashing passwords etc. +// .then(HsHostingAssetEntityValidatorRegistry::prepareForSave) // hashing passwords etc. +// .then(assetRepo::save) +// .then(HsHostingAssetEntityValidatorRegistry::validateInContext) +// .then(this::mapToResource) using postprocessProperties to remove write-only + add read-only properties + + final var saved = validated(assetRepo.save(current)); final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(mapped); @@ -146,7 +153,6 @@ public class HsHostingAssetController implements HsHostingAssetsApi { } }; - final BiConsumer ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { - cleanup(entity, resource); - }; + final BiConsumer ENTITY_TO_RESOURCE_POSTMAPPER + = HsHostingAssetEntityValidatorRegistry::postprocessProperties; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java index ef0a4899..df00c710 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsHostingAssetEntityValidatorRegistry.java @@ -50,9 +50,9 @@ public class HsHostingAssetEntityValidatorRegistry { return entityToSave; } - public static void cleanup(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) { + public static void postprocessProperties(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) { final var validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType()); - final var config = validator.cleanup(asMap(resource)); + final var config = validator.postprocess(entity, asMap(resource)); resource.setConfig(config); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java index e058a734..6b390767 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsUnixUserHostingAssetValidator.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; +import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import java.util.regex.Pattern; @@ -12,6 +13,8 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator { + private static final int DASH_LENGTH = "-".length(); + HsUnixUserHostingAssetValidator() { super( BookingItem.mustBeNull(), ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE), @@ -25,7 +28,7 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator { enumerationProperty("shell") .values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd") .withDefault("/bin/false"), - stringProperty("homedir").readOnly(), + stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir), stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).hidden().writeOnly().optional(), passwordProperty("password").minLength(8).maxLength(40).writeOnly()); } @@ -35,4 +38,11 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator { final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier(); return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$"); } + + private static String computeHomedir(final PropertiesProvider propertiesProvider) { + final var entity = (HsHostingAssetEntity) propertiesProvider; + final var webspaceName = entity.getParentAsset().getIdentifier(); + return "/home/pacs/" + webspaceName + + "/users/" + entity.getIdentifier().substring(webspaceName.length()+DASH_LENGTH); + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index 67540554..9bc4b3cb 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -11,7 +11,8 @@ import java.util.function.Supplier; import static java.util.Arrays.stream; import static java.util.Collections.emptyList; -public abstract class HsEntityValidator { +// TODO.refa: rename to HsEntityProcessor, also subclasses +public abstract class HsEntityValidator { public final ValidatableProperty[] propertyValidators; @@ -88,9 +89,16 @@ public abstract class HsEntityValidator { throw new IllegalArgumentException("Integer value (or null) expected, but got " + value); } - public Map cleanup(final Map config) { + public Map postprocess(final E entity, final Map config) { final var copy = new HashMap<>(config); - stream(propertyValidators).filter(p -> p.writeOnly).forEach(p -> copy.remove(p.propertyName)); + stream(propertyValidators).forEach(p -> { + if ( p.writeOnly) { + copy.remove(p.propertyName); + } + if (p.isComputed()) { + copy.put(p.propertyName, p.compute(entity)); + } + }); return copy; } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java index 5de5108f..441f0d06 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/PasswordProperty.java @@ -23,6 +23,8 @@ public class PasswordProperty extends StringProperty { validatePassword(result, propValue); } + // TODO.impl: only a SHA512 hash should be stored in the database, not the password itself + @Override protected String simpleTypeName() { return "password"; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java index 9bdf064b..c65f9325 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/ValidatableProperty.java @@ -32,6 +32,7 @@ public abstract class ValidatableProperty { private final String[] keyOrder; private Boolean required; private T defaultValue; + protected Function computedBy; protected boolean readOnly; protected boolean writeOnly; @@ -216,4 +217,17 @@ public abstract class ValidatableProperty { .flatMap(Collection::stream) .toList(); } + + public ValidatableProperty computedBy(final Function compute) { + this.computedBy = compute; + return this; + } + + public boolean isComputed() { + return computedBy != null; + } + + public T compute(final E entity) { + return computedBy.apply(entity); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java index 903d5385..258b55b7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntityUnitTest.java @@ -53,7 +53,7 @@ class HsBookingItemEntityUnitTest { void toStringContainsAllPropertiesAndResourcesSortedByKey() { final var result = givenBookingItem.toString(); - assertThat(result).isEqualTo("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); + assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index f125974a..5e32e23d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -170,9 +170,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // then allTheseBookingItemsAreReturned( result, - "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })", - "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 })", - "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })"); + "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )", + "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )", + "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).findAny()) .as("at least one relatedProject expected, but none found => fetching relatedProject does not work") .isNotEmpty(); @@ -193,9 +193,9 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // then: exactlyTheseBookingItemsAreReturned( result, - "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 })", - "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 })", - "HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 })"); + "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )", + "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )", + "HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); } } @@ -348,13 +348,17 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup final List actualResult, final String... bookingItemNames) { assertThat(actualResult) - .extracting(bookingItemEntity -> bookingItemEntity.toString()) + .extracting(HsBookingItemEntity::toString) + .extracting(string-> string.replaceAll("\\s+", " ")) + .extracting(string-> string.replaceAll("\"", "")) .containsExactlyInAnyOrder(bookingItemNames); } void allTheseBookingItemsAreReturned(final List actualResult, final String... bookingItemNames) { assertThat(actualResult) - .extracting(bookingItemEntity -> bookingItemEntity.toString()) + .extracting(HsBookingItemEntity::toString) + .extracting(string -> string.replaceAll("\\s+", " ")) + .extracting(string -> string.replaceAll("\"", "")) .contains(bookingItemNames); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index fb1f8512..11bfc45c 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -476,9 +476,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "identifier": "vm2001", "caption": "some test-asset", "alarmContact": { - "uuid": "%s", "caption": "second contact", - "emailAddresses": { "main": "contact-admin@secondcontact.example.com" } + "emailAddresses": { + "main": "contact-admin@secondcontact.example.com" + } }, "config": { "monit_max_cpu_usage": 90, @@ -487,7 +488,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "monit_min_free_ssd": 5 } } - """.formatted(alarmContactUuid))); + """)); // @formatter:on // finally, the asset is actually updated @@ -495,10 +496,18 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup context.define("superuser-alex@hostsharing.net"); assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get() .matches(asset -> { - assertThat(asset.getAlarmContact().toString()).isEqualTo( - "contact(caption='second contact', emailAddresses='{ main: contact-admin@secondcontact.example.com }')"); - assertThat(asset.getConfig().toString()).isEqualTo( - "{ monit_max_cpu_usage: 90, monit_max_ram_usage: 70, monit_max_ssd_usage: 85, monit_min_free_ssd: 5 }"); + assertThat(asset.getAlarmContact()).isNotNull() + .extracting(c -> c.getEmailAddresses().get("main")) + .isEqualTo("contact-admin@secondcontact.example.com"); + assertThat(asset.getConfig().toString()) + .isEqualToIgnoringWhitespace(""" + { + "monit_max_cpu_usage": 90, + "monit_max_ram_usage": 70, + "monit_max_ssd_usage": 85, + "monit_min_free_ssd": 5 + } + """); return true; }); } @@ -542,6 +551,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "identifier": "fir01-temp", "caption": "some patched test-unix-user", "config": { + "homedir": "/home/pacs/fir01/users/temp", "shell": "/bin/bash" } } @@ -549,6 +559,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup // the config separately but not-leniently to make sure that no write-only-properties are listed .body("config", strictlyEquals(""" { + "homedir": "/home/pacs/fir01/users/temp", "shell": "/bin/bash" } """)) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java index 1dd7c0e1..6460ae39 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetEntityUnitTest.java @@ -57,14 +57,14 @@ class HsHostingAssetEntityUnitTest { @Test void toStringContainsAllPropertiesAndResourcesSortedByKey() { - assertThat(givenWebspace.toString()).isEqualTo( - "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { CPUs: 2, HDD-storage: 2048, SSD-storage: 512 })"); + assertThat(givenWebspace.toString()).isEqualToIgnoringWhitespace( + "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); - assertThat(givenUnixUser.toString()).isEqualTo( - "HsHostingAssetEntity(UNIX_USER, xyz00-web, some unix-user, MANAGED_WEBSPACE:xyz00, { HDD-hard-quota: 512, HDD-soft-quota: 256, SSD-hard-quota: 256, SSD-soft-quota: 128 })"); + assertThat(givenUnixUser.toString()).isEqualToIgnoringWhitespace( + "HsHostingAssetEntity(UNIX_USER, xyz00-web, some unix-user, MANAGED_WEBSPACE:xyz00, { \"HDD-hard-quota\": 512, \"HDD-soft-quota\": 256, \"SSD-hard-quota\": 256, \"SSD-soft-quota\": 128 })"); - assertThat(givenDomainHttpSetup.toString()).isEqualTo( - "HsHostingAssetEntity(DOMAIN_HTTP_SETUP, example.org, some domain setup, MANAGED_WEBSPACE:xyz00, UNIX_USER:xyz00-web, { option-htdocsfallback: true, use-fcgiphpbin: /usr/lib/cgi-bin/php, validsubdomainnames: * })"); + assertThat(givenDomainHttpSetup.toString()).isEqualToIgnoringWhitespace( + "HsHostingAssetEntity(DOMAIN_HTTP_SETUP, example.org, some domain setup, MANAGED_WEBSPACE:xyz00, UNIX_USER:xyz00-web, { \"option-htdocsfallback\": true, \"use-fcgiphpbin\": \"/usr/lib/cgi-bin/php\", \"validsubdomainnames\": \"*\" })"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java index 5ada81b0..6c79da67 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetRepositoryIntegrationTest.java @@ -195,7 +195,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu exactlyTheseAssetsAreReturned( result, "HsHostingAssetEntity(MANAGED_WEBSPACE, fir01, some Webspace, MANAGED_SERVER:vm1011, D-1000111:D-1000111 default project:separate ManagedWebspace)", - "HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:separate ManagedServer, { monit_max_cpu_usage: 90, monit_max_ram_usage: 80, monit_max_ssd_usage: 70 })"); + "HsHostingAssetEntity(MANAGED_SERVER, vm1011, some ManagedServer, D-1000111:D-1000111 default project:separate ManagedServer, { monit_max_cpu_usage: 90, monit_max_ram_usage: 80, monit_max_ssd_usage: 70 } )"); } @Test @@ -407,6 +407,8 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu final String... serverNames) { assertThat(actualResult) .extracting(HsHostingAssetEntity::toString) + .extracting(input -> input.replaceAll("\\s+", " ")) + .extracting(input -> input.replaceAll("\"", "")) .containsExactlyInAnyOrder(serverNames); } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java b/src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java index 54208e4c..9431c184 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/test/JsonMatcher.java @@ -9,13 +9,15 @@ import org.json.JSONException; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; +import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT; + public class JsonMatcher extends BaseMatcher { - private final String expected; + private final String expectedJson; private JSONCompareMode compareMode; - public JsonMatcher(final String expected, final JSONCompareMode compareMode) { - this.expected = expected; + public JsonMatcher(final String expectedJson, final JSONCompareMode compareMode) { + this.expectedJson = expectedJson; this.compareMode = compareMode; } @@ -47,11 +49,13 @@ public class JsonMatcher extends BaseMatcher { return false; } try { - final var actualJson = new ObjectMapper().writeValueAsString(actual); - JSONAssert.assertEquals(expected, actualJson, compareMode); + final var actualJson = new ObjectMapper().enable(INDENT_OUTPUT).writeValueAsString(actual); + JSONAssert.assertEquals(expectedJson, actualJson, compareMode); return true; } catch (final JSONException | JsonProcessingException e) { throw new AssertionError(e); + } catch (final Exception e ) { + throw e; } }