1
0

add-domain-setup-validation (#71)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/71
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-07-05 11:56:32 +02:00
parent a77eaefb94
commit f6d66d5712
21 changed files with 821 additions and 122 deletions

View File

@@ -185,6 +185,67 @@ public class HsHostingAssetControllerRestTest {
}
}
]
"""),
DOMAIN_SETUP(
List.of(
HsHostingAssetEntity.builder()
.type(HsHostingAssetType.DOMAIN_SETUP)
.identifier("example.org")
.caption("some fake Domain-Setup")
.build()),
"""
[
{
"type": "DOMAIN_SETUP",
"identifier": "example.org",
"caption": "some fake Domain-Setup",
"alarmContact": null,
"config": {}
}
]
"""),
DOMAIN_DNS_SETUP(
List.of(
HsHostingAssetEntity.builder()
.type(HsHostingAssetType.DOMAIN_DNS_SETUP)
.identifier("example.org")
.caption("some fake Domain-DNS-Setup")
.config(Map.ofEntries(
entry("auto-WILDCARD-MX-RR", false),
entry("auto-WILDCARD-A-RR", false),
entry("auto-WILDCARD-AAAA-RR", false),
entry("auto-WILDCARD-DKIM-RR", false),
entry("auto-WILDCARD-SPF-RR", false),
entry("user-RR", Array.of(
"www IN CNAME example.com. ; www.example.com is an alias for example.com",
"test1 IN 1h30m CNAME example.com.",
"test2 1h30m IN CNAME example.com.",
"ns IN A 192.0.2.2; IPv4 address for ns.example.com")
)
))
.build()),
"""
[
{
"type": "DOMAIN_DNS_SETUP",
"identifier": "example.org",
"caption": "some fake Domain-DNS-Setup",
"alarmContact": null,
"config": {
"auto-WILDCARD-AAAA-RR": false,
"auto-WILDCARD-MX-RR": false,
"auto-WILDCARD-SPF-RR": false,
"auto-WILDCARD-DKIM-RR": false,
"auto-WILDCARD-A-RR": false,
"user-RR": [
"www IN CNAME example.com. ; www.example.com is an alias for example.com",
"test1 IN 1h30m CNAME example.com.",
"test2 1h30m IN CNAME example.com.",
"ns IN A 192.0.2.2; IPv4 address for ns.example.com"
]
}
}
]
""");
final HsHostingAssetType assetType;

View File

@@ -35,7 +35,9 @@ class HsHostingAssetPropsControllerAcceptanceTest {
"MANAGED_WEBSPACE",
"CLOUD_SERVER",
"UNIX_USER",
"EMAIL_ALIAS"
"EMAIL_ALIAS",
"DOMAIN_SETUP",
"DOMAIN_DNS_SETUP"
]
"""));
// @formatter:on

View File

@@ -27,6 +27,7 @@ import java.util.Map;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
@@ -129,6 +130,9 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
.containsExactlyInAnyOrder(fromFormatted(
initialGrantNames,
// global-admin
"{ grant perm:hs_hosting_asset#fir00:SELECT to role:global#global:ADMIN by system and assume }", // workaround
// owner
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }",
@@ -137,7 +141,6 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
// admin
"{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_hosting_asset#fir00:OWNER by system and assume }",
"{ grant role:hs_hosting_asset#fir00:ADMIN to role:hs_booking_item#fir01:AGENT by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:INSERT>hs_hosting_asset to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:UPDATE to role:hs_hosting_asset#fir00:ADMIN by system and assume }",
// agent
@@ -148,17 +151,44 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
"{ grant role:hs_booking_item#fir01:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
"{ grant role:hs_hosting_asset#fir00:TENANT to role:hs_hosting_asset#fir00:AGENT by system and assume }",
"{ grant role:hs_hosting_asset#vm1011:TENANT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:SELECT to role:hs_hosting_asset#fir00:TENANT by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:SELECT to role:hs_hosting_asset#fir00:TENANT by system and assume }", // workaround
null));
}
@Test
public void anyUser_canCreateNewDomainSetupAsset() {
// given
context("superuser-alex@hostsharing.net");
final var assetCount = assetRepo.count();
// when
context("person-SmithPeter@example.com");
final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder()
.caption("some new domain setup")
.type(DOMAIN_SETUP)
.identifier("example.org")
.build();
return toCleanup(assetRepo.save(newAsset));
});
// then
result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsHostingAssetEntity::getUuid).isNotNull();
assertThat(result.returnedValue().isLoaded()).isFalse();
context("superuser-alex@hostsharing.net");
assertThatAssetIsPersisted(result.returnedValue());
assertThat(assetRepo.count()).isEqualTo(assetCount + 1);
}
private void assertThatAssetIsPersisted(final HsHostingAssetEntity saved) {
final var context =
attempt(em, () -> {
context("superuser-alex@hostsharing.net");
final var found = assetRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).get().isEqualTo(saved.toString());
});
}
}

