hs-office-coopshares: add validation + RestTest for Controller + improve test data

This commit is contained in:
Michael Hoennig 2022-10-20 07:41:48 +02:00
parent ca0589d084
commit 5f4f50a325
7 changed files with 254 additions and 66 deletions

View File

@ -16,6 +16,7 @@ import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.persistence.EntityNotFoundException; import javax.persistence.EntityNotFoundException;
import javax.validation.ValidationException;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -57,7 +58,7 @@ public class RestResponseEntityExceptionHandler
return errorResponse(request, HttpStatus.BAD_REQUEST, message); return errorResponse(request, HttpStatus.BAD_REQUEST, message);
} }
@ExceptionHandler(Iban4jException.class) @ExceptionHandler({ Iban4jException.class, ValidationException.class })
protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions( protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
final Throwable exc, final WebRequest request) { final Throwable exc, final WebRequest request) {
final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage()); final var message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());

View File

@ -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 net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import javax.persistence.EntityManager;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.ValidationException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; 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.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 @RestController
@ -31,27 +35,22 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
@Autowired @Autowired
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
@Autowired
private EntityManager em;
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> listCoopShares( public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> listCoopShares(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID membershipUuid, final UUID membershipUuid,
final @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fromValueDate, final @DateTimeFormat(iso = ISO.DATE) LocalDate fromValueDate,
final @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate toValueDate) { final @DateTimeFormat(iso = ISO.DATE) LocalDate toValueDate) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var entities = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var entities = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(
membershipUuid, membershipUuid,
fromValueDate, fromValueDate,
toValueDate); toValueDate);
final var resources = Mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class, final var resources = Mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class);
COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
} }
@ -60,14 +59,12 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction( public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
@Valid final HsOfficeCoopSharesTransactionInsertResource body) { @Valid final HsOfficeCoopSharesTransactionInsertResource requestBody) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
validate(requestBody);
final var entityToSave = map( final var entityToSave = map(requestBody, HsOfficeCoopSharesTransactionEntity.class);
body,
HsOfficeCoopSharesTransactionEntity.class,
COOP_SHARES_RESOURCE_TO_ENTITY_POSTMAPPER);
entityToSave.setUuid(UUID.randomUUID()); entityToSave.setUuid(UUID.randomUUID());
final var saved = coopSharesTransactionRepo.save(entityToSave); final var saved = coopSharesTransactionRepo.save(entityToSave);
@ -77,19 +74,47 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
.path("/api/hs/office/CoopSharesTransactions/{id}") .path("/api/hs/office/CoopSharesTransactions/{id}")
.buildAndExpand(entityToSave.getUuid()) .buildAndExpand(entityToSave.getUuid())
.toUri(); .toUri();
final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class, final var mapped = map(saved, HsOfficeCoopSharesTransactionResource.class);
COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
final BiConsumer<HsOfficeCoopSharesTransactionEntity, HsOfficeCoopSharesTransactionResource> COOP_SHARES_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { private void validate(final HsOfficeCoopSharesTransactionInsertResource requestBody) {
// resource.setValidFrom(entity.getValidity().lower()); final var violations = new ArrayList<String>();
// if (entity.getValidity().hasUpperBound()) { validateSubscriptionTransaction(requestBody, violations);
// resource.setValidTo(entity.getValidity().upper().minusDays(1)); validateCancellationTransaction(requestBody, violations);
// } validateSharesCount(requestBody, violations);
}; if (violations.size() > 0) {
throw new ValidationException("[" + join(", ", violations) + "]");
}
}
private static void validateSubscriptionTransaction(
final HsOfficeCoopSharesTransactionInsertResource requestBody,
final ArrayList<String> 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<String> 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<String> violations) {
if (requestBody.getSharesCount() == 0) {
violations.add("sharesCount must not be 0 but is \"%d\"".formatted(
requestBody.getSharesCount()));
}
}
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> COOP_SHARES_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
// entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
};
} }

View File

@ -8,7 +8,7 @@ components:
enum: enum:
- ADJUSTMENT - ADJUSTMENT
- SUBSCRIPTION - SUBSCRIPTION
- CANCELLATION; - CANCELLATION
HsOfficeCoopSharesTransaction: HsOfficeCoopSharesTransaction:
type: object type: object
@ -44,9 +44,12 @@ components:
format: date format: date
reference: reference:
type: string type: string
minLength: 6
maxLength: 48
comment: comment:
type: string type: string
required: required:
- membershipUuid
- transactionType - transactionType
- sharesCount - sharesCount
- valueDate - valueDate

View File

