scoped programmatic i18n-keys (#190)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/190 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.val;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -10,6 +12,7 @@ import java.util.Locale;
|
||||
|
||||
@Service
|
||||
@RequestScope
|
||||
@Slf4j
|
||||
public class MessageTranslator {
|
||||
|
||||
@Autowired
|
||||
@@ -21,19 +24,26 @@ public class MessageTranslator {
|
||||
public String translateTo(final Locale locale, final String messageKey, final Object... args) {
|
||||
try {
|
||||
// we don't use the method which also takes a default message right away ...
|
||||
final var translatedMessage = messageSource.getMessage(messageKey, args, locale);
|
||||
val translatedMessage = messageSource.getMessage(messageKey, args, locale);
|
||||
return translatedMessage;
|
||||
} catch (final Exception e) {
|
||||
final var defaultMessage = messageKey.replace("'", "''");
|
||||
final var translatedMessage = messageSource.getMessage(messageKey, args, defaultMessage, locale);
|
||||
if (locale != Locale.ENGLISH) {
|
||||
// ... because we want to add a hint that the translation is missing, even if placeholders got replaced
|
||||
return translatedMessage + " [" + locale + " translation missing]";
|
||||
}
|
||||
return translatedMessage;
|
||||
// ... but log the missing translation ...
|
||||
log.error("Missing translation for message key '{}' in locale '{}'", messageKey, locale, e);
|
||||
|
||||
// and decorate the default message to mark it as not really translated:
|
||||
val defaultMessage = messageKey.substring(messageKey.indexOf('.') + 1)
|
||||
.replaceAll("--+", " - ")
|
||||
.replaceAll("(?<! )-(?! )", " ")
|
||||
.replace("'", "''");
|
||||
val fallbackMessage = messageSource.getMessage(messageKey, args, defaultMessage, Locale.ENGLISH);
|
||||
return decorateMissingTranslation(fallbackMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static String decorateMissingTranslation(final String translatedMessage) {
|
||||
return "【⍰" + translatedMessage + "⍰】";
|
||||
}
|
||||
|
||||
public String translate(final String messageKey, final Object... args) {
|
||||
return translateTo(httpRequest.getLocale(), messageKey, args);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ public class MessagesResourceConfig {
|
||||
final var source = new ResourceBundleMessageSource();
|
||||
source.setBasenames("i18n/messages");
|
||||
source.setDefaultEncoding("UTF-8");
|
||||
source.setFallbackToSystemLocale(false);
|
||||
source.setUseCodeAsDefaultMessage(false);
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class RealCasAuthenticator implements CasAuthenticator {
|
||||
|
||||
private Document verifyServiceTicket(final String serviceTicket) throws SAXException, IOException, ParserConfigurationException {
|
||||
if ( !serviceTicket.startsWith("ST-") ) {
|
||||
throwBadCredentialsException("unknown authorization ticket");
|
||||
throwBadCredentialsException("auth.unknown-authorization-ticket");
|
||||
}
|
||||
|
||||
final var url = casServerUrl + "/cas/p3/serviceValidate" +
|
||||
@@ -74,7 +74,7 @@ public class RealCasAuthenticator implements CasAuthenticator {
|
||||
private String extractUserName(final Document verification) {
|
||||
|
||||
if (verification.getElementsByTagName("cas:authenticationSuccess").getLength() == 0) {
|
||||
throwBadCredentialsException("CAS service-ticket could not be verified");
|
||||
throwBadCredentialsException("auth.cas-service-ticket-could-not-be-verified");
|
||||
}
|
||||
return verification.getElementsByTagName("cas:user").item(0).getTextContent();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import static java.util.Locale.ENGLISH;
|
||||
|
||||
public class ReferenceNotFoundException extends RuntimeException {
|
||||
|
||||
private final String TRANSLATABLE_MESSAGE = "{0} \"{1}\" not found";
|
||||
private final String TRANSLATABLE_MESSAGE = "general.{0}-{1}-not-found";
|
||||
|
||||
private final MessageTranslator translator;
|
||||
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ public class RestResponseEntityExceptionHandler
|
||||
}
|
||||
|
||||
private Function<FieldError, String> toEnrichedFieldErrorMessage() {
|
||||
final var translatedButIsLiteral = messageTranslator.translate("but is");
|
||||
final var translatedButIsLiteral = messageTranslator.translate("general.but-is");
|
||||
// TODO.i18n: the following does not work in all languages, e.g. not in right-to-left languages
|
||||
return fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage() +
|
||||
" " + translatedButIsLiteral + " " + optionallyQuoted(fieldError.getRejectedValue());
|
||||
|
||||
+4
-2
@@ -52,13 +52,15 @@ public class CredentialContextResourceToEntityMapper {
|
||||
final var existingContextEntity = em.find(HsCredentialsContextRealEntity.class, resource.getUuid());
|
||||
if (existingContextEntity == null) {
|
||||
throw new EntityNotFoundException(
|
||||
messageTranslator.translate("{0} \"{1}\" not found or not accessible",
|
||||
messageTranslator.translate(
|
||||
"general.{0}-{1}-not-found-or-not-accessible",
|
||||
"credentials uuid", resource.getUuid()));
|
||||
}
|
||||
if ((resource.getType() != null && !existingContextEntity.getType().equals(resource.getType())) ||
|
||||
(resource.getQualifier() != null && !existingContextEntity.getQualifier().equals(resource.getQualifier()))) {
|
||||
throw new EntityNotFoundException(
|
||||
messageTranslator.translate("existing {0} does not match given resource {1}",
|
||||
messageTranslator.translate(
|
||||
"credentials.existing-{0}-does-not-match-given-resource-{1}",
|
||||
existingContextEntity, resource));
|
||||
}
|
||||
entities.add(existingContextEntity);
|
||||
|
||||
@@ -203,12 +203,12 @@ public class HsCredentialsController implements CredentialsApi {
|
||||
private void validate(final HsCredentialsEntity newCredentialsEntity) {
|
||||
// the referenced person must be represented by currently logged in person
|
||||
final var personUuid = newCredentialsEntity.getPerson().getUuid();
|
||||
final var representedPersonUuids = rbacPersonRepo.findPersonsrepresentedByPersonWithUuid(personUuid)
|
||||
final var representedPersonUuids = rbacPersonRepo.findPersonsRepresentedByPersonWithUuid(personUuid)
|
||||
.stream().map(HsOfficePerson::getUuid).toList();
|
||||
if ( !representedPersonUuids.contains(personUuid)) {
|
||||
throw new ValidationException(
|
||||
messageTranslator.translate(
|
||||
"access-denied-personUuid-{0}-not-represented-by-currently-logged-in-person",
|
||||
"credentials.access-denied-person-uuid-{0}-not-represented-by-currently-logged-in-person",
|
||||
personUuid));
|
||||
}
|
||||
}
|
||||
@@ -224,7 +224,7 @@ public class HsCredentialsController implements CredentialsApi {
|
||||
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)
|
||||
messageTranslator.translate("general.{0}-{1}-not-found-or-not-accessible", "personUuid", personUuid)
|
||||
)
|
||||
|
||||
);
|
||||
@@ -269,7 +269,7 @@ public class HsCredentialsController implements CredentialsApi {
|
||||
final BiConsumer<CredentialsInsertResource, HsCredentialsEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
final var person = rbacPersonRepo.findByUuid(resource.getPersonUuid()).orElseThrow(
|
||||
() -> new EntityNotFoundException(
|
||||
messageTranslator.translate("{0} \"{1}\" not found or not accessible", "personUuid", resource.getPersonUuid())
|
||||
messageTranslator.translate("general.{0}-{1}-not-found-or-not-accessible", "personUuid", resource.getPersonUuid())
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ public class HsCoopAssetTranslations implements RetroactiveTranslator {
|
||||
|
||||
@Override
|
||||
public boolean canTranslate(final String message) {
|
||||
return message.equals("ERROR: [400] coop assets transaction would result in a negative balance of assets");
|
||||
return message.equals("ERROR: [400] office.coop-assets.transaction-would-result-in-a-negative-balance-of-assets");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,7 +26,7 @@ public class HsCoopAssetTranslations implements RetroactiveTranslator {
|
||||
// and in this case it's just one
|
||||
return ERROR_400_PREFIX + messageTranslator.translate(message.substring(ERROR_400_PREFIX.length()));
|
||||
|
||||
// HOWTO extract variable parts from a messages which got created without i18n support:
|
||||
// HOWTO extract variable parts from messages which got created without i18n support:
|
||||
// final var regex = "(?<propertyName>[^ ]+) (?<propertyValue>.+) not found";
|
||||
// final var pattern = Pattern.compile(regex);
|
||||
// final var matcher = pattern.matcher(message);
|
||||
|
||||
+15
-13
@@ -141,7 +141,8 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
final ArrayList<String> violations) {
|
||||
if (List.of(DEPOSIT, HsOfficeCoopAssetsTransactionTypeResource.ADOPTION).contains(requestBody.getTransactionType())
|
||||
&& requestBody.getAssetValue().signum() < 0) {
|
||||
violations.add(messageTranslator.translate("for transactionType={0}, assetValue must be positive but is {1,number,#0.00}",
|
||||
violations.add(messageTranslator.translate(
|
||||
"office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-positive-but-is-{1,number,#0.00}",
|
||||
requestBody.getTransactionType(), requestBody.getAssetValue()));
|
||||
}
|
||||
}
|
||||
@@ -152,7 +153,8 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
if (List.of(DISBURSAL, HsOfficeCoopAssetsTransactionTypeResource.TRANSFER, CLEARING, LOSS)
|
||||
.contains(requestBody.getTransactionType())
|
||||
&& requestBody.getAssetValue().signum() > 0) {
|
||||
violations.add(messageTranslator.translate("for transactionType={0}, assetValue must be negative but is {1,number,#0.00}",
|
||||
violations.add(messageTranslator.translate(
|
||||
"office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-negative-but-is-{1,number,#0.00}",
|
||||
requestBody.getTransactionType(), requestBody.getAssetValue()));
|
||||
}
|
||||
}
|
||||
@@ -161,7 +163,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
final HsOfficeCoopAssetsTransactionInsertResource requestBody,
|
||||
final ArrayList<String> violations) {
|
||||
if (requestBody.getAssetValue().signum() == 0) {
|
||||
violations.add(messageTranslator.translate("assetValue must not be 0"));
|
||||
violations.add(messageTranslator.translate("office.coop-assets.assetvalue-must-not-be-0"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,18 +229,18 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
resource.getMembershipUuid()))
|
||||
.orElseThrow(() -> new EntityNotFoundException(
|
||||
messageTranslator.translate(
|
||||
"{0} \"{1}\" not found", "membership.uuid", resource.getMembershipUuid())));
|
||||
"general.{0}-{1}-not-found", "membership.uuid", resource.getMembershipUuid())));
|
||||
entity.setMembership(membership);
|
||||
}
|
||||
|
||||
if (entity.getTransactionType() == REVERSAL) {
|
||||
if (resource.getRevertedAssetTxUuid() == null) {
|
||||
throw new ValidationException(messageTranslator.translate(
|
||||
"a REVERSAL asset transaction requires specifying a revertedAssetTx.uuid"));
|
||||
"office.coop-assets.a-reversal-asset-transaction-requires-specifying-a-revertedassettx-uuid"));
|
||||
}
|
||||
final var revertedAssetTx = coopAssetsTransactionRepo.findByUuid(resource.getRevertedAssetTxUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException(messageTranslator.translate(
|
||||
"{0} \"{1}\" not found",
|
||||
"general.{0}-{1}-not-found",
|
||||
"revertedAssetTx.uuid",
|
||||
resource.getRevertedAssetTxUuid())));
|
||||
revertedAssetTx.setReversalAssetTx(entity);
|
||||
@@ -246,7 +248,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
if (resource.getAssetValue().negate().compareTo(revertedAssetTx.getAssetValue()) != 0) {
|
||||
throw new ValidationException(
|
||||
messageTranslator.translate(
|
||||
"given assetValue {0,number,#0.00} must be the negative value of the reverted asset transaction: {1,number,#0.00}",
|
||||
"office.coop-assets.given-assetvalue-{0,number,#0.00}-must-be-the-negative-value-of-the-reverted-asset-transaction-{1,number,#0.00}",
|
||||
resource.getAssetValue(), revertedAssetTx.getAssetValue()));
|
||||
}
|
||||
|
||||
@@ -270,7 +272,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
final var adoptingMembership = determineAdoptingMembership(resource);
|
||||
if ( entity.getMembership() == adoptingMembership) {
|
||||
throw new ValidationException(messageTranslator.translate(
|
||||
"transferring and adopting membership must be different, but both are {0}",
|
||||
"office.coop-assets.transferring-and-adopting-membership-must-be-different-but-both-are-{0}",
|
||||
adoptingMembership.getTaggedMemberNumber()));
|
||||
}
|
||||
final var adoptingAssetTx = createAdoptingAssetTx(entity, adoptingMembership);
|
||||
@@ -285,8 +287,8 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
// @formatter:off
|
||||
final var message = messageTranslator.translate(
|
||||
resource.getTransactionType() == HsOfficeCoopAssetsTransactionTypeResource.TRANSFER
|
||||
? "either {0} or {1} must be given, not both"
|
||||
: "neither {0} nor {1} must be given for transactionType={2}",
|
||||
? "office.coop-assets.either-{0}-or-{1}-must-be-given-not-both"
|
||||
: "office.coop-assets.neither-{0}-nor-{1}-must-be-given-for-transactiontype-{2}",
|
||||
"adoptingMembership.uuid",
|
||||
"adoptingMembership.memberNumber",
|
||||
resource.getTransactionType());
|
||||
@@ -298,7 +300,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
final var adoptingMembership = membershipRepo.findByUuid(adoptingMembershipUuid);
|
||||
return adoptingMembership.orElseThrow(() ->
|
||||
new ValidationException(messageTranslator.translate(
|
||||
"{0} \"{1}\" not found or not accessible",
|
||||
"general.{0}-{1}-not-found-or-not-accessible",
|
||||
"adoptingMembership.uuid",
|
||||
adoptingMembershipUuid)));
|
||||
}
|
||||
@@ -309,14 +311,14 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
return adoptingMembership.orElseThrow( () ->
|
||||
new ValidationException(
|
||||
messageTranslator.translate(
|
||||
"{0} \"{1}\" not found or not accessible",
|
||||
"general.{0}-{1}-not-found-or-not-accessible",
|
||||
"adoptingMembership.memberNumber",
|
||||
adoptingMembershipMemberNumber)));
|
||||
}
|
||||
|
||||
throw new ValidationException(
|
||||
messageTranslator.translate(
|
||||
"either {0} or {1} must be given for transactionType={2}",
|
||||
"office.coop-assets.either-{0}-or-{1}-must-be-given-for-transactiontype-{2}",
|
||||
"adoptingMembership.uuid",
|
||||
"adoptingMembership.memberNumber",
|
||||
resource.getTransactionType()
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public class HsCoopShareTranslations implements RetroactiveTranslator {
|
||||
|
||||
@Override
|
||||
public boolean canTranslate(final String message) {
|
||||
return message.equals("ERROR: [400] coop shares transaction would result in a negative number of shares");
|
||||
return message.equals("ERROR: [400] office.coop-shares.transaction-would-result-in-a-negative-number-of-shares");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-3
@@ -129,7 +129,8 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
final ArrayList<String> violations) {
|
||||
if (requestBody.getTransactionType() == SUBSCRIPTION
|
||||
&& requestBody.getShareCount() < 0) {
|
||||
violations.add(messageTranslator.translate("for transactionType={0}, shareCount must be positive but is {1}",
|
||||
violations.add(messageTranslator.translate(
|
||||
"office.coop-shares.for-transactiontype-{0}-sharecount-must-be-positive-but-is-{1}",
|
||||
requestBody.getTransactionType(), requestBody.getShareCount()));
|
||||
}
|
||||
}
|
||||
@@ -139,7 +140,8 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
final ArrayList<String> violations) {
|
||||
if (requestBody.getTransactionType() == CANCELLATION
|
||||
&& requestBody.getShareCount() > 0) {
|
||||
violations.add(messageTranslator.translate("for transactionType={0}, shareCount must be negative but is {1}",
|
||||
violations.add(messageTranslator.translate(
|
||||
"office.coop-shares.for-transactiontype-{0}-sharecount-must-be-negative-but-is-{1}",
|
||||
requestBody.getTransactionType(), requestBody.getShareCount()));
|
||||
}
|
||||
}
|
||||
@@ -148,7 +150,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
final HsOfficeCoopSharesTransactionInsertResource requestBody,
|
||||
final ArrayList<String> violations) {
|
||||
if (requestBody.getShareCount() == 0) {
|
||||
violations.add(messageTranslator.translate("shareCount must not be 0"));
|
||||
violations.add(messageTranslator.translate("office.coop-shares.sharecount-must-not-be-0"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
|
||||
context.assumeRoles(assumedRoles);
|
||||
|
||||
final var entities = representedByPersonUuid != null
|
||||
? personRepo.findPersonsrepresentedByPersonWithUuid(representedByPersonUuid)
|
||||
? personRepo.findPersonsRepresentedByPersonWithUuid(representedByPersonUuid)
|
||||
: personRepo.findPersonByOptionalNameLike(name);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficePersonResource.class);
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ public interface HsOfficePersonRbacRepository extends Repository<HsOfficePersonR
|
||||
OR person.uuid = :personUuid
|
||||
""", nativeQuery = true)
|
||||
@Timed("app.office.persons.repo.findRepresentedPersons.rbac")
|
||||
List<HsOfficePersonRbacEntity> findPersonsrepresentedByPersonWithUuid(UUID personUuid);
|
||||
List<HsOfficePersonRbacEntity> findPersonsRepresentedByPersonWithUuid(UUID personUuid);
|
||||
|
||||
@Timed("app.office.persons.repo.save.rbac")
|
||||
HsOfficePersonRbacEntity save(final HsOfficePersonRbacEntity entity);
|
||||
|
||||
@@ -21,9 +21,8 @@ public class PingController implements TestApi {
|
||||
@PreAuthorize("permitAll()")
|
||||
@Timed("app.api.ping")
|
||||
public ResponseEntity<String> ping() {
|
||||
final var userName = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
// HOWTO translate text with placeholders - also see in resource files i18n/messages_*.properties.
|
||||
final var translatedMessage = messageTranslator.translate("pong {0} - in English", userName);
|
||||
final var translatedMessage = messageTranslator.translate("test.pinged--in-your-language");
|
||||
return ResponseEntity.ok(translatedMessage + "\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ metrics:
|
||||
logging:
|
||||
level:
|
||||
org.springframework.security: info
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.web.method.annotation: DEBUG
|
||||
org.springframework.validation: DEBUG
|
||||
# org.springframework.web: DEBUG
|
||||
# org.springframework.web.method.annotation: DEBUG
|
||||
# org.springframework.validation: DEBUG
|
||||
# HOWTO configure logging, e.g. logging to a separate file, see:
|
||||
# https://docs.spring.io/spring-boot/reference/features/logging.html
|
||||
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ alter table hs_office.coopsharetx
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset marc.sandlus:hs-office-coopshares-SHARE-COUNT-CONSTRAINT-BY-TRIGGER endDelimiter:--//
|
||||
--changeset marc.sandlus:hs-office-coopshares-SHARE-COUNT-CONSTRAINT-BY-TRIGGER runOnChange:true validCheckSum:ANY endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
alter table hs_office.coopsharetx drop constraint if exists check_positive_total_shares_count;
|
||||
@@ -42,7 +42,6 @@ drop function if exists hs_office.coopsharestx_check_positive_total cascade;
|
||||
|
||||
create or replace function hs_office.coopsharetx_enforce_positive_total()
|
||||
returns trigger as $$
|
||||
|
||||
declare
|
||||
currentShareCount integer;
|
||||
totalShareCount integer;
|
||||
@@ -53,12 +52,13 @@ begin
|
||||
into currentShareCount;
|
||||
totalShareCount := currentShareCount + NEW.shareCount;
|
||||
if totalShareCount < 0 then
|
||||
raise exception '[400] coop shares transaction would result in a negative number of shares';
|
||||
raise exception '[400] office.coop-shares.transaction-would-result-in-a-negative-number-of-shares';
|
||||
end if;
|
||||
return NEW;
|
||||
end;
|
||||
$$ LANGUAGE plpgsql;;
|
||||
|
||||
drop trigger if exists positive_total_shares_count_tg on hs_office.coopsharetx;
|
||||
|
||||
create trigger positive_total_shares_count_tg before insert
|
||||
on hs_office.coopsharetx
|
||||
|
||||
+4
-3
@@ -73,7 +73,7 @@ CREATE TRIGGER enforce_transaction_constraints
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
--changeset marc.sandlus:hs-office-coopassets-ASSET-VALUE-CONSTRAINT-BY-TRIGGER endDelimiter:--//
|
||||
--changeset marc.sandlus:hs-office-coopassets-ASSET-VALUE-CONSTRAINT-BY-TRIGGER runOnChange:true validCheckSum:ANY endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
alter table hs_office.coopassettx
|
||||
@@ -83,7 +83,6 @@ drop function if exists hs_office.coopassetstx_check_positive_total cascade;
|
||||
|
||||
create or replace function hs_office.coopassettx_enforce_positive_total()
|
||||
returns trigger as $$
|
||||
|
||||
declare
|
||||
currentAssetValue numeric(12,2);
|
||||
totalAssetValue numeric(12,2);
|
||||
@@ -94,12 +93,14 @@ begin
|
||||
into currentAssetValue;
|
||||
totalAssetValue := currentAssetValue + NEW.assetValue;
|
||||
if totalAssetValue::numeric < 0 then
|
||||
raise exception '[400] coop assets transaction would result in a negative balance of assets';
|
||||
raise exception '[400] office.coop-assets.transaction-would-result-in-a-negative-balance-of-assets';
|
||||
end if;
|
||||
return NEW;
|
||||
end;
|
||||
$$ LANGUAGE plpgsql;;
|
||||
|
||||
drop trigger if exists positive_total_assets_count_tg on hs_office.coopassettx;
|
||||
|
||||
create trigger positive_total_assets_count_tg before insert
|
||||
on hs_office.coopassettx
|
||||
for each row execute function hs_office.coopassettx_enforce_positive_total();
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
# This file must be in UTF-8 encoding. Check if you see umlauts or garbage: äöüÄÖÜß.
|
||||
# HINT IntelliJ IDEA shows unused keys in gray.
|
||||
|
||||
pong\ {0}\ -\ in\ English=pong {0} - auf Deutsch
|
||||
test.pinged--in-your-language=pinged - auf Deutsch
|
||||
test.ponged-{0}--in-your-language=ponged {0} - auf Deutsch
|
||||
test.available-in-all-properties-files=Hallo {0} - DE!
|
||||
|
||||
# config (including authorization)
|
||||
CAS\ service-ticket\ could\ not\ be\ verified=CAS Service-Ticket konnte nicht verifiziert werden
|
||||
unknown\ authorization\ ticket=unbekanntes Autorisierungs-Ticket
|
||||
# authorization
|
||||
auth.cas-service-ticket-could-not-be-verified=CAS Service-Ticket konnte nicht verifiziert werden
|
||||
auth.unknown-authorization-ticket=unbekanntes Autorisierungs-Ticket
|
||||
|
||||
# general validations
|
||||
{0}\ "{1}"\ not\ found={0} "{1}" nicht gefunden
|
||||
{0}\ "{1}"\ not\ found\ or\ not\ accessible={0} "{1}" nicht gefunden oder nicht zugänglich
|
||||
but\ is=ist aber
|
||||
general.{0}-{1}-not-found={0} "{1}" nicht gefunden
|
||||
general.{0}-{1}-not-found-or-not-accessible={0} "{1}" nicht gefunden oder nicht zugänglich
|
||||
general.but-is=ist aber
|
||||
|
||||
# credentials validations
|
||||
existing\ {0}\ does\ not\ match\ given\ resource\ {1}=existierender Credentials-Context {0} passt nicht zum angegebenen {1}
|
||||
access-denied-personUuid-{0}-not-represented-by-currently-logged-in-person=Zugriff verweigert: personUuid "{0}" wird von der eingeloggten Person nicht repräsentiert
|
||||
credentials.existing-{0}-does-not-match-given-resource-{1}=existierender Credentials-Context {0} passt nicht zum angegebenen {1}
|
||||
credentials.access-denied-person-uuid-{0}-not-represented-by-currently-logged-in-person=Zugriff verweigert: personUuid "{0}" wird von der eingeloggten Person nicht repräsentiert
|
||||
|
||||
# office.coop-shares
|
||||
for\ transactionType\={0},\ shareCount\ must\ be\ positive\ but\ is\ {1}=für transactionType={0}, muss shareCount positiv sein, ist aber {1}
|
||||
for\ transactionType\={0},\ shareCount\ must\ be\ negative\ but\ is\ {1}=für transactionType={0}, muss shareCount negativ sein, ist aber {1}
|
||||
shareCount\ must\ not\ be\ 0=shareCount darf nicht 0 sein
|
||||
coop\ shares\ transaction\ would\ result\ in\ a\ negative\ number\ of\ shares=Geschäftsanteile-Transaktion würde zu negativen Geschäftsanteilen führen
|
||||
office.coop-shares.for-transactiontype-{0}-sharecount-must-be-positive-but-is-{1}=für transactionType={0}, muss shareCount positiv sein, ist aber {1}
|
||||
office.coop-shares.for-transactiontype-{0}-sharecount-must-be-negative-but-is-{1}=für transactionType={0}, muss shareCount negativ sein, ist aber {1}
|
||||
office.coop-shares.sharecount-must-not-be-0=shareCount darf nicht 0 sein
|
||||
office.coop-shares.transaction-would-result-in-a-negative-number-of-shares=Geschäftsanteile-Transaktion würde zu negativen Geschäftsanteilen führen
|
||||
|
||||
# office.coop-assets
|
||||
either\ {0}\ or\ {1}\ must\ be\ given=entweder {0} oder {1} muss angegeben werden
|
||||
either\ {0}\ or\ {1}\ must\ be\ given,\ not\ both=entweder {0} oder {1} muss angegeben werden, nicht beide
|
||||
either\ {0}\ or\ {1}\ must\ be\ given\ for\ transactionType\={2}=für transactionType={2} muss entweder {0} oder {1} angegeben werden
|
||||
neither\ {0}\ nor\ {1}\ must\ be\ given\ for\ transactionType\={2}=für transactionType={2} darf weder {0} noch {1} angegeben werden
|
||||
assetValue\ must\ not\ be\ 0=assetValue darf nicht 0 sein
|
||||
for\ transactionType\={0},\ assetValue\ must\ be\ positive\ but\ is\ {1,number,#0.00}=für transactionType={0}, muss assetValue positiv sein, ist aber {1,number,#0.00}
|
||||
for\ transactionType\={0},\ assetValue\ must\ be\ negative\ but\ is\ {1,number,#0.00}=für transactionType={0}, muss assetValue negativ sein, ist aber {1,number,#0.00}
|
||||
given\ assetValue\ {0,number,#0.00}\ must\ be\ the\ negative\ value\ of\ the\ reverted\ asset\ transaction\:\ {1,number,#0.00}=assetValue={0,number,#0.00} muss dem negativen Wert des Wertes der stornierten Geschäftsguthaben-Transaktion entsprechen: {1,number,#0.00}
|
||||
a\ REVERSAL\ asset\ transaction\ requires\ specifying\ a\ revertedAssetTx.uuid=eine REVERSAL Geschäftsguthaben-Transaktion erfordert die Angabe einer revertedAssetTx.uuid
|
||||
transferring\ and\ adopting\ membership\ must\ be\ different,\ but\ both\ are\ {0}=übertragende und annehmende Mitgliedschaft müssen unterschiedlich sein, aber beide sind {0}
|
||||
coop\ assets\ transaction\ would\ result\ in\ a\ negative\ balance\ of\ assets=Geschäftsguthaben-Transaktion würde zu einem negativen Geschäftsguthaben-Saldo führen
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given=entweder {0} oder {1} muss angegeben werden
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given-not-both=entweder {0} oder {1} muss angegeben werden, nicht beide
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given-for-transactiontype-{2}=für transactionType={2} muss entweder {0} oder {1} angegeben werden
|
||||
office.coop-assets.neither-{0}-nor-{1}-must-be-given-for-transactiontype-{2}=für transactionType={2} darf weder {0} noch {1} angegeben werden
|
||||
office.coop-assets.assetvalue-must-not-be-0=assetValue darf nicht 0 sein
|
||||
office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-positive-but-is-{1,number,#0.00}=für transactionType={0}, muss assetValue positiv sein, ist aber {1,number,#0.00}
|
||||
office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-negative-but-is-{1,number,#0.00}=für transactionType={0}, muss assetValue negativ sein, ist aber {1,number,#0.00}
|
||||
office.coop-assets.given-assetvalue-{0,number,#0.00}-must-be-the-negative-value-of-the-reverted-asset-transaction-{1,number,#0.00}=assetValue={0,number,#0.00} muss dem negativen Wert des Wertes der stornierten Geschäftsguthaben-Transaktion entsprechen: {1,number,#0.00}
|
||||
office.coop-assets.a-reversal-asset-transaction-requires-specifying-a-revertedassettx-uuid=eine REVERSAL Geschäftsguthaben-Transaktion erfordert die Angabe einer revertedAssetTx.uuid
|
||||
office.coop-assets.transferring-and-adopting-membership-must-be-different-but-both-are-{0}=übertragende und annehmende Mitgliedschaft müssen unterschiedlich sein, aber beide sind {0}
|
||||
office.coop-assets.transaction-would-result-in-a-negative-balance-of-assets=Geschäftsguthaben-Transaktion würde zu einem negativen Geschäftsguthaben-Saldo führen
|
||||
|
||||
@@ -1,9 +1,39 @@
|
||||
# This file must be in UTF-8 encoding. Check if you see umlauts or garbage: äöüÄÖÜß
|
||||
# HINT IntelliJ IDEA shows unused keys in gray.
|
||||
|
||||
# If the English translation is identical to the translation-key, it does not need to be included here.
|
||||
# But in that case, you can NOT use a prefix - or the prefix would be shown to the user as well.
|
||||
# I'm not sure, though, if using the english default translations as keys is really a good idea.
|
||||
test.pinged--in-your-language=pinged - in English
|
||||
test.ponged-{0}--in-your-language=ponged {0} - in English
|
||||
test.available-in-all-properties-files=Hello {0} - EN!
|
||||
|
||||
# authorization
|
||||
auth.cas-service-ticket-could-not-be-verified=CAS service-ticket could not be verified
|
||||
auth.unknown-authorization-ticket=unknown authorization ticket
|
||||
|
||||
# general validations
|
||||
general.{0}-{1}-not-found={0} "{1}" not found
|
||||
general.{0}-{1}-not-found-or-not-accessible={0} "{1}" not found or not accessible
|
||||
general.but-is=but is
|
||||
|
||||
# credentials validations
|
||||
access-denied-personUuid-{0}-not-represented-by-currently-logged-in-person=access denied: personUuid "{0}" not represented by currently logged in person
|
||||
credentials.existing-{0}-does-not-match-given-resource-{1}=existing {0} does not match given resource {1}
|
||||
credentials.access-denied-person-uuid-{0}-not-represented-by-currently-logged-in-person=access denied: personUuid "{0}" not represented by currently logged in person
|
||||
|
||||
# office.coop-shares
|
||||
office.coop-shares.for-transactiontype-{0}-sharecount-must-be-positive-but-is-{1}=for transactiontType {0} shareCount must be positive but is {1}
|
||||
office.coop-shares.for-transactiontype-{0}-sharecount-must-be-negative-but-is-{1}=for transactiontType {0} shareCount must be negative but is {1}
|
||||
office.coop-shares.sharecount-must-not-be-0=shareCount must not be 0
|
||||
office.coop-shares.transaction-would-result-in-a-negative-number-of-shares=coop shares transaction would result in a negative number of shares
|
||||
|
||||
# office.coop-assets
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given=either {0} or {1} must be given
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given-not-both=either {0} or {1} must be given not both
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given-for-transactiontype-{2}=either {0} or {1} must be given for transactionType {2}
|
||||
office.coop-assets.neither-{0}-nor-{1}-must-be-given-for-transactiontype-{2}=neither {0} nor {1} must be given for transactionType {2}
|
||||
office.coop-assets.assetvalue-must-not-be-0=assetvalue must not be 0
|
||||
office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-positive-but-is-{1,number,#0.00}=for transactionType {0} assetValue must be positive but is {1,number,#0.00}
|
||||
office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-negative-but-is-{1,number,#0.00}=for transactionType {0} assetValue must be negative but is {1,number,#0.00}
|
||||
office.coop-assets.given-assetvalue-{0,number,#0.00}-must-be-the-negative-value-of-the-reverted-asset-transaction-{1,number,#0.00}=given assetValue {0,number,#0.00} must be the negative value of the reverted asset transaction {1,number,#0.00}
|
||||
office.coop-assets.a-reversal-asset-transaction-requires-specifying-a-revertedassettx-uuid=a reversal asset transaction requires specifying a revertedAssetTx uuid
|
||||
office.coop-assets.transferring-and-adopting-membership-must-be-different-but-both-are-{0}=transferring and adopting membership must be different but both are {0}
|
||||
office.coop-assets.transaction-would-result-in-a-negative-balance-of-assets=coop assets transaction would result in a negative balance of assets
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# This file must be in UTF-8 encoding. Check if you see umlauts or garbage: äöüÄÖÜß
|
||||
# HINT IntelliJ IDEA shows unused keys in gray.
|
||||
|
||||
# test.ponged--in-your-language=this translation is deliberately missing
|
||||
test.pinged--in-your-language=ponged {0} - en Francais
|
||||
test.available-in-all-properties-files=Salut {0} - FR!
|
||||
|
||||
# authorization
|
||||
auth.cas-service-ticket-could-not-be-verified=Le ticket de service CAS n'a pas pu être vérifié
|
||||
auth.unknown-authorization-ticket=ticket d'autorisation inconnu
|
||||
|
||||
# general validations
|
||||
general.{0}-{1}-not-found={0} "{1}" non trouvé
|
||||
general.{0}-{1}-not-found-or-not-accessible={0} "{1}" non trouvé ou non accessible
|
||||
general.but-is=mais c'est
|
||||
|
||||
# credentials validations
|
||||
credentials.existing-{0}-does-not-match-given-resource-{1}={0} existant ne correspond pas à la ressource donnée {1}
|
||||
credentials.access-denied-person-uuid-{0}-not-represented-by-currently-logged-in-person=accès refusé : personUuid "{0}" non représenté par la personne actuellement connectée
|
||||
|
||||
# office.coop-shares
|
||||
office.coop-shares.for-transactiontype-{0}-sharecount-must-be-positive-but-is-{1}=pour le type de transaction {0}, shareCount doit être positif mais est {1}
|
||||
office.coop-shares.for-transactiontype-{0}-sharecount-must-be-negative-but-is-{1}=pour le type de transaction {0}, shareCount doit être négatif mais est {1}
|
||||
office.coop-shares.sharecount-must-not-be-0=shareCount ne doit pas être 0
|
||||
office.coop-shares.transaction-would-result-in-a-negative-number-of-shares=la transaction de parts coopératives résulterait en un nombre négatif de parts
|
||||
|
||||
# office.coop-assets
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given=soit {0} soit {1} doit être fourni
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given-not-both=soit {0} soit {1} doit être fourni, pas les deux
|
||||
office.coop-assets.either-{0}-or-{1}-must-be-given-for-transactiontype-{2}=soit {0} soit {1} doit être fourni pour le type de transaction {2}
|
||||
office.coop-assets.neither-{0}-nor-{1}-must-be-given-for-transactiontype-{2}=ni {0} ni {1} ne doit être fourni pour le type de transaction {2}
|
||||
office.coop-assets.assetvalue-must-not-be-0=assetvalue ne doit pas être 0
|
||||
office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-positive-but-is-{1,number,#0.00}=pour le type de transaction {0}, assetValue doit être positif mais est {1,number,#0.00}
|
||||
office.coop-assets.for-transactiontype-{0}-assetvalue-must-be-negative-but-is-{1,number,#0.00}=pour le type de transaction {0}, assetValue doit être négatif mais est {1,number,#0.00}
|
||||
office.coop-assets.given-assetvalue-{0,number,#0.00}-must-be-the-negative-value-of-the-reverted-asset-transaction-{1,number,#0.00}=assetValue donné {0,number,#0.00} doit être la valeur négative de la transaction d'actif annulée {1,number,#0.00}
|
||||
office.coop-assets.a-reversal-asset-transaction-requires-specifying-a-revertedassettx-uuid=une transaction d'actif d'annulation nécessite de spécifier un uuid revertedAssetTx
|
||||
office.coop-assets.transferring-and-adopting-membership-must-be-different-but-both-are-{0}=l'adhésion transférant et adoptante doit être différente, mais les deux sont {0}
|
||||
office.coop-assets.transaction-would-result-in-a-negative-balance-of-assets=la transaction d'actifs coopératifs résulterait en un solde négatif d'actifs
|
||||
+2
-2
@@ -69,7 +69,7 @@ class CasAuthenticationFilterIntegrationTest {
|
||||
|
||||
// when
|
||||
final var result = restTemplate.exchange(
|
||||
"http://localhost:" + this.serverPort + "/api/ping",
|
||||
"http://localhost:" + this.serverPort + "/api/pong",
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(null, headers(entry("Authorization", "ST-valid"))),
|
||||
String.class
|
||||
@@ -77,7 +77,7 @@ class CasAuthenticationFilterIntegrationTest {
|
||||
|
||||
// then
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(result.getBody()).startsWith("pong " + username);
|
||||
assertThat(result.getBody()).startsWith("ponged " + username);
|
||||
// HOWTO assert log messages
|
||||
assertThat(capturedOutput.getOut()).containsPattern(
|
||||
LogbackLogPattern.of(LogLevel.DEBUG, RealCasAuthenticator.class, "CAS-user: " + username));
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import lombok.val;
|
||||
|
||||
@SpringBootTest(classes = {
|
||||
MessagesResourceConfig.class,
|
||||
MessageTranslator.class
|
||||
})
|
||||
@ActiveProfiles("test")
|
||||
@Tag("generalIntegrationTest")
|
||||
class MessageTranslatorIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext webApplicationContext;
|
||||
|
||||
@MockitoBean
|
||||
private Context contextMock; // avoiding dependency issues
|
||||
|
||||
@AllArgsConstructor
|
||||
enum TestCases {
|
||||
ENGLISH_KNOWN(Locale.ENGLISH, "test.ponged-{0}--in-your-language",
|
||||
"ponged testUser - in English"),
|
||||
ENGLISH_UNKNOWN(Locale.ENGLISH, "test.ponged-{0}--unknown-key",
|
||||
"【⍰ponged testUser - unknown key⍰】"),
|
||||
ENGLISH_US(Locale.of("en", "US"), "test.ponged-{0}--in-your-language",
|
||||
"ponged testUser - in English"),
|
||||
ENGLISH_UK(Locale.of("en", "UK"), "test.ponged-{0}--in-your-language",
|
||||
"ponged testUser - in English"),
|
||||
GERMAN_KNOWN(Locale.GERMAN, "test.ponged-{0}--in-your-language",
|
||||
"ponged testUser - auf Deutsch"),
|
||||
FRENCH_UNKNOWN_BUT_ENGLISH_KNOWN(Locale.FRENCH, "test.ponged-{0}--in-your-language",
|
||||
"【⍰ponged testUser - in English⍰】"),
|
||||
FRENCH_UNKNOWN_AND_ENGLISH_UNKNOWN(Locale.FRENCH, "test.ponged-{0}--unknown-key",
|
||||
"【⍰ponged testUser - unknown key⍰】"),
|
||||
UNKNOWN_LOCALE_AND_ENGLISH_KNOWN(Locale.TRADITIONAL_CHINESE,
|
||||
"test.ponged-{0}--in-your-language", "【⍰ponged testUser - in English⍰】"),
|
||||
UNKNOWN_LOCALE_AND_ENGLISH_UNKNOWN(Locale.TRADITIONAL_CHINESE, "test.ponged-{0}--unknown-key",
|
||||
"【⍰ponged testUser - unknown key⍰】");
|
||||
final Locale locale;
|
||||
final String messageKey;
|
||||
final String expectedTranslation;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(TestCases.class)
|
||||
void shouldHandleDifferentLocalesAppropriately(final TestCases testCase) {
|
||||
// given
|
||||
val messageTranslator = webApplicationContext.getBean(MessageTranslator.class);
|
||||
|
||||
// when
|
||||
val result = messageTranslator.translateTo(testCase.locale, testCase.messageKey, "testUser");
|
||||
|
||||
// then
|
||||
assertThat(result).isEqualTo(testCase.expectedTranslation);
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -67,14 +67,14 @@ class WebSecurityConfigIntegrationTest {
|
||||
|
||||
// http request
|
||||
final var result = restTemplate.exchange(
|
||||
"http://localhost:" + this.serverPort + "/api/ping",
|
||||
"http://localhost:" + this.serverPort + "/api/pong",
|
||||
HttpMethod.GET,
|
||||
httpHeaders(entry("Authorization", "Bearer ST-fake-cas-ticket")),
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(result.getBody()).startsWith("pong fake-user-name");
|
||||
assertThat(result.getBody()).startsWith("ponged fake-user-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,18 +85,18 @@ class WebSecurityConfigIntegrationTest {
|
||||
|
||||
// http request
|
||||
final var result = restTemplate.exchange(
|
||||
"http://localhost:" + this.serverPort + "/api/ping",
|
||||
"http://localhost:" + this.serverPort + "/api/pong",
|
||||
HttpMethod.GET,
|
||||
httpHeaders(entry("Authorization", "Bearer TGT-fake-cas-ticket")),
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(result.getBody()).startsWith("pong fake-user-name");
|
||||
assertThat(result.getBody()).startsWith("ponged fake-user-name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessToApiWithInvalidTicketGrantingTicketShouldBePermitted() {
|
||||
void accessToOpenApiWithInvalidTicketGrantingTicketShouldBePermitted() {
|
||||
// given
|
||||
givenCasServiceTicketForTicketGrantingTicket("TGT-fake-cas-ticket", "ST-fake-cas-ticket");
|
||||
givenCasTicketValidationResponse("ST-fake-cas-ticket", "fake-user-name");
|
||||
@@ -113,14 +113,14 @@ class WebSecurityConfigIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessToPingApiWithoutTokenShouldBePermitted() {
|
||||
void accessToOpenApiWithoutTokenShouldBePermitted() {
|
||||
final var result = this.restTemplate.getForEntity(
|
||||
"http://localhost:" + this.serverPort + "/api/ping", String.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessToPongApiWithValidTokenShouldBePermitted() {
|
||||
void accessToProtectedApiWithValidTokenShouldBePermitted() {
|
||||
// given
|
||||
givenCasTicketValidationResponse("ST-fake-cas-ticket", "fake-user-name");
|
||||
|
||||
@@ -137,7 +137,7 @@ class WebSecurityConfigIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessToPongApiWithInvalidTokenShouldBeDenied() {
|
||||
void accessToProtectedApiWithInvalidTokenShouldBeDenied() {
|
||||
// given
|
||||
givenCasTicketValidationResponse("ST-fake-cas-ticket", "fake-user-name");
|
||||
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@ class HsCredentialsControllerRestTest {
|
||||
given(rbacPersonRepo.findByUuid(personUuid)).willReturn(Optional.of(
|
||||
HsOfficePersonRbacEntity.builder().uuid(personUuid).personType(NATURAL_PERSON).build()
|
||||
));
|
||||
given(rbacPersonRepo.findPersonsrepresentedByPersonWithUuid(personUuid)).willReturn(List.of(
|
||||
given(rbacPersonRepo.findPersonsRepresentedByPersonWithUuid(personUuid)).willReturn(List.of(
|
||||
// some persons, but not the one from the login-user itself
|
||||
HsOfficePersonRbacEntity.builder().uuid(UUID.randomUUID()).personType(NATURAL_PERSON).build(),
|
||||
HsOfficePersonRbacEntity.builder().uuid(UUID.randomUUID()).personType(LEGAL_PERSON).build()
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||
import net.hostsharing.hsadminng.config.MessageTranslator;
|
||||
import net.hostsharing.hsadminng.config.MessagesResourceConfig;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
@@ -36,7 +37,7 @@ import static org.hamcrest.Matchers.startsWith;
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { HsadminNgApplication.class, DisableSecurityConfig.class, JpaAttempt.class,
|
||||
MessageTranslator.class}
|
||||
MessagesResourceConfig.class, MessageTranslator.class}
|
||||
)
|
||||
@ActiveProfiles("test")
|
||||
@Transactional
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.membership;
|
||||
|
||||
import io.hypersistence.utils.hibernate.type.range.Range;
|
||||
import net.hostsharing.hsadminng.config.MessageTranslator;
|
||||
import net.hostsharing.hsadminng.config.MessagesResourceConfig;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacEntity;
|
||||
@@ -41,7 +42,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@WebMvcTest(HsOfficeMembershipController.class)
|
||||
@Import({StrictMapper.class, DisableSecurityConfig.class, MessageTranslator.class})
|
||||
@Import({ StrictMapper.class, DisableSecurityConfig.class, MessagesResourceConfig.class, MessageTranslator.class})
|
||||
@ActiveProfiles("test")
|
||||
public class HsOfficeMembershipControllerRestTest {
|
||||
|
||||
|
||||
+2
-2
@@ -259,14 +259,14 @@ class HsOfficePersonRbacRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPersonsrepresentedByPersonWithUuid() {
|
||||
public void findPersonsRepresentedByPersonWithUuid() {
|
||||
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var personUuid = personRbacRepo.findPersonByOptionalNameLike("Fouler").getFirst().getUuid();
|
||||
|
||||
// when
|
||||
@SuppressWarnings("unchecked") final List<HsOfficePersonRbacEntity> representedPersons = personRbacRepo.findPersonsrepresentedByPersonWithUuid(personUuid);
|
||||
@SuppressWarnings("unchecked") final List<HsOfficePersonRbacEntity> representedPersons = personRbacRepo.findPersonsRepresentedByPersonWithUuid(personUuid);
|
||||
|
||||
// then
|
||||
assertThat(representedPersons).map(Object::toString).containsExactlyInAnyOrder(
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.config.DisableSecurityConfig;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -40,36 +41,52 @@ class PingControllerAcceptanceTest {
|
||||
@Autowired
|
||||
Context contextMock;
|
||||
|
||||
enum PingTranslationTestCase {
|
||||
EN(Locale.ENGLISH, "pong superuser-alex@hostsharing.net - in English"),
|
||||
DE(Locale.GERMAN, "pong superuser-alex@hostsharing.net - auf Deutsch"),
|
||||
FR(Locale.FRENCH, "pong superuser-alex@hostsharing.net - in English [fr translation missing]");
|
||||
enum PongTranslationTestCase {
|
||||
EN(Locale.ENGLISH, "ponged superuser-alex@hostsharing.net - in English"),
|
||||
DE(Locale.GERMAN, "ponged superuser-alex@hostsharing.net - auf Deutsch");
|
||||
|
||||
Locale givenLocale;
|
||||
CharSequence expectedPongTranslation;
|
||||
|
||||
PingTranslationTestCase(final Locale givenLocale, final String expectedPongTranslation) {
|
||||
PongTranslationTestCase(final Locale givenLocale, final String expectedPongTranslation) {
|
||||
this.givenLocale = givenLocale;
|
||||
this.expectedPongTranslation = expectedPongTranslation;
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(PingTranslationTestCase.class)
|
||||
void pingRepliesWithTranslatedPongResponse(final PingTranslationTestCase testCase) {
|
||||
@EnumSource(PongTranslationTestCase.class)
|
||||
void pongRepliesWithTranslatedPongResponse(final PongTranslationTestCase testCase) {
|
||||
final var responseBody = RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Authorization", "Bearer superuser-alex@hostsharing.net")
|
||||
.header("Accept-Language", testCase.givenLocale)
|
||||
.port(port)
|
||||
.when()
|
||||
.get("http://localhost/api/ping")
|
||||
.get("http://localhost/api/pong")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(200)
|
||||
.contentType("text/plain;charset=UTF-8")
|
||||
.extract().body().asString();
|
||||
// @formatter:on
|
||||
// @formatter:on
|
||||
|
||||
assertThat(responseBody).isEqualTo(testCase.expectedPongTranslation + "\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pingRepliesWithTranslatedPongResponse() {
|
||||
final var responseBody = RestAssured // @formatter:off
|
||||
.given()
|
||||
.header("Accept-Language", Locale.GERMAN)
|
||||
.port(port)
|
||||
.when()
|
||||
.get("http://localhost/api/ping")
|
||||
.then().log().all().assertThat()
|
||||
.statusCode(200)
|
||||
.contentType("text/plain;charset=UTF-8")
|
||||
.extract().body().asString();
|
||||
// @formatter:on
|
||||
|
||||
assertThat(responseBody).isEqualTo("pinged - auf Deutsch\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ class PingControllerRestTest {
|
||||
|
||||
@RequiredArgsConstructor
|
||||
enum I18nTestCases {
|
||||
EN("en", "pong anonymousUser - in English"),
|
||||
DE("de", "pong anonymousUser - auf Deutsch");
|
||||
EN("en", "pinged - in English"),
|
||||
DE("de", "pinged - auf Deutsch");
|
||||
|
||||
final String language;
|
||||
final String expectedTranslation;
|
||||
|
||||
Reference in New Issue
Block a user