View File

@@ -0,0 +1,245 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
import net.hostsharing.hsadminng.mapper.Array;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_COMMENT;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_RECORD_DATA;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_RECORD_TYPE;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_REGEX_IN;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_REGEX_NAME;
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsDomainDnsSetupHostingAssetValidator.RR_REGEX_TTL;
import static org.assertj.core.api.Assertions.assertThat;
class HsDomainDnsSetupHostingAssetValidatorUnitTest {
static final HsHostingAssetEntity validDomainSetupEntity = HsHostingAssetEntity.builder()
.type(DOMAIN_SETUP)
.identifier("example.org")
.build();
static HsHostingAssetEntityBuilder validEntityBuilder() {
return HsHostingAssetEntity.builder()
.type(DOMAIN_DNS_SETUP)
.parentAsset(validDomainSetupEntity)
.identifier("example.org")
.config(Map.ofEntries(
entry("user-RR", Array.of(
"@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )",
"www IN CNAME example.com. ; www.example.com is an alias for example.com",
"test1 IN 1h30m CNAME example.com.",
"test2 1h30m IN CNAME example.com.",
"ns IN A 192.0.2.2; IPv4 address for ns.example.com")
)
));
}
@Test
void containsExpectedProperties() {
// when
final var validator = HsHostingAssetEntityValidatorRegistry.forType(DOMAIN_DNS_SETUP);
// then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=TTL, min=0, defaultValue=21600}",
"{type=boolean, propertyName=auto-SOA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-NS-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-MX-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-A-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-AAAA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-MAILSERVICES-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-AUTOCONFIG-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-AUTODISCOVER-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-DKIM-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-SPF-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-MX-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-A-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-AAAA-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-DKIM-RR, defaultValue=true}",
"{type=boolean, propertyName=auto-WILDCARD-SPF-RR, defaultValue=true}",
"{type=string[], propertyName=user-RR, elementsOf={type=string, propertyName=user-RR, matchesRegEx=[([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*], required=true}}"
);
}
@Test
void preprocessesTakesIdentifierFromParent() {
// given
final var givenEntity = validEntityBuilder().build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
validator.preprocessEntity(givenEntity);
// then
assertThat(givenEntity.getIdentifier()).isEqualTo(givenEntity.getParentAsset().getIdentifier());
}
@Test
void rejectsInvalidIdentifier() {
// given
final var givenEntity = validEntityBuilder().identifier("wrong.org").build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var result = validator.validateEntity(givenEntity);
// then
assertThat(result).containsExactly(
"'identifier' expected to match '^example.org$', but is 'wrong.org'"
);
}
@Test
void acceptsValidIdentifier() {
// given
final var givenEntity = validEntityBuilder().identifier(validDomainSetupEntity.getIdentifier()).build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var result = validator.validateEntity(givenEntity);
// then
assertThat(result).isEmpty();
}
@Test
void validatesReferencedEntities() {
// given
final var mangedServerHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_DNS_SETUP:example.org.bookingItem' must be null but is set to D-???????-?:null",
"'DOMAIN_DNS_SETUP:example.org.parentAsset' must be of type DOMAIN_SETUP but is of type null",
"'DOMAIN_DNS_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null");
}
@Test
void acceptsValidEntity() {
// given
final var givenEntity = validEntityBuilder().build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var errors = validator.validateEntity(givenEntity);
// then
assertThat(errors).isEmpty();
}
@Test
void recectsInvalidProperties() {
// given
final var mangedServerHostingAssetEntity = validEntityBuilder()
.config(Map.ofEntries(
entry("TTL", "1d30m"), // currently only an integer for seconds is implemented here
entry("user-RR", Array.of(
"@ 1814400 IN 1814400 BAD1 TTL only allowed once",
"www BAD1 Record-Class missing / not enough columns"))
))
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_DNS_SETUP:example.org.config.TTL' is expected to be of type class java.lang.Integer, but is of type 'String'",
"'DOMAIN_DNS_SETUP:example.org.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any",
"'DOMAIN_DNS_SETUP:example.org.config.user-RR' is expected to match any of [([a-z0-9\\.-]+|@)\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*IN\\s+[A-Z]+\\s+[^;].*(;.*)*, ([a-z0-9\\.-]+|@)\\s+IN\\s+(([1-9][0-9]*[mMhHdDwW]{0,1})+\\s+)*[A-Z]+\\s+[^;].*(;.*)*] but 'www BAD1 Record-Class missing / not enough columns' does not match any");
}
@Test
void validStringMatchesRegEx() {
assertThat("@ ").matches(RR_REGEX_NAME);
assertThat("ns ").matches(RR_REGEX_NAME);
assertThat("example.com. ").matches(RR_REGEX_NAME);
assertThat("12400 ").matches(RR_REGEX_TTL);
assertThat("12400\t\t ").matches(RR_REGEX_TTL);
assertThat("12400 \t\t").matches(RR_REGEX_TTL);
assertThat("1h30m ").matches(RR_REGEX_TTL);
assertThat("30m ").matches(RR_REGEX_TTL);
assertThat("IN ").matches(RR_REGEX_IN);
assertThat("IN\t\t ").matches(RR_REGEX_IN);
assertThat("IN \t\t").matches(RR_REGEX_IN);
assertThat("CNAME ").matches(RR_RECORD_TYPE);
assertThat("CNAME\t\t ").matches(RR_RECORD_TYPE);
assertThat("CNAME \t\t").matches(RR_RECORD_TYPE);
assertThat("example.com.").matches(RR_RECORD_DATA);
assertThat("123.123.123.123").matches(RR_RECORD_DATA);
assertThat("(some more complex argument in parenthesis)").matches(RR_RECORD_DATA);
assertThat("\"some more complex argument; including a semicolon\"").matches(RR_RECORD_DATA);
assertThat("; whatever ; \" really anything").matches(RR_COMMENT);
}
@Test
void generatesZonefile() {
// given
final var givenEntity = validEntityBuilder().build();
final var validator = (HsDomainDnsSetupHostingAssetValidator) HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var zonefile = validator.toZonefileString(givenEntity);
// then
assertThat(zonefile).isEqualTo("""
$ORIGIN example.org.
$TTL 21600
; these records are just placeholders to create a valid zonefile for the validation
@ 1814400 IN SOA example.org. root.example.org ( 1999010100 10800 900 604800 86400 )
@ IN NS ns
@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )
www IN CNAME example.com. ; www.example.com is an alias for example.com
test1 IN 1h30m CNAME example.com.
test2 1h30m IN CNAME example.com.
ns IN A 192.0.2.2; IPv4 address for ns.example.com
""");
}
@Test
void rejectsInvalidZonefile() {
// given
final var givenEntity = validEntityBuilder().config(Map.ofEntries(
entry("user-RR", Array.of(
"example.org. 1814400 IN SOA example.org. root.example.org (1234 10800 900 604800 86400)"
))
))
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var errors = validator.validateContext(givenEntity);
// then
assertThat(errors).containsExactlyInAnyOrder(
"dns_master_load: example.org: multiple RRs of singleton type",
"zone example.org/IN: loading from master file (null) failed: multiple RRs of singleton type",
"zone example.org/IN: not loaded due to errors."
);
}
}

