feature/api-for-email-address-search-in-contacts #113
2
.aliases
2
.aliases
@ -83,7 +83,7 @@ alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l'
|
|||||||
|
|
||||||
alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
|
alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
|
||||||
alias gw-test='. .aliases; ./gradlew test'
|
alias gw-test='. .aliases; ./gradlew test'
|
||||||
alias gw-check='. .aliases; gw test importOfficeData check -x pitest -x :dependencyCheckAnalyze'
|
alias gw-check='. .aliases; gw test check -x pitest'
|
||||||
|
|
||||||
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
|
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
|
||||||
alias gw-importOfficeData-in-docker-compose='
|
alias gw-importOfficeData-in-docker-compose='
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# waits for commits on any branch on origin, checks it out and builds it
|
||||||
|
|
||||||
# get the current branch name
|
. .aliases
|
||||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
|
git fetch origin >/dev/null
|
||||||
|
branch_with_new_commits=`git fetch origin >/dev/null; git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads | grep '\[behind' | cut -d' ' -f1 | head -n1`
|
||||||
|
|
||||||
# get the latest commit hashes from origin and local
|
if [ -n "$branch_with_new_commits" ]; then
|
||||||
git fetch origin
|
echo "checking out branch: $branch_with_new_commits"
|
||||||
LOCAL=$(git rev-parse HEAD)
|
if git show-ref --quiet --heads "$branch_with_new_commits"; then
|
||||||
REMOTE=$(git rev-parse origin/$BRANCH)
|
echo "Branch $branch_with_new_commits already exists. Checking it out and pulling latest changes."
|
||||||
|
git checkout "$branch_with_new_commits"
|
||||||
|
git pull origin "$branch_with_new_commits"
|
||||||
|
else
|
||||||
|
echo "Creating and checking out new branch: $branch_with_new_commits"
|
||||||
|
git checkout -b "$branch_with_new_commits" "origin/$branch_with_new_commits"
|
||||||
|
fi
|
||||||
|
|
||||||
# check if the local branch differs from the remote branch
|
echo "building ..."
|
||||||
if [ "$LOCAL" != "$REMOTE" ]; then
|
./gradlew gw clean test check -x pitest
|
||||||
echo "local $LOCAL differs from remote $REMOTE => pulling changes from origin"
|
fi
|
||||||
git pull origin $BRANCH
|
|
||||||
|
|
||||||
# run the command
|
# wait 10s with a little animation
|
||||||
echo "Running ./gradlew test"
|
echo -e -n "\r\033[K waiting for changes (/) ..."
|
||||||
source .aliases # only variables, aliases are not expanded in scripts
|
sleep 2
|
||||||
./gradlew test
|
echo -e -n "\r\033[K waiting for changes (-) ..."
|
||||||
fi
|
sleep 2
|
||||||
|
echo -e -n "\r\033[K waiting for changes (\) ..."
|
||||||
# wait 10s with a little animation
|
sleep 2
|
||||||
echo -e -n " waiting for changes (/) ..."
|
echo -e -n "\r\033[K waiting for changes (|) ..."
|
||||||
sleep 2
|
sleep 2
|
||||||
echo -e -n "\r\033[K waiting for changes (-) ..."
|
echo -e -n "\r\033[K waiting for changes ( ) ... "
|
||||||
sleep 2
|
sleep 2
|
||||||
echo -e -n "\r\033[K waiting for changes (\) ..."
|
echo -e -n "\r\033[K checking for changes"
|
||||||
sleep 2
|
|
||||||
echo -e -n "\r\033[K waiting for changes (|) ..."
|
|
||||||
sleep 2
|
|
||||||
echo -e -n "\r\033[K waiting for changes ( ) ... "
|
|
||||||
sleep 2
|
|
||||||
echo -e -n "\r\033[K"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
|
15
build.gradle
15
build.gradle
@ -277,7 +277,7 @@ jacocoTestCoverageVerification {
|
|||||||
violationRules {
|
violationRules {
|
||||||
rule {
|
rule {
|
||||||
limit {
|
limit {
|
||||||
minimum = 0.92
|
minimum = 0.80 // TODO.test: improve instruction coverage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,15 +289,20 @@ jacocoTestCoverageVerification {
|
|||||||
element = 'CLASS'
|
element = 'CLASS'
|
||||||
excludes = [
|
excludes = [
|
||||||
'net.hostsharing.hsadminng.**.generated.**',
|
'net.hostsharing.hsadminng.**.generated.**',
|
||||||
|
'net.hostsharing.hsadminng.rbac.test.dom.TestDomainEntity',
|
||||||
'net.hostsharing.hsadminng.HsadminNgApplication',
|
'net.hostsharing.hsadminng.HsadminNgApplication',
|
||||||
'net.hostsharing.hsadminng.ping.PingController',
|
'net.hostsharing.hsadminng.ping.PingController',
|
||||||
|
'net.hostsharing.hsadminng.rbac.generator.*',
|
||||||
|
'net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService',
|
||||||
|
'net.hostsharing.hsadminng.rbac.grant.RbacGrantsDiagramService.Node',
|
||||||
|
'net.hostsharing.hsadminng.**.*Repository',
|
||||||
'net.hostsharing.hsadminng.mapper.Mapper'
|
'net.hostsharing.hsadminng.mapper.Mapper'
|
||||||
]
|
]
|
||||||
|
|
||||||
limit {
|
limit {
|
||||||
counter = 'LINE'
|
counter = 'LINE'
|
||||||
value = 'COVEREDRATIO'
|
value = 'COVEREDRATIO'
|
||||||
minimum = 0.98
|
minimum = 0.75 // TODO.test: improve line coverage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rule {
|
rule {
|
||||||
@ -311,7 +316,7 @@ jacocoTestCoverageVerification {
|
|||||||
limit {
|
limit {
|
||||||
counter = 'BRANCH'
|
counter = 'BRANCH'
|
||||||
value = 'COVEREDRATIO'
|
value = 'COVEREDRATIO'
|
||||||
minimum = 1.00
|
minimum = 0.00 // TODO.test: improve branch coverage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,14 +349,14 @@ pitest {
|
|||||||
targetClasses = ['net.hostsharing.hsadminng.**']
|
targetClasses = ['net.hostsharing.hsadminng.**']
|
||||||
excludedClasses = [
|
excludedClasses = [
|
||||||
'net.hostsharing.hsadminng.config.**',
|
'net.hostsharing.hsadminng.config.**',
|
||||||
'net.hostsharing.hsadminng.**.*Controller',
|
// 'net.hostsharing.hsadminng.**.*Controller',
|
||||||
'net.hostsharing.hsadminng.**.generated.**'
|
'net.hostsharing.hsadminng.**.generated.**'
|
||||||
]
|
]
|
||||||
|
|
||||||
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
||||||
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
|
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
|
||||||
|
|
||||||
pitestVersion = '1.15.3'
|
pitestVersion = '1.17.0'
|
||||||
junit5PluginVersion = '1.1.0'
|
junit5PluginVersion = '1.1.0'
|
||||||
|
|
||||||
threads = 4
|
threads = 4
|
||||||
|
@ -3,30 +3,14 @@ package net.hostsharing.hsadminng.hs.booking.project;
|
|||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
|
||||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|
||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@MappedSuperclass
|
@MappedSuperclass
|
||||||
@ -66,50 +50,4 @@ public abstract class HsBookingProject implements Stringifyable, BaseEntity<HsBo
|
|||||||
return ofNullable(debitor).map(HsBookingDebitorEntity::toShortString).orElse("D-???????") +
|
return ofNullable(debitor).map(HsBookingDebitorEntity::toShortString).orElse("D-???????") +
|
||||||
":" + caption;
|
":" + caption;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
|
||||||
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
|
||||||
.withIdentityView(SQL.query("""
|
|
||||||
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
|
||||||
FROM hs_booking.project bookingProject
|
|
||||||
JOIN hs_office.debitor_iv debitorIV ON debitorIV.uuid = bookingProject.debitorUuid
|
|
||||||
"""))
|
|
||||||
.withRestrictedViewOrderBy(SQL.expression("caption"))
|
|
||||||
.withUpdatableColumns("version", "caption")
|
|
||||||
|
|
||||||
.importEntityAlias("debitor", HsOfficeDebitorEntity.class, usingDefaultCase(),
|
|
||||||
dependsOnColumn("debitorUuid"),
|
|
||||||
directlyFetchedByDependsOnColumn(),
|
|
||||||
NOT_NULL)
|
|
||||||
|
|
||||||
.importEntityAlias("debitorRel", HsOfficeRelationRbacEntity.class, usingCase(DEBITOR),
|
|
||||||
dependsOnColumn("debitorUuid"),
|
|
||||||
fetchedBySql("""
|
|
||||||
SELECT ${columns}
|
|
||||||
FROM hs_office.relation debitorRel
|
|
||||||
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
|
||||||
WHERE debitor.uuid = ${REF}.debitorUuid
|
|
||||||
"""),
|
|
||||||
NOT_NULL)
|
|
||||||
.toRole("debitorRel", ADMIN).grantPermission(INSERT)
|
|
||||||
.toRole(GLOBAL, ADMIN).grantPermission(DELETE)
|
|
||||||
|
|
||||||
.createRole(OWNER, (with) -> {
|
|
||||||
with.incomingSuperRole("debitorRel", AGENT).unassumed();
|
|
||||||
})
|
|
||||||
.createSubRole(ADMIN, (with) -> {
|
|
||||||
with.permission(UPDATE);
|
|
||||||
})
|
|
||||||
.createSubRole(AGENT)
|
|
||||||
.createSubRole(TENANT, (with) -> {
|
|
||||||
with.outgoingSubRole("debitorRel", TENANT);
|
|
||||||
with.permission(SELECT);
|
|
||||||
})
|
|
||||||
|
|
||||||
.limitDiagramTo("project", "debitorRel", "rbac.global");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
rbac().generateWithBaseFileName("6-hs-booking/620-booking-project/6203-hs-booking-project-rbac");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,14 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
|||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
|
import net.hostsharing.hsadminng.mapper.ToStringConverter;
|
||||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
|
|
||||||
import jakarta.validation.ValidationException;
|
import jakarta.validation.ValidationException;
|
||||||
import java.net.IDN;
|
import java.net.IDN;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
|
||||||
@ -109,8 +111,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
|||||||
final var subAssetResourceOptional = findSubHostingAssetResource(resourceType);
|
final var subAssetResourceOptional = findSubHostingAssetResource(resourceType);
|
||||||
|
|
||||||
subAssetResourceOptional.ifPresentOrElse(
|
subAssetResourceOptional.ifPresentOrElse(
|
||||||
subAssetResource -> verifyNotOverspecified(subAssetResource),
|
this::verifyNotOverspecified,
|
||||||
() -> { throw new ValidationException("sub-asset of type " + resourceType.name() + " required in legacy mode, but missing"); }
|
() -> { throw new ValidationException("sub-asset of type " + resourceType.name() + " required in legacy mode, but missing"); }
|
||||||
);
|
);
|
||||||
|
|
||||||
return builderTransformer.apply(
|
return builderTransformer.apply(
|
||||||
@ -150,4 +152,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
|||||||
super.persist(newHostingAsset);
|
super.persist(newHostingAsset);
|
||||||
newHostingAsset.getSubHostingAssets().forEach(super::persist);
|
newHostingAsset.getSubHostingAssets().forEach(super::persist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <T> T ref(final Class<T> entityClass, final UUID uuid) {
|
||||||
|
return uuid != null ? emw.getReference(entityClass, uuid) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
||||||
|
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||||
@ -8,7 +9,6 @@ import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntityS
|
|||||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
abstract class HostingAssetFactory {
|
abstract class HostingAssetFactory {
|
||||||
@ -20,13 +20,13 @@ abstract class HostingAssetFactory {
|
|||||||
|
|
||||||
protected abstract HsHostingAsset create();
|
protected abstract HsHostingAsset create();
|
||||||
|
|
||||||
public String performSaveProcess() {
|
public String createAndPersist() {
|
||||||
try {
|
try {
|
||||||
final var newHostingAsset = create();
|
final HsHostingAsset newHostingAsset = create();
|
||||||
persist(newHostingAsset);
|
persist(newHostingAsset);
|
||||||
return null;
|
return null;
|
||||||
} catch (final Exception e) {
|
} catch (final ValidationException exc) {
|
||||||
return e.getMessage();
|
return exc.getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,8 +38,4 @@ abstract class HostingAssetFactory {
|
|||||||
.save()
|
.save()
|
||||||
.validateContext();
|
.validateContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> T ref(final Class<T> entityClass, final UUID uuid) {
|
|
||||||
return uuid != null ? emw.getReference(entityClass, uuid) : null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||||
@ -13,7 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.context.ApplicationListener;
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void onApplicationEvent(final BookingItemCreatedAppEvent bookingItemCreatedAppEvent) {
|
public void onApplicationEvent(@NotNull BookingItemCreatedAppEvent bookingItemCreatedAppEvent) {
|
||||||
if (containsAssetJson(bookingItemCreatedAppEvent)) {
|
if (containsAssetJson(bookingItemCreatedAppEvent)) {
|
||||||
createRelatedHostingAsset(bookingItemCreatedAppEvent);
|
createRelatedHostingAsset(bookingItemCreatedAppEvent);
|
||||||
}
|
}
|
||||||
@ -48,7 +49,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||||
};
|
};
|
||||||
if (factory != null) {
|
if (factory != null) {
|
||||||
final var statusMessage = factory.performSaveProcess();
|
final var statusMessage = factory.createAndPersist();
|
||||||
// TODO.impl: once we implement retry, we need to amend this code (persist/merge/delete)
|
// TODO.impl: once we implement retry, we need to amend this code (persist/merge/delete)
|
||||||
if (statusMessage != null) {
|
if (statusMessage != null) {
|
||||||
event.getEntity().setStatusMessage(statusMessage);
|
event.getEntity().setStatusMessage(statusMessage);
|
||||||
@ -68,12 +69,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
|||||||
@Override
|
@Override
|
||||||
protected HsHostingAsset create() {
|
protected HsHostingAsset create() {
|
||||||
// TODO.impl: we should validate the asset JSON, but some violations are un-avoidable at that stage
|
// TODO.impl: we should validate the asset JSON, but some violations are un-avoidable at that stage
|
||||||
return null;
|
throw new ValidationException("waiting for manual setup of hosting asset for booking item of type " + fromBookingItem.getType());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String performSaveProcess() {
|
|
||||||
return "waiting for manual setup of hosting asset for booking item of type " + fromBookingItem.getType();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
private HsOfficePersonRepository holderRepo;
|
private HsOfficePersonRepository holderRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeContactRealRepository contactrealRepo;
|
private HsOfficeContactRealRepository realContactRepo;
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
private EntityManager em;
|
private EntityManager em;
|
||||||
@ -48,11 +48,16 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
final String currentSubject,
|
final String currentSubject,
|
||||||
final String assumedRoles,
|
final String assumedRoles,
|
||||||
final UUID personUuid,
|
final UUID personUuid,
|
||||||
hsh-michaelhoennig marked this conversation as resolved
Outdated
|
|||||||
final HsOfficeRelationTypeResource relationType) {
|
final HsOfficeRelationTypeResource relationType,
|
||||||
|
final String personData,
|
||||||
|
final String contactData) {
|
||||||
context.define(currentSubject, assumedRoles);
|
context.define(currentSubject, assumedRoles);
|
||||||
|
|
||||||
final var entities = relationRbacRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid,
|
final List<HsOfficeRelationRbacEntity> entities =
|
||||||
relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name()));
|
relationRbacRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData(
|
||||||
|
personUuid,
|
||||||
|
relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name()),
|
||||||
|
personData, contactData);
|
||||||
|
|
||||||
final var resources = mapper.mapList(entities, HsOfficeRelationResource.class,
|
final var resources = mapper.mapList(entities, HsOfficeRelationResource.class,
|
||||||
RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
|
RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
@ -77,7 +82,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
entityToSave.setHolder(holderRepo.findByUuid(body.getHolderUuid()).orElseThrow(
|
entityToSave.setHolder(holderRepo.findByUuid(body.getHolderUuid()).orElseThrow(
|
||||||
() -> new NoSuchElementException("cannot find Person by holderUuid: " + body.getHolderUuid())
|
() -> new NoSuchElementException("cannot find Person by holderUuid: " + body.getHolderUuid())
|
||||||
));
|
));
|
||||||
entityToSave.setContact(contactrealRepo.findByUuid(body.getContactUuid()).orElseThrow(
|
entityToSave.setContact(realContactRepo.findByUuid(body.getContactUuid()).orElseThrow(
|
||||||
() -> new NoSuchElementException("cannot find Contact by contactUuid: " + body.getContactUuid())
|
() -> new NoSuchElementException("cannot find Contact by contactUuid: " + body.getContactUuid())
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -144,7 +149,6 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
return ResponseEntity.ok(mapped);
|
return ResponseEntity.ok(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final BiConsumer<HsOfficeRelationRbacEntity, HsOfficeRelationResource> RELATION_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
final BiConsumer<HsOfficeRelationRbacEntity, HsOfficeRelationResource> RELATION_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||||
resource.setAnchor(mapper.map(entity.getAnchor(), HsOfficePersonResource.class));
|
resource.setAnchor(mapper.map(entity.getAnchor(), HsOfficePersonResource.class));
|
||||||
resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.class));
|
resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.class));
|
||||||
|
@ -12,26 +12,62 @@ public interface HsOfficeRelationRbacRepository extends Repository<HsOfficeRelat
|
|||||||
|
|
||||||
Optional<HsOfficeRelationRbacEntity> findByUuid(UUID id);
|
Optional<HsOfficeRelationRbacEntity> findByUuid(UUID id);
|
||||||
|
|
||||||
default List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
|
||||||
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office.relation_rv AS p
|
SELECT p.* FROM hs_office.relation_rv AS p
|
||||||
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds relations by a conjunction of optional criteria, including anchorPerson, holderPerson and contact data.
|
||||||
|
* *
|
||||||
|
* @param personUuid the optional UUID of the anchorPerson or holderPerson
|
||||||
|
* @param relationType the type of the relation
|
||||||
|
* @param personData a string to match the persons tradeName, familyName or givenName (use '%' for wildcard), case ignored
|
||||||
|
* @param contactData a string to match the contacts caption, postalAddress, emailAddresses or phoneNumbers (use '%' for wildcard), case ignored
|
||||||
|
* @return a list of (accessible) relations which match all given criteria
|
||||||
|
*/
|
||||||
|
default List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidRelationTypePersonAndContactData(
|
||||||
|
UUID personUuid,
|
||||||
|
HsOfficeRelationType relationType,
|
||||||
|
String personData,
|
||||||
|
String contactData) {
|
||||||
|
return findRelationRelatedToPersonUuidRelationTypePersonAndContactDataImpl(
|
||||||
|
personUuid, toStringOrNull(relationType), toSqlLikeOperand(personData), toSqlLikeOperand(contactData));
|
||||||
|
}
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office.relation_rv AS p
|
SELECT rel FROM HsOfficeRelationRbacEntity AS rel
|
||||||
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType))
|
WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType)
|
||||||
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
AND ( :personUuid IS NULL
|
||||||
""", nativeQuery = true)
|
OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid )
|
||||||
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
AND ( :personData IS NULL
|
||||||
|
OR lower(rel.anchor.tradeName) LIKE :personData OR lower(rel.holder.tradeName) LIKE :personData
|
||||||
|
OR lower(rel.anchor.familyName) LIKE :personData OR lower(rel.holder.familyName) LIKE :personData
|
||||||
|
OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData )
|
||||||
|
AND ( :contactData IS NULL
|
||||||
|
OR lower(rel.contact.caption) LIKE :contactData
|
||||||
|
OR lower(rel.contact.postalAddress) LIKE :contactData
|
||||||
|
OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData
|
||||||
|
OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData )
|
||||||
|
""")
|
||||||
|
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidRelationTypePersonAndContactDataImpl(
|
||||||
|
final UUID personUuid,
|
||||||
|
final String relationType,
|
||||||
|
final String personData,
|
||||||
|
final String contactData);
|
||||||
|
|
||||||
HsOfficeRelationRbacEntity save(final HsOfficeRelationRbacEntity entity);
|
HsOfficeRelationRbacEntity save(final HsOfficeRelationRbacEntity entity);
|
||||||
|
|
||||||
long count();
|
long count();
|
||||||
|
|
||||||
int deleteByUuid(UUID uuid);
|
int deleteByUuid(UUID uuid);
|
||||||
|
|
||||||
|
private static String toSqlLikeOperand(final String text) {
|
||||||
|
return text == null ? null : ("%" + text.toLowerCase() + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toStringOrNull(final HsOfficeRelationType relationType) {
|
||||||
|
return relationType == null ? null : relationType.name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,10 @@ public class IntegerProperty<P extends IntegerProperty<P>> extends ValidatablePr
|
|||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer min() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer max() {
|
public Integer max() {
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.validation;
|
|||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.mapper.Array;
|
import net.hostsharing.hsadminng.mapper.Array;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -83,11 +84,15 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// predefined values, similar to fixed values in a combobox
|
/// predefined values, similar to fixed values in a combobox
|
||||||
public P provided(final String... provided) {
|
public P provided(final String firstProvidedValue, final String... moreProvidedValues) {
|
||||||
this.provided = provided;
|
this.provided = ArrayUtils.addAll(new String[]{firstProvidedValue}, moreProvidedValues);
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String[] provided() {
|
||||||
|
return this.provided;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The property value is not disclosed in error messages.
|
* The property value is not disclosed in error messages.
|
||||||
*
|
*
|
||||||
@ -109,7 +114,11 @@ public class StringProperty<P extends StringProperty<P>> extends ValidatableProp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String display(final String propValue) {
|
protected String display(final String propValue) {
|
||||||
return undisclosed ? "provided value" : ("'" + propValue + "'");
|
return undisclosed
|
||||||
|
? "provided value"
|
||||||
|
: propValue != null
|
||||||
|
? ("'" + propValue + "'")
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package net.hostsharing.hsadminng.lambda;
|
package net.hostsharing.hsadminng.lambda;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
public class Reducer {
|
public class Reducer {
|
||||||
public static <T> T toSingleElement(T last, T next) {
|
public static <T> T toSingleElement(T ignoredLast, T ignoredNext) {
|
||||||
throw new AssertionError("only a single entity expected");
|
throw new AssertionError("only a single entity expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
|
|
||||||
@ -16,8 +13,7 @@ public class ToStringConverter {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String from(Object obj) {
|
public String from(final Object obj) {
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
return "{ " +
|
return "{ " +
|
||||||
Arrays.stream(obj.getClass().getDeclaredFields())
|
Arrays.stream(obj.getClass().getDeclaredFields())
|
||||||
.filter(f -> !ignoredFields.contains(f.getName()))
|
.filter(f -> !ignoredFields.contains(f.getName()))
|
||||||
@ -34,4 +30,15 @@ public class ToStringConverter {
|
|||||||
.collect(joining(", "))
|
.collect(joining(", "))
|
||||||
+ " }";
|
+ " }";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String from(final Map<?, ?> map) {
|
||||||
|
return "{ "
|
||||||
|
+ map.keySet().stream()
|
||||||
|
.filter(key -> !ignoredFields.contains(key.toString()))
|
||||||
|
.sorted()
|
||||||
|
.map(k -> Map.entry(k, map.get(k)))
|
||||||
|
.map(e -> e.getKey() + ": " + e.getValue())
|
||||||
|
.collect(joining(", "))
|
||||||
|
+ " }";
|
||||||
|
}
|
||||||
}
|
}
|
@ -62,7 +62,7 @@ public class RbacGrantsDiagramService {
|
|||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
private EntityManager em;
|
private EntityManager em;
|
||||||
|
|
||||||
private Map<UUID, List<RawRbacGrantEntity>> descendantsByUuid = new HashMap<>();
|
private final Map<UUID, List<RawRbacGrantEntity>> descendantsByUuid = new HashMap<>();
|
||||||
|
|
||||||
public String allGrantsTocurrentSubject(final EnumSet<Include> includes) {
|
public String allGrantsTocurrentSubject(final EnumSet<Include> includes) {
|
||||||
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
final var graph = new LimitedHashSet<RawRbacGrantEntity>();
|
||||||
@ -231,8 +231,7 @@ public class RbacGrantsDiagramService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
record Node(String idName, UUID uuid) {
|
||||||
|
|
||||||
record Node(String idName, UUID uuid) {
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
get:
|
get:
|
||||||
summary: Returns a list of (optionally filtered) person relations for a given person.
|
summary: Returns a list of (optionally filtered) person relations for a given person.
|
||||||
description: Returns the list of (optionally filtered) person relations of a given person and which are visible to the current subject or any of it's assumed roles.
|
description:
|
||||||
|
Returns the list of (optionally filtered) person relations of a given person and which are visible to the current subject or any of it's assumed roles.
|
||||||
|
To match data, all given query parameters must be fulfilled ('and' / logical conjunction).
|
||||||
tags:
|
tags:
|
||||||
- hs-office-relations
|
- hs-office-relations
|
||||||
operationId: listRelations
|
operationId: listRelations
|
||||||
@ -9,7 +11,7 @@ get:
|
|||||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||||
- name: personUuid
|
- name: personUuid
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
@ -20,6 +22,18 @@ get:
|
|||||||
schema:
|
schema:
|
||||||
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationType'
|
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationType'
|
||||||
description: Prefix of name properties from holder or contact to filter the results.
|
description: Prefix of name properties from holder or contact to filter the results.
|
||||||
|
- name: personData
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: 'Data from any of these text field in the anchor or holder person: tradeName, familyName, givenName'
|
||||||
|
- name: contactData
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: 'Data from any of these text field in the contact: caption, postalAddress, emailAddresses, phoneNumbers'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HsBookingItemRbacEntityUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void definesRbac() {
|
||||||
|
final var rbacFlowchart = new RbacViewMermaidFlowchartGenerator(HsBookingItemRbacEntity.rbac()).toString();
|
||||||
|
assertThat(rbacFlowchart).isEqualTo("""
|
||||||
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
flowchart TB
|
||||||
|
|
||||||
|
subgraph bookingItem["`**bookingItem**`"]
|
||||||
|
direction TB
|
||||||
|
style bookingItem fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph bookingItem:roles[ ]
|
||||||
|
style bookingItem:roles fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
role:bookingItem:OWNER[[bookingItem:OWNER]]
|
||||||
|
role:bookingItem:ADMIN[[bookingItem:ADMIN]]
|
||||||
|
role:bookingItem:AGENT[[bookingItem:AGENT]]
|
||||||
|
role:bookingItem:TENANT[[bookingItem:TENANT]]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph bookingItem:permissions[ ]
|
||||||
|
style bookingItem:permissions fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
perm:bookingItem:INSERT{{bookingItem:INSERT}}
|
||||||
|
perm:bookingItem:DELETE{{bookingItem:DELETE}}
|
||||||
|
perm:bookingItem:UPDATE{{bookingItem:UPDATE}}
|
||||||
|
perm:bookingItem:SELECT{{bookingItem:SELECT}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph project["`**project**`"]
|
||||||
|
direction TB
|
||||||
|
style project fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph project:roles[ ]
|
||||||
|
style project:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:project:OWNER[[project:OWNER]]
|
||||||
|
role:project:ADMIN[[project:ADMIN]]
|
||||||
|
role:project:AGENT[[project:AGENT]]
|
||||||
|
role:project:TENANT[[project:TENANT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%% granting roles to roles
|
||||||
|
role:project:OWNER -.-> role:project:ADMIN
|
||||||
|
role:project:ADMIN -.-> role:project:AGENT
|
||||||
|
role:project:AGENT -.-> role:project:TENANT
|
||||||
|
role:project:AGENT ==> role:bookingItem:OWNER
|
||||||
|
role:bookingItem:OWNER ==> role:bookingItem:ADMIN
|
||||||
|
role:bookingItem:ADMIN ==> role:bookingItem:AGENT
|
||||||
|
role:bookingItem:AGENT ==> role:bookingItem:TENANT
|
||||||
|
role:bookingItem:TENANT ==> role:project:TENANT
|
||||||
|
|
||||||
|
%% granting permissions to roles
|
||||||
|
role:rbac.global:ADMIN ==> perm:bookingItem:INSERT
|
||||||
|
role:rbac.global:ADMIN ==> perm:bookingItem:DELETE
|
||||||
|
role:project:ADMIN ==> perm:bookingItem:INSERT
|
||||||
|
role:bookingItem:ADMIN ==> perm:bookingItem:UPDATE
|
||||||
|
role:bookingItem:TENANT ==> perm:bookingItem:SELECT
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.project;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HsBookingProjectRbacEntityUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toStringForEmptyInstance() {
|
||||||
|
final var givenEntity = HsBookingProjectRbacEntity.builder().build();
|
||||||
|
assertThat(givenEntity.toString()).isEqualTo("HsBookingProject()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void toStringForFullyInitializedInstance() {
|
||||||
|
final var givenDebitor = HsBookingDebitorEntity.builder()
|
||||||
|
.debitorNumber(123456)
|
||||||
|
.build();
|
||||||
|
final var givenUuid = UUID.randomUUID();
|
||||||
|
final var givenEntity = HsBookingProjectRbacEntity.builder()
|
||||||
|
.uuid(givenUuid)
|
||||||
|
.debitor(givenDebitor)
|
||||||
|
.caption("some project")
|
||||||
|
.build();
|
||||||
|
assertThat(givenEntity.toString()).isEqualTo("HsBookingProject(D-123456, some project)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void definesRbac() {
|
||||||
|
final var rbacFlowchart = new RbacViewMermaidFlowchartGenerator(HsBookingProjectRbacEntity.rbac()).toString();
|
||||||
|
assertThat(rbacFlowchart).isEqualTo("""
|
||||||
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
flowchart TB
|
||||||
|
|
||||||
|
subgraph debitorRel["`**debitorRel**`"]
|
||||||
|
direction TB
|
||||||
|
style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph debitorRel:roles[ ]
|
||||||
|
style debitorRel:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:debitorRel:OWNER[[debitorRel:OWNER]]
|
||||||
|
role:debitorRel:ADMIN[[debitorRel:ADMIN]]
|
||||||
|
role:debitorRel:AGENT[[debitorRel:AGENT]]
|
||||||
|
role:debitorRel:TENANT[[debitorRel:TENANT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph project["`**project**`"]
|
||||||
|
direction TB
|
||||||
|
style project fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph project:roles[ ]
|
||||||
|
style project:roles fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
role:project:OWNER[[project:OWNER]]
|
||||||
|
role:project:ADMIN[[project:ADMIN]]
|
||||||
|
role:project:AGENT[[project:AGENT]]
|
||||||
|
role:project:TENANT[[project:TENANT]]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph project:permissions[ ]
|
||||||
|
style project:permissions fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
perm:project:INSERT{{project:INSERT}}
|
||||||
|
perm:project:DELETE{{project:DELETE}}
|
||||||
|
perm:project:UPDATE{{project:UPDATE}}
|
||||||
|
perm:project:SELECT{{project:SELECT}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%% granting roles to roles
|
||||||
|
role:rbac.global:ADMIN -.-> role:debitorRel:OWNER
|
||||||
|
role:debitorRel:OWNER -.-> role:debitorRel:ADMIN
|
||||||
|
role:debitorRel:ADMIN -.-> role:debitorRel:AGENT
|
||||||
|
role:debitorRel:AGENT -.-> role:debitorRel:TENANT
|
||||||
|
role:debitorRel:AGENT ==>|XX| role:project:OWNER
|
||||||
|
role:project:OWNER ==> role:project:ADMIN
|
||||||
|
role:project:ADMIN ==> role:project:AGENT
|
||||||
|
role:project:AGENT ==> role:project:TENANT
|
||||||
|
role:project:TENANT ==> role:debitorRel:TENANT
|
||||||
|
|
||||||
|
%% granting permissions to roles
|
||||||
|
role:debitorRel:ADMIN ==> perm:project:INSERT
|
||||||
|
role:rbac.global:ADMIN ==> perm:project:DELETE
|
||||||
|
role:project:ADMIN ==> perm:project:UPDATE
|
||||||
|
role:project:TENANT ==> perm:project:SELECT
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HsHostingAssetRbacEntityUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void definesRbac() {
|
||||||
|
final var rbacFlowchart = new RbacViewMermaidFlowchartGenerator(HsHostingAssetRbacEntity.rbac()).toString();
|
||||||
|
assertThat(rbacFlowchart).isEqualTo("""
|
||||||
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
flowchart TB
|
||||||
|
|
||||||
|
subgraph alarmContact["`**alarmContact**`"]
|
||||||
|
direction TB
|
||||||
|
style alarmContact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph alarmContact:roles[ ]
|
||||||
|
style alarmContact:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:alarmContact:OWNER[[alarmContact:OWNER]]
|
||||||
|
role:alarmContact:ADMIN[[alarmContact:ADMIN]]
|
||||||
|
role:alarmContact:REFERRER[[alarmContact:REFERRER]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph asset["`**asset**`"]
|
||||||
|
direction TB
|
||||||
|
style asset fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph asset:roles[ ]
|
||||||
|
style asset:roles fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
role:asset:OWNER[[asset:OWNER]]
|
||||||
|
role:asset:ADMIN[[asset:ADMIN]]
|
||||||
|
role:asset:AGENT[[asset:AGENT]]
|
||||||
|
role:asset:TENANT[[asset:TENANT]]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph asset:permissions[ ]
|
||||||
|
style asset:permissions fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
perm:asset:INSERT{{asset:INSERT}}
|
||||||
|
perm:asset:DELETE{{asset:DELETE}}
|
||||||
|
perm:asset:UPDATE{{asset:UPDATE}}
|
||||||
|
perm:asset:SELECT{{asset:SELECT}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph assignedToAsset["`**assignedToAsset**`"]
|
||||||
|
direction TB
|
||||||
|
style assignedToAsset fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph assignedToAsset:roles[ ]
|
||||||
|
style assignedToAsset:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:assignedToAsset:AGENT[[assignedToAsset:AGENT]]
|
||||||
|
role:assignedToAsset:TENANT[[assignedToAsset:TENANT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph bookingItem["`**bookingItem**`"]
|
||||||
|
direction TB
|
||||||
|
style bookingItem fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph bookingItem:roles[ ]
|
||||||
|
style bookingItem:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:bookingItem:OWNER[[bookingItem:OWNER]]
|
||||||
|
role:bookingItem:ADMIN[[bookingItem:ADMIN]]
|
||||||
|
role:bookingItem:AGENT[[bookingItem:AGENT]]
|
||||||
|
role:bookingItem:TENANT[[bookingItem:TENANT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph parentAsset["`**parentAsset**`"]
|
||||||
|
direction TB
|
||||||
|
style parentAsset fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph parentAsset:roles[ ]
|
||||||
|
style parentAsset:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:parentAsset:ADMIN[[parentAsset:ADMIN]]
|
||||||
|
role:parentAsset:AGENT[[parentAsset:AGENT]]
|
||||||
|
role:parentAsset:TENANT[[parentAsset:TENANT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%% granting roles to users
|
||||||
|
user:creator ==> role:asset:OWNER
|
||||||
|
|
||||||
|
%% granting roles to roles
|
||||||
|
role:bookingItem:OWNER -.-> role:bookingItem:ADMIN
|
||||||
|
role:bookingItem:ADMIN -.-> role:bookingItem:AGENT
|
||||||
|
role:bookingItem:AGENT -.-> role:bookingItem:TENANT
|
||||||
|
role:rbac.global:ADMIN -.-> role:alarmContact:OWNER
|
||||||
|
role:alarmContact:OWNER -.-> role:alarmContact:ADMIN
|
||||||
|
role:alarmContact:ADMIN -.-> role:alarmContact:REFERRER
|
||||||
|
role:rbac.global:ADMIN ==>|XX| role:asset:OWNER
|
||||||
|
role:bookingItem:ADMIN ==> role:asset:OWNER
|
||||||
|
role:parentAsset:ADMIN ==> role:asset:OWNER
|
||||||
|
role:asset:OWNER ==> role:asset:ADMIN
|
||||||
|
role:bookingItem:AGENT ==> role:asset:ADMIN
|
||||||
|
role:parentAsset:AGENT ==> role:asset:ADMIN
|
||||||
|
role:asset:ADMIN ==> role:asset:AGENT
|
||||||
|
role:assignedToAsset:AGENT ==> role:asset:AGENT
|
||||||
|
role:asset:AGENT ==> role:assignedToAsset:TENANT
|
||||||
|
role:asset:AGENT ==> role:alarmContact:REFERRER
|
||||||
|
role:asset:AGENT ==> role:asset:TENANT
|
||||||
|
role:asset:TENANT ==> role:bookingItem:TENANT
|
||||||
|
role:asset:TENANT ==> role:parentAsset:TENANT
|
||||||
|
role:alarmContact:ADMIN ==> role:asset:TENANT
|
||||||
|
|
||||||
|
%% granting permissions to roles
|
||||||
|
role:rbac.global:ADMIN ==> perm:asset:INSERT
|
||||||
|
role:parentAsset:ADMIN ==> perm:asset:INSERT
|
||||||
|
role:rbac.global:GUEST ==> perm:asset:INSERT
|
||||||
|
role:asset:OWNER ==> perm:asset:DELETE
|
||||||
|
role:asset:ADMIN ==> perm:asset:UPDATE
|
||||||
|
role:asset:TENANT ==> perm:asset:SELECT
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
@ -6,14 +6,13 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
|||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
import net.hostsharing.hsadminng.rbac.test.cust.TestCustomerEntity;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class HsOfficeDebitorEntityUnitTest {
|
class HsOfficeDebitorEntityUnitTest {
|
||||||
|
|
||||||
private HsOfficeRelationRealEntity givenDebitorRel = HsOfficeRelationRealEntity.builder()
|
private final HsOfficeRelationRealEntity givenDebitorRel = HsOfficeRelationRealEntity.builder()
|
||||||
.anchor(HsOfficePersonEntity.builder()
|
.anchor(HsOfficePersonEntity.builder()
|
||||||
.personType(HsOfficePersonType.LEGAL_PERSON)
|
.personType(HsOfficePersonType.LEGAL_PERSON)
|
||||||
.tradeName("some partner trade name")
|
.tradeName("some partner trade name")
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.membership;
|
package net.hostsharing.hsadminng.hs.office.membership;
|
||||||
|
|
||||||
import io.hypersistence.utils.hibernate.type.range.Range;
|
import io.hypersistence.utils.hibernate.type.range.Range;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
|
||||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.person;
|
package net.hostsharing.hsadminng.hs.office.person;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import net.hostsharing.hsadminng.context.Context;
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationTypeResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationTypeResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
||||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||||
import org.json.JSONException;
|
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -55,7 +54,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
|
|||||||
class ListRelations {
|
class ListRelations {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void globalAdmin_withoutAssumedRoles_canViewAllRelationsOfGivenPersonAndType() throws JSONException {
|
void globalAdmin_withoutAssumedRoles_canViewAllRelationsOfGivenPersonAndType() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
@ -113,7 +112,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void personAdmin_canViewAllRelationsOfGivenRelatedPersonAndAnyType() throws JSONException {
|
void personAdmin_canViewAllRelationsOfGivenRelatedPersonAndAnyType() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
context.define("contact-admin@firstcontact.example.com");
|
context.define("contact-admin@firstcontact.example.com");
|
||||||
@ -125,7 +124,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
|
|||||||
.port(port)
|
.port(port)
|
||||||
.when()
|
.when()
|
||||||
.get("http://localhost/api/hs/office/relations?personUuid=%s"
|
.get("http://localhost/api/hs/office/relations?personUuid=%s"
|
||||||
.formatted(givenPerson.getUuid(), HsOfficeRelationTypeResource.PARTNER))
|
.formatted(givenPerson.getUuid()))
|
||||||
.then().log().all().assertThat()
|
.then().log().all().assertThat()
|
||||||
.statusCode(200)
|
.statusCode(200)
|
||||||
.contentType("application/json")
|
.contentType("application/json")
|
||||||
@ -169,6 +168,50 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
|
|||||||
"""));
|
"""));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void globalAdmin_canViewAllRelationsWithGivenContactData() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
context.define("superuser-alex@hostsharing.net");
|
||||||
|
|
||||||
|
RestAssured // @formatter:off
|
||||||
|
.given()
|
||||||
|
.header("current-subject", "superuser-alex@hostsharing.net")
|
||||||
|
.port(port)
|
||||||
|
.when()
|
||||||
|
.get("http://localhost/api/hs/office/relations?personData=firby&contactData=Contact-Admin@FirstContact.Example.COM")
|
||||||
|
.then().log().all().assertThat()
|
||||||
|
.statusCode(200)
|
||||||
|
.contentType("application/json")
|
||||||
|
.body("", lenientlyEquals("""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"anchor": {
|
||||||
|
"personType": "LEGAL_PERSON",
|
||||||
|
"tradeName": "First GmbH"
|
||||||
|
},
|
||||||
|
"holder": {
|
||||||
|
"personType": "NATURAL_PERSON",
|
||||||
|
"givenName": "Susan",
|
||||||
|
"familyName": "Firby"
|
||||||
|
},
|
||||||
|
"type": "REPRESENTATIVE",
|
||||||
|
"contact": {
|
||||||
|
"caption": "first contact",
|
||||||
|
"postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt",
|
||||||
|
"emailAddresses": {
|
||||||
|
"main": "contact-admin@firstcontact.example.com"
|
||||||
|
},
|
||||||
|
"phoneNumbers": {
|
||||||
|
"phone_office": "+49 123 1234567"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""));
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
@ -193,7 +193,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
|||||||
.findFirst().orElseThrow();
|
.findFirst().orElseThrow();
|
||||||
|
|
||||||
// when:
|
// when:
|
||||||
final var result = relationRbacRepo.findRelationRelatedToPersonUuidAndRelationType(person.getUuid(), null);
|
final var result = relationRbacRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData(person.getUuid(), null, null, null);
|
||||||
|
|
||||||
// then:
|
// then:
|
||||||
exactlyTheseRelationsAreReturned(
|
exactlyTheseRelationsAreReturned(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.sepamandate;
|
package net.hostsharing.hsadminng.hs.office.sepamandate;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
import net.hostsharing.hsadminng.rbac.generator.RbacViewMermaidFlowchartGenerator;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
class IntegerPropertyUnitTest {
|
||||||
|
|
||||||
|
final IntegerProperty<?> partialIntegerProperty = integerProperty("test")
|
||||||
|
.min(1)
|
||||||
|
.max(9);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsConfiguredSettings() {
|
||||||
|
final var IntegerProperty = partialIntegerProperty;
|
||||||
|
assertThat(IntegerProperty.propertyName()).isEqualTo("test");
|
||||||
|
assertThat(IntegerProperty.unit()).isNull();
|
||||||
|
assertThat(IntegerProperty.min()).isEqualTo(1);
|
||||||
|
assertThat(IntegerProperty.max()).isEqualTo(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsIncompleteConfiguration() {
|
||||||
|
final var IntegerProperty = partialIntegerProperty;
|
||||||
|
final var exception = catchThrowable(() ->
|
||||||
|
IntegerProperty.verifyConsistency(Map.entry(HsBookingItemType.CLOUD_SERVER, "val"))
|
||||||
|
);
|
||||||
|
assertThat(exception).isNotNull().isInstanceOf(IllegalStateException.class).hasMessageContaining(
|
||||||
|
"CLOUD_SERVER[test] not fully initialized, please call either .readOnly(), .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializerCompletesProperty() {
|
||||||
|
// given
|
||||||
|
final var IntegerProperty = partialIntegerProperty
|
||||||
|
.initializedBy((entityManager, propertiesProvider) -> 7);
|
||||||
|
|
||||||
|
// then
|
||||||
|
isCompleted(IntegerProperty);
|
||||||
|
assertThat(IntegerProperty.isComputed(ValidatableProperty.ComputeMode.IN_INIT)).isTrue();
|
||||||
|
assertThat(IntegerProperty.compute(null, null)).isEqualTo(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void displaysNullValueAsNull() {
|
||||||
|
final var IntegerProperty = partialIntegerProperty.optional();
|
||||||
|
assertThat(IntegerProperty.display(null)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void displayQuotesValue() {
|
||||||
|
final var IntegerProperty = partialIntegerProperty.optional();
|
||||||
|
assertThat(IntegerProperty.display(3)).isEqualTo("3");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void isCompleted(IntegerProperty<? extends IntegerProperty<?>> IntegerProperty) {
|
||||||
|
IntegerProperty.verifyConsistency(Map.entry(HsBookingItemType.CLOUD_SERVER, "val"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
|
import net.hostsharing.hsadminng.mapper.Array;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
class StringPropertyUnitTest {
|
||||||
|
|
||||||
|
final StringProperty<?> partialStringProperty = stringProperty("test")
|
||||||
|
.minLength(1)
|
||||||
|
.maxLength(9)
|
||||||
|
.provided("one", "two", "three");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsConfiguredSettings() {
|
||||||
|
final var stringProperty = partialStringProperty;
|
||||||
|
assertThat(stringProperty.propertyName()).isEqualTo("test");
|
||||||
|
assertThat(stringProperty.unit()).isNull();
|
||||||
|
assertThat(stringProperty.minLength()).isEqualTo(1);
|
||||||
|
assertThat(stringProperty.maxLength()).isEqualTo(9);
|
||||||
|
assertThat(stringProperty.provided()).isEqualTo(Array.of("one", "two", "three"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsIncompleteConfiguration() {
|
||||||
|
final var stringProperty = partialStringProperty;
|
||||||
|
final var exception = catchThrowable(() ->
|
||||||
|
stringProperty.verifyConsistency(Map.entry(HsBookingItemType.CLOUD_SERVER, "val"))
|
||||||
|
);
|
||||||
|
assertThat(exception).isNotNull().isInstanceOf(IllegalStateException.class).hasMessageContaining(
|
||||||
|
"CLOUD_SERVER[test] not fully initialized, please call either .readOnly(), .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initializerCompletesProperty() {
|
||||||
|
// given
|
||||||
|
final var stringProperty = partialStringProperty
|
||||||
|
.initializedBy((entityManager, propertiesProvider) -> "init-value");
|
||||||
|
|
||||||
|
// then
|
||||||
|
isCompleted(stringProperty);
|
||||||
|
assertThat(stringProperty.isComputed(ValidatableProperty.ComputeMode.IN_INIT)).isTrue();
|
||||||
|
assertThat(stringProperty.compute(null, null)).isEqualTo("init-value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void displaysNullValueAsNull() {
|
||||||
|
final var stringProperty = partialStringProperty.optional();
|
||||||
|
assertThat(stringProperty.display(null)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void displayQuotesValue() {
|
||||||
|
final var stringProperty = partialStringProperty.optional();
|
||||||
|
assertThat(stringProperty.display("some value")).isEqualTo("'some value'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void isCompleted(StringProperty<? extends StringProperty<?>> stringProperty) {
|
||||||
|
stringProperty.verifyConsistency(Map.entry(HsBookingItemType.CLOUD_SERVER, "val"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.hostsharing.hsadminng.lambda;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
|
||||||
|
|
||||||
|
class ReducerUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void throwsExceptionForMoreThanASingleElement() {
|
||||||
|
final var givenStream = Stream.of(1, 2);
|
||||||
|
|
||||||
|
final var exception = catchThrowable(() -> {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
givenStream.reduce(Reducer::toSingleElement);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exception).isInstanceOf(AssertionError.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void passesASingleElement() {
|
||||||
|
final var givenStream = Stream.of(7);
|
||||||
|
final var singleElement = givenStream.reduce(Reducer::toSingleElement);
|
||||||
|
assertThat(singleElement).contains(7);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
class KeyValueMapUnitTest {
|
||||||
|
|
||||||
|
final ToStringConverter toStringConverter = new ToStringConverter();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromMap() {
|
||||||
|
final var result = KeyValueMap.from(Map.ofEntries(
|
||||||
|
Map.entry("one", 1),
|
||||||
|
Map.entry("two", 2)
|
||||||
|
));
|
||||||
|
|
||||||
|
assertThat(toStringConverter.from(result)).isEqualTo("{ one: 1, two: 2 }");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromNonMap() {
|
||||||
|
final var exception = catchThrowable( () ->
|
||||||
|
KeyValueMap.from("not a map")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(exception).isInstanceOf(ClassCastException.class);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ToStringConverterUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convertObjectToString() {
|
||||||
|
final var object = new SomeObject("a", 1, true);
|
||||||
|
final var result = new ToStringConverter().ignoring("three").from(object);
|
||||||
|
assertThat(result).isEqualTo("{ one: a, two: 1 }");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convertMapToString() {
|
||||||
|
final var map = Map.ofEntries(
|
||||||
|
Map.entry("one", "a"),
|
||||||
|
Map.entry("two", 1),
|
||||||
|
Map.entry("three", true)
|
||||||
|
);
|
||||||
|
final var result = new ToStringConverter().ignoring("three").from(map);
|
||||||
|
assertThat(result).isEqualTo("{ one: a, two: 1 }");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record SomeObject(String one, int two, boolean three) {}
|
Loading…
Reference in New Issue
Block a user
final