@ -24,9 +24,9 @@ begin
insert insert
into hs_office_coopsharestransaction(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment) into hs_office_coopsharestransaction(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment)
values values
(uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 2, 'ref '||givenMembershipNumber||'-1', 'initial subscription'), (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, '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', '2021-09-01', -2, 'ref '||givenMembershipNumber||'-2', 'cancelling some'),
(uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2022-10-20', 12, 'ref '||givenMembershipNumber||'-3', 'cancelling some'); (uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-20', 2, 'ref '||givenMembershipNumber||'-3', 'some adjustment');
end; $$; end; $$;
--// --//

View File

@ -92,24 +92,24 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
[ [
{ {
"transactionType": "SUBSCRIPTION", "transactionType": "SUBSCRIPTION",
"sharesCount": 2, "sharesCount": 4,
"valueDate": "2010-03-15", "valueDate": "2010-03-15",
"reference": "ref 10002-1", "reference": "ref 10002-1",
"comment": "initial subscription" "comment": "initial subscription"
}, },
{ {
"transactionType": "SUBSCRIPTION", "transactionType": "CANCELLATION",
"sharesCount": 24, "sharesCount": -2,
"valueDate": "2021-09-01", "valueDate": "2021-09-01",
"reference": "ref 10002-2", "reference": "ref 10002-2",
"comment": "subscibing more" "comment": "cancelling some"
}, },
{ {
"transactionType": "CANCELLATION;", "transactionType": "ADJUSTMENT",
"sharesCount": 12, "sharesCount": 2,
"valueDate": "2022-10-20", "valueDate": "2022-10-20",
"reference": "ref 10002-3", "reference": "ref 10002-3",
"comment": "cancelling some" "comment": "some adjustment"
} }
] ]
""")); // @formatter:on """)); // @formatter:on
@ -135,11 +135,11 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"transactionType": "SUBSCRIPTION", "transactionType": "CANCELLATION",
"sharesCount": 24, "sharesCount": -2,
"valueDate": "2021-09-01", "valueDate": "2021-09-01",
"reference": "ref 10002-2", "reference": "ref 10002-2",
"comment": "subscibing more" "comment": "cancelling some"
} }
] ]
""")); // @formatter:on """)); // @formatter:on
@ -195,13 +195,6 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
location.substring(location.lastIndexOf('/') + 1)); location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isNotNull(); assertThat(newUserUuid).isNotNull();
} }
// TODO.test: move validation tests to a ...WebMvcTest
@Test
void globalAdmin_canNotAddCoopSharesTransactionWhenMembershipUuidIsMissing() {
}
} }
@BeforeEach @BeforeEach

View File

@ -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)));
}
}

View File

@ -147,24 +147,25 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 2, ref 10001-1)", "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)",
"CoopShareTransaction(10001, 2021-09-01, SUBSCRIPTION, 24, ref 10001-2)", "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)",
"CoopShareTransaction(10001, 2022-10-20, CANCELLATION, 12, ref 10001-3)", "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)",
"CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 2, ref 10002-1)", "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)",
"CoopShareTransaction(10002, 2021-09-01, SUBSCRIPTION, 24, ref 10002-2)", "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)",
"CoopShareTransaction(10002, 2022-10-20, CANCELLATION, 12, ref 10002-3)", "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)",
"CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 2, ref 10003-1)", "CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 4, ref 10003-1)",
"CoopShareTransaction(10003, 2021-09-01, SUBSCRIPTION, 24, ref 10003-2)", "CoopShareTransaction(10003, 2021-09-01, CANCELLATION, -2, ref 10003-2)",
"CoopShareTransaction(10003, 2022-10-20, CANCELLATION, 12, ref 10003-3)"); "CoopShareTransaction(10003, 2022-10-20, ADJUSTMENT, 2, ref 10003-3)");
} }
@Test @Test
public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() { public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002).get(0); final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002)
.get(0);
// when // when
final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(
@ -175,16 +176,17 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 2, ref 10002-1)", "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)",
"CoopShareTransaction(10002, 2021-09-01, SUBSCRIPTION, 24, ref 10002-2)", "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)",
"CoopShareTransaction(10002, 2022-10-20, CANCELLATION, 12, ref 10002-3)"); "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)");
} }
@Test @Test
public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() { public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002).get(0); final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002)
.get(0);
// when // when
final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(
@ -195,7 +197,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10002, 2021-09-01, SUBSCRIPTION, 24, ref 10002-2)"); "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)");
} }
@Test @Test
@ -213,9 +215,9 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then: // then:
exactlyTheseCoopSharesTransactionsAreReturned( exactlyTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 2, ref 10001-1)", "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)",
"CoopShareTransaction(10001, 2021-09-01, SUBSCRIPTION, 24, ref 10001-2)", "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)",
"CoopShareTransaction(10001, 2022-10-20, CANCELLATION, 12, ref 10001-3)"); "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)");
} }
} }