add-unix-user-hosting-asset-validation #66
@ -1,6 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
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.hs.hosting.generated.api.v1.api.HsHostingAssetsApi;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
@ -23,7 +24,6 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
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;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.validated;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -79,7 +79,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
.path("/api/hs/hosting/assets/{id}")
|
.path("/api/hs/hosting/assets/{id}")
|
||||||
.buildAndExpand(saved.getUuid())
|
.buildAndExpand(saved.getUuid())
|
||||||
.toUri();
|
.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);
|
return ResponseEntity.created(uri).body(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
final var result = assetRepo.findByUuid(assetUuid);
|
final var result = assetRepo.findByUuid(assetUuid);
|
||||||
return result
|
return result
|
||||||
.map(assetEntity -> ResponseEntity.ok(
|
.map(assetEntity -> ResponseEntity.ok(
|
||||||
mapper.map(assetEntity, HsHostingAssetResource.class)))
|
mapper.map(assetEntity, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
|
||||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +127,13 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
|
|
||||||
new HsHostingAssetEntityPatcher(em, current).apply(body);
|
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 saved = validated(assetRepo.save(current));
|
||||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
return ResponseEntity.ok(mapped);
|
return ResponseEntity.ok(mapped);
|
||||||
@ -146,7 +153,6 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER
|
||||||
cleanup(entity, resource);
|
= HsHostingAssetEntityValidatorRegistry::postprocessProperties;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,9 @@ public class HsHostingAssetEntityValidatorRegistry {
|
|||||||
return entityToSave;
|
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 validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
|
||||||
final var config = validator.cleanup(asMap(resource));
|
final var config = validator.postprocess(entity, asMap(resource));
|
||||||
resource.setConfig(config);
|
resource.setConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -12,6 +13,8 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
|
|||||||
|
|
||||||
class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
|
class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||||
|
|
||||||
|
private static final int DASH_LENGTH = "-".length();
|
||||||
|
|
||||||
HsUnixUserHostingAssetValidator() {
|
HsUnixUserHostingAssetValidator() {
|
||||||
super( BookingItem.mustBeNull(),
|
super( BookingItem.mustBeNull(),
|
||||||
ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE),
|
ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE),
|
||||||
@ -25,7 +28,7 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
|
|||||||
enumerationProperty("shell")
|
enumerationProperty("shell")
|
||||||
.values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd")
|
.values("/bin/false", "/bin/bash", "/bin/csh", "/bin/dash", "/usr/bin/tcsh", "/usr/bin/zsh", "/usr/bin/passwd")
|
||||||
.withDefault("/bin/false"),
|
.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(),
|
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).hidden().writeOnly().optional(),
|
||||||
passwordProperty("password").minLength(8).maxLength(40).writeOnly());
|
passwordProperty("password").minLength(8).maxLength(40).writeOnly());
|
||||||
}
|
}
|
||||||
@ -35,4 +38,11 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
|
|||||||
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
||||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$");
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ import java.util.function.Supplier;
|
|||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
public abstract class HsEntityValidator<E> {
|
// TODO.refa: rename to HsEntityProcessor, also subclasses
|
||||||
|
public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
||||||
|
|
||||||
public final ValidatableProperty<?>[] propertyValidators;
|
public final ValidatableProperty<?>[] propertyValidators;
|
||||||
|
|
||||||
@ -88,9 +89,16 @@ public abstract class HsEntityValidator<E> {
|
|||||||
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> cleanup(final Map<String, Object> config) {
|
public Map<String, Object> postprocess(final E entity, final Map<String, Object> config) {
|
||||||
final var copy = new HashMap<>(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;
|
return copy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ public class PasswordProperty extends StringProperty {
|
|||||||
validatePassword(result, propValue);
|
validatePassword(result, propValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO.impl: only a SHA512 hash should be stored in the database, not the password itself
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String simpleTypeName() {
|
protected String simpleTypeName() {
|
||||||
return "password";
|
return "password";
|
||||||
|
@ -32,6 +32,7 @@ public abstract class ValidatableProperty<T> {
|
|||||||
private final String[] keyOrder;
|
private final String[] keyOrder;
|
||||||
private Boolean required;
|
private Boolean required;
|
||||||
private T defaultValue;
|
private T defaultValue;
|
||||||
|
protected Function<PropertiesProvider, T> computedBy;
|
||||||
protected boolean readOnly;
|
protected boolean readOnly;
|
||||||
protected boolean writeOnly;
|
protected boolean writeOnly;
|
||||||
|
|
||||||
@ -216,4 +217,17 @@ public abstract class ValidatableProperty<T> {
|
|||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<T> computedBy(final Function<PropertiesProvider, T> compute) {
|
||||||
|
this.computedBy = compute;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isComputed() {
|
||||||
|
return computedBy != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E extends PropertiesProvider> T compute(final E entity) {
|
||||||
|
return computedBy.apply(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ class HsBookingItemEntityUnitTest {
|
|||||||
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
||||||
final var result = givenBookingItem.toString();
|
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
|
@Test
|
||||||
|
@ -193,8 +193,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
|
|||||||
// then:
|
// then:
|
||||||
exactlyTheseBookingItemsAreReturned(
|
exactlyTheseBookingItemsAreReturned(
|
||||||
result,
|
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, 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 } )");
|
"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<HsBookingItemEntity> actualResult,
|
final List<HsBookingItemEntity> actualResult,
|
||||||
final String... bookingItemNames) {
|
final String... bookingItemNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
.extracting(bookingItemEntity -> bookingItemEntity.toString())
|
.extracting(HsBookingItemEntity::toString)
|
||||||
|
.extracting(string-> string.replaceAll("\\s+", " "))
|
||||||
|
.extracting(string-> string.replaceAll("\"", ""))
|
||||||
.containsExactlyInAnyOrder(bookingItemNames);
|
.containsExactlyInAnyOrder(bookingItemNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
void allTheseBookingItemsAreReturned(final List<HsBookingItemEntity> actualResult, final String... bookingItemNames) {
|
void allTheseBookingItemsAreReturned(final List<HsBookingItemEntity> actualResult, final String... bookingItemNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
.extracting(bookingItemEntity -> bookingItemEntity.toString())
|
.extracting(HsBookingItemEntity::toString)
|
||||||
|
.extracting(string -> string.replaceAll("\\s+", " "))
|
||||||
|
.extracting(string -> string.replaceAll("\"", ""))
|
||||||
.contains(bookingItemNames);
|
.contains(bookingItemNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,9 +476,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
"identifier": "vm2001",
|
"identifier": "vm2001",
|
||||||
"caption": "some test-asset",
|
"caption": "some test-asset",
|
||||||
"alarmContact": {
|
"alarmContact": {
|
||||||
"uuid": "%s",
|
|
||||||
"caption": "second contact",
|
"caption": "second contact",
|
||||||
"emailAddresses": { "main": "contact-admin@secondcontact.example.com" }
|
"emailAddresses": {
|
||||||
|
"main": "contact-admin@secondcontact.example.com"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"monit_max_cpu_usage": 90,
|
"monit_max_cpu_usage": 90,
|
||||||
@ -487,7 +488,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
"monit_min_free_ssd": 5
|
"monit_min_free_ssd": 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""".formatted(alarmContactUuid)));
|
"""));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
// finally, the asset is actually updated
|
// finally, the asset is actually updated
|
||||||
@ -495,10 +496,18 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get()
|
assertThat(assetRepo.findByUuid(givenAsset.getUuid())).isPresent().get()
|
||||||
.matches(asset -> {
|
.matches(asset -> {
|
||||||
assertThat(asset.getAlarmContact().toString()).isEqualTo(
|
assertThat(asset.getAlarmContact()).isNotNull()
|
||||||
"contact(caption='second contact', emailAddresses='{ main: contact-admin@secondcontact.example.com }')");
|
.extracting(c -> c.getEmailAddresses().get("main"))
|
||||||
assertThat(asset.getConfig().toString()).isEqualTo(
|
.isEqualTo("contact-admin@secondcontact.example.com");
|
||||||
"{ monit_max_cpu_usage: 90, monit_max_ram_usage: 70, monit_max_ssd_usage: 85, monit_min_free_ssd: 5 }");
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -542,6 +551,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
"identifier": "fir01-temp",
|
"identifier": "fir01-temp",
|
||||||
"caption": "some patched test-unix-user",
|
"caption": "some patched test-unix-user",
|
||||||
"config": {
|
"config": {
|
||||||
|
"homedir": "/home/pacs/fir01/users/temp",
|
||||||
"shell": "/bin/bash"
|
"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
|
// the config separately but not-leniently to make sure that no write-only-properties are listed
|
||||||
.body("config", strictlyEquals("""
|
.body("config", strictlyEquals("""
|
||||||
{
|
{
|
||||||
|
"homedir": "/home/pacs/fir01/users/temp",
|
||||||
"shell": "/bin/bash"
|
"shell": "/bin/bash"
|
||||||
}
|
}
|
||||||
"""))
|
"""))
|
||||||
|
@ -57,14 +57,14 @@ class HsHostingAssetEntityUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
void toStringContainsAllPropertiesAndResourcesSortedByKey() {
|
||||||
|
|
||||||
assertThat(givenWebspace.toString()).isEqualTo(
|
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 })");
|
"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(
|
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 })");
|
"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(
|
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: * })");
|
"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
|
@Test
|
||||||
|
@ -407,6 +407,8 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
|||||||
final String... serverNames) {
|
final String... serverNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
.extracting(HsHostingAssetEntity::toString)
|
.extracting(HsHostingAssetEntity::toString)
|
||||||
|
.extracting(input -> input.replaceAll("\\s+", " "))
|
||||||
|
.extracting(input -> input.replaceAll("\"", ""))
|
||||||
.containsExactlyInAnyOrder(serverNames);
|
.containsExactlyInAnyOrder(serverNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,13 +9,15 @@ import org.json.JSONException;
|
|||||||
import org.skyscreamer.jsonassert.JSONAssert;
|
import org.skyscreamer.jsonassert.JSONAssert;
|
||||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||||
|
|
||||||
|
import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
|
||||||
|
|
||||||
public class JsonMatcher extends BaseMatcher<CharSequence> {
|
public class JsonMatcher extends BaseMatcher<CharSequence> {
|
||||||
|
|
||||||
private final String expected;
|
private final String expectedJson;
|
||||||
private JSONCompareMode compareMode;
|
private JSONCompareMode compareMode;
|
||||||
|
|
||||||
public JsonMatcher(final String expected, final JSONCompareMode compareMode) {
|
public JsonMatcher(final String expectedJson, final JSONCompareMode compareMode) {
|
||||||
this.expected = expected;
|
this.expectedJson = expectedJson;
|
||||||
this.compareMode = compareMode;
|
this.compareMode = compareMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,11 +49,13 @@ public class JsonMatcher extends BaseMatcher<CharSequence> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final var actualJson = new ObjectMapper().writeValueAsString(actual);
|
final var actualJson = new ObjectMapper().enable(INDENT_OUTPUT).writeValueAsString(actual);
|
||||||
JSONAssert.assertEquals(expected, actualJson, compareMode);
|
JSONAssert.assertEquals(expectedJson, actualJson, compareMode);
|
||||||
return true;
|
return true;
|
||||||
} catch (final JSONException | JsonProcessingException e) {
|
} catch (final JSONException | JsonProcessingException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
|
} catch (final Exception e ) {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user