add i18n support for CoopShareTx (#168)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/168 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
		| @@ -1,4 +1,4 @@ | |||||||
| package net.hostsharing.hsadminng.hs.hosting.asset; | package net.hostsharing.hsadminng.hs.office.coopassets; | ||||||
| 
 | 
 | ||||||
| import net.hostsharing.hsadminng.config.MessageTranslator; | import net.hostsharing.hsadminng.config.MessageTranslator; | ||||||
| import net.hostsharing.hsadminng.config.RetroactiveTranslator; | import net.hostsharing.hsadminng.config.RetroactiveTranslator; | ||||||
| @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service; | |||||||
| 
 | 
 | ||||||
| // HOWTO translate messages which got created without i18n support, in this case in a PostgreSQL constraint trigger | // HOWTO translate messages which got created without i18n support, in this case in a PostgreSQL constraint trigger | ||||||
| @Service | @Service | ||||||
| public class HsHostingAssetTranslations implements RetroactiveTranslator { | public class HsCoopAssetTranslations implements RetroactiveTranslator { | ||||||
| 
 | 
 | ||||||
|     public static final String ERROR_400_PREFIX = "ERROR: [400] "; |     public static final String ERROR_400_PREFIX = "ERROR: [400] "; | ||||||
| 
 | 
 | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | package net.hostsharing.hsadminng.hs.office.coopshares; | ||||||
|  |  | ||||||
|  | import net.hostsharing.hsadminng.config.MessageTranslator; | ||||||
|  | import net.hostsharing.hsadminng.config.RetroactiveTranslator; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | // HOWTO translate messages which got created without i18n support, in this case in a PostgreSQL constraint trigger | ||||||
|  | @Service | ||||||
|  | public class HsCoopShareTranslations implements RetroactiveTranslator { | ||||||
|  |  | ||||||
|  |     public static final String ERROR_400_PREFIX = "ERROR: [400] "; | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private MessageTranslator messageTranslator; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean canTranslate(final String message) { | ||||||
|  |         return message.equals("ERROR: [400] coop shares transaction would result in a negative number of shares"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String translate(final String message) { | ||||||
|  |         return ERROR_400_PREFIX + messageTranslator.translate(message.substring(ERROR_400_PREFIX.length())); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares; | |||||||
|  |  | ||||||
| import io.micrometer.core.annotation.Timed; | import io.micrometer.core.annotation.Timed; | ||||||
| import io.swagger.v3.oas.annotations.security.SecurityRequirement; | import io.swagger.v3.oas.annotations.security.SecurityRequirement; | ||||||
|  | import net.hostsharing.hsadminng.config.MessageTranslator; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.errors.MultiValidationException; | import net.hostsharing.hsadminng.errors.MultiValidationException; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi; | ||||||
| @@ -37,6 +38,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar | |||||||
|     @Autowired |     @Autowired | ||||||
|     private StrictMapper mapper; |     private StrictMapper mapper; | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private MessageTranslator messageTranslator; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; |     private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; | ||||||
|  |  | ||||||
| @@ -118,32 +122,31 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar | |||||||
|         MultiValidationException.throwIfNotEmpty(violations); |         MultiValidationException.throwIfNotEmpty(violations); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void validateSubscriptionTransaction( |     private void validateSubscriptionTransaction( | ||||||
|             final HsOfficeCoopSharesTransactionInsertResource requestBody, |             final HsOfficeCoopSharesTransactionInsertResource requestBody, | ||||||
|             final ArrayList<String> violations) { |             final ArrayList<String> violations) { | ||||||
|         if (requestBody.getTransactionType() == SUBSCRIPTION |         if (requestBody.getTransactionType() == SUBSCRIPTION | ||||||
|                 && requestBody.getShareCount() < 0) { |                 && requestBody.getShareCount() < 0) { | ||||||
|             violations.add("for %s, shareCount must be positive but is \"%d\"".formatted( |             violations.add(messageTranslator.translate("for transactionType={0}, shareCount must be positive but is {1}", | ||||||
|                     requestBody.getTransactionType(), requestBody.getShareCount())); |                     requestBody.getTransactionType(), requestBody.getShareCount())); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void validateCancellationTransaction( |     private void validateCancellationTransaction( | ||||||
|             final HsOfficeCoopSharesTransactionInsertResource requestBody, |             final HsOfficeCoopSharesTransactionInsertResource requestBody, | ||||||
|             final ArrayList<String> violations) { |             final ArrayList<String> violations) { | ||||||
|         if (requestBody.getTransactionType() == CANCELLATION |         if (requestBody.getTransactionType() == CANCELLATION | ||||||
|                 && requestBody.getShareCount() > 0) { |                 && requestBody.getShareCount() > 0) { | ||||||
|             violations.add("for %s, shareCount must be negative but is \"%d\"".formatted( |             violations.add(messageTranslator.translate("for transactionType={0}, shareCount must be negative but is {1}", | ||||||
|                     requestBody.getTransactionType(), requestBody.getShareCount())); |                     requestBody.getTransactionType(), requestBody.getShareCount())); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void validateshareCount( |     private void validateshareCount( | ||||||
|             final HsOfficeCoopSharesTransactionInsertResource requestBody, |             final HsOfficeCoopSharesTransactionInsertResource requestBody, | ||||||
|             final ArrayList<String> violations) { |             final ArrayList<String> violations) { | ||||||
|         if (requestBody.getShareCount() == 0) { |         if (requestBody.getShareCount() == 0) { | ||||||
|             violations.add("shareCount must not be 0 but is \"%d\"".formatted( |             violations.add(messageTranslator.translate("shareCount must not be 0")); | ||||||
|                     requestBody.getShareCount())); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,12 @@ unknown\ authorization\ ticket=unbekanntes Autorisierungs-Ticket | |||||||
| {0}\ "{1}"\ not\ found\ or\ not\ accessible={0} "{1}" nicht gefunden oder nicht zugänglich | {0}\ "{1}"\ not\ found\ or\ not\ accessible={0} "{1}" nicht gefunden oder nicht zugänglich | ||||||
| but\ is=ist aber | but\ is=ist aber | ||||||
|  |  | ||||||
|  | # 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-assets | # office.coop-assets | ||||||
| either\ {0}\ or\ {1}\ must\ be\ given=entweder {0} oder {1} muss angegeben werden | 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,\ not\ both=entweder {0} oder {1} muss angegeben werden, nicht beide | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares; | |||||||
| import io.restassured.RestAssured; | import io.restassured.RestAssured; | ||||||
| import io.restassured.http.ContentType; | import io.restassured.http.ContentType; | ||||||
| import net.hostsharing.hsadminng.HsadminNgApplication; | import net.hostsharing.hsadminng.HsadminNgApplication; | ||||||
|  | import net.hostsharing.hsadminng.config.MessageTranslator; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; | import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; | ||||||
| import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; | import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; | ||||||
| @@ -32,7 +33,7 @@ import static org.hamcrest.Matchers.hasSize; | |||||||
| import static org.hamcrest.Matchers.startsWith; | import static org.hamcrest.Matchers.startsWith; | ||||||
|  |  | ||||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, | ||||||
|         classes = {HsadminNgApplication.class, DisableSecurityConfig.class, JpaAttempt.class}) |         classes = {HsadminNgApplication.class, DisableSecurityConfig.class, MessageTranslator.class, JpaAttempt.class}) | ||||||
| @ActiveProfiles("test") | @ActiveProfiles("test") | ||||||
| @Transactional | @Transactional | ||||||
| @Tag("officeIntegrationTest") | @Tag("officeIntegrationTest") | ||||||
| @@ -180,7 +181,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nested |     @Nested | ||||||
|     class AddCoopSharesTransaction { |     class PostNewCoopSharesTransaction { | ||||||
|  |  | ||||||
|         @Test |         @Test | ||||||
|         void globalAdmin_canAddCoopSharesTransaction() { |         void globalAdmin_canAddCoopSharesTransaction() { | ||||||
| @@ -191,6 +192,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased | |||||||
|             final var location = RestAssured // @formatter:off |             final var location = RestAssured // @formatter:off | ||||||
|                 .given() |                 .given() | ||||||
|                     .header("Authorization", "Bearer superuser-alex@hostsharing.net") |                     .header("Authorization", "Bearer superuser-alex@hostsharing.net") | ||||||
|  |                     .header("Accept-Language", "de") | ||||||
|                     .contentType(ContentType.JSON).body(""" |                     .contentType(ContentType.JSON).body(""" | ||||||
|                        { |                        { | ||||||
|                            "membership.uuid": "%s", |                            "membership.uuid": "%s", | ||||||
| @@ -306,6 +308,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased | |||||||
|             RestAssured // @formatter:off |             RestAssured // @formatter:off | ||||||
|                 .given() |                 .given() | ||||||
|                     .header("Authorization", "Bearer superuser-alex@hostsharing.net") |                     .header("Authorization", "Bearer superuser-alex@hostsharing.net") | ||||||
|  |                     .header("Accept-Language", "de") | ||||||
|                     .contentType(ContentType.JSON) |                     .contentType(ContentType.JSON) | ||||||
|                     .body(""" |                     .body(""" | ||||||
|                         { |                         { | ||||||
| @@ -329,7 +332,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased | |||||||
|                         { |                         { | ||||||
|                             "statusCode": 400, |                             "statusCode": 400, | ||||||
|                             "statusPhrase": "Bad Request", |                             "statusPhrase": "Bad Request", | ||||||
|                             "message": "ERROR: [400] coop shares transaction would result in a negative number of shares" |                             "message": "ERROR: [400] Geschäftsanteile-Transaktion würde zu negativen Geschäftsanteilen führen" | ||||||
|                         } |                         } | ||||||
|                         """));  // @formatter:on |                         """));  // @formatter:on | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.hs.office.coopshares; | package net.hostsharing.hsadminng.hs.office.coopshares; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.config.MessageTranslator; | import net.hostsharing.hsadminng.config.MessageTranslator; | ||||||
|  | import net.hostsharing.hsadminng.config.MessagesResourceConfig; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; | import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository; | ||||||
| import net.hostsharing.hsadminng.mapper.StrictMapper; | import net.hostsharing.hsadminng.mapper.StrictMapper; | ||||||
| @@ -27,7 +28,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. | |||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(HsOfficeCoopSharesTransactionController.class) | @WebMvcTest(HsOfficeCoopSharesTransactionController.class) | ||||||
| @Import({DisableSecurityConfig.class, MessageTranslator.class}) | @Import({DisableSecurityConfig.class, | ||||||
|  |          MessagesResourceConfig.class, | ||||||
|  |          MessageTranslator.class}) | ||||||
| @ActiveProfiles("test") | @ActiveProfiles("test") | ||||||
| class HsOfficeCoopSharesTransactionControllerRestTest { | class HsOfficeCoopSharesTransactionControllerRestTest { | ||||||
|  |  | ||||||
| @@ -61,45 +64,45 @@ class HsOfficeCoopSharesTransactionControllerRestTest { | |||||||
|     enum BadRequestTestCases { |     enum BadRequestTestCases { | ||||||
|         MEMBERSHIP_UUID_MISSING( |         MEMBERSHIP_UUID_MISSING( | ||||||
|                 requestBody -> requestBody.without("membership.uuid"), |                 requestBody -> requestBody.without("membership.uuid"), | ||||||
|                 "membershipUuid must not be null"), |                 "membershipUuid darf nicht null sein ist aber null"), | ||||||
|  |  | ||||||
|         TRANSACTION_TYPE_MISSING( |         TRANSACTION_TYPE_MISSING( | ||||||
|                 requestBody -> requestBody.without("transactionType"), |                 requestBody -> requestBody.without("transactionType"), | ||||||
|                 "transactionType must not be null"), |                 "transactionType darf nicht null sein ist aber null"), | ||||||
|  |  | ||||||
|         VALUE_DATE_MISSING( |         VALUE_DATE_MISSING( | ||||||
|                 requestBody -> requestBody.without("valueDate"), |                 requestBody -> requestBody.without("valueDate"), | ||||||
|                 "valueDate must not be null"), |                 "valueDate darf nicht null sein ist aber null"), | ||||||
|  |  | ||||||
|         SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE( |         SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE( | ||||||
|                 requestBody -> requestBody |                 requestBody -> requestBody | ||||||
|                         .with("transactionType", "SUBSCRIPTION") |                         .with("transactionType", "SUBSCRIPTION") | ||||||
|                         .with("shareCount", -1), |                         .with("shareCount", -1), | ||||||
|                 "for SUBSCRIPTION, shareCount must be positive but is \"-1\""), |                 "für transactionType=SUBSCRIPTION, muss shareCount positiv sein, ist aber -1"), | ||||||
|  |  | ||||||
|         SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE( |         SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE( | ||||||
|                 requestBody -> requestBody |                 requestBody -> requestBody | ||||||
|                         .with("transactionType", "CANCELLATION") |                         .with("transactionType", "CANCELLATION") | ||||||
|                         .with("shareCount", 1), |                         .with("shareCount", 1), | ||||||
|                 "for CANCELLATION, shareCount must be negative but is \"1\""), |                 "für transactionType=CANCELLATION, muss shareCount negativ sein, ist aber 1"), | ||||||
|  |  | ||||||
|         SHARES_COUNT_MUST_NOT_BE_NULL( |         SHARES_COUNT_MUST_NOT_BE_NULL( | ||||||
|                 requestBody -> requestBody |                 requestBody -> requestBody | ||||||
|                         .with("transactionType", "REVERSAL") |                         .with("transactionType", "REVERSAL") | ||||||
|                         .with("shareCount", 0), |                         .with("shareCount", 0), | ||||||
|                 "shareCount must not be 0 but is \"0\""), |                 "shareCount darf nicht 0 sein"), | ||||||
|  |  | ||||||
|         REFERENCE_MISSING( |         REFERENCE_MISSING( | ||||||
|                 requestBody -> requestBody.without("reference"), |                 requestBody -> requestBody.without("reference"), | ||||||
|                 "reference must not be null"), |                 "reference darf nicht null sein ist aber null"), | ||||||
|  |  | ||||||
|         REFERENCE_TOO_SHORT( |         REFERENCE_TOO_SHORT( | ||||||
|                 requestBody -> requestBody.with("reference", "12345"), |                 requestBody -> requestBody.with("reference", "12345"), | ||||||
|                 "reference size must be between 6 and 48 but is \"12345\""), |                 "reference Größe muss zwischen 6 und 48 sein ist aber \"12345\""), | ||||||
|  |  | ||||||
|         REFERENCE_TOO_LONG( |         REFERENCE_TOO_LONG( | ||||||
|                 requestBody -> requestBody.with("reference", "0123456789012345678901234567890123456789012345678"), |                 requestBody -> requestBody.with("reference", "0123456789012345678901234567890123456789012345678"), | ||||||
|                 "reference size must be between 6 and 48 but is \"0123456789012345678901234567890123456789012345678\""); |                 "reference Größe muss zwischen 6 und 48 sein ist aber \"0123456789012345678901234567890123456789012345678\""); | ||||||
|  |  | ||||||
|         private final Function<JsonBuilder, JsonBuilder> givenBodyTransformation; |         private final Function<JsonBuilder, JsonBuilder> givenBodyTransformation; | ||||||
|         private final String expectedErrorMessage; |         private final String expectedErrorMessage; | ||||||
| @@ -124,6 +127,7 @@ class HsOfficeCoopSharesTransactionControllerRestTest { | |||||||
|         mockMvc.perform(MockMvcRequestBuilders |         mockMvc.perform(MockMvcRequestBuilders | ||||||
|                         .post("/api/hs/office/coopsharestransactions") |                         .post("/api/hs/office/coopsharestransactions") | ||||||
|                         .header("Authorization", "Bearer superuser-alex@hostsharing.net") |                         .header("Authorization", "Bearer superuser-alex@hostsharing.net") | ||||||
|  |                         .header("Accept-Language", "de") | ||||||
|                         .contentType(MediaType.APPLICATION_JSON) |                         .contentType(MediaType.APPLICATION_JSON) | ||||||
|                         .content(testCase.givenRequestBody()) |                         .content(testCase.givenRequestBody()) | ||||||
|                         .accept(MediaType.APPLICATION_JSON)) |                         .accept(MediaType.APPLICATION_JSON)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user