From dc0835fa25dbb8904bbe789bd1789a029405b763 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 20 Oct 2022 14:08:19 +0200 Subject: [PATCH] hs-office-coopshares: add non-negative validation --- ...OfficeCoopSharesTransactionController.java | 25 +++++----- .../hs-office-coopshares-schemas.yaml | 6 +-- .../db/changelog/310-hs-office-coopshares.sql | 27 +++++++++++ ...esTransactionControllerAcceptanceTest.java | 48 ++++++++++++++++--- ...opSharesTransactionControllerRestTest.java | 14 +++--- ...OfficeCoopSharesTransactionEntityTest.java | 8 ++-- 6 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java index 8d4c973f..9c8a6c64 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java @@ -26,7 +26,6 @@ import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOffic import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION; @RestController - public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi { @Autowired @@ -71,7 +70,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar final var uri = MvcUriComponentsBuilder.fromController(getClass()) - .path("/api/hs/office/CoopSharesTransactions/{id}") + .path("/api/hs/office/coopsharestransactions/{id}") .buildAndExpand(entityToSave.getUuid()) .toUri(); final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class); @@ -82,7 +81,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar final var violations = new ArrayList(); validateSubscriptionTransaction(requestBody, violations); validateCancellationTransaction(requestBody, violations); - validateSharesCount(requestBody, violations); + validateshareCount(requestBody, violations); if (violations.size() > 0) { throw new ValidationException("[" + join(", ", violations) + "]"); } @@ -92,9 +91,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar final HsOfficeCoopSharesTransactionInsertResource requestBody, final ArrayList violations) { if (requestBody.getTransactionType() == SUBSCRIPTION - && requestBody.getSharesCount() < 0) { - violations.add("for %s, sharesCount must be positive but is \"%d\"".formatted( - requestBody.getTransactionType(), requestBody.getSharesCount())); + && requestBody.getShareCount() < 0) { + violations.add("for %s, shareCount must be positive but is \"%d\"".formatted( + requestBody.getTransactionType(), requestBody.getShareCount())); } } @@ -102,18 +101,18 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar final HsOfficeCoopSharesTransactionInsertResource requestBody, final ArrayList violations) { if (requestBody.getTransactionType() == CANCELLATION - && requestBody.getSharesCount() > 0) { - violations.add("for %s, sharesCount must be negative but is \"%d\"".formatted( - requestBody.getTransactionType(), requestBody.getSharesCount())); + && requestBody.getShareCount() > 0) { + violations.add("for %s, shareCount must be negative but is \"%d\"".formatted( + requestBody.getTransactionType(), requestBody.getShareCount())); } } - private static void validateSharesCount( + private static void validateshareCount( final HsOfficeCoopSharesTransactionInsertResource requestBody, final ArrayList violations) { - if (requestBody.getSharesCount() == 0) { - violations.add("sharesCount must not be 0 but is \"%d\"".formatted( - requestBody.getSharesCount())); + if (requestBody.getShareCount() == 0) { + violations.add("shareCount must not be 0 but is \"%d\"".formatted( + requestBody.getShareCount())); } } diff --git a/src/main/resources/api-definition/hs-office/hs-office-coopshares-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-coopshares-schemas.yaml index 2f10d921..e20786da 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-coopshares-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-coopshares-schemas.yaml @@ -18,7 +18,7 @@ components: format: uuid transactionType: $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' - sharesCount: + shareCount: type: integer valueDate: type: string @@ -37,7 +37,7 @@ components: nullable: false transactionType: $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' - sharesCount: + shareCount: type: integer valueDate: type: string @@ -51,7 +51,7 @@ components: required: - membershipUuid - transactionType - - sharesCount + - shareCount - valueDate - reference additionalProperties: false diff --git a/src/main/resources/db/changelog/310-hs-office-coopshares.sql b/src/main/resources/db/changelog/310-hs-office-coopshares.sql index b6635c9a..4ba70ecc 100644 --- a/src/main/resources/db/changelog/310-hs-office-coopshares.sql +++ b/src/main/resources/db/changelog/310-hs-office-coopshares.sql @@ -20,6 +20,33 @@ create table if not exists hs_office_coopsharestransaction ); --// +-- ============================================================================ +--changeset hs-office-coopshares-SHARE-COUNT-CONSTRAINT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +create or replace function checkSharesByMembershipUuid(forMembershipUuid UUID, newShareCount integer) +returns boolean +language plpgsql as $$ +declare + currentShareCount integer; + totalShareCount integer; +begin + select sum(cst.shareCount) + from hs_office_coopsharestransaction cst + where cst.membershipUuid = forMembershipUuid + into currentShareCount; + totalShareCount := currentShareCount + newShareCount; + if totalShareCount < 0 then + raise exception '[400] coop shares transaction would result in a negative number of shares'; + end if; + return true; +end; $$; + +alter table hs_office_coopsharestransaction + add constraint hs_office_coopshares_positive + check ( checkSharesByMembershipUuid(membershipUuid, shareCount) ); + +--// -- ============================================================================ --changeset hs-office-coopshares-MAIN-TABLE-JOURNAL:1 endDelimiter:--// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java index 995f3ba0..d2ca7606 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java @@ -92,21 +92,21 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { [ { "transactionType": "SUBSCRIPTION", - "sharesCount": 4, + "shareCount": 4, "valueDate": "2010-03-15", "reference": "ref 10002-1", "comment": "initial subscription" }, { "transactionType": "CANCELLATION", - "sharesCount": -2, + "shareCount": -2, "valueDate": "2021-09-01", "reference": "ref 10002-2", "comment": "cancelling some" }, { "transactionType": "ADJUSTMENT", - "sharesCount": 2, + "shareCount": 2, "valueDate": "2022-10-20", "reference": "ref 10002-3", "comment": "some adjustment" @@ -136,7 +136,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { [ { "transactionType": "CANCELLATION", - "sharesCount": -2, + "shareCount": -2, "valueDate": "2021-09-01", "reference": "ref 10002-2", "comment": "cancelling some" @@ -165,7 +165,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { { "membershipUuid": "%s", "transactionType": "SUBSCRIPTION", - "sharesCount": 8, + "shareCount": 8, "valueDate": "2022-10-13", "reference": "temp ref A", "comment": "just some test coop shares transaction" @@ -181,7 +181,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { .body("", lenientlyEquals(""" { "transactionType": "SUBSCRIPTION", - "sharesCount": 0, + "shareCount": 8, "valueDate": "2022-10-13", "reference": "temp ref A", "comment": "just some test coop shares transaction" @@ -195,6 +195,42 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { location.substring(location.lastIndexOf('/') + 1)); assertThat(newUserUuid).isNotNull(); } + + @Test + void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() { + + context.define("superuser-alex@hostsharing.net"); + final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001) + .get(0); + + final var location = RestAssured // @formatter:off + .given() + .header("current-user", "superuser-alex@hostsharing.net") + .contentType(ContentType.JSON) + .body(""" + { + "membershipUuid": "%s", + "transactionType": "CANCELLATION", + "shareCount": -80, + "valueDate": "2022-10-13", + "reference": "temp ref X", + "comment": "just some test coop shares transaction" + } + """.formatted(givenMembership.getUuid())) + .port(port) + .when() + .post("http://localhost/api/hs/office/coopsharestransactions") + .then().log().all().assertThat() + .statusCode(400) + .contentType(ContentType.JSON) + .body("", lenientlyEquals(""" + { + "status": 400, + "error": "Bad Request", + "message": "ERROR: [400] coop shares transaction would result in a negative number of shares" + } + """)); // @formatter:on + } } @BeforeEach diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java index 730f7421..66594a22 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java @@ -35,7 +35,7 @@ class HsOfficeCoopSharesTransactionControllerRestTest { { "membershipUuid": "%s", "transactionType": "SUBSCRIPTION", - "sharesCount": 8, + "shareCount": 8, "valueDate": "2022-10-13", "reference": "valid reference", "comment": "valid comment" @@ -58,20 +58,20 @@ class HsOfficeCoopSharesTransactionControllerRestTest { SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE( requestBody -> requestBody .with("transactionType", "SUBSCRIPTION") - .with("sharesCount", -1), - "[for SUBSCRIPTION, sharesCount must be positive but is \"-1\"]"), + .with("shareCount", -1), + "[for SUBSCRIPTION, shareCount must be positive but is \"-1\"]"), SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE( requestBody -> requestBody .with("transactionType", "CANCELLATION") - .with("sharesCount", 1), - "[for CANCELLATION, sharesCount must be negative but is \"1\"]"), + .with("shareCount", 1), + "[for CANCELLATION, shareCount must be negative but is \"1\"]"), SHARES_COUNT_MUST_NOT_BE_NULL( requestBody -> requestBody .with("transactionType", "ADJUSTMENT") - .with("sharesCount", 0), - "[sharesCount must not be 0 but is \"0\"]"), + .with("shareCount", 0), + "[shareCount must not be 0 but is \"0\"]"), REFERENCE_MISSING( requestBody -> requestBody.without("reference"), diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityTest.java index 3bb43f56..63171f53 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityTest.java @@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; class HsOfficeCoopSharesTransactionEntityTest { - final HsOfficeCoopSharesTransactionEntity givenSepaMandate = HsOfficeCoopSharesTransactionEntity.builder() + final HsOfficeCoopSharesTransactionEntity givenCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder() .membership(testMembership) .reference("some-ref") .valueDate(LocalDate.parse("2020-01-01")) @@ -19,14 +19,14 @@ class HsOfficeCoopSharesTransactionEntityTest { @Test void toStringContainsAlmostAllPropertiesAccount() { - final var result = givenSepaMandate.toString(); + final var result = givenCoopSharesTransaction.toString(); assertThat(result).isEqualTo("CoopShareTransaction(300001, 2020-01-01, SUBSCRIPTION, 4, some-ref)"); } @Test - void toShortStringContainsOnlyMemberNumberAndSharesCountOnly() { - final var result = givenSepaMandate.toShortString(); + void toShortStringContainsOnlyMemberNumberAndshareCountOnly() { + final var result = givenCoopSharesTransaction.toShortString(); assertThat(result).isEqualTo("300001+4"); }