diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java index 1e7eeade..8dc0dacc 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.UUID; import java.util.function.BiConsumer; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionTypeResource.CLEARING; import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionTypeResource.DEPOSIT; import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionTypeResource.DISBURSAL; @@ -147,23 +148,37 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse } final BiConsumer ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { + resource.setMembershipUuid(entity.getMembership().getUuid()); + resource.setMembershipMemberNumber(entity.getMembership().getTaggedMemberNumber()); + // FIXME: extract to one method to be used in all 4 cases if (entity.getReversalAssetTx() != null) { resource.getReversalAssetTx().setRevertedAssetTxUuid(entity.getUuid()); + resource.getReversalAssetTx().setMembershipUuid(entity.getRevertedAssetTx().getMembership().getUuid()); + resource.getReversalAssetTx().setMembershipMemberNumber(entity.getRevertedAssetTx().getTaggedMemberNumber()); } if (entity.getRevertedAssetTx() != null) { resource.getRevertedAssetTx().setReversalAssetTxUuid(entity.getUuid()); + resource.getRevertedAssetTx().setMembershipUuid(entity.getReversalAssetTx().getMembership().getUuid()); + resource.getRevertedAssetTx().setMembershipMemberNumber(entity.getReversalAssetTx().getTaggedMemberNumber()); } if (entity.getAdoptionAssetTx() != null) { resource.getAdoptionAssetTx().setTransferAssetTxUuid(entity.getUuid()); + resource.getAdoptionAssetTx().setMembershipUuid(entity.getAdoptionAssetTx().getMembership().getUuid()); + resource.getAdoptionAssetTx().setMembershipMemberNumber(entity.getAdoptionAssetTx().getTaggedMemberNumber()); } if (entity.getTransferAssetTx() != null) { resource.getTransferAssetTx().setAdoptionAssetTxUuid(entity.getUuid()); + resource.getTransferAssetTx().setMembershipUuid(entity.getTransferAssetTx().getMembership().getUuid()); + resource.getTransferAssetTx().setMembershipMemberNumber(entity.getTransferAssetTx().getTaggedMemberNumber()); } }; final BiConsumer RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { if (resource.getMembershipUuid() != null) { - entity.setMembership(emw.getReference(HsOfficeMembershipEntity.class, resource.getMembershipUuid())); + final HsOfficeMembershipEntity membership = ofNullable(emw.find(HsOfficeMembershipEntity.class, resource.getMembershipUuid())) + .orElseThrow(() -> new EntityNotFoundException("ERROR: [400] membership.uuid %s not found".formatted( + resource.getMembershipUuid()))); + entity.setMembership(membership); } if (resource.getRevertedAssetTxUuid() != null) { entity.setRevertedAssetTx(coopAssetsTransactionRepo.findByUuid(resource.getRevertedAssetTxUuid()) diff --git a/src/main/resources/api-definition/hs-office/hs-office-coopassets-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-coopassets-schemas.yaml index 4474b221..67da1790 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-coopassets-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-coopassets-schemas.yaml @@ -20,6 +20,15 @@ components: uuid: type: string format: uuid + membership.uuid: + type: string + format: uuid + nullable: false + membership.memberNumber: + type: string + minLength: 9 + maxLength: 9 + pattern: 'M-[0-9]{7}' transactionType: $ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType' assetValue: @@ -53,6 +62,15 @@ components: uuid: type: string format: uuid + membership.uuid: + type: string + format: uuid + nullable: false + membership.memberNumber: + type: string + minLength: 9 + maxLength: 9 + pattern: 'M-[0-9]{7}' transactionType: $ref: '#/components/schemas/HsOfficeCoopAssetsTransactionType' assetValue: 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 8d190634..8b641d90 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 @@ -16,6 +16,10 @@ components: uuid: type: string format: uuid + membership.uuid: + type: string + format: uuid + nullable: false transactionType: $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' shareCount: @@ -41,6 +45,10 @@ components: uuid: type: string format: uuid + membership.uuid: + type: string + format: uuid + nullable: false transactionType: $ref: '#/components/schemas/HsOfficeCoopSharesTransactionType' shareCount: diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java index dff6f356..dd50594c 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerRestTest.java @@ -8,6 +8,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.mapper.StrictMapper; import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; import net.hostsharing.hsadminng.rbac.test.JsonBuilder; +import net.hostsharing.hsadminng.test.TestUuidGenerator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -25,10 +26,12 @@ import java.util.Optional; import java.util.UUID; import java.util.function.Function; +import static net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionControllerRestTest.SuccessfullyCreatedTestCases.ADOPTING_MEMBERSHIP_UUID_FOR_TRANSFER_MUST_BE_GIVEN_AND_AVAILABLE; import static net.hostsharing.hsadminng.rbac.test.JsonBuilder.jsonObject; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -38,17 +41,30 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @RunWith(SpringRunner.class) class HsOfficeCoopAssetsTransactionControllerRestTest { - private static final UUID AVAILABLE_MEMBERSHIP_UUID = UUID.randomUUID(); - public static final HsOfficeMembershipEntity AVAILABLE_MEMBER_ENTITY = HsOfficeMembershipEntity.builder() - .uuid(AVAILABLE_MEMBERSHIP_UUID) - .partner(HsOfficePartnerEntity.builder() - .partnerNumber(12345) - .build()) - .memberNumberSuffix("00") - .build(); - private static final UUID UNAVAILABLE_MEMBERSHIP_UUID = UUID.randomUUID(); + private static final UUID UNAVAILABLE_MEMBERSHIP_UUID = TestUuidGenerator.get(0); private static final String UNAVAILABLE_MEMBER_NUMBER = "M-1234699"; - private static final String AVAILABLE_MEMBER_NUMBER = "M-1234600"; + + private static final UUID ORIGIN_MEMBERSHIP_UUID = TestUuidGenerator.get(1); + private static final String ORIGIN_MEMBER_NUMBER = "M-1111100"; + public final HsOfficeMembershipEntity ORIGIN_TARGET_MEMBER_ENTITY = HsOfficeMembershipEntity.builder() + .uuid(ORIGIN_MEMBERSHIP_UUID) + .partner(HsOfficePartnerEntity.builder() + .partnerNumber(partnerNumberOf(ORIGIN_MEMBER_NUMBER)) + .build()) + .memberNumberSuffix(suffixOf(ORIGIN_MEMBER_NUMBER)) + .build(); + + private static final UUID AVAILABLE_TARGET_MEMBERSHIP_UUID = TestUuidGenerator.get(2); + private static final String AVAILABLE_TARGET_MEMBER_NUMBER = "M-1234500"; + public final HsOfficeMembershipEntity AVAILABLE_MEMBER_ENTITY = HsOfficeMembershipEntity.builder() + .uuid(AVAILABLE_TARGET_MEMBERSHIP_UUID) + .partner(HsOfficePartnerEntity.builder() + .partnerNumber(partnerNumberOf(AVAILABLE_TARGET_MEMBER_NUMBER)) + .build()) + .memberNumberSuffix(suffixOf(AVAILABLE_TARGET_MEMBER_NUMBER)) + .build(); + + private static final UUID NEW_EXPLICITLY_CREATED_ASSET_TX_UUID = TestUuidGenerator.get(3); @Autowired MockMvc mockMvc; @@ -61,8 +77,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { StrictMapper mapper; @MockBean - @SuppressWarnings("unused") // not used in test, but in base-class of StrictMapper - EntityManagerWrapper em; + EntityManagerWrapper emw; // even if not used in test anymore, it's needed by base-class of StrictMapper @MockBean HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; @@ -70,7 +85,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { @MockBean HsOfficeMembershipRepository membershipRepo; - static final String VALID_INSERT_REQUEST_BODY = """ + static final String INSERT_REQUEST_BODY_TEMPLATE = """ { "membership.uuid": "%s", "transactionType": "DEPOSIT", @@ -81,7 +96,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { "adoptingMembership.uuid": null, "adoptingMembership.memberNumber": null } - """.formatted(UUID.randomUUID()); + """.formatted(ORIGIN_MEMBERSHIP_UUID); enum BadRequestTestCases { MEMBERSHIP_UUID_MISSING( @@ -153,14 +168,16 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { } String givenRequestBody() { - return givenBodyTransformation.apply(jsonObject(VALID_INSERT_REQUEST_BODY)).toString(); + return givenBodyTransformation.apply(jsonObject(INSERT_REQUEST_BODY_TEMPLATE)).toString(); } } @ParameterizedTest @EnumSource(BadRequestTestCases.class) void respondWithBadRequest(final BadRequestTestCases testCase) throws Exception { -// assumeThat(testCase == ADOPTING_MEMBERSHIP_NUMBER_FOR_TRANSFER_MUST_BE_GIVEN_AND_AVAILABLE).isTrue(); + // HOWTO: run just a single test-case in a data-driven test-method + // org.assertj.core.api.Assumptions.assumeThat( + // testCase == ADOPTING_MEMBERSHIP_NUMBER_FOR_TRANSFER_MUST_BE_GIVEN_AND_AVAILABLE).isTrue(); // when mockMvc.perform(MockMvcRequestBuilders @@ -183,39 +200,76 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { requestBody -> requestBody .with("transactionType", "TRANSFER") .with("assetValue", -64.00) - .with("adoptingMembership.memberNumber", AVAILABLE_MEMBER_NUMBER), + .with("adoptingMembership.memberNumber", AVAILABLE_TARGET_MEMBER_NUMBER), """ - { - "transactionType": "TRANSFER", - "assetValue": -64.00, - "adoptionAssetTx": { - "transactionType": "ADOPTION", - "assetValue": 64.00 - }, - "reversalAssetTx": null, - "transferAssetTx": null, - "revertedAssetTx": null - } - """), + { + "transactionType": "TRANSFER", + "assetValue": -64.00, + "adoptionAssetTx": { + "transactionType": "ADOPTION", + "assetValue": 64.00 + }, + "reversalAssetTx": null, + "transferAssetTx": null, + "revertedAssetTx": null + } + """), ADOPTING_MEMBERSHIP_UUID_FOR_TRANSFER_MUST_BE_GIVEN_AND_AVAILABLE( requestBody -> requestBody .with("transactionType", "TRANSFER") .with("assetValue", -64.00) - .with("adoptingMembership.uuid", AVAILABLE_MEMBERSHIP_UUID.toString()), + .with("membership.uuid", ORIGIN_MEMBERSHIP_UUID.toString()) + .with("adoptingMembership.uuid", AVAILABLE_TARGET_MEMBERSHIP_UUID.toString()), +// """ +// { +// "uuid": "%{NEW_EXPLICITLY_CREATED_ASSET_TX_UUID}", +// "membership.uuid": "%{ORIGIN_MEMBERSHIP_UUID}", +// "transactionType": "TRANSFER", +// "assetValue": -64.00, +// "adoptionAssetTx": { +// "membership.uuid": "%{AVAILABLE_MEMBERSHIP_UUID}", +// "transactionType": "ADOPTION", +// "assetValue": 64.00, +// "transferAssetTx.uuid": "%{NEW_EXPLICITLY_CREATED_ASSET_TX_UUID}" +// }, +// "transferAssetTx": null, +// "revertedAssetTx": null, +// "reversalAssetTx": null +// } +// """ +// .replace("%{NEW_EXPLICITLY_CREATED_ASSET_TX_UUID}", NEW_EXPLICITLY_CREATED_ASSET_TX_UUID.toString()) +// .replace("%{ORIGIN_MEMBERSHIP_UUID}", ORIGIN_MEMBERSHIP_UUID.toString()) +// .replace("%{AVAILABLE_MEMBERSHIP_UUID}", AVAILABLE_TARGET_MEMBERSHIP_UUID.toString())); """ - { - "transactionType": "TRANSFER", - "assetValue": -64.00, - "adoptionAssetTx": { - "transactionType": "ADOPTION", - "assetValue": 64.00 - }, - "transferAssetTx": null, - "revertedAssetTx": null, - "reversalAssetTx": null - } - """); + { + "uuid": "55555555-5555-5555-5555-555555555555", + "membership.uuid": "11111111-1111-1111-1111-111111111111", + "membership.memberNumber": "M-1111100", + "transactionType": "TRANSFER", + "assetValue": -64, + "valueDate": "2022-10-13", + "reference": "valid reference", + "comment": "valid comment", + "adoptionAssetTx": { + "uuid": "44444444-4444-4444-4444-444444444444", + "membership.uuid": "22222222-2222-2222-2222-222222222222", + "membership.memberNumber": "M-1234500", + "transactionType": "ADOPTION", + "assetValue": 64, + "valueDate": "2022-10-13", + "reference": "valid reference", + "comment": "valid comment", + "adoptionAssetTx.uuid": null, + "transferAssetTx.uuid": "55555555-5555-5555-5555-555555555555", + "revertedAssetTx.uuid": null, + "reversalAssetTx.uuid": null + }, + "transferAssetTx": null, + "revertedAssetTx": null, + "reversalAssetTx": null + } + """); private final Function givenBodyTransformation; private final String expectedResponseBody; @@ -228,13 +282,16 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { } String givenRequestBody() { - return givenBodyTransformation.apply(jsonObject(VALID_INSERT_REQUEST_BODY)).toString(); + return givenBodyTransformation.apply(jsonObject(INSERT_REQUEST_BODY_TEMPLATE)).toString(); } } @ParameterizedTest @EnumSource(SuccessfullyCreatedTestCases.class) void respondWithSuccessfullyCreated(final SuccessfullyCreatedTestCases testCase) throws Exception { + // uncomment, if you need to run just a single test-case in this data-driven test-method + org.assertj.core.api.Assumptions.assumeThat( + testCase == ADOPTING_MEMBERSHIP_UUID_FOR_TRANSFER_MUST_BE_GIVEN_AND_AVAILABLE).isTrue(); // when mockMvc.perform(MockMvcRequestBuilders @@ -251,17 +308,33 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { @BeforeEach void initMocks() { - final var availableMemberNumber = Integer.valueOf(AVAILABLE_MEMBER_NUMBER.substring("M-".length())); - when(membershipRepo.findMembershipByMemberNumber(availableMemberNumber)).thenReturn(AVAILABLE_MEMBER_ENTITY); - when(membershipRepo.findByUuid(AVAILABLE_MEMBERSHIP_UUID)).thenReturn(Optional.of(AVAILABLE_MEMBER_ENTITY)); + TestUuidGenerator.start(4); + + when(emw.find(eq(HsOfficeMembershipEntity.class), eq(ORIGIN_MEMBERSHIP_UUID))).thenReturn(ORIGIN_TARGET_MEMBER_ENTITY); + when(emw.find(eq(HsOfficeMembershipEntity.class), eq(AVAILABLE_TARGET_MEMBERSHIP_UUID))).thenReturn(AVAILABLE_MEMBER_ENTITY); + + final var availableMemberNumber = Integer.valueOf(AVAILABLE_TARGET_MEMBER_NUMBER.substring("M-".length())); + when(membershipRepo.findMembershipByMemberNumber(eq(availableMemberNumber))).thenReturn(AVAILABLE_MEMBER_ENTITY); + + when(membershipRepo.findByUuid(eq(ORIGIN_MEMBERSHIP_UUID))).thenReturn(Optional.of(ORIGIN_TARGET_MEMBER_ENTITY)); + when(membershipRepo.findByUuid(eq(AVAILABLE_TARGET_MEMBERSHIP_UUID))).thenReturn(Optional.of(AVAILABLE_MEMBER_ENTITY)); + when(coopAssetsTransactionRepo.save(any(HsOfficeCoopAssetsTransactionEntity.class))) .thenAnswer(invocation -> { final var entity = (HsOfficeCoopAssetsTransactionEntity) invocation.getArgument(0); if (entity.getUuid() == null) { - entity.setUuid(UUID.randomUUID()); + entity.setUuid(TestUuidGenerator.next()); } return entity; } ); } + + private int partnerNumberOf(final String memberNumber) { + return Integer.parseInt(memberNumber.substring("M-".length(), memberNumber.length()-2)); + } + + private String suffixOf(final String memberNumber) { + return memberNumber.substring("M-".length()+5); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/test/TestUuidGenerator.java b/src/test/java/net/hostsharing/hsadminng/test/TestUuidGenerator.java new file mode 100644 index 00000000..1f8c10e3 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/test/TestUuidGenerator.java @@ -0,0 +1,53 @@ +package net.hostsharing.hsadminng.test; + +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.UUID; + +@UtilityClass +public class TestUuidGenerator { + + private static final UUID ZEROES_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + private static final List GIVEN_UUIDS = List.of( + ZEROES_UUID, + uuidWithDigit(1), + uuidWithDigit(2), + uuidWithDigit(3), + uuidWithDigit(4), + uuidWithDigit(5), + uuidWithDigit(6), + uuidWithDigit(7), + uuidWithDigit(8), + uuidWithDigit(9) + ); + + private Queue availableUuids = null; + + + public static void start(final int firstIndex) { + availableUuids = new LinkedList<>(GIVEN_UUIDS.subList(firstIndex, GIVEN_UUIDS.size())); + } + + public static UUID next() { + if (availableUuids == null) { + throw new IllegalStateException("UUID generator not started yet, call start() in @BeforeEach."); + } + if (availableUuids.isEmpty()) { + throw new IllegalStateException("No UUIDs available anymore."); + } + return availableUuids.poll(); + } + + public static UUID get(final int index) { + return GIVEN_UUIDS.get(index); + } + + private static @NotNull UUID uuidWithDigit(final int digit) { + return UUID.fromString(ZEROES_UUID.toString().replace('0', Character.forDigit(digit, 10))); + } +}