View File

@@ -0,0 +1,111 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import java.util.Map;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
import static org.assertj.core.api.Assertions.assertThat;
class HsDomainSetupHostingAssetValidatorUnitTest {
static HsHostingAssetEntityBuilder validEntityBuilder() {
return HsHostingAssetEntity.builder()
.type(DOMAIN_SETUP)
.identifier("example.org");
}
enum InvalidDomainNameIdentifier {
EMPTY(""),
TOO_LONG("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456890123456789.de"),
DASH_AT_BEGINNING("-example.com"),
DOT_AT_BEGINNING(".example.com"),
DOT_AT_END("example.com.");
final String domainName;
InvalidDomainNameIdentifier(final String domainName) {
this.domainName = domainName;
}
}
@ParameterizedTest
@EnumSource(InvalidDomainNameIdentifier.class)
void rejectsInvalidIdentifier(final InvalidDomainNameIdentifier testCase) {
// given
final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var result = validator.validateEntity(givenEntity);
// then
assertThat(result).containsExactly(
"'identifier' expected to match '^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}', but is '"+testCase.domainName+"'"
);
}
enum ValidDomainNameIdentifier {
SIMPLE("exampe.org"),
MAX_LENGTH("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234568901.de"),
WITH_DASH("example-test.com"),
SUBDOMAIN("test.example.com");
final String domainName;
ValidDomainNameIdentifier(final String domainName) {
this.domainName = domainName;
}
}
@ParameterizedTest
@EnumSource(ValidDomainNameIdentifier.class)
void acceptsValidIdentifier(final ValidDomainNameIdentifier testCase) {
// given
final var givenEntity = validEntityBuilder().identifier(testCase.domainName).build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when
final var result = validator.validateEntity(givenEntity);
// then
assertThat(result).isEmpty();
}
@Test
void containsNoProperties() {
// when
final var validator = HsHostingAssetEntityValidatorRegistry.forType(CLOUD_SERVER);
// then
assertThat(validator.properties()).map(Map::toString).isEmpty();
}
@Test
void validatesReferencedEntities() {
// given
final var mangedServerHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
// when
final var result = validator.validateEntity(mangedServerHostingAssetEntity);
// then
assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_SETUP:example.org.bookingItem' must be null but is set to D-???????-?:null",
"'DOMAIN_SETUP:example.org.parentAsset' must be null but is set to D-???????-?:null",
"'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null");
}
}

