From 60028697d69e968c1016035ba345307c081b7824 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 12 Aug 2025 12:50:48 +0200 Subject: [PATCH] align React-GUI and Java API -backend (#188) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/188 Reviewed-by: Timotheus Pokorra --- .../hs/accounts/HsCredentialsContext.java | 8 ++ .../hs/accounts/HsCredentialsController.java | 22 ++-- .../hs/accounts/HsCredentialsRepository.java | 32 ++++- .../hostsharing/hsadminng/repr/Stringify.java | 114 +++++++++++------- .../hostsharing/hsadminng/repr/Symbol.java | 25 ++++ .../accounts/context-schemas.yaml | 4 + .../api-definition/accounts/credentials.yaml | 6 +- .../950-accounts/9510-hs-accounts.sql | 2 + .../9519-hs-accounts-test-data.sql | 42 ++++--- ...sCredentialsContextRbacEntityUnitTest.java | 3 +- ...sContextRbacRepositoryIntegrationTest.java | 4 +- ...sCredentialsContextRealEntityUnitTest.java | 3 +- ...sContextRealRepositoryIntegrationTest.java | 12 +- ...sCredentialsRepositoryIntegrationTest.java | 2 +- .../person/HsOfficePersonEntityUnitTest.java | 2 +- .../relation/HsOfficeRelationUnitTest.java | 4 +- .../rbac/test/StringifyUnitTest.java | 18 +-- 17 files changed, 212 insertions(+), 91 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/repr/Symbol.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java index 99decc09..9716fa6a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContext.java @@ -19,6 +19,7 @@ import net.hostsharing.hsadminng.repr.Stringifyable; import java.util.UUID; import static net.hostsharing.hsadminng.repr.Stringify.stringify; +import static net.hostsharing.hsadminng.repr.Symbol.symbol; @Getter @Setter @@ -31,6 +32,10 @@ public abstract class HsCredentialsContext implements Stringifyable, BaseEntity< private static Stringify stringify = stringify(HsCredentialsContext.class, "loginContext") .withProp(HsCredentialsContext::getType) .withProp(HsCredentialsContext::getQualifier) + .withProp(HsCredentialsContext::isOnlyForNaturalPersons, + value -> value ? symbol("NP-ONLY") : null) + .withProp(HsCredentialsContext::isPublicAccess, + value -> value ? symbol("PUBLIC") : symbol("INTERNAL")) .quotedValues(false) .withSeparator(":"); @@ -53,6 +58,9 @@ public abstract class HsCredentialsContext implements Stringifyable, BaseEntity< @Column(name = "only_for_natural_persons") private boolean onlyForNaturalPersons; + @Column(name = "public_access") + private boolean publicAccess; + @Override public String toShortString() { return toString(); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java index 96882f6d..bcfcd8bf 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsController.java @@ -81,19 +81,15 @@ public class HsCredentialsController implements CredentialsApi { @Override @Transactional(readOnly = true) @Timed("app.credentials.credentials.getListOfCredentialsByPersonUuid") - public ResponseEntity> getListOfCredentialsByPersonUuid( + public ResponseEntity> getListOfCredentials( final String assumedRoles, final UUID personUuid ) { context.assumeRoles(assumedRoles); - final var person = rbacPersonRepo.findByUuid(personUuid).orElseThrow( - () -> new EntityNotFoundException( - messageTranslator.translate("{0} \"{1}\" not found or not accessible", "personUuid", personUuid) - ) - - ); - final var credentials = credentialsRepo.findByPerson(person); + final var credentials = personUuid == null + ? credentialsRepo.findByCurrentSubject() + : findByPersonUuid(personUuid); final var result = mapper.mapList( credentials, CredentialsResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(result); @@ -183,6 +179,16 @@ public class HsCredentialsController implements CredentialsApi { return subjectRepo.findByUuid(newRbacSubject.getUuid()); // attached to EM } + private List findByPersonUuid(final UUID personUuid) { + final var person = rbacPersonRepo.findByUuid(personUuid).orElseThrow( + () -> new EntityNotFoundException( + messageTranslator.translate("{0} \"{1}\" not found or not accessible", "personUuid", personUuid) + ) + + ); + return credentialsRepo.findByPerson(person); + } + final BiConsumer ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { ofNullable(entity.getLastUsed()).ifPresent( dt -> resource.setLastUsed(dt.atOffset(ZoneOffset.UTC))); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepository.java index 85dc445d..8e013478 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepository.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.accounts; import io.micrometer.core.annotation.Timed; import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import java.util.List; @@ -14,7 +15,36 @@ public interface HsCredentialsRepository extends Repository findByUuid(final UUID uuid); @Timed("app.login.credentials.repo.findByPerson") - List findByPerson(final HsOfficePerson personUuid); + List findByPerson(final HsOfficePerson personUuid); + + @Timed("app.login.credentials.repo.findByCurrentSubject") + @Query(nativeQuery = true, value = """ + WITH RECURSIVE owned_persons AS ( + -- Start with the person linked to current subject's credentials + SELECT p.uuid AS person_uuid + FROM hs_accounts.credentials c + JOIN hs_office.person p ON p.uuid = c.person_uuid + WHERE c.uuid = rbac.currentSubjectUuid() + + UNION + + -- Add persons where the current person has OWNER role + SELECT p.uuid AS person_uuid + FROM owned_persons op + CROSS JOIN hs_office.person p + WHERE rbac.isGranted( + rbac.currentSubjectUuid(), + rbac.findRoleId( + rbac.roleDescriptorOf('hs_office.person', p.uuid, 'OWNER'::rbac.RoleType, false) + ) + ) + ) + SELECT DISTINCT c.* + FROM hs_accounts.credentials c + WHERE c.uuid = rbac.currentSubjectUuid() -- Include current subject's own credentials + OR c.person_uuid IN (SELECT person_uuid FROM owned_persons) -- Include credentials of owned persons + """) + List findByCurrentSubject(); @Timed("app.login.credentials.repo.save") HsCredentialsEntity save(final HsCredentialsEntity entity); diff --git a/src/main/java/net/hostsharing/hsadminng/repr/Stringify.java b/src/main/java/net/hostsharing/hsadminng/repr/Stringify.java index f1e1c709..fa533ca8 100644 --- a/src/main/java/net/hostsharing/hsadminng/repr/Stringify.java +++ b/src/main/java/net/hostsharing/hsadminng/repr/Stringify.java @@ -1,5 +1,8 @@ package net.hostsharing.hsadminng.repr; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Value; import net.hostsharing.hsadminng.errors.DisplayAs; import jakarta.validation.constraints.NotNull; @@ -11,14 +14,13 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import static java.lang.Boolean.TRUE; import static java.util.Optional.ofNullable; public final class Stringify { private final String name; private Function idProp; - private final List> props = new ArrayList<>(); + private final List> props = new ArrayList<>(); private String separator = ", "; private Boolean quotedValues = null; @@ -30,18 +32,6 @@ public final class Stringify { return new Stringify<>(clazz, null); } - public Stringify using(final Class subClass) { - //noinspection unchecked - final var stringify = new Stringify(subClass, null) - .withIdProp(cast(idProp)) - .withProps(cast(props)) - .withSeparator(separator); - if (quotedValues != null) { - stringify.quotedValues(quotedValues); - } - return stringify; - } - private Stringify(final Class clazz, final String name) { if (name != null) { this.name = name; @@ -55,31 +45,30 @@ public final class Stringify { } } - public Stringify withIdProp(final Function getter) { + public Stringify withIdProp(final Function getter) { idProp = getter; return this; } - public Stringify withProp(final String propName, final Function getter) { + public Stringify withProp(final String propName, final Function getter) { props.add(new Property<>(propName, getter)); return this; } - public Stringify withProp(final Function getter) { + public Stringify withProp(final Function getter) { props.add(new Property<>(null, getter)); return this; } - private Stringify withProps(final List> props) { - this.props.addAll(props); + public Stringify withProp(final Function getter, final Function mapper) { + props.add(new Property<>(null, getter, mapper)); return this; } public String apply(@NotNull B object) { final var propValues = props.stream() - .map(prop -> PropertyValue.of(prop, prop.getter.apply(object))) - .filter(Objects::nonNull) - .filter(PropertyValue::nonEmpty) + .map(prop -> new PropertyValue<>(object, prop)) + .filter(PropertyValue::notNullAndNotEmpty) .map(propVal -> propName(propVal, "=") + optionallyQuoted(propVal)) .collect(Collectors.joining(separator)); return idProp != null @@ -92,24 +81,36 @@ public final class Stringify { return this; } - private String propName(final PropertyValue propVal, final String delimiter) { + private String propName(final PropertyValue propVal, final String delimiter) { return ofNullable(propVal.prop.name).map(v -> v + delimiter).orElse(""); } - private String optionallyQuoted(final PropertyValue propVal) { + private String optionallyQuoted(final PropertyValue propVal) { if (quotedValues == null) - return quotedQuotedValueType(propVal) - ? ("'" + propVal.value + "'") - : propVal.value; - return TRUE == quotedValues - ? ("'" + propVal.value + "'") - : propVal.value; + return quotableValueType(propVal.getValue()) + ? ("'" + propVal.stringValue + "'") + : propVal.stringValue; + return quotedValues + ? ("'" + propVal.stringValue + "'") + : propVal.stringValue; } - private static boolean quotedQuotedValueType(final PropertyValue propVal) { - return !(propVal.rawValue instanceof Number || propVal.rawValue instanceof Boolean); + private boolean quotableValueType(final V rawValue) { + return !(rawValue instanceof Enum) && + !(rawValue instanceof Symbol) && + !(rawValue instanceof Number) && + !(rawValue instanceof Boolean); } + /** + * Specifies whether the values should be quoted (true) or not (false). + * + * If not specified, Enum, Symbol, Number and Boolean values are not quoted; + * other value types are quoted. + * + * @param quotedValues + * @return + */ public Stringify quotedValues(final boolean quotedValues) { this.quotedValues = quotedValues; return this; @@ -120,23 +121,48 @@ public final class Stringify { return (T)object; } - private record Property(String name, Function getter) {} + @Value + @AllArgsConstructor + private class Property { + String name; + Function getter; + Function mapper; // FIXME: better generics? - private record PropertyValue(Property prop, Object rawValue, String value) { - - static PropertyValue of(Property prop, Object rawValue) { - return rawValue != null ? new PropertyValue<>(prop, rawValue, toStringOrShortString(rawValue)) : null; + Property(String name, Function getter) { + this(name, getter, v -> v); } - private static String toStringOrShortString(final Object rawValue) { - return rawValue instanceof Stringifyable stringifyable ? stringifyable.toShortString() : rawValue.toString(); + Object getValue(final B object) { + return ofNullable(getter.apply(object)) + .map(mapper) + .orElse(null); + } + } + + @Getter + private class PropertyValue { + private Property prop; + private V value; + private String stringValue; + + @SuppressWarnings("unchecked") + PropertyValue(final B object, final Property prop) { + // FIXME: simplify + final var typedProp = (Property) prop; + final var value = typedProp.getValue(object); + final var stringifiedValue = value instanceof Stringifyable stringifyable + ? stringifyable.toShortString() + : Objects.toString(value); + this.prop = typedProp; + this.value = (V) value; + this.stringValue = stringifiedValue; } - boolean nonEmpty() { - return rawValue != null && - (!(rawValue instanceof Collection c) || !c.isEmpty()) && - (!(rawValue instanceof Map m) || !m.isEmpty()) && - (!(rawValue instanceof String s) || !s.isEmpty()); + boolean notNullAndNotEmpty() { + return value != null && + (!(value instanceof Collection c) || !c.isEmpty()) && + (!(value instanceof Map m) || !m.isEmpty()) && + (!(value instanceof String s) || !s.isEmpty()); } } } diff --git a/src/main/java/net/hostsharing/hsadminng/repr/Symbol.java b/src/main/java/net/hostsharing/hsadminng/repr/Symbol.java new file mode 100644 index 00000000..907de266 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/repr/Symbol.java @@ -0,0 +1,25 @@ +package net.hostsharing.hsadminng.repr; + + +import jakarta.validation.constraints.NotNull; + +/** + * A String value which is used as a symbol and thus does not get quoted + * and is, by definition, different from any other symbol with the same name. + */ +public class Symbol { + private final String value; + + public static Symbol symbol(@NotNull final String value) { + return new Symbol(value); + } + + private Symbol(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/resources/api-definition/accounts/context-schemas.yaml b/src/main/resources/api-definition/accounts/context-schemas.yaml index 236db188..7ed5acd7 100644 --- a/src/main/resources/api-definition/accounts/context-schemas.yaml +++ b/src/main/resources/api-definition/accounts/context-schemas.yaml @@ -17,7 +17,11 @@ components: maxLength: 80 onlyForNaturalPersons: type: boolean + publicAccess: + type: boolean required: - uuid - type - qualifier + - onlyForNaturalPersons + - publicAccess diff --git a/src/main/resources/api-definition/accounts/credentials.yaml b/src/main/resources/api-definition/accounts/credentials.yaml index 0c3e0999..6e2b0d04 100644 --- a/src/main/resources/api-definition/accounts/credentials.yaml +++ b/src/main/resources/api-definition/accounts/credentials.yaml @@ -3,16 +3,16 @@ get: description: Returns the list of all credentials which are visible to the current subject or any of it's assumed roles. tags: - credentials - operationId: getListOfCredentialsByPersonUuid + operationId: getListOfCredentials parameters: - $ref: 'auth.yaml#/components/parameters/assumedRoles' - name: personUuid in: query - required: true + required: false schema: type: string format: uuid - description: The UUID of the person, whose credentials are to be fetched. + description: The UUID of the person, whose credentials are to be fetched. Or null, if all credentials of the login-use should be fetched. responses: "200": description: OK diff --git a/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql b/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql index 5938607e..1ffcee9a 100644 --- a/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql +++ b/src/main/resources/db/changelog/9-hs-global/950-accounts/9510-hs-accounts.sql @@ -40,6 +40,8 @@ create table hs_accounts.context only_for_natural_persons boolean default false, + public_access boolean default false, + unique (type, qualifier) ); --// diff --git a/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql b/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql index 27b38c14..53c15ed0 100644 --- a/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql +++ b/src/main/resources/db/changelog/9-hs-global/950-accounts/9519-hs-accounts-test-data.sql @@ -15,7 +15,9 @@ declare context_HSADMIN_prod hs_accounts.context; context_SSH_internal hs_accounts.context; + context_SSH_external hs_accounts.context; context_MATRIX_internal hs_accounts.context; + context_MATRIX_external hs_accounts.context; begin call base.defineContext('creating booking-project test-data', null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN'); @@ -26,17 +28,25 @@ begin personFranUuid = (SELECT uuid FROM hs_office.person WHERE givenName='Fran'); -- Add test contexts - INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES - ('11111111-1111-1111-1111-111111111111', 'HSADMIN', 'prod', true) + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('11111111-1111-1111-1111-111111111111', 'HSADMIN', 'prod', true, true) RETURNING * INTO context_HSADMIN_prod; - INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES - ('22222222-2222-2222-2222-222222222222', 'SSH', 'internal', true) - RETURNING * INTO context_SSH_internal; - INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES - ('33333333-3333-3333-3333-333333333333', 'MATRIX', 'internal', true) - RETURNING * INTO context_MATRIX_internal; - INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons) VALUES - ('44444444-4444-4444-4444-444444444444', 'MASTODON', 'external', false); + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('22222222-2222-2222-2222-222222222222', 'SSH', 'internal', true, false) + RETURNING * INTO context_SSH_internal; + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('33333333-3333-3333-3333-333333333333', 'SSH', 'external', false, true) + RETURNING * INTO context_SSH_external; + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('44444444-4444-4444-4444-444444444444', 'MATRIX', 'internal', true, false) + RETURNING * INTO context_MATRIX_internal; + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('55555555-5555-5555-5555-555555555555', 'MATRIX', 'external', true, true) + RETURNING * INTO context_MATRIX_external; + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('66666666-6666-6666-6666-666666666666', 'MASTODON', 'external', false, true); + INSERT INTO hs_accounts.context (uuid, type, qualifier, only_for_natural_persons, public_access) VALUES + ('77777777-7777-7777-7777-777777777777', 'BBB', 'external', false, true); -- grant general access to public credential contexts -- TODO_impl: RBAC rules for _rv do not yet work properly @@ -59,12 +69,12 @@ begin -- Map credentials to contexts INSERT INTO hs_accounts.context_mapping (credentials_uuid, context_uuid) VALUES - (superuserAlexSubjectUuid, '11111111-1111-1111-1111-111111111111'), -- HSADMIN context - (superuserFranSubjectUuid, '11111111-1111-1111-1111-111111111111'), -- HSADMIN context - (superuserAlexSubjectUuid, '22222222-2222-2222-2222-222222222222'), -- SSH context - (superuserFranSubjectUuid, '22222222-2222-2222-2222-222222222222'), -- SSH context - (superuserAlexSubjectUuid, '33333333-3333-3333-3333-333333333333'), -- MATRIX context - (superuserFranSubjectUuid, '33333333-3333-3333-3333-333333333333'); -- MATRIX context + (superuserAlexSubjectUuid, context_HSADMIN_prod.uuid), + (superuserFranSubjectUuid, context_HSADMIN_prod.uuid), + (superuserAlexSubjectUuid, context_SSH_internal.uuid), + (superuserFranSubjectUuid, context_SSH_internal.uuid), + (superuserAlexSubjectUuid, context_MATRIX_internal.uuid), + (superuserFranSubjectUuid, context_MATRIX_internal.uuid); end; $$; --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacEntityUnitTest.java index b0f727e9..48b3c788 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacEntityUnitTest.java @@ -14,7 +14,8 @@ class HsCredentialsContextRbacEntityUnitTest { .uuid(UUID.randomUUID()) .type("SSH") .qualifier("prod") + .publicAccess(true) .build(); - assertEquals("loginContext(SSH:prod)", entity.toShortString()); + assertEquals("loginContext(SSH:prod:PUBLIC)", entity.toShortString()); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacRepositoryIntegrationTest.java index 428a71e7..5b64b5b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRbacRepositoryIntegrationTest.java @@ -77,7 +77,7 @@ class HsCredentialsContextRbacRepositoryIntegrationTest extends ContextBasedTest // then assertThat(foundEntityOptional).isPresent(); - assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(HSADMIN:prod)"); + assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(HSADMIN:prod:NP-ONLY:PUBLIC)"); } @Test @@ -89,7 +89,7 @@ class HsCredentialsContextRbacRepositoryIntegrationTest extends ContextBasedTest // then assertThat(foundEntityOptional).isPresent(); - assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(SSH:internal)"); + assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(SSH:internal:NP-ONLY:INTERNAL)"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealEntityUnitTest.java index e7a87f10..63fe70b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealEntityUnitTest.java @@ -14,7 +14,8 @@ class HsCredentialsContextRealEntityUnitTest { .uuid(UUID.randomUUID()) .type("testType") .qualifier("testQualifier") + .onlyForNaturalPersons(true) .build(); - assertEquals("loginContext(testType:testQualifier)", entity.toShortString()); + assertEquals("loginContext(testType:testQualifier:NP-ONLY:INTERNAL)", entity.toShortString()); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java index eece0999..9ac264ab 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsContextRealRepositoryIntegrationTest.java @@ -52,7 +52,9 @@ class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest final var rowsBefore = query.getResultList(); // then - assertThat(rowsBefore).as("hs_accounts.context_hv only contain no rows for a timestamp before test data creation").hasSize(0); + assertThat(rowsBefore) + .as("hs_accounts.context_hv only contain no rows for a timestamp before test data creation") + .hasSize(0); // and when historicalContext(Timestamp.from(ZonedDateTime.now().toInstant())); @@ -60,7 +62,9 @@ class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest final var rowsAfter = query.getResultList(); // then - assertThat(rowsAfter).as("hs_accounts.context_hv should now contain the test-data rows for the current timestamp").hasSize(4); + assertThat(rowsAfter) + .as("hs_accounts.context_hv should now contain the test-data rows for the current timestamp") + .hasSize(7); } @Test @@ -87,7 +91,7 @@ class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest // then assertThat(foundEntityOptional).isPresent(); - assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(HSADMIN:prod)"); + assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(HSADMIN:prod:NP-ONLY:PUBLIC)"); } @Test @@ -99,7 +103,7 @@ class HsCredentialsContextRealRepositoryIntegrationTest extends ContextBasedTest // then assertThat(foundEntityOptional).isPresent(); - assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(SSH:internal)"); + assertThat(foundEntityOptional).map(Object::toString).contains("loginContext(SSH:internal:NP-ONLY:INTERNAL)"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepositoryIntegrationTest.java index 770fe658..55bfe649 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/accounts/HsCredentialsRepositoryIntegrationTest.java @@ -125,7 +125,7 @@ class HsCredentialsRepositoryIntegrationTest extends ContextBasedTest { assertThat(foundEntity.getGlobalUid()).isEqualTo(2001); assertThat(foundEntity.getLoginContexts()).hasSize(1) - .map(HsCredentialsContextRealEntity::toString).contains("loginContext(HSADMIN:prod)"); + .map(HsCredentialsContextRealEntity::toString).contains("loginContext(HSADMIN:prod:NP-ONLY:PUBLIC)"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java index 40b52ae1..0912bc7e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java @@ -117,7 +117,7 @@ class HsOfficePersonEntityUnitTest { final var actualDisplay = givenPersonEntity.toString(); - assertThat(actualDisplay).isEqualTo("person(personType='NP', tradeName='some trade name', title='Dr.', familyName='some family name', givenName='some given name')"); + assertThat(actualDisplay).isEqualTo("person(personType=NP, tradeName='some trade name', title='Dr.', familyName='some family name', givenName='some given name')"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationUnitTest.java index 5ae594b0..0678a783 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationUnitTest.java @@ -28,7 +28,7 @@ class HsOfficeRelationUnitTest { .holder(holder) .build(); - assertThat(given.toString()).isEqualTo("rel(anchor='LP some trade name', type='SUBSCRIBER', mark='members-announce', holder='NP Meier, Mellie')"); + assertThat(given.toString()).isEqualTo("rel(anchor='LP some trade name', type=SUBSCRIBER, mark='members-announce', holder='NP Meier, Mellie')"); } @Test @@ -39,7 +39,7 @@ class HsOfficeRelationUnitTest { .holder(holder) .build(); - assertThat(given.toShortString()).isEqualTo("rel(anchor='LP some trade name', type='REPRESENTATIVE', holder='NP Meier, Mellie')"); + assertThat(given.toShortString()).isEqualTo("rel(anchor='LP some trade name', type=REPRESENTATIVE, holder='NP Meier, Mellie')"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/test/StringifyUnitTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/test/StringifyUnitTest.java index 1d22056d..b16934f2 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/test/StringifyUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/test/StringifyUnitTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; import java.util.UUID; +import static net.hostsharing.hsadminng.repr.Symbol.symbol; import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static org.assertj.core.api.Assertions.assertThat; @@ -58,11 +59,12 @@ class StringifyUnitTest { private static Stringify toString = stringify(SubBeanWithUnquotedValues.class) .withProp(SubBeanWithUnquotedValues::getKey) .withProp(SubBeanWithUnquotedValues::getValue) - .withSeparator(": ") + .withProp(SubBeanWithUnquotedValues::isActive, v -> v ? symbol("active") : symbol("inactive")) .quotedValues(false); private String key; private String value; + private boolean active; @Override public String toString() { @@ -84,11 +86,13 @@ class StringifyUnitTest { private static Stringify toString = stringify(SubBeanWithQuotedValues.class) .withProp(SubBeanWithQuotedValues::getKey) .withProp(SubBeanWithQuotedValues::getValue) + .withProp(SubBeanWithQuotedValues::isActive, v -> v ? "active" : "inactive") .withSeparator(": ") .quotedValues(true); private String key; private Integer value; + private boolean active; @Override public String toString() { @@ -104,8 +108,8 @@ class StringifyUnitTest { @Test void stringifyWhenAllPropsHaveValues() { final var given = new TestBean(UUID.randomUUID(), "some caption", - new SubBeanWithUnquotedValues("some key", "some value"), - new SubBeanWithQuotedValues("some key", 1234), + new SubBeanWithUnquotedValues("some key", "some value", true), + new SubBeanWithQuotedValues("some key", 1234, false), 42, false); final var result = given.toString(); @@ -122,15 +126,15 @@ class StringifyUnitTest { @Test void stringifyWithoutExplicitNameUsesSimpleClassName() { - final var given = new SubBeanWithUnquotedValues("some key", "some value"); + final var given = new SubBeanWithUnquotedValues("some key", "some value", false); final var result = given.toString(); - assertThat(result).isEqualTo("SubBeanWithUnquotedValues(some key: some value)"); + assertThat(result).isEqualTo("SubBeanWithUnquotedValues(some key, some value, inactive)"); } @Test void stringifyWithQuotedValueTrueQuotesEvenIntegers() { - final var given = new SubBeanWithQuotedValues("some key", 1234); + final var given = new SubBeanWithQuotedValues("some key", 1234, true); final var result = given.toString(); - assertThat(result).isEqualTo("SubBeanWithQuotedValues('some key': '1234')"); + assertThat(result).isEqualTo("SubBeanWithQuotedValues('some key': '1234': 'active')"); } }