From 5f4f50a325e14fcfdce2dbbc72db1d96ad5f0073 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 20 Oct 2022 07:41:48 +0200 Subject: [PATCH] hs-office-coopshares: add validation + RestTest for Controller + improve test data --- .../RestResponseEntityExceptionHandler.java | 3 +- ...OfficeCoopSharesTransactionController.java | 77 +++++--- .../hs-office-coopshares-schemas.yaml | 5 +- .../318-hs-office-coopshares-test-data.sql | 6 +- ...esTransactionControllerAcceptanceTest.java | 27 ++- ...opSharesTransactionControllerRestTest.java | 164 ++++++++++++++++++ ...sTransactionRepositoryIntegrationTest.java | 38 ++-- 7 files changed, 254 insertions(+), 66 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandler.java b/src/main/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandler.java index 2df06e1d..0d181752 100644 --- a/src/main/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandler.java +++ b/src/main/java/net/hostsharing/hsadminng/errors/RestResponseEntityExceptionHandler.java @@ -16,6 +16,7 @@ import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.persistence.EntityNotFoundException; +import javax.validation.ValidationException; import java.util.NoSuchElementException; import java.util.Optional; import java.util.regex.Pattern; @@ -57,7 +58,7 @@ public class RestResponseEntityExceptionHandler return errorResponse(request, HttpStatus.BAD_REQUEST, message); } - @ExceptionHandler(Iban4jException.class) + @ExceptionHandler({ Iban4jException.class, ValidationException.class }) protected ResponseEntity handleIbanAndBicExceptions( final Throwable exc, final WebRequest request) { final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage()); 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 bf6500da..8d4c973f 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 @@ -7,19 +7,23 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSh import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; -import javax.persistence.EntityManager; import javax.validation.Valid; +import javax.validation.ValidationException; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.function.BiConsumer; +import static java.lang.String.join; import static net.hostsharing.hsadminng.Mapper.map; +import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION; +import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION; @RestController @@ -31,27 +35,22 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar @Autowired private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; - @Autowired - private EntityManager em; - @Override @Transactional(readOnly = true) public ResponseEntity> listCoopShares( final String currentUser, final String assumedRoles, final UUID membershipUuid, - final @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fromValueDate, - final @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate toValueDate) { + final @DateTimeFormat(iso = ISO.DATE) LocalDate fromValueDate, + final @DateTimeFormat(iso = ISO.DATE) LocalDate toValueDate) { context.define(currentUser, assumedRoles); - final var entities = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( membershipUuid, fromValueDate, toValueDate); - final var resources = Mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class, - COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER); + final var resources = Mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class); return ResponseEntity.ok(resources); } @@ -60,14 +59,12 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar public ResponseEntity addCoopSharesTransaction( final String currentUser, final String assumedRoles, - @Valid final HsOfficeCoopSharesTransactionInsertResource body) { + @Valid final HsOfficeCoopSharesTransactionInsertResource requestBody) { context.define(currentUser, assumedRoles); + validate(requestBody); - final var entityToSave = map( - body, - HsOfficeCoopSharesTransactionEntity.class, - COOP_SHARES_RESOURCE_TO_ENTITY_POSTMAPPER); + final var entityToSave = map(requestBody, HsOfficeCoopSharesTransactionEntity.class); entityToSave.setUuid(UUID.randomUUID()); final var saved = coopSharesTransactionRepo.save(entityToSave); @@ -77,19 +74,47 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar .path("/api/hs/office/CoopSharesTransactions/{id}") .buildAndExpand(entityToSave.getUuid()) .toUri(); - final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class, - COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER); + final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class); return ResponseEntity.created(uri).body(mapped); } - final BiConsumer COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { - // resource.setValidFrom(entity.getValidity().lower()); - // if (entity.getValidity().hasUpperBound()) { - // resource.setValidTo(entity.getValidity().upper().minusDays(1)); - // } - }; + private void validate(final HsOfficeCoopSharesTransactionInsertResource requestBody) { + final var violations = new ArrayList(); + validateSubscriptionTransaction(requestBody, violations); + validateCancellationTransaction(requestBody, violations); + validateSharesCount(requestBody, violations); + if (violations.size() > 0) { + throw new ValidationException("[" + join(", ", violations) + "]"); + } + } + + private static void validateSubscriptionTransaction( + 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())); + } + } + + private static void validateCancellationTransaction( + 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())); + } + } + + private static void validateSharesCount( + final HsOfficeCoopSharesTransactionInsertResource requestBody, + final ArrayList violations) { + if (requestBody.getSharesCount() == 0) { + violations.add("sharesCount must not be 0 but is \"%d\"".formatted( + requestBody.getSharesCount())); + } + } - final BiConsumer COOP_SHARES_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { - // entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo())); - }; } 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 b71fe4bd..2f10d921 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 @@ -8,7 +8,7 @@ components: enum: - ADJUSTMENT - SUBSCRIPTION - - CANCELLATION; + - CANCELLATION HsOfficeCoopSharesTransaction: type: object @@ -44,9 +44,12 @@ components: format: date reference: type: string + minLength: 6 + maxLength: 48 comment: type: string required: + - membershipUuid - transactionType - sharesCount - valueDate diff --git a/src/main/resources/db/changelog/318-hs-office-coopshares-test-data.sql b/src/main/resources/db/changelog/318-hs-office-coopshares-test-data.sql index 9323b09e..c9252568 100644 --- a/src/main/resources/db/changelog/318-hs-office-coopshares-test-data.sql +++ b/src/main/resources/db/changelog/318-hs-office-coopshares-test-data.sql @@ -24,9 +24,9 @@ begin insert into hs_office_coopsharestransaction(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment) values - (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 2, 'ref '||givenMembershipNumber||'-1', 'initial subscription'), - (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2021-09-01', 24, 'ref '||givenMembershipNumber||'-2', 'subscibing more'), - (uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2022-10-20', 12, 'ref '||givenMembershipNumber||'-3', 'cancelling some'); + (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, 'ref '||givenMembershipNumber||'-1', 'initial subscription'), + (uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2021-09-01', -2, 'ref '||givenMembershipNumber||'-2', 'cancelling some'), + (uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-20', 2, 'ref '||givenMembershipNumber||'-3', 'some adjustment'); end; $$; --// 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 38a8d2e0..995f3ba0 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,24 +92,24 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { [ { "transactionType": "SUBSCRIPTION", - "sharesCount": 2, + "sharesCount": 4, "valueDate": "2010-03-15", "reference": "ref 10002-1", "comment": "initial subscription" }, { - "transactionType": "SUBSCRIPTION", - "sharesCount": 24, + "transactionType": "CANCELLATION", + "sharesCount": -2, "valueDate": "2021-09-01", "reference": "ref 10002-2", - "comment": "subscibing more" + "comment": "cancelling some" }, { - "transactionType": "CANCELLATION;", - "sharesCount": 12, + "transactionType": "ADJUSTMENT", + "sharesCount": 2, "valueDate": "2022-10-20", "reference": "ref 10002-3", - "comment": "cancelling some" + "comment": "some adjustment" } ] """)); // @formatter:on @@ -135,11 +135,11 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { .body("", lenientlyEquals(""" [ { - "transactionType": "SUBSCRIPTION", - "sharesCount": 24, + "transactionType": "CANCELLATION", + "sharesCount": -2, "valueDate": "2021-09-01", "reference": "ref 10002-2", - "comment": "subscibing more" + "comment": "cancelling some" } ] """)); // @formatter:on @@ -195,13 +195,6 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest { location.substring(location.lastIndexOf('/') + 1)); assertThat(newUserUuid).isNotNull(); } - - // TODO.test: move validation tests to a ...WebMvcTest - @Test - void globalAdmin_canNotAddCoopSharesTransactionWhenMembershipUuidIsMissing() { - - } - } @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 new file mode 100644 index 00000000..c46d9bee --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerRestTest.java @@ -0,0 +1,164 @@ +package net.hostsharing.hsadminng.hs.office.coopshares; + +import net.hostsharing.hsadminng.context.Context; +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.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.util.UUID; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(HsOfficeCoopSharesTransactionController.class) +class HsOfficeCoopSharesTransactionControllerRestTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + Context contextMock; + + @MockBean + HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; + + enum BadRequestTestCases { + MEMBERSHIP_UUID_MISSING( + """ + { + "transactionType": "SUBSCRIPTION", + "sharesCount": 8, + "valueDate": "2022-10-13", + "reference": "temp ref A" + } + """, + "[membershipUuid must not be null but is \"null\"]"), + + TRANSACTION_TYPE_MISSING( + """ + { + "membershipUuid": "%s", + "sharesCount": 8, + "valueDate": "2022-10-13", + "reference": "temp ref A" + } + """.formatted(UUID.randomUUID()), + "[transactionType must not be null but is \"null\"]"), + + VALUE_DATE_MISSING( + """ + { + "membershipUuid": "%s", + "transactionType": "SUBSCRIPTION", + "sharesCount": 8, + "reference": "temp ref A" + } + """.formatted(UUID.randomUUID()), + "[valueDate must not be null but is \"null\"]"), + + SHARES_COUNT_FOR_SUBSCRIPTION_MUST_BE_POSITIVE( + """ + { + "membershipUuid": "%s", + "transactionType": "SUBSCRIPTION", + "sharesCount": -1, + "valueDate": "2022-10-13", + "reference": "temp ref A" + } + """.formatted(UUID.randomUUID()), + "[for SUBSCRIPTION, sharesCount must be positive but is \"-1\"]"), + + SHARES_COUNT_FOR_CANCELLATION_MUST_BE_NEGATIVE( + """ + { + "membershipUuid": "%s", + "transactionType": "CANCELLATION", + "sharesCount": 1, + "valueDate": "2022-10-13", + "reference": "temp ref A" + } + """.formatted(UUID.randomUUID()), + "[for CANCELLATION, sharesCount must be negative but is \"1\"]"), + + SHARES_COUNT_MUST_NOT_BE_NULL( + """ + { + "membershipUuid": "%s", + "transactionType": "ADJUSTMENT", + "sharesCount": 0, + "valueDate": "2022-10-13", + "reference": "temp ref A" + } + """.formatted(UUID.randomUUID()), + "[sharesCount must not be 0 but is \"0\"]"), + + REFERENCE_MISSING( + """ + { + "membershipUuid": "%s", + "transactionType": "SUBSCRIPTION", + "sharesCount": 8, + "valueDate": "2022-10-13" + } + """.formatted(UUID.randomUUID()), + "[reference must not be null but is \"null\"]"), + + REFERENCE_TOO_SHORT( + """ + { + "membershipUuid": "%s", + "transactionType": "SUBSCRIPTION", + "sharesCount": 8, + "valueDate": "2022-10-13", + "reference": "12345" + } + """.formatted(UUID.randomUUID()), + "[reference size must be between 6 and 48 but is \"12345\"]"), + + REFERENCE_TOO_LONG( + """ + { + "membershipUuid": "%s", + "transactionType": "SUBSCRIPTION", + "sharesCount": 8, + "valueDate": "2022-10-13", + "reference": "0123456789012345678901234567890123456789012345678" + } + """.formatted(UUID.randomUUID()), + "[reference size must be between 6 and 48 but is \"0123456789012345678901234567890123456789012345678\"]"); + + private final String givenBody; + private final String expectedErrorMessage; + + BadRequestTestCases(final String givenBody, final String expectedErrorMessage) { + this.givenBody = givenBody; + this.expectedErrorMessage = expectedErrorMessage; + } + } + + @ParameterizedTest + @EnumSource(BadRequestTestCases.class) + void respondWithBadRequest(final BadRequestTestCases testCase) throws Exception { + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/hs/office/coopsharestransactions") + .header("current-user", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .content(testCase.givenBody) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is4xxClientError()) + .andExpect(jsonPath("status", is(400))) + .andExpect(jsonPath("error", is("Bad Request"))) + .andExpect(jsonPath("message", is(testCase.expectedErrorMessage))); + } + +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java index 3dea5491..035c026d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java @@ -147,24 +147,25 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 2, ref 10001-1)", - "CoopShareTransaction(10001, 2021-09-01, SUBSCRIPTION, 24, ref 10001-2)", - "CoopShareTransaction(10001, 2022-10-20, CANCELLATION, 12, ref 10001-3)", + "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)", + "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)", + "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)", - "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 2, ref 10002-1)", - "CoopShareTransaction(10002, 2021-09-01, SUBSCRIPTION, 24, ref 10002-2)", - "CoopShareTransaction(10002, 2022-10-20, CANCELLATION, 12, ref 10002-3)", + "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)", + "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)", + "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)", - "CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 2, ref 10003-1)", - "CoopShareTransaction(10003, 2021-09-01, SUBSCRIPTION, 24, ref 10003-2)", - "CoopShareTransaction(10003, 2022-10-20, CANCELLATION, 12, ref 10003-3)"); + "CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 4, ref 10003-1)", + "CoopShareTransaction(10003, 2021-09-01, CANCELLATION, -2, ref 10003-2)", + "CoopShareTransaction(10003, 2022-10-20, ADJUSTMENT, 2, ref 10003-3)"); } @Test public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002).get(0); + final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002) + .get(0); // when final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( @@ -175,16 +176,17 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 2, ref 10002-1)", - "CoopShareTransaction(10002, 2021-09-01, SUBSCRIPTION, 24, ref 10002-2)", - "CoopShareTransaction(10002, 2022-10-20, CANCELLATION, 12, ref 10002-3)"); + "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)", + "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)", + "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)"); } @Test public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002).get(0); + final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002) + .get(0); // when final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( @@ -195,7 +197,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10002, 2021-09-01, SUBSCRIPTION, 24, ref 10002-2)"); + "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)"); } @Test @@ -213,9 +215,9 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then: exactlyTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 2, ref 10001-1)", - "CoopShareTransaction(10001, 2021-09-01, SUBSCRIPTION, 24, ref 10001-2)", - "CoopShareTransaction(10001, 2022-10-20, CANCELLATION, 12, ref 10001-3)"); + "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)", + "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)", + "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)"); } }