View File

@@ -33,7 +33,9 @@ class HsHostingAssetEntityValidatorRegistryUnitTest {
HsHostingAssetType.MANAGED_SERVER,
HsHostingAssetType.MANAGED_WEBSPACE,
HsHostingAssetType.UNIX_USER,
HsHostingAssetType.EMAIL_ALIAS
HsHostingAssetType.EMAIL_ALIAS,
HsHostingAssetType.DOMAIN_SETUP,
HsHostingAssetType.DOMAIN_DNS_SETUP
);
}
}

View File

@@ -0,0 +1,81 @@
package net.hostsharing.hsadminng.system;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.jupiter.api.condition.OS.LINUX;
class SystemProcessTest {
@Test
@EnabledOnOs(LINUX)
void shouldExecuteAndFetchOutput() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("bash", "-c", "echo 'Hello, World!'; echo 'Error!' >&2");
// when
final var returnCode = process.execute();
// then
assertThat(returnCode).isEqualTo(0);
assertThat(process.getStdOut()).isEqualTo("Hello, World!\n");
assertThat(process.getStdErr()).isEqualTo("Error!\n");
}
@Test
@EnabledOnOs(LINUX)
void shouldReturnErrorCode() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("false");
// when
final int returnCode = process.execute();
// then
assertThat(returnCode).isEqualTo(1);
}
@Test
@EnabledOnOs(LINUX)
void shouldExecuteAndFeedInput() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("tr", "[:lower:]", "[:upper:]");
// when
final int returnCode = process.execute("Hallo");
// then
assertThat(returnCode).isEqualTo(0);
assertThat(process.getStdOut()).isEqualTo("HALLO\n");
}
@Test
void shouldThrowExceptionIfProgramNotFound() {
// given
final var process = new SystemProcess("non-existing program");
// when
final var exception = catchThrowable(process::execute);
// then
assertThat(exception).isInstanceOf(IOException.class)
.hasMessage("Cannot run program \"non-existing program\": error=2, No such file or directory");
}
@Test
void shouldBeAbleToRunMultipleTimes() throws IOException, InterruptedException {
// given
final var process = new SystemProcess("true");
// when
process.execute();
final int returnCode = process.execute();
// then
assertThat(returnCode).isEqualTo(0);
}
}