align React-GUI and Java API -backend (#188)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/188 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
@@ -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<HsCredentialsContext> 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();
|
||||
|
||||
@@ -81,19 +81,15 @@ public class HsCredentialsController implements CredentialsApi {
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
@Timed("app.credentials.credentials.getListOfCredentialsByPersonUuid")
|
||||
public ResponseEntity<List<CredentialsResource>> getListOfCredentialsByPersonUuid(
|
||||
public ResponseEntity<List<CredentialsResource>> 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<HsCredentialsEntity> 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<HsCredentialsEntity, CredentialsResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
ofNullable(entity.getLastUsed()).ifPresent(
|
||||
dt -> resource.setLastUsed(dt.atOffset(ZoneOffset.UTC)));
|
||||
|
||||
@@ -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<HsCredentialsEntity,
|
||||
Optional<HsCredentialsEntity> findByUuid(final UUID uuid);
|
||||
|
||||
@Timed("app.login.credentials.repo.findByPerson")
|
||||
List<HsCredentialsEntity> findByPerson(final HsOfficePerson personUuid);
|
||||
List<HsCredentialsEntity> 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<HsCredentialsEntity> findByCurrentSubject();
|
||||
|
||||
@Timed("app.login.credentials.repo.save")
|
||||
HsCredentialsEntity save(final HsCredentialsEntity entity);
|
||||
|
||||
@@ -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<B> {
|
||||
|
||||
private final String name;
|
||||
private Function<? extends B, ?> idProp;
|
||||
private final List<Property<B>> props = new ArrayList<>();
|
||||
private final List<Property<B, ?>> props = new ArrayList<>();
|
||||
private String separator = ", ";
|
||||
private Boolean quotedValues = null;
|
||||
|
||||
@@ -30,18 +32,6 @@ public final class Stringify<B> {
|
||||
return new Stringify<>(clazz, null);
|
||||
}
|
||||
|
||||
public <T extends B> Stringify<T> using(final Class<T> subClass) {
|
||||
//noinspection unchecked
|
||||
final var stringify = new Stringify<T>(subClass, null)
|
||||
.withIdProp(cast(idProp))
|
||||
.withProps(cast(props))
|
||||
.withSeparator(separator);
|
||||
if (quotedValues != null) {
|
||||
stringify.quotedValues(quotedValues);
|
||||
}
|
||||
return stringify;
|
||||
}
|
||||
|
||||
private Stringify(final Class<B> clazz, final String name) {
|
||||
if (name != null) {
|
||||
this.name = name;
|
||||
@@ -55,31 +45,30 @@ public final class Stringify<B> {
|
||||
}
|
||||
}
|
||||
|
||||
public Stringify<B> withIdProp(final Function<? extends B, ?> getter) {
|
||||
public <V> Stringify<B> withIdProp(final Function<? extends B, V> getter) {
|
||||
idProp = getter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Stringify<B> withProp(final String propName, final Function<B, ?> getter) {
|
||||
public <V> Stringify<B> withProp(final String propName, final Function<B, V> getter) {
|
||||
props.add(new Property<>(propName, getter));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Stringify<B> withProp(final Function<B, ?> getter) {
|
||||
public <V> Stringify<B> withProp(final Function<B, V> getter) {
|
||||
props.add(new Property<>(null, getter));
|
||||
return this;
|
||||
}
|
||||
|
||||
private Stringify<B> withProps(final List<Property<B>> props) {
|
||||
this.props.addAll(props);
|
||||
public <V> Stringify<B> withProp(final Function<B, V> getter, final Function<V, ?> 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<B> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private String propName(final PropertyValue<B> propVal, final String delimiter) {
|
||||
private <V> String propName(final PropertyValue<B, V> propVal, final String delimiter) {
|
||||
return ofNullable(propVal.prop.name).map(v -> v + delimiter).orElse("");
|
||||
}
|
||||
|
||||
private String optionallyQuoted(final PropertyValue<B> propVal) {
|
||||
private <B, V> String optionallyQuoted(final PropertyValue<B, V> 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 <B> boolean quotedQuotedValueType(final PropertyValue<B> propVal) {
|
||||
return !(propVal.rawValue instanceof Number || propVal.rawValue instanceof Boolean);
|
||||
private <V> 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<B> quotedValues(final boolean quotedValues) {
|
||||
this.quotedValues = quotedValues;
|
||||
return this;
|
||||
@@ -120,23 +121,48 @@ public final class Stringify<B> {
|
||||
return (T)object;
|
||||
}
|
||||
|
||||
private record Property<B>(String name, Function<B, ?> getter) {}
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
private class Property<B, V> {
|
||||
String name;
|
||||
Function<B, V> getter;
|
||||
Function<V, ?> mapper; // FIXME: better generics?
|
||||
|
||||
private record PropertyValue<B>(Property<B> prop, Object rawValue, String value) {
|
||||
|
||||
static <B> PropertyValue<B> of(Property<B> prop, Object rawValue) {
|
||||
return rawValue != null ? new PropertyValue<>(prop, rawValue, toStringOrShortString(rawValue)) : null;
|
||||
Property(String name, Function<B, V> 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<B, V> {
|
||||
private Property<B, V> prop;
|
||||
private V value;
|
||||
private String stringValue;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
PropertyValue(final B object, final Property<B, ?> prop) {
|
||||
// FIXME: simplify
|
||||
final var typedProp = (Property<B, V>) 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,11 @@ components:
|
||||
maxLength: 80
|
||||
onlyForNaturalPersons:
|
||||
type: boolean
|
||||
publicAccess:
|
||||
type: boolean
|
||||
required:
|
||||
- uuid
|
||||
- type
|
||||
- qualifier
|
||||
- onlyForNaturalPersons
|
||||
- publicAccess
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,8 @@ create table hs_accounts.context
|
||||
|
||||
only_for_natural_persons boolean default false,
|
||||
|
||||
public_access boolean default false,
|
||||
|
||||
unique (type, qualifier)
|
||||
);
|
||||
--//
|
||||
|
||||
+26
-16
@@ -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; $$;
|
||||
--//
|
||||
|
||||
+2
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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<SubBeanWithUnquotedValues> 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<SubBeanWithQuotedValues> 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')");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user