1
0

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:
Michael Hoennig
2025-08-26 15:28:42 +02:00
parent 2a6e86aca8
commit 68e642c034
29 changed files with 283 additions and 104 deletions
@@ -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);
}
}
@@ -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");
@@ -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()
@@ -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,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 {
@@ -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;