From fc47cb0fce57bfba8655d657546b342b97c1582f Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Dec 2024 14:57:32 +0100 Subject: [PATCH 01/30] add endpoint HTTP GET /api/hs/office/partners/P-{partnerNumber} --- .../partner/HsOfficePartnerController.java | 17 ++++++++ .../partner/HsOfficePartnerRepository.java | 2 +- ...hs-office-partners-with-partnerNumber.yaml | 28 +++++++++++++ .../api-definition/hs-office/hs-office.yaml | 3 ++ ...fficeDebitorRepositoryIntegrationTest.java | 2 +- ...iceMembershipControllerAcceptanceTest.java | 8 ++-- .../HsOfficePartnerControllerRestTest.java | 39 +++++++++++++++++++ ...fficePartnerRepositoryIntegrationTest.java | 2 +- 8 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java index a375174b..f3c25bd5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java @@ -104,6 +104,23 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class)); } + @Override + @Transactional(readOnly = true) + @Timed("app.office.partners.api.getSinglePartnerByPartnerNumber") + public ResponseEntity getSinglePartnerByPartnerNumber( + final String currentSubject, + final String assumedRoles, + final Integer partnerNumber) { + + context.define(currentSubject, assumedRoles); + + final var result = partnerRepo.findPartnerByPartnerNumber(partnerNumber); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class)); + } + @Override @Transactional @Timed("app.office.partners.api.deletePartnerByUuid") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java index 282d3db3..3ae4a26a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java @@ -32,7 +32,7 @@ public interface HsOfficePartnerRepository extends Repository findPartnerByOptionalNameLike(String name); @Timed("app.office.partners.repo.findPartnerByPartnerNumber") - HsOfficePartnerEntity findPartnerByPartnerNumber(Integer partnerNumber); + Optional findPartnerByPartnerNumber(Integer partnerNumber); @Timed("app.office.partners.repo.save") HsOfficePartnerEntity save(final HsOfficePartnerEntity entity); diff --git a/src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml new file mode 100644 index 00000000..b402048f --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml @@ -0,0 +1,28 @@ +get: + tags: + - hs-office-partners + description: 'Fetch a single business partner by its partner-number (prefixed with "P-"), if visible for the current subject.' + operationId: getSinglePartnerByPartnerNumber + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: partnerNumber + in: path + required: true + schema: + type: integer + minimum: 10000 + maximum: 99999 + description: partner-number (prefixed with "P-") of the partner to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index e8e7816d..71aa708b 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -13,6 +13,9 @@ paths: /api/hs/office/partners: $ref: "hs-office-partners.yaml" + /api/hs/office/partners/P-{partnerNumber}: + $ref: "hs-office-partners-with-partnerNumber.yaml" + /api/hs/office/partners/{partnerUUID}: $ref: "hs-office-partners-with-uuid.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index 781077cb..fb0c0c94 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -83,7 +83,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var count = debitorRepo.count(); - final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001); + final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index c2dd44c1..9f609e53 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -112,16 +112,16 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle void globalAdmin_canViewMembershipsByPartnerUuid() { context.define("superuser-alex@hostsharing.net"); - final var partner = partnerRepo.findPartnerByPartnerNumber(10001); + final var partner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); RestAssured // @formatter:off - .given() + .given() .header("current-subject", "superuser-alex@hostsharing.net") .port(port) - .when() + .when() .queryParam("partnerUuid", partner.getUuid() ) .get("http://localhost/api/hs/office/memberships") - .then().log().all().assertThat() + .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java index dbc6081b..4d016725 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java @@ -168,6 +168,45 @@ class HsOfficePartnerControllerRestTest { } } + @Nested + class GetSinglePartnerByPartnerNumber { + + @Test + void respondWithPartner_ifPartnerNumberIsAvailable() throws Exception { + // given + when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.of(HsOfficePartnerEntity.builder() + .partnerNumber(12345) + .build())); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/partners/P-12345") + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("partnerNumber", is("P-12345"))); + } + + @Test + void respondNotFound_ifPartnerNumberIsNotAvailable() throws Exception { + // given + when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.empty()); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/partners/P-12345") + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isNotFound()); + } + } + @Nested class DeletePartner { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 79ff449d..92683a6b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -243,7 +243,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); // when - final var result = partnerRepo.findPartnerByPartnerNumber(10001); + final var result = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); // then assertThat(result) -- 2.39.5 From 781fcef1d32c610bd6bc9145ecfdff853f9f47be Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Dec 2024 15:56:07 +0100 Subject: [PATCH 02/30] create new PARTNER relation for community of heirs --- .../scenarios/HsOfficeScenarioTests.java | 30 +++++++ ...ceDeceasedPartnerWithCommunityOfHeirs.java | 84 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 9f957472..41a1e4fe 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -29,6 +29,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContac import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddRepresentativeToPartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.DeletePartner; +import net.hostsharing.hsadminng.hs.office.scenarios.partner.ReplaceDeceasedPartnerWithCommunityOfHeirs; import net.hostsharing.hsadminng.hs.office.scenarios.person.ShouldUpdatePersonData; import net.hostsharing.hsadminng.hs.office.scenarios.subscription.RemoveOperationsContactFromPartner; import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeToMailinglist; @@ -585,4 +586,33 @@ class HsOfficeScenarioTests extends ScenarioTest { .doRun(); } } + + @Nested + @Order(60) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class PartnerDeceasedScenarios { + + @Test + @Order(6010) + @Requires("Partner: P-31011 - Michelle Matthieu") + @Produces("Partner-Relation: Erbengemeinschaft Michelle Matthieu") + void shouldReplaceDeceasedPartnerByCommunityOfHeirs() { + new ReplaceDeceasedPartnerWithCommunityOfHeirs(scenarioTest) + .given("partnerNumber", "P-31011") + .given("nameOfDeceasedPerson", "Michelle Matthieu") // FIXME: redundant + .given( + "communityOfHeirsPostalAddress", """ + "name": "Erbengemeinschaft Michelle Matthieu", // FIXME: automatic? + "co": "Lena Stadland", + "street": "Im Wischer", + "zipcode": "22987", + "city": "Hamburg", + "country": "Germany" + """) + .given("communityOfHeirsOfficePhoneNumber", "+49 40 666666") + .given("communityOfHeirsEmailAddress", "lena.stadland@example.org") + .doRun() + .keep(); + } + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java new file mode 100644 index 00000000..de5b45b0 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -0,0 +1,84 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.partner; + +import io.restassured.http.ContentType; +import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; +import net.hostsharing.hsadminng.hs.scenarios.UseCase; +import org.springframework.http.HttpStatus; + +import static io.restassured.http.ContentType.JSON; +import static org.springframework.http.HttpStatus.OK; + +public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase { + + public ReplaceDeceasedPartnerWithCommunityOfHeirs(final ScenarioTest testSuite) { + super(testSuite); + + } + + @Override + protected HttpResponse run() { + + obtain("Person: Hostsharing eG", () -> + httpGet("/api/hs/office/persons?name=Hostsharing+eG") + .expecting(OK).expecting(JSON), + response -> response.expectArrayElements(1).getFromBody("[0].uuid"), + "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? + ); + + obtain("Partner: deceased", () -> + httpGet("/api/hs/office/partners/%{partnerNumber}") + .expecting(OK).expecting(JSON), + response -> response.getFromBody("uuid"), + "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? + ); + + obtain("Person: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> + httpPost("/api/hs/office/persons", usingJsonBody(""" + { + "personType": "UNINCORPORATED_FIRM", + "tradeName": "Erbengemeinschaft %{nameOfDeceasedPerson}", + "givenName": ${givenName???}, + "familyName": ${familyName???} + } + """)) + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) + ); + + obtain("Contact: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> + httpPost("/api/hs/office/contacts", usingJsonBody(""" + { + "caption": "Erbengemeinschaft %{nameOfDeceasedPerson}", + "postalAddress": { + %{communityOfHeirsPostalAddress} + }, + "phoneNumbers": { + "office": ${communityOfHeirsOfficePhoneNumber} + }, + "emailAddresses": { + "main": ${communityOfHeirsEmailAddress} + } + } + """)) + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) + ); + + return httpPost("/api/hs/office/relations", usingJsonBody(""" + { + "type": "PARTNER", + "anchor.uuid": ${Person: Hostsharing eG}, + "holder.uuid": ${Person: Erbengemeinschaft %{nameOfDeceasedPerson}}, + "contact.uuid": ${Contact: Erbengemeinschaft %{nameOfDeceasedPerson}} + } + """)) + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON); + } + + @Override + protected void verify(final UseCase.HttpResponse response) { + verify( + "Verify the New Partner Relation", + () -> httpGet("/api/hs/office/relations?relationType=PARTNER&contactData=Erbengemeinschaft%20&{nameOfDeceasedPerson}") + .expecting(OK).expecting(JSON).expectArrayElements(1) + ); + } +} -- 2.39.5 From e4e509a23a1843e0eb4fa6659a9754eeb79fc652 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Dec 2024 16:19:09 +0100 Subject: [PATCH 03/30] verify community of heirs to be the new holder of the partnerRel - currently failing --- .../ReplaceDeceasedPartnerWithCommunityOfHeirs.java | 7 ++++--- .../net/hostsharing/hsadminng/hs/scenarios/UseCase.java | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index de5b45b0..c2b5966e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -76,9 +76,10 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase.HttpResponse response) { verify( - "Verify the New Partner Relation", - () -> httpGet("/api/hs/office/relations?relationType=PARTNER&contactData=Erbengemeinschaft%20&{nameOfDeceasedPerson}") - .expecting(OK).expecting(JSON).expectArrayElements(1) + "Verify the Updated Partner", + () -> httpGet("/api/hs/office/partners/%{partnerNumber}") + .expecting(OK).expecting(JSON).expectObject(), + path("partnerRel.holder.tradeName").contains("Erbengemeinschaft %{nameOfDeceasedPerson}") ); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index 7b438134..5da51293 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -335,6 +335,13 @@ public abstract class UseCase> { return this; } + @SneakyThrows + public HttpResponse expectObject() { + final var rootNode = objectMapper.readTree(response.body()); + assertThat(rootNode.isArray()).as("object expected, but got array: " + response.body()).isFalse(); + return this; + } + @SneakyThrows public V getFromBody(final String path) { final var body = response.body(); -- 2.39.5 From b845671617c03dd4e24cae2f88f4a2785c4be34e Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 07:18:27 +0100 Subject: [PATCH 04/30] create distinct partner details for incorporated vs. natural person --- .../office/scenarios/HsOfficeScenarioTests.java | 17 +++++++++++------ .../office/scenarios/partner/CreatePartner.java | 7 +++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 41a1e4fe..fd14b3a6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -93,6 +93,8 @@ class HsOfficeScenarioTests extends ScenarioTest { """) .given("officePhoneNumber", "+49 40 654321-0") .given("emailAddress", "hamburg@test-ag.example.org") + .given("registrationOffice", "Registergericht Hamburg") + .given("registrationNumber", "1234567") .doRun() .keep(); } @@ -118,6 +120,9 @@ class HsOfficeScenarioTests extends ScenarioTest { """) .given("officePhoneNumber", "+49 40 123456") .given("emailAddress", "michelle.matthieu@example.org") + .given("birthday", "1951-03-25") + .given("birthPlace", "Neustadt a.d.R.") + .given("birthName", "Eichbaum") .doRun() .keep(); } @@ -601,13 +606,13 @@ class HsOfficeScenarioTests extends ScenarioTest { .given("partnerNumber", "P-31011") .given("nameOfDeceasedPerson", "Michelle Matthieu") // FIXME: redundant .given( + // "name": "Erbengemeinschaft Michelle Matthieu", // FIXME: automatic? "communityOfHeirsPostalAddress", """ - "name": "Erbengemeinschaft Michelle Matthieu", // FIXME: automatic? - "co": "Lena Stadland", - "street": "Im Wischer", - "zipcode": "22987", - "city": "Hamburg", - "country": "Germany" + "co": "Lena Stadland", + "street": "Im Wischer 14", + "zipcode": "22987", + "city": "Hamburg", + "country": "Germany" """) .given("communityOfHeirsOfficePhoneNumber", "+49 40 666666") .given("communityOfHeirsEmailAddress", "lena.stadland@example.org") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java index 87ca7c87..a1ddccc4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/CreatePartner.java @@ -69,8 +69,11 @@ public class CreatePartner extends UseCase { "contact.uuid": ${Contact: %{contactCaption}} }, "details": { - "registrationOffice": "Registergericht Hamburg", - "registrationNumber": "1234567" + "birthday": ${birthday???}, + "birthPlace": ${birthPlace???}, + "birthName": ${birthName???}, + "registrationOffice": ${registrationOffice???}, + "registrationNumber": ${registrationNumber???} } } """)) -- 2.39.5 From 886b621fce702cf803292a84220ef897204cdc43 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 07:19:15 +0100 Subject: [PATCH 05/30] no need for input of postalAddresss.name anymore - now generated --- .../partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index c2b5966e..bb27e063 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -25,7 +25,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase + obtain("Partner: %{partnerNumber}", () -> httpGet("/api/hs/office/partners/%{partnerNumber}") .expecting(OK).expecting(JSON), response -> response.getFromBody("uuid"), @@ -49,6 +49,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase Date: Sat, 7 Dec 2024 07:27:01 +0100 Subject: [PATCH 06/30] pretty JSON in test report --- .../hsadminng/hs/scenarios/TestReport.java | 19 ++++++++++++++++++- .../hsadminng/hs/scenarios/UseCase.java | 6 ++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index b700d556..016633a7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -1,5 +1,7 @@ package net.hostsharing.hsadminng.hs.scenarios; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import net.hostsharing.hsadminng.system.SystemProcess; import org.jetbrains.annotations.NotNull; @@ -23,9 +25,11 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestReport { public static final File BUILD_DOC_SCENARIOS = new File("build/doc/scenarios"); - private final static File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); public static final SimpleDateFormat MM_DD_YYYY_HH_MM_SS = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss"); + private static final File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); + private static final ObjectMapper objectMapper = new ObjectMapper(); + private final Map aliases; private final PrintWriter markdownLog; // records everything for debugging purposes private File markdownReportFile; @@ -76,6 +80,11 @@ public class TestReport { printLine("\n" +output + "\n"); } + @SneakyThrows + public void printJson(final String json) { + printLine(prettyJson(json)); + } + void silent(final Runnable code) { silent++; code.run(); @@ -100,6 +109,14 @@ public class TestReport { return convertedTestMethodName.replaceAll(": should ", ": "); } + private static String prettyJson(final String json) throws JsonProcessingException { + if (json == null) { + return ""; + } + final var jsonNode = objectMapper.readTree(json); + return "// pretty json\n" + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); + } + private String asClickableLink(final File file) { return file.toURI().toString().replace("file:/", "file:///"); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index 5da51293..a64052d8 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -379,14 +379,12 @@ public abstract class UseCase> { // the request testReport.printLine("```"); testReport.printLine(httpMethod.name() + " " + uri); - testReport.printLine((requestBody != null ? requestBody.trim() : "")); + testReport.printJson(requestBody); // the response testReport.printLine("=> status: " + status + " " + (locationUuid != null ? locationUuid : "")); if (httpMethod == HttpMethod.GET || status.isError()) { - final var jsonNode = objectMapper.readTree(response.body()); - final var prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); - testReport.printLine(prettyJson); + testReport.printJson(response.body()); } testReport.printLine("```"); testReport.printLine(""); -- 2.39.5 From cc72a5fb94f5ac6742e8cf9d8dfbb3cf6cecb818 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 07:31:00 +0100 Subject: [PATCH 07/30] cleanup --- .../java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index 016633a7..c55bddce 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -114,7 +114,7 @@ public class TestReport { return ""; } final var jsonNode = objectMapper.readTree(json); - return "// pretty json\n" + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); } private String asClickableLink(final File file) { -- 2.39.5 From dc6da887a0fd1f299325f6397200b20780a66591 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 12:01:37 +0100 Subject: [PATCH 08/30] getListOfDebitors with partnerNumber and getSingleDebitorByDebitorNumber --- .../debitor/HsOfficeDebitorController.java | 23 ++++++-- .../debitor/HsOfficeDebitorRepository.java | 14 +++-- .../hs-office/api-mappings.yaml | 1 + ...hs-office-debitors-with-debitorNumber.yaml | 29 ++++++++++ .../hs-office/hs-office-debitors.yaml | 6 +-- .../api-definition/hs-office/hs-office.yaml | 3 ++ ...OfficeDebitorControllerAcceptanceTest.java | 53 ++++++++++++++++++- ...fficeDebitorRepositoryIntegrationTest.java | 31 ++++++++--- ...ceSepaMandateControllerAcceptanceTest.java | 2 +- 9 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java index 1792acdb..b1dd3172 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java @@ -57,11 +57,11 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { final String currentSubject, final String assumedRoles, final String name, - final String debitorNumber) { + final String partnerNumber) { context.define(currentSubject, assumedRoles); - final var entities = debitorNumber != null - ? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber)) + final var entities = partnerNumber != null + ? debitorRepo.findDebitorByPartnerNumber(cropTag("P-", partnerNumber)) : debitorRepo.findDebitorByOptionalNameLike(name); final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); @@ -133,6 +133,23 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); } + @Override + @Transactional(readOnly = true) + @Timed("app.office.debitors.api.getSingleDebitorByDebitorNumber") + public ResponseEntity getSingleDebitorByDebitorNumber( + final String currentSubject, + final String assumedRoles, + final Integer debitorNumber) { + + context.define(currentSubject, assumedRoles); + + final var result = debitorRepo.findDebitorByDebitorNumber(debitorNumber); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); + } + @Override @Transactional @Timed("app.office.debitors.api.deleteDebitorByUuid") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java index a62d1c42..a48bfa2b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.hs.office.debitor; import io.micrometer.core.annotation.Timed; +import net.hostsharing.hsadminng.lambda.Reducer; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @@ -19,15 +20,20 @@ public interface HsOfficeDebitorRepository extends Repository findDebitorByPartnerNumberAndDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); + List findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); - default List findDebitorByDebitorNumber(int debitorNumber) { + default Optional findDebitorByDebitorNumber(int debitorNumber) { final var partnerNumber = debitorNumber / 100; final String suffix = String.format("%02d", debitorNumber % 100); - final var result = findDebitorByPartnerNumberAndDebitorNumberSuffix(partnerNumber, suffix); + final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, suffix); + return result.stream().reduce(Reducer::toSingleElement); + } + + default List findDebitorByPartnerNumber(int partnerNumber) { + final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, null); return result; } diff --git a/src/main/resources/api-definition/hs-office/api-mappings.yaml b/src/main/resources/api-definition/hs-office/api-mappings.yaml index 2403e1e4..3a13afd4 100644 --- a/src/main/resources/api-definition/hs-office/api-mappings.yaml +++ b/src/main/resources/api-definition/hs-office/api-mappings.yaml @@ -13,6 +13,7 @@ map: - type: string:uuid => java.util.UUID - type: string:format => java.lang.String - type: number:currency => java.math.BigDecimal + - type: number:integer => java.lang.Integer paths: /api/hs/office/partners/{partnerUUID}: diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml new file mode 100644 index 00000000..9e34b758 --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml @@ -0,0 +1,29 @@ +get: + tags: + - hs-office-debitors + description: 'Fetch a single debitor by its debitorNumber, if visible for the current subject.' + operationId: getSingleDebitorByDebitorNumber + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: debitorNumber + in: path + required: true + schema: + type: number + format: integer + minimum: 1000000 + maximum: 9999999 + description: debitor-number of the debitor to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml index c825817f..74fab6dc 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml @@ -13,15 +13,15 @@ get: schema: type: string description: Prefix of name properties from person or contact to filter the results. - - name: debitorNumber + - name: partnerNumber in: query required: false schema: type: string minLength: 9 maxLength: 9 - pattern: 'D-[0-9]{7}' - description: Debitor number of the requested debitor. + pattern: 'P-[0-9]{5}' + description: Partner number of the requested debitor. responses: "200": description: OK diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index 71aa708b..b7725285 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -61,6 +61,9 @@ paths: /api/hs/office/debitors: $ref: "hs-office-debitors.yaml" + /api/hs/office/debitors/D-{debitorNumber}: + $ref: "hs-office-debitors-with-debitorNumber.yaml" + /api/hs/office/debitors/{debitorUUID}: $ref: "hs-office-debitors-with-uuid.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index d0b56745..4fb18e61 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -233,7 +233,56 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu } @Test - void globalAdmin_withoutAssumedRoles_canFindDebitorDebitorByDebitorNumber() { + void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorNumber() { + + RestAssured // @formatter:off + .given() + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/office/debitors/P-10002") + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "debitorNumber": "D-1000211", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + }, + { + "debitorNumber": "D-1000212", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + }, + { + "debitorNumber": "D-1000213", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + } + ] + """)); + // @formatter:on + } + + @Test + void globalAdmin_withoutAssumedRoles_canFindDebitorsByPartnerNumber() { RestAssured // @formatter:off .given() @@ -258,7 +307,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu } ] """)); - // @formatter:on + // @formatter:on } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index fb0c0c94..f821d3b6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -50,7 +50,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean HsOfficePartnerRepository partnerRepo; @Autowired - HsOfficeContactRealRepository contactrealRepo; + HsOfficeContactRealRepository contactRealRepo; @Autowired HsOfficePersonRealRepository personRepo; @@ -85,7 +85,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean final var count = debitorRepo.count(); final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact")); // when final var result = attempt(em, () -> { @@ -119,7 +119,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact")); // when final var result = attempt(em, () -> { @@ -158,7 +158,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean attempt(em, () -> { final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenDebitorPerson = one(personRepo.findPersonByOptionalNameLike("Fourth eG")); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("fourth contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("fourth contact")); final var newDebitor = HsOfficeDebitorEntity.builder() .debitorNumberSuffix("22") .debitorRel(HsOfficeRelationRealEntity.builder() @@ -278,7 +278,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean } @Nested - class FindByDebitorNumberLike { + class FindByDebitorNumber { @Test public void globalAdmin_canViewAllDebitors() { @@ -288,6 +288,23 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // when final var result = debitorRepo.findDebitorByDebitorNumber(1000313); + // then + assertThat(result).map(Object::toString).contains( + "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); + } + } + + @Nested + class FindByPartnerNumber { + + @Test + public void globalAdmin_canViewAllDebitors() { + // given + context("superuser-alex@hostsharing.net"); + + // when + final var result = debitorRepo.findDebitorByPartnerNumber(10003); + // then exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); @@ -324,7 +341,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN", true); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby")); - final var givenNewContact = one(contactrealRepo.findContactByOptionalCaptionLike("sixth contact")); + final var givenNewContact = one(contactRealRepo.findContactByOptionalCaptionLike("sixth contact")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final String givenNewVatId = "NEW-VAT-ID"; final String givenNewVatCountryCode = "NC"; @@ -613,7 +630,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); final var givenPartner = one(partnerRepo.findPartnerByOptionalNameLike(partnerName)); final var givenPartnerPerson = givenPartner.getPartnerRel().getHolder(); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike(contactCaption)); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike(contactCaption)); final var givenBankAccount = bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null; final var newDebitor = HsOfficeDebitorEntity.builder() diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java index 63cce53f..b7198aaa 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java @@ -524,7 +524,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandateForDebitorNumber(final int debitorNumber) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).get(0); + final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).orElseThrow(); final var bankAccountHolder = ofNullable(givenDebitor.getPartner().getPartnerRel().getHolder().getTradeName()) .orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName()); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0); -- 2.39.5 From d9166067dcdc02727571585960b525bd8059412e Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 12:31:43 +0100 Subject: [PATCH 09/30] add endpoint /api/hs/office/memberships/M-{membershipNumber} - WIP --- ...ice-memberships-with-membershipNumber.yaml | 85 +++++++++++++++++++ .../api-definition/hs-office/hs-office.yaml | 4 + 2 files changed, 89 insertions(+) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml diff --git a/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml new file mode 100644 index 00000000..88cf784a --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml @@ -0,0 +1,85 @@ +get: + tags: + - hs-office-memberships + description: 'Fetch a single membership by its membershipNumber, if visible for the current subject.' + operationId: getSingleMembershipByUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: membershipNumber + in: path + required: true + schema: + type: number + format: integer + minimum: 1000000 + maximum: 9999999 + description: membershipNumber of the membership to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +patch: + tags: + - hs-office-memberships + description: 'Updates a single membership by its uuid, if permitted for the current subject.' + operationId: patchMembership + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: membershipUUID + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + 'application/json': + schema: + $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembershipPatch' + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership' + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +delete: + tags: + - hs-office-memberships + description: 'Delete a single membership by its uuid, if permitted for the current subject.' + operationId: deleteMembershipByUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: membershipUUID + in: path + required: true + schema: + type: string + format: uuid + description: UUID of the membership to delete. + responses: + "204": + description: No Content + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + "404": + $ref: 'error-responses.yaml#/components/responses/NotFound' diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index b7725285..915afb61 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -82,6 +82,10 @@ paths: /api/hs/office/memberships: $ref: "hs-office-memberships.yaml" + /api/hs/office/memberships/M-{membershipNumber}: + $ref: "hs-office-memberships-with-membershipNumber.yaml" + + /api/hs/office/memberships/{membershipUUID}: $ref: "hs-office-memberships-with-uuid.yaml" -- 2.39.5 From 6107a5df3361dacc892d3d8d7280091ceae10b91 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 12:32:01 +0100 Subject: [PATCH 10/30] Assigning the new new Partner-Relation to the existing Partner --- .../scenarios/HsOfficeScenarioTests.java | 1 - ...ceDeceasedPartnerWithCommunityOfHeirs.java | 31 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index fd14b3a6..ff7c261c 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -600,7 +600,6 @@ class HsOfficeScenarioTests extends ScenarioTest { @Test @Order(6010) @Requires("Partner: P-31011 - Michelle Matthieu") - @Produces("Partner-Relation: Erbengemeinschaft Michelle Matthieu") void shouldReplaceDeceasedPartnerByCommunityOfHeirs() { new ReplaceDeceasedPartnerWithCommunityOfHeirs(scenarioTest) .given("partnerNumber", "P-31011") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index bb27e063..92ff7924 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -63,15 +63,28 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase + httpPost("/api/hs/office/relations", usingJsonBody(""" + { + "type": "PARTNER", + "anchor.uuid": ${Person: Hostsharing eG}, + "holder.uuid": ${Person: Erbengemeinschaft %{nameOfDeceasedPerson}}, + "contact.uuid": ${Contact: Erbengemeinschaft %{nameOfDeceasedPerson}} + } + """)) + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) + ); + + // httpGet("/api/hs/office/debitors/%{partnerNumber}") FIXME + + return withTitle("Assigning the new new Partner-Relation to the existing Partner", () -> + httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody(""" + { + "partnerRel": ${Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}} + } + """)) + .expecting(HttpStatus.OK) + ); } @Override -- 2.39.5 From 668dc184212ee28e701cc4b3e84ad8a662202222 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 9 Dec 2024 15:23:30 +0100 Subject: [PATCH 11/30] add endpoint /api/hs/office/memberships/M-{membershipNumber} and amend query to partnerNumber --- ...OfficeCoopAssetsTransactionController.java | 9 +- .../HsOfficeMembershipController.java | 46 ++++-- .../HsOfficeMembershipRepository.java | 19 ++- ...ice-memberships-with-membershipNumber.yaml | 58 +------ .../hs-office/hs-office-memberships.yaml | 10 +- .../api-definition/hs-office/hs-office.yaml | 1 - ...tsTransactionControllerAcceptanceTest.java | 10 +- ...opAssetsTransactionControllerRestTest.java | 2 +- ...sTransactionRepositoryIntegrationTest.java | 8 +- ...esTransactionControllerAcceptanceTest.java | 10 +- ...sTransactionRepositoryIntegrationTest.java | 8 +- ...iceMembershipControllerAcceptanceTest.java | 44 +++--- .../HsOfficeMembershipControllerRestTest.java | 147 +++++++++++++++++- ...ceMembershipRepositoryIntegrationTest.java | 36 ++++- 14 files changed, 277 insertions(+), 131 deletions(-) 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 fbb59788..9073e564 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 @@ -290,11 +290,10 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse if (adoptingMembershipMemberNumber != null) { final var adoptingMemberNumber = Integer.valueOf(adoptingMembershipMemberNumber.substring("M-".length())); final var adoptingMembership = membershipRepo.findMembershipByMemberNumber(adoptingMemberNumber); - if (adoptingMembership != null) { - return adoptingMembership; - } - throw new ValidationException("adoptingMembership.memberNumber='" + adoptingMembershipMemberNumber - + "' not found or not accessible"); + return adoptingMembership.orElseThrow( () -> + new ValidationException("adoptingMembership.memberNumber='" + adoptingMembershipMemberNumber + + "' not found or not accessible") + ); } throw new ValidationException( diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java index 3ba36f5c..84994ceb 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java @@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; +import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.mapper.StandardMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -17,7 +18,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.errors.Validate.validate; import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag; @RestController @@ -39,16 +40,20 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { final String currentSubject, final String assumedRoles, final UUID partnerUuid, - final String memberNumber) { + final String partnerNumber) { context.define(currentSubject, assumedRoles); - final var entities = (memberNumber != null) - ? ofNullable(membershipRepo.findMembershipByMemberNumber( - cropTag(HsOfficeMembershipEntity.MEMBER_NUMBER_TAG, memberNumber))).stream() - .toList() - : membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid); + validate("partnerUuid, partnerNumber").atMaxOneNonNull(partnerUuid, partnerNumber); - final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class, + final var entities = partnerNumber != null + ? membershipRepo.findMembershipsByPartnerNumber( + cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber)) + : partnerUuid != null + ? membershipRepo.findMembershipsByPartnerUuid(partnerUuid) + : membershipRepo.findAll(); + + final var resources = mapper.mapList( + entities, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(resources); } @@ -72,7 +77,8 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { .path("/api/hs/office/memberships/{id}") .buildAndExpand(saved.getUuid()) .toUri(); - final var mapped = mapper.map(saved, HsOfficeMembershipResource.class, + final var mapped = mapper.map( + saved, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.created(uri).body(mapped); } @@ -91,7 +97,27 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { if (result.isEmpty()) { return ResponseEntity.notFound().build(); } - return ResponseEntity.ok(mapper.map(result.get(), HsOfficeMembershipResource.class, + return ResponseEntity.ok(mapper.map( + result.get(), HsOfficeMembershipResource.class, + SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER)); + } + + @Override + @Transactional(readOnly = true) + @Timed("app.office.membership.api.getSingleMembershipByMembershipNumber") + public ResponseEntity getSingleMembershipByMembershipNumber( + final String currentSubject, + final String assumedRoles, + final Integer membershipNumber) { + + context.define(currentSubject, assumedRoles); + + final var result = membershipRepo.findMembershipByMemberNumber(membershipNumber); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(mapper.map( + result.get(), HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER)); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java index 5a537b26..47a3608e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java @@ -22,12 +22,19 @@ public interface HsOfficeMembershipRepository extends Repository findMembershipsByOptionalPartnerUuid(UUID partnerUuid); + List findMembershipsByPartnerUuid(UUID partnerUuid); + + @Query(""" + SELECT membership FROM HsOfficeMembershipEntity membership + WHERE membership.partner.partnerNumber = :partnerNumber + ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix + """) + @Timed("app.office.membership.repo.findMembershipsByPartnerNumber") + List findMembershipsByPartnerNumber(Integer partnerNumber); @Query(""" SELECT membership FROM HsOfficeMembershipEntity membership @@ -35,12 +42,12 @@ public interface HsOfficeMembershipRepository extends Repository findMembershipByPartnerNumberAndSuffix( @NotNull Integer partnerNumber, @NotNull String suffix); - default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) { + default Optional findMembershipByMemberNumber(final Integer memberNumber) { final var partnerNumber = memberNumber / 100; final String suffix = String.format("%02d", memberNumber % 100); final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix); diff --git a/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml index 88cf784a..8e0f8a64 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml @@ -2,7 +2,7 @@ get: tags: - hs-office-memberships description: 'Fetch a single membership by its membershipNumber, if visible for the current subject.' - operationId: getSingleMembershipByUuid + operationId: getSingleMembershipByMembershipNumber parameters: - $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/assumedRoles' @@ -27,59 +27,3 @@ get: $ref: 'error-responses.yaml#/components/responses/Unauthorized' "403": $ref: 'error-responses.yaml#/components/responses/Forbidden' - -patch: - tags: - - hs-office-memberships - description: 'Updates a single membership by its uuid, if permitted for the current subject.' - operationId: patchMembership - parameters: - - $ref: 'auth.yaml#/components/parameters/currentSubject' - - $ref: 'auth.yaml#/components/parameters/assumedRoles' - - name: membershipUUID - in: path - required: true - schema: - type: string - format: uuid - requestBody: - content: - 'application/json': - schema: - $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembershipPatch' - responses: - "200": - description: OK - content: - 'application/json': - schema: - $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership' - "401": - $ref: 'error-responses.yaml#/components/responses/Unauthorized' - "403": - $ref: 'error-responses.yaml#/components/responses/Forbidden' - -delete: - tags: - - hs-office-memberships - description: 'Delete a single membership by its uuid, if permitted for the current subject.' - operationId: deleteMembershipByUuid - parameters: - - $ref: 'auth.yaml#/components/parameters/currentSubject' - - $ref: 'auth.yaml#/components/parameters/assumedRoles' - - name: membershipUUID - in: path - required: true - schema: - type: string - format: uuid - description: UUID of the membership to delete. - responses: - "204": - description: No Content - "401": - $ref: 'error-responses.yaml#/components/responses/Unauthorized' - "403": - $ref: 'error-responses.yaml#/components/responses/Forbidden' - "404": - $ref: 'error-responses.yaml#/components/responses/NotFound' diff --git a/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml b/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml index 9b2c27b4..f0da7c61 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml @@ -15,15 +15,15 @@ get: type: string format: uuid description: UUID of the business partner, exclusive to `memberNumber`. - - name: memberNumber + - name: partnerNumber in: query required: false schema: type: string - minLength: 9 - maxLength: 9 - pattern: 'M-[0-9]{7}' - description: Member number, exclusive to `partnerUuid`. + minLength: 7 + maxLength: 7 + pattern: 'P-[0-9]{5}' + description: partnerNumber of the partner the memberships belong to responses: "200": description: OK diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index 915afb61..40c0ce93 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -85,7 +85,6 @@ paths: /api/hs/office/memberships/M-{membershipNumber}: $ref: "hs-office-memberships-with-membershipNumber.yaml" - /api/hs/office/memberships/{membershipUUID}: $ref: "hs-office-memberships-with-uuid.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java index 6e54acfa..1239c2a4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java @@ -79,7 +79,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopAssetsTransactionsByMemberNumber() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given() @@ -202,7 +202,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopAssetsTransactionsByMembershipUuidAndDateRange() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given() @@ -235,7 +235,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canPostNewCoopAssetTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var location = RestAssured // @formatter:off .given() @@ -280,7 +280,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canAddCoopAssetsReversalTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var givenTransaction = jpaAttempt.transacted(() -> { // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) context.define("superuser-alex@hostsharing.net"); @@ -348,7 +348,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canNotCancelMoreAssetsThanCurrentlySubscribed() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); RestAssured // @formatter:off .given() 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 9409b856..c064a848 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 @@ -915,7 +915,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { 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.findMembershipByMemberNumber(eq(availableMemberNumber))).thenReturn(Optional.of(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)); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java index 40f9d0a7..01248496 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java @@ -62,7 +62,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // given context("superuser-alex@hostsharing.net"); final var count = coopAssetsTransactionRepo.count(); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load(); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().load(); // when final var result = attempt(em, () -> { @@ -94,7 +94,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // when attempt(em, () -> { - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() .membership(givenMembership) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) @@ -166,7 +166,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuid() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( @@ -189,7 +189,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuidAndValueDateRange() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( 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 e7b1e52f..844e8db5 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 @@ -87,7 +87,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions?membershipUuid=" + givenMembership.getUuid()).then().log().all().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals(""" @@ -142,7 +142,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopSharesTransactionsByMembershipUuidAndDateRange() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when() @@ -167,7 +167,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canAddCoopSharesTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var location = RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" @@ -198,7 +198,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canAddCoopSharesReversalTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var givenTransaction = jpaAttempt.transacted(() -> { // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) context.define("superuser-alex@hostsharing.net"); @@ -266,7 +266,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" 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 613ccc2b..d428a9d7 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 @@ -61,7 +61,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // given context("superuser-alex@hostsharing.net"); final var count = coopSharesTransactionRepo.count(); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load(); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().load(); // when final var result = attempt(em, () -> { @@ -93,7 +93,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // when attempt(em, () -> { - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var newCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder() .membership(givenMembership) .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) @@ -159,7 +159,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( @@ -180,7 +180,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index 9f609e53..b0f99593 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -140,30 +140,30 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle } @Test - void globalAdmin_canViewMembershipsByMemberNumber() { + void globalAdmin_canViewMembershipsByPartnerNumber() { RestAssured // @formatter:off .given() - .header("current-subject", "superuser-alex@hostsharing.net") - .port(port) + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) .when() - .queryParam("memberNumber", "M-1000202" ) - .get("http://localhost/api/hs/office/memberships") + .queryParam("partnerNumber", "P-10002" ) + .get("http://localhost/api/hs/office/memberships") .then().log().all().assertThat() - .statusCode(200) - .contentType("application/json") - .body("", lenientlyEquals(""" - [ - { - "partner": { "partnerNumber": "P-10002" }, - "memberNumber": "M-1000202", - "memberNumberSuffix": "02", - "validFrom": "2022-10-01", - "validTo": null, - "status": "ACTIVE" - } - ] - """)); + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "partner": { "partnerNumber": "P-10002" }, + "memberNumber": "M-1000202", + "memberNumberSuffix": "02", + "validFrom": "2022-10-01", + "validTo": null, + "status": "ACTIVE" + } + ] + """)); // @formatter:on } } @@ -220,7 +220,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle @Test void globalAdmin_canGetArbitraryMembership() { context.define("superuser-alex@hostsharing.net"); - final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).getUuid(); + final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().getUuid(); RestAssured // @formatter:off .given() @@ -246,7 +246,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle @Test void normalUser_canNotGetUnrelatedMembership() { context.define("superuser-alex@hostsharing.net"); - final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).getUuid(); + final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().getUuid(); RestAssured // @formatter:off .given() @@ -261,7 +261,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle @Test void partnerRelAgent_canGetRelatedMembership() { context.define("superuser-alex@hostsharing.net"); - final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).getUuid(); + final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).orElseThrow().getUuid(); RestAssured // @formatter:off .given() diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java index 432a38e6..0d937904 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java @@ -19,11 +19,16 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.util.List; +import java.util.Optional; import java.util.UUID; +import static io.hypersistence.utils.hibernate.type.range.Range.localDateRange; +import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; 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; @@ -33,6 +38,34 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ActiveProfiles("test") public class HsOfficeMembershipControllerRestTest { + private static final HsOfficePartnerEntity PARTNER_12345 = HsOfficePartnerEntity.builder() + .partnerNumber(12345) + .build(); + public static final HsOfficeMembershipEntity MEMBERSHIP_1234501 = HsOfficeMembershipEntity.builder() + .partner(PARTNER_12345) + .memberNumberSuffix("01") + .validity(localDateRange("[2013-10-01,]")) + .status(HsOfficeMembershipStatus.ACTIVE) + .build(); + public static final HsOfficeMembershipEntity MEMBERSHIP_1234500 = HsOfficeMembershipEntity.builder() + .partner(PARTNER_12345) + .memberNumberSuffix("00") + .validity(localDateRange("[2011-04-01,2016-12-31]")) + .status(HsOfficeMembershipStatus.CANCELLED) + .build(); + public static final String MEMBERSHIP_1234501_JSON = """ + { + "partner": { + "partnerNumber":"P-12345" + }, + "memberNumber": "M-1234500", + "memberNumberSuffix": "00", + "validFrom": "2011-04-01", + "validTo": "2016-12-30", + "status":"CANCELLED" + } + """; + @Autowired MockMvc mockMvc; @@ -52,11 +85,11 @@ public class HsOfficeMembershipControllerRestTest { class GetListOfMemberships { @Test - void findMembershipByNonExistingMemberNumberReturnsEmptyList() throws Exception { + void findMembershipByNonExistingPartnerNumberReturnsEmptyList() throws Exception { // when mockMvc.perform(MockMvcRequestBuilders - .get("/api/hs/office/memberships?memberNumber=M-1234501") + .get("/api/hs/office/memberships?partnerNumber=P-12345") .header("current-subject", "superuser-alex@hostsharing.net") .contentType(MediaType.APPLICATION_JSON) .content(""" @@ -73,6 +106,116 @@ public class HsOfficeMembershipControllerRestTest { .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$", hasSize(0))); } + + @Test + void findMembershipByExistingPartnerNumberReturnsAllRelatedMemberships() throws Exception { + + // given + when(membershipRepo.findMembershipsByPartnerNumber(12345)) + .thenReturn(List.of( + MEMBERSHIP_1234500, + MEMBERSHIP_1234501 + )); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships?partnerNumber=P-12345") + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "partner.uuid": null, + "memberNumberSuffix": "01", + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" + } + """) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", hasSize(2))); + } + } + + @Nested + class GetSingleMembership { + + @Test + void byUuid() throws Exception { + + // given + final var givenUuid = UUID.randomUUID(); + when(membershipRepo.findByUuid(givenUuid)).thenReturn( + Optional.of(MEMBERSHIP_1234500) + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/" + givenUuid) + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", lenientlyEquals(MEMBERSHIP_1234501_JSON))); + } + + @Test + void byUnavailableUuid() throws Exception { + + // given + when(membershipRepo.findByUuid(any(UUID.class))).thenReturn( + Optional.empty() + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/" + UUID.randomUUID()) + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isNotFound()); + } + + @Test + void byMemberNumber() throws Exception { + + // given + when(membershipRepo.findMembershipByMemberNumber(1234501)).thenReturn( + Optional.of(MEMBERSHIP_1234500) + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/M-1234501") + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", lenientlyEquals(MEMBERSHIP_1234501_JSON))); + } + + @Test + void byUnavailableMemberNumber() throws Exception { + + // given + when(membershipRepo.findMembershipByMemberNumber(any(Integer.class))).thenReturn( + Optional.empty() + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/M-0000000") + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isNotFound()); + } + } @Nested diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java index 0929f370..fc3599d2 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java @@ -156,7 +156,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl context("superuser-alex@hostsharing.net"); // when - final var result = membershipRepo.findMembershipsByOptionalPartnerUuid(null); + final var result = membershipRepo.findAll(); // then exactlyTheseMembershipsAreReturned( @@ -173,7 +173,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); // when - final var result = membershipRepo.findMembershipsByOptionalPartnerUuid(givenPartner.getUuid()); + final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid()); // then exactlyTheseMembershipsAreReturned(result, @@ -186,7 +186,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl context("superuser-alex@hostsharing.net"); // when - final var result = membershipRepo.findMembershipByMemberNumber(1000202); + final var result = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // then assertThat(result) @@ -194,6 +194,34 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl .extracting(Object::toString) .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); } + + @Test + public void globalAdmin_withoutAssumedRole_canFindAllMembershipsByPartnerNumberAndSuffix() { + // given + context("superuser-alex@hostsharing.net"); + + // when + final var result = membershipRepo.findMembershipByPartnerNumberAndSuffix(10002, "02").orElseThrow(); + + // then + assertThat(result) + .isNotNull() + .extracting(Object::toString) + .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); + } + + @Test + public void globalAdmin_withoutAssumedRole_canFindAllMembershipsByPartnerNumber() { + // given + context("superuser-alex@hostsharing.net"); + + // when + final var result = membershipRepo.findMembershipsByPartnerNumber(10002); + + // then + exactlyTheseMembershipsAreReturned(result, + "Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); + } } @Nested @@ -339,7 +367,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl select currentTask, targetTable, targetOp, targetdelta->>'membernumbersuffix' from base.tx_journal_v where targettable = 'hs_office.membership'; - """); + """); // when @SuppressWarnings("unchecked") final List customerLogEntries = query.getResultList(); -- 2.39.5 From 7188022ae5adf5adb0f3e649084b28acca63a08d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 9 Dec 2024 15:56:50 +0100 Subject: [PATCH 12/30] add missing Validate.java + Test --- .../hsadminng/errors/Validate.java | 23 ++++++++++++++ .../hsadminng/errors/ValidateUnitTest.java | 31 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/main/java/net/hostsharing/hsadminng/errors/Validate.java create mode 100644 src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/errors/Validate.java b/src/main/java/net/hostsharing/hsadminng/errors/Validate.java new file mode 100644 index 00000000..3ce68fe9 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/errors/Validate.java @@ -0,0 +1,23 @@ +package net.hostsharing.hsadminng.errors; + +import lombok.AllArgsConstructor; + +import jakarta.validation.ValidationException; + +@AllArgsConstructor +public class Validate { + + final String variableNames; + + public static Validate validate(final String variableNames) { + return new Validate(variableNames); + } + + public final void atMaxOneNonNull(final Object var1, final Object var2) { + if (var1 != null && var2 != null) { + throw new ValidationException( + "Exactly one of (" + variableNames + ") must be non-null, " + + "but are (" + var1 + ", " + var2 + ")"); + } + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java b/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java new file mode 100644 index 00000000..0d212c90 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java @@ -0,0 +1,31 @@ +package net.hostsharing.hsadminng.errors; + +import org.junit.jupiter.api.Test; + +import jakarta.validation.ValidationException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class ValidateUnitTest { + + @Test + void shouldFailValidationIfBothParametersAreNotNull() { + final var throwable = catchThrowable(() -> + Validate.validate("var1, var2").atMaxOneNonNull("val1", "val2") + ); + assertThat(throwable).isInstanceOf(ValidationException.class) + .hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)"); + } + + @Test + void shouldNotFailValidationIfBothParametersAreull() { + Validate.validate("var1, var2").atMaxOneNonNull(null, null); + } + + @Test + void shouldNotFailValidationIfExactlyOneParameterIsNonNull() { + Validate.validate("var1, var2").atMaxOneNonNull("val1", null); + Validate.validate("var1, var2").atMaxOneNonNull(null, "val2"); + } +} -- 2.39.5 From d464a4cf612de1aff20ee62fc8bfef18f763cf56 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 6 Dec 2024 14:57:32 +0100 Subject: [PATCH 13/30] add endpoint HTTP GET /api/hs/office/partners/P-{partnerNumber} --- .../partner/HsOfficePartnerController.java | 17 ++++++++ .../partner/HsOfficePartnerRepository.java | 2 +- ...hs-office-partners-with-partnerNumber.yaml | 28 +++++++++++++ .../api-definition/hs-office/hs-office.yaml | 3 ++ ...fficeDebitorRepositoryIntegrationTest.java | 2 +- ...iceMembershipControllerAcceptanceTest.java | 8 ++-- .../HsOfficePartnerControllerRestTest.java | 39 +++++++++++++++++++ ...fficePartnerRepositoryIntegrationTest.java | 2 +- 8 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java index a375174b..f3c25bd5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java @@ -104,6 +104,23 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class)); } + @Override + @Transactional(readOnly = true) + @Timed("app.office.partners.api.getSinglePartnerByPartnerNumber") + public ResponseEntity getSinglePartnerByPartnerNumber( + final String currentSubject, + final String assumedRoles, + final Integer partnerNumber) { + + context.define(currentSubject, assumedRoles); + + final var result = partnerRepo.findPartnerByPartnerNumber(partnerNumber); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class)); + } + @Override @Transactional @Timed("app.office.partners.api.deletePartnerByUuid") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java index 282d3db3..3ae4a26a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepository.java @@ -32,7 +32,7 @@ public interface HsOfficePartnerRepository extends Repository findPartnerByOptionalNameLike(String name); @Timed("app.office.partners.repo.findPartnerByPartnerNumber") - HsOfficePartnerEntity findPartnerByPartnerNumber(Integer partnerNumber); + Optional findPartnerByPartnerNumber(Integer partnerNumber); @Timed("app.office.partners.repo.save") HsOfficePartnerEntity save(final HsOfficePartnerEntity entity); diff --git a/src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml new file mode 100644 index 00000000..b402048f --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-partners-with-partnerNumber.yaml @@ -0,0 +1,28 @@ +get: + tags: + - hs-office-partners + description: 'Fetch a single business partner by its partner-number (prefixed with "P-"), if visible for the current subject.' + operationId: getSinglePartnerByPartnerNumber + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: partnerNumber + in: path + required: true + schema: + type: integer + minimum: 10000 + maximum: 99999 + description: partner-number (prefixed with "P-") of the partner to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index e8e7816d..71aa708b 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -13,6 +13,9 @@ paths: /api/hs/office/partners: $ref: "hs-office-partners.yaml" + /api/hs/office/partners/P-{partnerNumber}: + $ref: "hs-office-partners-with-partnerNumber.yaml" + /api/hs/office/partners/{partnerUUID}: $ref: "hs-office-partners-with-uuid.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index 781077cb..fb0c0c94 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -83,7 +83,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var count = debitorRepo.count(); - final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001); + final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index c2dd44c1..9f609e53 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -112,16 +112,16 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle void globalAdmin_canViewMembershipsByPartnerUuid() { context.define("superuser-alex@hostsharing.net"); - final var partner = partnerRepo.findPartnerByPartnerNumber(10001); + final var partner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); RestAssured // @formatter:off - .given() + .given() .header("current-subject", "superuser-alex@hostsharing.net") .port(port) - .when() + .when() .queryParam("partnerUuid", partner.getUuid() ) .get("http://localhost/api/hs/office/memberships") - .then().log().all().assertThat() + .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java index dbc6081b..4d016725 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java @@ -168,6 +168,45 @@ class HsOfficePartnerControllerRestTest { } } + @Nested + class GetSinglePartnerByPartnerNumber { + + @Test + void respondWithPartner_ifPartnerNumberIsAvailable() throws Exception { + // given + when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.of(HsOfficePartnerEntity.builder() + .partnerNumber(12345) + .build())); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/partners/P-12345") + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isOk()) + .andExpect(jsonPath("partnerNumber", is("P-12345"))); + } + + @Test + void respondNotFound_ifPartnerNumberIsNotAvailable() throws Exception { + // given + when(partnerRepo.findPartnerByPartnerNumber(12345)).thenReturn(Optional.empty()); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/partners/P-12345") + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isNotFound()); + } + } + @Nested class DeletePartner { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 79ff449d..92683a6b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -243,7 +243,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); // when - final var result = partnerRepo.findPartnerByPartnerNumber(10001); + final var result = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); // then assertThat(result) -- 2.39.5 From ab2c5245e97483da5af45b3a4083456790448264 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 12:01:37 +0100 Subject: [PATCH 14/30] getListOfDebitors with partnerNumber and getSingleDebitorByDebitorNumber --- .../debitor/HsOfficeDebitorController.java | 23 ++++++-- .../debitor/HsOfficeDebitorRepository.java | 14 +++-- .../hs-office/api-mappings.yaml | 1 + ...hs-office-debitors-with-debitorNumber.yaml | 29 ++++++++++ .../hs-office/hs-office-debitors.yaml | 6 +-- .../api-definition/hs-office/hs-office.yaml | 3 ++ ...OfficeDebitorControllerAcceptanceTest.java | 53 ++++++++++++++++++- ...fficeDebitorRepositoryIntegrationTest.java | 31 ++++++++--- ...ceSepaMandateControllerAcceptanceTest.java | 2 +- 9 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java index 1792acdb..b1dd3172 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java @@ -57,11 +57,11 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { final String currentSubject, final String assumedRoles, final String name, - final String debitorNumber) { + final String partnerNumber) { context.define(currentSubject, assumedRoles); - final var entities = debitorNumber != null - ? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber)) + final var entities = partnerNumber != null + ? debitorRepo.findDebitorByPartnerNumber(cropTag("P-", partnerNumber)) : debitorRepo.findDebitorByOptionalNameLike(name); final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); @@ -133,6 +133,23 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); } + @Override + @Transactional(readOnly = true) + @Timed("app.office.debitors.api.getSingleDebitorByDebitorNumber") + public ResponseEntity getSingleDebitorByDebitorNumber( + final String currentSubject, + final String assumedRoles, + final Integer debitorNumber) { + + context.define(currentSubject, assumedRoles); + + final var result = debitorRepo.findDebitorByDebitorNumber(debitorNumber); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); + } + @Override @Transactional @Timed("app.office.debitors.api.deleteDebitorByUuid") diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java index a62d1c42..a48bfa2b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.hs.office.debitor; import io.micrometer.core.annotation.Timed; +import net.hostsharing.hsadminng.lambda.Reducer; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @@ -19,15 +20,20 @@ public interface HsOfficeDebitorRepository extends Repository findDebitorByPartnerNumberAndDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); + List findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(int partnerNumber, String debitorNumberSuffix); - default List findDebitorByDebitorNumber(int debitorNumber) { + default Optional findDebitorByDebitorNumber(int debitorNumber) { final var partnerNumber = debitorNumber / 100; final String suffix = String.format("%02d", debitorNumber % 100); - final var result = findDebitorByPartnerNumberAndDebitorNumberSuffix(partnerNumber, suffix); + final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, suffix); + return result.stream().reduce(Reducer::toSingleElement); + } + + default List findDebitorByPartnerNumber(int partnerNumber) { + final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, null); return result; } diff --git a/src/main/resources/api-definition/hs-office/api-mappings.yaml b/src/main/resources/api-definition/hs-office/api-mappings.yaml index 2403e1e4..3a13afd4 100644 --- a/src/main/resources/api-definition/hs-office/api-mappings.yaml +++ b/src/main/resources/api-definition/hs-office/api-mappings.yaml @@ -13,6 +13,7 @@ map: - type: string:uuid => java.util.UUID - type: string:format => java.lang.String - type: number:currency => java.math.BigDecimal + - type: number:integer => java.lang.Integer paths: /api/hs/office/partners/{partnerUUID}: diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml new file mode 100644 index 00000000..9e34b758 --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml @@ -0,0 +1,29 @@ +get: + tags: + - hs-office-debitors + description: 'Fetch a single debitor by its debitorNumber, if visible for the current subject.' + operationId: getSingleDebitorByDebitorNumber + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: debitorNumber + in: path + required: true + schema: + type: number + format: integer + minimum: 1000000 + maximum: 9999999 + description: debitor-number of the debitor to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml index c825817f..74fab6dc 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml @@ -13,15 +13,15 @@ get: schema: type: string description: Prefix of name properties from person or contact to filter the results. - - name: debitorNumber + - name: partnerNumber in: query required: false schema: type: string minLength: 9 maxLength: 9 - pattern: 'D-[0-9]{7}' - description: Debitor number of the requested debitor. + pattern: 'P-[0-9]{5}' + description: Partner number of the requested debitor. responses: "200": description: OK diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index 71aa708b..b7725285 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -61,6 +61,9 @@ paths: /api/hs/office/debitors: $ref: "hs-office-debitors.yaml" + /api/hs/office/debitors/D-{debitorNumber}: + $ref: "hs-office-debitors-with-debitorNumber.yaml" + /api/hs/office/debitors/{debitorUUID}: $ref: "hs-office-debitors-with-uuid.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index d0b56745..4fb18e61 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -233,7 +233,56 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu } @Test - void globalAdmin_withoutAssumedRoles_canFindDebitorDebitorByDebitorNumber() { + void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorNumber() { + + RestAssured // @formatter:off + .given() + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/office/debitors/P-10002") + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "debitorNumber": "D-1000211", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + }, + { + "debitorNumber": "D-1000212", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + }, + { + "debitorNumber": "D-1000213", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + } + ] + """)); + // @formatter:on + } + + @Test + void globalAdmin_withoutAssumedRoles_canFindDebitorsByPartnerNumber() { RestAssured // @formatter:off .given() @@ -258,7 +307,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu } ] """)); - // @formatter:on + // @formatter:on } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index fb0c0c94..f821d3b6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -50,7 +50,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean HsOfficePartnerRepository partnerRepo; @Autowired - HsOfficeContactRealRepository contactrealRepo; + HsOfficeContactRealRepository contactRealRepo; @Autowired HsOfficePersonRealRepository personRepo; @@ -85,7 +85,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean final var count = debitorRepo.count(); final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001).orElseThrow(); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact")); // when final var result = attempt(em, () -> { @@ -119,7 +119,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // given context("superuser-alex@hostsharing.net"); final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("first contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("first contact")); // when final var result = attempt(em, () -> { @@ -158,7 +158,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean attempt(em, () -> { final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH")); final var givenDebitorPerson = one(personRepo.findPersonByOptionalNameLike("Fourth eG")); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike("fourth contact")); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike("fourth contact")); final var newDebitor = HsOfficeDebitorEntity.builder() .debitorNumberSuffix("22") .debitorRel(HsOfficeRelationRealEntity.builder() @@ -278,7 +278,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean } @Nested - class FindByDebitorNumberLike { + class FindByDebitorNumber { @Test public void globalAdmin_canViewAllDebitors() { @@ -288,6 +288,23 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean // when final var result = debitorRepo.findDebitorByDebitorNumber(1000313); + // then + assertThat(result).map(Object::toString).contains( + "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); + } + } + + @Nested + class FindByPartnerNumber { + + @Test + public void globalAdmin_canViewAllDebitors() { + // given + context("superuser-alex@hostsharing.net"); + + // when + final var result = debitorRepo.findDebitorByPartnerNumber(10003); + // then exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); @@ -324,7 +341,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean "hs_office.relation#FourtheG-with-DEBITOR-FourtheG:ADMIN", true); final var givenNewPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First")); final var givenNewBillingPerson = one(personRepo.findPersonByOptionalNameLike("Firby")); - final var givenNewContact = one(contactrealRepo.findContactByOptionalCaptionLike("sixth contact")); + final var givenNewContact = one(contactRealRepo.findContactByOptionalCaptionLike("sixth contact")); final var givenNewBankAccount = one(bankAccountRepo.findByOptionalHolderLike("first")); final String givenNewVatId = "NEW-VAT-ID"; final String givenNewVatCountryCode = "NC"; @@ -613,7 +630,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); final var givenPartner = one(partnerRepo.findPartnerByOptionalNameLike(partnerName)); final var givenPartnerPerson = givenPartner.getPartnerRel().getHolder(); - final var givenContact = one(contactrealRepo.findContactByOptionalCaptionLike(contactCaption)); + final var givenContact = one(contactRealRepo.findContactByOptionalCaptionLike(contactCaption)); final var givenBankAccount = bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null; final var newDebitor = HsOfficeDebitorEntity.builder() diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java index 63cce53f..b7198aaa 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java @@ -524,7 +524,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandateForDebitorNumber(final int debitorNumber) { return jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).get(0); + final var givenDebitor = debitorRepo.findDebitorByDebitorNumber(debitorNumber).orElseThrow(); final var bankAccountHolder = ofNullable(givenDebitor.getPartner().getPartnerRel().getHolder().getTradeName()) .orElse(givenDebitor.getPartner().getPartnerRel().getHolder().getFamilyName()); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike(bankAccountHolder).get(0); -- 2.39.5 From 352446f1c71f7f42aae7e32c40ab1f1c65745be1 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 7 Dec 2024 12:31:43 +0100 Subject: [PATCH 15/30] add endpoint /api/hs/office/memberships/M-{membershipNumber} - WIP --- ...ice-memberships-with-membershipNumber.yaml | 85 +++++++++++++++++++ .../api-definition/hs-office/hs-office.yaml | 4 + 2 files changed, 89 insertions(+) create mode 100644 src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml diff --git a/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml new file mode 100644 index 00000000..88cf784a --- /dev/null +++ b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml @@ -0,0 +1,85 @@ +get: + tags: + - hs-office-memberships + description: 'Fetch a single membership by its membershipNumber, if visible for the current subject.' + operationId: getSingleMembershipByUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: membershipNumber + in: path + required: true + schema: + type: number + format: integer + minimum: 1000000 + maximum: 9999999 + description: membershipNumber of the membership to fetch. + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership' + + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +patch: + tags: + - hs-office-memberships + description: 'Updates a single membership by its uuid, if permitted for the current subject.' + operationId: patchMembership + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: membershipUUID + in: path + required: true + schema: + type: string + format: uuid + requestBody: + content: + 'application/json': + schema: + $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembershipPatch' + responses: + "200": + description: OK + content: + 'application/json': + schema: + $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership' + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + +delete: + tags: + - hs-office-memberships + description: 'Delete a single membership by its uuid, if permitted for the current subject.' + operationId: deleteMembershipByUuid + parameters: + - $ref: 'auth.yaml#/components/parameters/currentSubject' + - $ref: 'auth.yaml#/components/parameters/assumedRoles' + - name: membershipUUID + in: path + required: true + schema: + type: string + format: uuid + description: UUID of the membership to delete. + responses: + "204": + description: No Content + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' + "404": + $ref: 'error-responses.yaml#/components/responses/NotFound' diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index b7725285..915afb61 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -82,6 +82,10 @@ paths: /api/hs/office/memberships: $ref: "hs-office-memberships.yaml" + /api/hs/office/memberships/M-{membershipNumber}: + $ref: "hs-office-memberships-with-membershipNumber.yaml" + + /api/hs/office/memberships/{membershipUUID}: $ref: "hs-office-memberships-with-uuid.yaml" -- 2.39.5 From 04bb9aa206018edf4cac2b3c7680a3f870db1140 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 9 Dec 2024 15:23:30 +0100 Subject: [PATCH 16/30] add endpoint /api/hs/office/memberships/M-{membershipNumber} and amend query to partnerNumber --- ...OfficeCoopAssetsTransactionController.java | 9 +- .../HsOfficeMembershipController.java | 46 ++++-- .../HsOfficeMembershipRepository.java | 19 ++- ...ice-memberships-with-membershipNumber.yaml | 58 +------ .../hs-office/hs-office-memberships.yaml | 10 +- .../api-definition/hs-office/hs-office.yaml | 1 - ...tsTransactionControllerAcceptanceTest.java | 10 +- ...opAssetsTransactionControllerRestTest.java | 2 +- ...sTransactionRepositoryIntegrationTest.java | 8 +- ...esTransactionControllerAcceptanceTest.java | 10 +- ...sTransactionRepositoryIntegrationTest.java | 8 +- ...iceMembershipControllerAcceptanceTest.java | 44 +++--- .../HsOfficeMembershipControllerRestTest.java | 147 +++++++++++++++++- ...ceMembershipRepositoryIntegrationTest.java | 36 ++++- 14 files changed, 277 insertions(+), 131 deletions(-) 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 fbb59788..9073e564 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 @@ -290,11 +290,10 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse if (adoptingMembershipMemberNumber != null) { final var adoptingMemberNumber = Integer.valueOf(adoptingMembershipMemberNumber.substring("M-".length())); final var adoptingMembership = membershipRepo.findMembershipByMemberNumber(adoptingMemberNumber); - if (adoptingMembership != null) { - return adoptingMembership; - } - throw new ValidationException("adoptingMembership.memberNumber='" + adoptingMembershipMemberNumber - + "' not found or not accessible"); + return adoptingMembership.orElseThrow( () -> + new ValidationException("adoptingMembership.memberNumber='" + adoptingMembershipMemberNumber + + "' not found or not accessible") + ); } throw new ValidationException( diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java index 3ba36f5c..84994ceb 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java @@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; +import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.mapper.StandardMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -17,7 +18,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.errors.Validate.validate; import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag; @RestController @@ -39,16 +40,20 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { final String currentSubject, final String assumedRoles, final UUID partnerUuid, - final String memberNumber) { + final String partnerNumber) { context.define(currentSubject, assumedRoles); - final var entities = (memberNumber != null) - ? ofNullable(membershipRepo.findMembershipByMemberNumber( - cropTag(HsOfficeMembershipEntity.MEMBER_NUMBER_TAG, memberNumber))).stream() - .toList() - : membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid); + validate("partnerUuid, partnerNumber").atMaxOneNonNull(partnerUuid, partnerNumber); - final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class, + final var entities = partnerNumber != null + ? membershipRepo.findMembershipsByPartnerNumber( + cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber)) + : partnerUuid != null + ? membershipRepo.findMembershipsByPartnerUuid(partnerUuid) + : membershipRepo.findAll(); + + final var resources = mapper.mapList( + entities, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(resources); } @@ -72,7 +77,8 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { .path("/api/hs/office/memberships/{id}") .buildAndExpand(saved.getUuid()) .toUri(); - final var mapped = mapper.map(saved, HsOfficeMembershipResource.class, + final var mapped = mapper.map( + saved, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.created(uri).body(mapped); } @@ -91,7 +97,27 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { if (result.isEmpty()) { return ResponseEntity.notFound().build(); } - return ResponseEntity.ok(mapper.map(result.get(), HsOfficeMembershipResource.class, + return ResponseEntity.ok(mapper.map( + result.get(), HsOfficeMembershipResource.class, + SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER)); + } + + @Override + @Transactional(readOnly = true) + @Timed("app.office.membership.api.getSingleMembershipByMembershipNumber") + public ResponseEntity getSingleMembershipByMembershipNumber( + final String currentSubject, + final String assumedRoles, + final Integer membershipNumber) { + + context.define(currentSubject, assumedRoles); + + final var result = membershipRepo.findMembershipByMemberNumber(membershipNumber); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(mapper.map( + result.get(), HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER)); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java index 5a537b26..47a3608e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepository.java @@ -22,12 +22,19 @@ public interface HsOfficeMembershipRepository extends Repository findMembershipsByOptionalPartnerUuid(UUID partnerUuid); + List findMembershipsByPartnerUuid(UUID partnerUuid); + + @Query(""" + SELECT membership FROM HsOfficeMembershipEntity membership + WHERE membership.partner.partnerNumber = :partnerNumber + ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix + """) + @Timed("app.office.membership.repo.findMembershipsByPartnerNumber") + List findMembershipsByPartnerNumber(Integer partnerNumber); @Query(""" SELECT membership FROM HsOfficeMembershipEntity membership @@ -35,12 +42,12 @@ public interface HsOfficeMembershipRepository extends Repository findMembershipByPartnerNumberAndSuffix( @NotNull Integer partnerNumber, @NotNull String suffix); - default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) { + default Optional findMembershipByMemberNumber(final Integer memberNumber) { final var partnerNumber = memberNumber / 100; final String suffix = String.format("%02d", memberNumber % 100); final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix); diff --git a/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml index 88cf784a..8e0f8a64 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-memberships-with-membershipNumber.yaml @@ -2,7 +2,7 @@ get: tags: - hs-office-memberships description: 'Fetch a single membership by its membershipNumber, if visible for the current subject.' - operationId: getSingleMembershipByUuid + operationId: getSingleMembershipByMembershipNumber parameters: - $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/assumedRoles' @@ -27,59 +27,3 @@ get: $ref: 'error-responses.yaml#/components/responses/Unauthorized' "403": $ref: 'error-responses.yaml#/components/responses/Forbidden' - -patch: - tags: - - hs-office-memberships - description: 'Updates a single membership by its uuid, if permitted for the current subject.' - operationId: patchMembership - parameters: - - $ref: 'auth.yaml#/components/parameters/currentSubject' - - $ref: 'auth.yaml#/components/parameters/assumedRoles' - - name: membershipUUID - in: path - required: true - schema: - type: string - format: uuid - requestBody: - content: - 'application/json': - schema: - $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembershipPatch' - responses: - "200": - description: OK - content: - 'application/json': - schema: - $ref: 'hs-office-membership-schemas.yaml#/components/schemas/HsOfficeMembership' - "401": - $ref: 'error-responses.yaml#/components/responses/Unauthorized' - "403": - $ref: 'error-responses.yaml#/components/responses/Forbidden' - -delete: - tags: - - hs-office-memberships - description: 'Delete a single membership by its uuid, if permitted for the current subject.' - operationId: deleteMembershipByUuid - parameters: - - $ref: 'auth.yaml#/components/parameters/currentSubject' - - $ref: 'auth.yaml#/components/parameters/assumedRoles' - - name: membershipUUID - in: path - required: true - schema: - type: string - format: uuid - description: UUID of the membership to delete. - responses: - "204": - description: No Content - "401": - $ref: 'error-responses.yaml#/components/responses/Unauthorized' - "403": - $ref: 'error-responses.yaml#/components/responses/Forbidden' - "404": - $ref: 'error-responses.yaml#/components/responses/NotFound' diff --git a/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml b/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml index 9b2c27b4..f0da7c61 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-memberships.yaml @@ -15,15 +15,15 @@ get: type: string format: uuid description: UUID of the business partner, exclusive to `memberNumber`. - - name: memberNumber + - name: partnerNumber in: query required: false schema: type: string - minLength: 9 - maxLength: 9 - pattern: 'M-[0-9]{7}' - description: Member number, exclusive to `partnerUuid`. + minLength: 7 + maxLength: 7 + pattern: 'P-[0-9]{5}' + description: partnerNumber of the partner the memberships belong to responses: "200": description: OK diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index 915afb61..40c0ce93 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -85,7 +85,6 @@ paths: /api/hs/office/memberships/M-{membershipNumber}: $ref: "hs-office-memberships-with-membershipNumber.yaml" - /api/hs/office/memberships/{membershipUUID}: $ref: "hs-office-memberships-with-uuid.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java index 6e54acfa..1239c2a4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java @@ -79,7 +79,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopAssetsTransactionsByMemberNumber() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given() @@ -202,7 +202,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopAssetsTransactionsByMembershipUuidAndDateRange() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given() @@ -235,7 +235,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canPostNewCoopAssetTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var location = RestAssured // @formatter:off .given() @@ -280,7 +280,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canAddCoopAssetsReversalTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var givenTransaction = jpaAttempt.transacted(() -> { // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) context.define("superuser-alex@hostsharing.net"); @@ -348,7 +348,7 @@ class HsOfficeCoopAssetsTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canNotCancelMoreAssetsThanCurrentlySubscribed() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); RestAssured // @formatter:off .given() 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 9409b856..c064a848 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 @@ -915,7 +915,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { 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.findMembershipByMemberNumber(eq(availableMemberNumber))).thenReturn(Optional.of(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)); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java index 40f9d0a7..01248496 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java @@ -62,7 +62,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // given context("superuser-alex@hostsharing.net"); final var count = coopAssetsTransactionRepo.count(); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load(); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().load(); // when final var result = attempt(em, () -> { @@ -94,7 +94,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // when attempt(em, () -> { - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() .membership(givenMembership) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) @@ -166,7 +166,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuid() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( @@ -189,7 +189,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopAssetsTransactions_filteredByMembershipUuidAndValueDateRange() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( 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 e7b1e52f..844e8db5 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 @@ -87,7 +87,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions?membershipUuid=" + givenMembership.getUuid()).then().log().all().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals(""" @@ -142,7 +142,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canFindCoopSharesTransactionsByMembershipUuidAndDateRange() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").port(port).when() @@ -167,7 +167,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canAddCoopSharesTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var location = RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" @@ -198,7 +198,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canAddCoopSharesReversalTransaction() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var givenTransaction = jpaAttempt.transacted(() -> { // TODO.impl: introduce something like transactedAsSuperuser / transactedAs("...", ...) context.define("superuser-alex@hostsharing.net"); @@ -266,7 +266,7 @@ class HsOfficeCoopSharesTransactionControllerAcceptanceTest extends ContextBased void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() { context.define("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); RestAssured // @formatter:off .given().header("current-subject", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body(""" 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 613ccc2b..d428a9d7 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 @@ -61,7 +61,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // given context("superuser-alex@hostsharing.net"); final var count = coopSharesTransactionRepo.count(); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load(); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().load(); // when final var result = attempt(em, () -> { @@ -93,7 +93,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // when attempt(em, () -> { - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow(); final var newCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder() .membership(givenMembership) .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) @@ -159,7 +159,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuid() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( @@ -180,7 +180,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase public void globalAdmin_canViewCoopSharesTransactions_filteredByMembershipUuidAndValueDateRange() { // given context("superuser-alex@hostsharing.net"); - final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202); + final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // when final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index 9f609e53..b0f99593 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -140,30 +140,30 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle } @Test - void globalAdmin_canViewMembershipsByMemberNumber() { + void globalAdmin_canViewMembershipsByPartnerNumber() { RestAssured // @formatter:off .given() - .header("current-subject", "superuser-alex@hostsharing.net") - .port(port) + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) .when() - .queryParam("memberNumber", "M-1000202" ) - .get("http://localhost/api/hs/office/memberships") + .queryParam("partnerNumber", "P-10002" ) + .get("http://localhost/api/hs/office/memberships") .then().log().all().assertThat() - .statusCode(200) - .contentType("application/json") - .body("", lenientlyEquals(""" - [ - { - "partner": { "partnerNumber": "P-10002" }, - "memberNumber": "M-1000202", - "memberNumberSuffix": "02", - "validFrom": "2022-10-01", - "validTo": null, - "status": "ACTIVE" - } - ] - """)); + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "partner": { "partnerNumber": "P-10002" }, + "memberNumber": "M-1000202", + "memberNumberSuffix": "02", + "validFrom": "2022-10-01", + "validTo": null, + "status": "ACTIVE" + } + ] + """)); // @formatter:on } } @@ -220,7 +220,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle @Test void globalAdmin_canGetArbitraryMembership() { context.define("superuser-alex@hostsharing.net"); - final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).getUuid(); + final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().getUuid(); RestAssured // @formatter:off .given() @@ -246,7 +246,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle @Test void normalUser_canNotGetUnrelatedMembership() { context.define("superuser-alex@hostsharing.net"); - final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).getUuid(); + final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000101).orElseThrow().getUuid(); RestAssured // @formatter:off .given() @@ -261,7 +261,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle @Test void partnerRelAgent_canGetRelatedMembership() { context.define("superuser-alex@hostsharing.net"); - final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).getUuid(); + final var givenMembershipUuid = membershipRepo.findMembershipByMemberNumber(1000303).orElseThrow().getUuid(); RestAssured // @formatter:off .given() diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java index 432a38e6..0d937904 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java @@ -19,11 +19,16 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.util.List; +import java.util.Optional; import java.util.UUID; +import static io.hypersistence.utils.hibernate.type.range.Range.localDateRange; +import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; 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; @@ -33,6 +38,34 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ActiveProfiles("test") public class HsOfficeMembershipControllerRestTest { + private static final HsOfficePartnerEntity PARTNER_12345 = HsOfficePartnerEntity.builder() + .partnerNumber(12345) + .build(); + public static final HsOfficeMembershipEntity MEMBERSHIP_1234501 = HsOfficeMembershipEntity.builder() + .partner(PARTNER_12345) + .memberNumberSuffix("01") + .validity(localDateRange("[2013-10-01,]")) + .status(HsOfficeMembershipStatus.ACTIVE) + .build(); + public static final HsOfficeMembershipEntity MEMBERSHIP_1234500 = HsOfficeMembershipEntity.builder() + .partner(PARTNER_12345) + .memberNumberSuffix("00") + .validity(localDateRange("[2011-04-01,2016-12-31]")) + .status(HsOfficeMembershipStatus.CANCELLED) + .build(); + public static final String MEMBERSHIP_1234501_JSON = """ + { + "partner": { + "partnerNumber":"P-12345" + }, + "memberNumber": "M-1234500", + "memberNumberSuffix": "00", + "validFrom": "2011-04-01", + "validTo": "2016-12-30", + "status":"CANCELLED" + } + """; + @Autowired MockMvc mockMvc; @@ -52,11 +85,11 @@ public class HsOfficeMembershipControllerRestTest { class GetListOfMemberships { @Test - void findMembershipByNonExistingMemberNumberReturnsEmptyList() throws Exception { + void findMembershipByNonExistingPartnerNumberReturnsEmptyList() throws Exception { // when mockMvc.perform(MockMvcRequestBuilders - .get("/api/hs/office/memberships?memberNumber=M-1234501") + .get("/api/hs/office/memberships?partnerNumber=P-12345") .header("current-subject", "superuser-alex@hostsharing.net") .contentType(MediaType.APPLICATION_JSON) .content(""" @@ -73,6 +106,116 @@ public class HsOfficeMembershipControllerRestTest { .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$", hasSize(0))); } + + @Test + void findMembershipByExistingPartnerNumberReturnsAllRelatedMemberships() throws Exception { + + // given + when(membershipRepo.findMembershipsByPartnerNumber(12345)) + .thenReturn(List.of( + MEMBERSHIP_1234500, + MEMBERSHIP_1234501 + )); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships?partnerNumber=P-12345") + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "partner.uuid": null, + "memberNumberSuffix": "01", + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" + } + """) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", hasSize(2))); + } + } + + @Nested + class GetSingleMembership { + + @Test + void byUuid() throws Exception { + + // given + final var givenUuid = UUID.randomUUID(); + when(membershipRepo.findByUuid(givenUuid)).thenReturn( + Optional.of(MEMBERSHIP_1234500) + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/" + givenUuid) + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", lenientlyEquals(MEMBERSHIP_1234501_JSON))); + } + + @Test + void byUnavailableUuid() throws Exception { + + // given + when(membershipRepo.findByUuid(any(UUID.class))).thenReturn( + Optional.empty() + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/" + UUID.randomUUID()) + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isNotFound()); + } + + @Test + void byMemberNumber() throws Exception { + + // given + when(membershipRepo.findMembershipByMemberNumber(1234501)).thenReturn( + Optional.of(MEMBERSHIP_1234500) + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/M-1234501") + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", lenientlyEquals(MEMBERSHIP_1234501_JSON))); + } + + @Test + void byUnavailableMemberNumber() throws Exception { + + // given + when(membershipRepo.findMembershipByMemberNumber(any(Integer.class))).thenReturn( + Optional.empty() + ); + + // when + mockMvc.perform(MockMvcRequestBuilders + .get("/api/hs/office/memberships/M-0000000") + .header("current-subject", "superuser-alex@hostsharing.net") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isNotFound()); + } + } @Nested diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java index 0929f370..fc3599d2 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java @@ -156,7 +156,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl context("superuser-alex@hostsharing.net"); // when - final var result = membershipRepo.findMembershipsByOptionalPartnerUuid(null); + final var result = membershipRepo.findAll(); // then exactlyTheseMembershipsAreReturned( @@ -173,7 +173,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); // when - final var result = membershipRepo.findMembershipsByOptionalPartnerUuid(givenPartner.getUuid()); + final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid()); // then exactlyTheseMembershipsAreReturned(result, @@ -186,7 +186,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl context("superuser-alex@hostsharing.net"); // when - final var result = membershipRepo.findMembershipByMemberNumber(1000202); + final var result = membershipRepo.findMembershipByMemberNumber(1000202).orElseThrow(); // then assertThat(result) @@ -194,6 +194,34 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl .extracting(Object::toString) .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); } + + @Test + public void globalAdmin_withoutAssumedRole_canFindAllMembershipsByPartnerNumberAndSuffix() { + // given + context("superuser-alex@hostsharing.net"); + + // when + final var result = membershipRepo.findMembershipByPartnerNumberAndSuffix(10002, "02").orElseThrow(); + + // then + assertThat(result) + .isNotNull() + .extracting(Object::toString) + .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); + } + + @Test + public void globalAdmin_withoutAssumedRole_canFindAllMembershipsByPartnerNumber() { + // given + context("superuser-alex@hostsharing.net"); + + // when + final var result = membershipRepo.findMembershipsByPartnerNumber(10002); + + // then + exactlyTheseMembershipsAreReturned(result, + "Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); + } } @Nested @@ -339,7 +367,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl select currentTask, targetTable, targetOp, targetdelta->>'membernumbersuffix' from base.tx_journal_v where targettable = 'hs_office.membership'; - """); + """); // when @SuppressWarnings("unchecked") final List customerLogEntries = query.getResultList(); -- 2.39.5 From 1cdb01b6d46f47420a81d822594fe5e37406c337 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 9 Dec 2024 15:56:50 +0100 Subject: [PATCH 17/30] add missing Validate.java + Test --- .../hsadminng/errors/Validate.java | 23 ++++++++++++++ .../hsadminng/errors/ValidateUnitTest.java | 31 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/main/java/net/hostsharing/hsadminng/errors/Validate.java create mode 100644 src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/errors/Validate.java b/src/main/java/net/hostsharing/hsadminng/errors/Validate.java new file mode 100644 index 00000000..3ce68fe9 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/errors/Validate.java @@ -0,0 +1,23 @@ +package net.hostsharing.hsadminng.errors; + +import lombok.AllArgsConstructor; + +import jakarta.validation.ValidationException; + +@AllArgsConstructor +public class Validate { + + final String variableNames; + + public static Validate validate(final String variableNames) { + return new Validate(variableNames); + } + + public final void atMaxOneNonNull(final Object var1, final Object var2) { + if (var1 != null && var2 != null) { + throw new ValidationException( + "Exactly one of (" + variableNames + ") must be non-null, " + + "but are (" + var1 + ", " + var2 + ")"); + } + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java b/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java new file mode 100644 index 00000000..0d212c90 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java @@ -0,0 +1,31 @@ +package net.hostsharing.hsadminng.errors; + +import org.junit.jupiter.api.Test; + +import jakarta.validation.ValidationException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +class ValidateUnitTest { + + @Test + void shouldFailValidationIfBothParametersAreNotNull() { + final var throwable = catchThrowable(() -> + Validate.validate("var1, var2").atMaxOneNonNull("val1", "val2") + ); + assertThat(throwable).isInstanceOf(ValidationException.class) + .hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)"); + } + + @Test + void shouldNotFailValidationIfBothParametersAreull() { + Validate.validate("var1, var2").atMaxOneNonNull(null, null); + } + + @Test + void shouldNotFailValidationIfExactlyOneParameterIsNonNull() { + Validate.validate("var1, var2").atMaxOneNonNull("val1", null); + Validate.validate("var1, var2").atMaxOneNonNull(null, "val2"); + } +} -- 2.39.5 From 57112931033f88f58a69bdc12fd72de30e00b348 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 10 Dec 2024 09:52:41 +0100 Subject: [PATCH 18/30] amend endpoint-usages in scenario-tests --- .../scenarios/debitor/CreateSepaMandateForDebitor.java | 4 ++-- .../hs/office/scenarios/membership/CancelMembership.java | 5 ++--- .../membership/coopassets/CreateCoopAssetsTransaction.java | 6 +++--- .../membership/coopshares/CreateCoopSharesTransaction.java | 6 +++--- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSepaMandateForDebitor.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSepaMandateForDebitor.java index b50cc7c9..62ae5b54 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSepaMandateForDebitor.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSepaMandateForDebitor.java @@ -17,9 +17,9 @@ public class CreateSepaMandateForDebitor extends UseCase - httpGet("/api/hs/office/debitors?debitorNumber=&{debitorNumber}") + httpGet("/api/hs/office/debitors/%{debitorNumber}") .expecting(OK).expecting(JSON), - response -> response.expectArrayElements(1).getFromBody("[0].uuid") + response -> response.getFromBody("uuid") ); obtain("BankAccount: Test AG - debit bank account", () -> diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CancelMembership.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CancelMembership.java index d5d00900..5f3c3196 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CancelMembership.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CancelMembership.java @@ -18,9 +18,8 @@ public class CancelMembership extends UseCase { protected HttpResponse run() { obtain("Membership: %{memberNumber}", () -> - httpGet("/api/hs/office/memberships?memberNumber=%{memberNumber}") - .expectArrayElements(1), - response -> response.expectArrayElements(1).getFromBody("[0].uuid") + httpGet("/api/hs/office/memberships/%{memberNumber}"), + response -> response.getFromBody("uuid") ); return withTitle("Patch the New Status Into the Membership", () -> diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopassets/CreateCoopAssetsTransaction.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopassets/CreateCoopAssetsTransaction.java index beb78b52..1052db28 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopassets/CreateCoopAssetsTransaction.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopassets/CreateCoopAssetsTransaction.java @@ -18,9 +18,9 @@ public abstract class CreateCoopAssetsTransaction extends UseCase - httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}") - .expecting(OK).expecting(JSON).expectArrayElements(1), - response -> response.getFromBody("$[0].uuid") + httpGet("/api/hs/office/memberships/%{memberNumber}") + .expecting(OK).expecting(JSON), + response -> response.getFromBody("uuid") ); return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () -> diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopshares/CreateCoopSharesTransaction.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopshares/CreateCoopSharesTransaction.java index a70cbdf5..6c215b5d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopshares/CreateCoopSharesTransaction.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/coopshares/CreateCoopSharesTransaction.java @@ -18,9 +18,9 @@ public abstract class CreateCoopSharesTransaction extends UseCase - httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}") - .expecting(OK).expecting(JSON).expectArrayElements(1), - response -> response.getFromBody("$[0].uuid") + httpGet("/api/hs/office/memberships/%{memberNumber}") + .expecting(OK).expecting(JSON), + response -> response.getFromBody("uuid") ); return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () -> -- 2.39.5 From 65f48160f811a3d9e315c907d299a0a387985d43 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 10 Dec 2024 13:01:27 +0100 Subject: [PATCH 19/30] add query parameter partnerUuid and amend tests --- .../debitor/HsOfficeDebitorController.java | 7 +- .../debitor/HsOfficeDebitorRepository.java | 7 +- ...hs-office-debitors-with-debitorNumber.yaml | 4 +- .../hs-office/hs-office-debitors.yaml | 11 +- ...sBookingItemRepositoryIntegrationTest.java | 4 +- ...OfficeDebitorControllerAcceptanceTest.java | 151 ++++++++++-------- ...fficeDebitorRepositoryIntegrationTest.java | 10 +- ...ceSepaMandateControllerAcceptanceTest.java | 6 +- ...eSepaMandateRepositoryIntegrationTest.java | 6 +- 9 files changed, 116 insertions(+), 90 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java index b1dd3172..1e82b848 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java @@ -57,12 +57,15 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { final String currentSubject, final String assumedRoles, final String name, + final UUID partnerUuid, final String partnerNumber) { context.define(currentSubject, assumedRoles); final var entities = partnerNumber != null - ? debitorRepo.findDebitorByPartnerNumber(cropTag("P-", partnerNumber)) - : debitorRepo.findDebitorByOptionalNameLike(name); + ? debitorRepo.findDebitorsByPartnerNumber(cropTag("P-", partnerNumber)) + : partnerUuid != null + ? debitorRepo.findDebitorsByPartnerUuid(partnerUuid) + : debitorRepo.findDebitorsByOptionalNameLike(name); final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(resources); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java index a48bfa2b..fa02053b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java @@ -14,6 +14,9 @@ public interface HsOfficeDebitorRepository extends Repository findByUuid(UUID id); + @Timed("app.office.debitors.repo.findDebitorByPartnerUuid") + List findDebitorsByPartnerUuid(UUID partnerUuid); + @Query(""" SELECT debitor FROM HsOfficeDebitorEntity debitor JOIN HsOfficePartnerEntity partner @@ -32,7 +35,7 @@ public interface HsOfficeDebitorRepository extends Repository findDebitorByPartnerNumber(int partnerNumber) { + default List findDebitorsByPartnerNumber(int partnerNumber) { final var result = findDebitorByPartnerNumberAndOptionalDebitorNumberSuffix(partnerNumber, null); return result; } @@ -56,7 +59,7 @@ public interface HsOfficeDebitorRepository extends Repository findDebitorByOptionalNameLike(String name); + List findDebitorsByOptionalNameLike(String name); @Timed("app.office.debitors.repo.save") HsOfficeDebitorEntity save(final HsOfficeDebitorEntity entity); diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml index 9e34b758..565f2523 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors-with-debitorNumber.yaml @@ -12,8 +12,8 @@ get: schema: type: number format: integer - minimum: 1000000 - maximum: 9999999 +# minimum: 1000000 +# maximum: 9999999 description: debitor-number of the debitor to fetch. responses: "200": diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml index 74fab6dc..05533d03 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitors.yaml @@ -13,13 +13,20 @@ get: schema: type: string description: Prefix of name properties from person or contact to filter the results. + - name: partnerUuid + in: query + required: false + schema: + type: string + format: uuid + description: UUID of the business partner, exclusive to `memberNumber`. - name: partnerNumber in: query required: false schema: type: string - minLength: 9 - maxLength: 9 + minLength: 7 + maxLength: 7 pattern: 'P-[0-9]{5}' description: Partner number of the requested debitor. responses: diff --git a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java index 091c2c62..b6c8d757 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemRepositoryIntegrationTest.java @@ -120,7 +120,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // given context("superuser-alex@hostsharing.net"); final var count = rbacBookingItemRepo.count(); - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0); final var givenProject = realProjectRepo.findAllByDebitorUuid(givenDebitor.getUuid()).get(0); // when @@ -151,7 +151,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup // when attempt(em, () -> { - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0); final var givenProject = realProjectRepo.findAllByDebitorUuid(givenDebitor.getUuid()).get(0); final var newBookingItem = HsBookingItemRbacEntity.builder() .project(givenProject) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index 4fb18e61..a05687e4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -31,7 +31,10 @@ import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType. import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, @@ -74,6 +77,69 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu @PersistenceContext EntityManager em; + @Nested + class GetSingleDebitor { + + @Test + void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorUuid() { + + final var givenDebitor = jpaAttempt.transacted(() -> { + context("superuser-alex@hostsharing.net"); + return debitorRepo.findDebitorByDebitorNumber(1000212).orElseThrow(); + }).assertSuccessful().returnedValue(); + + RestAssured // @formatter:off + .given() + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/office/debitors/" + givenDebitor.getUuid()) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + { + "debitorNumber": "D-1000212", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + } + """)); + // @formatter:on + } + + @Test + void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorNumber() { + + RestAssured // @formatter:off + .given() + .header("current-subject", "superuser-alex@hostsharing.net") + .port(port) + .when() + .get("http://localhost/api/hs/office/debitors/D-1000212") + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + { + "debitorNumber": "D-1000212", + "partner": { "partnerNumber": "P-10002" }, + "debitorRel": { + "contact": { "caption": "second contact" } + }, + "vatId": null, + "vatCountryCode": null, + "vatBusiness": true + } + """)); + // @formatter:on + } + } + @Nested class GetListOfDebitors { @@ -232,80 +298,27 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu // @formatter:on } - @Test - void globalAdmin_withoutAssumedRoles_canGetDebitorByDebitorNumber() { - - RestAssured // @formatter:off - .given() - .header("current-subject", "superuser-alex@hostsharing.net") - .port(port) - .when() - .get("http://localhost/api/hs/office/debitors/P-10002") - .then().log().all().assertThat() - .statusCode(200) - .contentType("application/json") - .body("", lenientlyEquals(""" - [ - { - "debitorNumber": "D-1000211", - "partner": { "partnerNumber": "P-10002" }, - "debitorRel": { - "contact": { "caption": "second contact" } - }, - "vatId": null, - "vatCountryCode": null, - "vatBusiness": true - }, - { - "debitorNumber": "D-1000212", - "partner": { "partnerNumber": "P-10002" }, - "debitorRel": { - "contact": { "caption": "second contact" } - }, - "vatId": null, - "vatCountryCode": null, - "vatBusiness": true - }, - { - "debitorNumber": "D-1000213", - "partner": { "partnerNumber": "P-10002" }, - "debitorRel": { - "contact": { "caption": "second contact" } - }, - "vatId": null, - "vatCountryCode": null, - "vatBusiness": true - } - ] - """)); - // @formatter:on - } - @Test void globalAdmin_withoutAssumedRoles_canFindDebitorsByPartnerNumber() { RestAssured // @formatter:off - .given() + .given() .header("current-subject", "superuser-alex@hostsharing.net") .port(port) - .when() - .get("http://localhost/api/hs/office/debitors?debitorNumber=D-1000212") - .then().log().all().assertThat() + .when() + .get("http://localhost/api/hs/office/debitors?partnerNumber=P-10002") + .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" - [ - { - "debitorNumber": "D-1000212", - "partner": { "partnerNumber": "P-10002" }, - "debitorRel": { - "contact": { "caption": "second contact" } - }, - "vatId": null, - "vatCountryCode": null, - "vatBusiness": true - } - ] + [ + { + "debitorNumber": "D-1000212", + "partner": { + "partnerNumber": "P-10002" + } + } + ] """)); // @formatter:on } @@ -493,7 +506,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu @Test void globalAdmin_withoutAssumedRole_canGetArbitraryDebitor() { context.define("superuser-alex@hostsharing.net"); - final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("First").get(0).getUuid(); + final var givenDebitorUuid = debitorRepo.findDebitorsByOptionalNameLike("First").get(0).getUuid(); RestAssured // @formatter:off .given() @@ -558,7 +571,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu @Test void normalUser_canNotGetUnrelatedDebitor() { context.define("superuser-alex@hostsharing.net"); - final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("First").get(0).getUuid(); + final var givenDebitorUuid = debitorRepo.findDebitorsByOptionalNameLike("First").get(0).getUuid(); RestAssured // @formatter:off .given() @@ -573,7 +586,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu @Test void contactAdminUser_canGetRelatedDebitorExceptRefundBankAccount() { context.define("superuser-alex@hostsharing.net"); - final var givenDebitorUuid = debitorRepo.findDebitorByOptionalNameLike("first contact").get(0).getUuid(); + final var givenDebitorUuid = debitorRepo.findDebitorsByOptionalNameLike("first contact").get(0).getUuid(); RestAssured // @formatter:off .given() diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index f821d3b6..0be9d9ca 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -234,7 +234,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); // when - final var result = debitorRepo.findDebitorByOptionalNameLike(null); + final var result = debitorRepo.findDebitorsByOptionalNameLike(null); // then allTheseDebitorsAreReturned( @@ -256,7 +256,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net", assumedRole); // when: - final var result = debitorRepo.findDebitorByOptionalNameLike(""); + final var result = debitorRepo.findDebitorsByOptionalNameLike(""); // then: exactlyTheseDebitorsAreReturned(result, @@ -270,7 +270,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("selfregistered-test-user@hostsharing.org"); // when: - final var result = debitorRepo.findDebitorByOptionalNameLike(null); + final var result = debitorRepo.findDebitorsByOptionalNameLike(null); // then: assertThat(result).isEmpty(); @@ -303,7 +303,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); // when - final var result = debitorRepo.findDebitorByPartnerNumber(10003); + final var result = debitorRepo.findDebitorsByPartnerNumber(10003); // then exactlyTheseDebitorsAreReturned(result, @@ -320,7 +320,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean context("superuser-alex@hostsharing.net"); // when - final var result = debitorRepo.findDebitorByOptionalNameLike("third contact"); + final var result = debitorRepo.findDebitorsByOptionalNameLike("third contact"); // then exactlyTheseDebitorsAreReturned(result, "debitor(D-1000313: rel(anchor='IF Third OHG', type='DEBITOR', holder='IF Third OHG'), thi)"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java index b7198aaa..ff5c4e46 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java @@ -138,7 +138,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl void globalAdmin_canPostNewSepaMandate() { context.define("superuser-alex@hostsharing.net"); - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0); final var location = RestAssured // @formatter:off @@ -180,7 +180,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() { context.define("superuser-alex@hostsharing.net"); - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc("DE02200505501015871393").get(0); final var location = RestAssured // @formatter:off @@ -205,7 +205,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl void globalAdmin_canNotPostNewSepaMandate_ifBankAccountDoesNotExist() { context.define("superuser-alex@hostsharing.net"); - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("Third").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0); final var givenBankAccountUuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); final var location = RestAssured // @formatter:off diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java index 8046fc68..8e7512c3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java @@ -66,7 +66,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC // given context("superuser-alex@hostsharing.net"); final var count = sepaMandateRepo.count(); - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0); // when @@ -100,7 +100,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC // when attempt(em, () -> { - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0); final var givenBankAccount = bankAccountRepo.findByOptionalHolderLike("Paul Winkler").get(0); final var newSepaMandate = HsOfficeSepaMandateEntity.builder() .debitor(givenDebitor) @@ -397,7 +397,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC private HsOfficeSepaMandateEntity givenSomeTemporarySepaMandate(final String iban) { return jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0); + final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("First").get(0); final var givenBankAccount = bankAccountRepo.findByIbanOrderByIbanAsc(iban).get(0); final var newSepaMandate = HsOfficeSepaMandateEntity.builder() .debitor(givenDebitor) -- 2.39.5 From 15900d83e4f6c64925a3367f108fc6b6856e2856 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 11 Dec 2024 10:59:43 +0100 Subject: [PATCH 20/30] TODOs in ReplaceDeceasedPartnerWithCommunityOfHeirs --- .../scenarios/HsOfficeScenarioTests.java | 1 + ...ceDeceasedPartnerWithCommunityOfHeirs.java | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index ff7c261c..7f797c14 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -604,6 +604,7 @@ class HsOfficeScenarioTests extends ScenarioTest { new ReplaceDeceasedPartnerWithCommunityOfHeirs(scenarioTest) .given("partnerNumber", "P-31011") .given("nameOfDeceasedPerson", "Michelle Matthieu") // FIXME: redundant + .given("dateOfDeath", "2024-11-15") .given( // "name": "Erbengemeinschaft Michelle Matthieu", // FIXME: automatic? "communityOfHeirsPostalAddress", """ diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index 92ff7924..a59da0d4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -63,6 +63,11 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpPost("/api/hs/office/relations", usingJsonBody(""" { @@ -77,7 +82,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase + obtain("Partner: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody(""" { "partnerRel": ${Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}} @@ -85,6 +90,27 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase Date: Wed, 11 Dec 2024 16:08:32 +0100 Subject: [PATCH 21/30] implement holder and contact data in HTTP POST to relations --- .../hsadminng/errors/Validate.java | 10 ++- .../HsOfficeMembershipController.java | 2 +- .../relation/HsOfficeRelationController.java | 58 +++++++++++++----- .../hs-office/hs-office-relation-schemas.yaml | 16 +++-- .../hsadminng/errors/ValidateUnitTest.java | 61 ++++++++++++++----- ...fficeRelationControllerAcceptanceTest.java | 61 ++++++++++++++++++- 6 files changed, 169 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/errors/Validate.java b/src/main/java/net/hostsharing/hsadminng/errors/Validate.java index 3ce68fe9..73f02e89 100644 --- a/src/main/java/net/hostsharing/hsadminng/errors/Validate.java +++ b/src/main/java/net/hostsharing/hsadminng/errors/Validate.java @@ -13,8 +13,16 @@ public class Validate { return new Validate(variableNames); } - public final void atMaxOneNonNull(final Object var1, final Object var2) { + public final void atMaxOne(final Object var1, final Object var2) { if (var1 != null && var2 != null) { + throw new ValidationException( + "At maximum one of (" + variableNames + ") must be non-null, " + + "but are (" + var1 + ", " + var2 + ")"); + } + } + + public final void exactlyOne(final Object var1, final Object var2) { + if ((var1 != null) == (var2 != null)) { throw new ValidationException( "Exactly one of (" + variableNames + ") must be non-null, " + "but are (" + var1 + ", " + var2 + ")"); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java index 84994ceb..43cc292d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java @@ -43,7 +43,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { final String partnerNumber) { context.define(currentSubject, assumedRoles); - validate("partnerUuid, partnerNumber").atMaxOneNonNull(partnerUuid, partnerNumber); + validate("partnerUuid, partnerNumber").atMaxOne(partnerUuid, partnerNumber); final var entities = partnerNumber != null ? membershipRepo.findMembershipsByPartnerNumber( diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java index d543e81e..97e7dac0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java @@ -2,9 +2,12 @@ package net.hostsharing.hsadminng.hs.office.relation; import io.micrometer.core.annotation.Timed; import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.errors.Validate; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository; import net.hostsharing.hsadminng.mapper.StandardMapper; import org.springframework.beans.factory.annotation.Autowired; @@ -20,6 +23,7 @@ import java.util.NoSuchElementException; import java.util.UUID; import java.util.function.BiConsumer; +import static net.hostsharing.hsadminng.mapper.KeyValueMap.from; @RestController public class HsOfficeRelationController implements HsOfficeRelationsApi { @@ -31,10 +35,10 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { private StandardMapper mapper; @Autowired - private HsOfficeRelationRbacRepository relationRbacRepo; + private HsOfficeRelationRbacRepository rbacRelationRepo; @Autowired - private HsOfficePersonRealRepository personRepo; + private HsOfficePersonRealRepository realPersonRepo; @Autowired private HsOfficeContactRealRepository realContactRepo; @@ -55,7 +59,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { context.define(currentSubject, assumedRoles); final List entities = - relationRbacRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData( + rbacRelationRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData( personUuid, relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name()), personData, contactData); @@ -78,17 +82,34 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { final var entityToSave = new HsOfficeRelationRbacEntity(); entityToSave.setType(HsOfficeRelationType.valueOf(body.getType())); entityToSave.setMark(body.getMark()); - entityToSave.setAnchor(personRepo.findByUuid(body.getAnchorUuid()).orElseThrow( + + entityToSave.setAnchor(realPersonRepo.findByUuid(body.getAnchorUuid()).orElseThrow( () -> new NoSuchElementException("cannot find Person by anchorUuid: " + body.getAnchorUuid()) )); - entityToSave.setHolder(personRepo.findByUuid(body.getHolderUuid()).orElseThrow( - () -> new NoSuchElementException("cannot find Person by holderUuid: " + body.getHolderUuid()) - )); - entityToSave.setContact(realContactRepo.findByUuid(body.getContactUuid()).orElseThrow( - () -> new NoSuchElementException("cannot find Contact by contactUuid: " + body.getContactUuid()) - )); - final var saved = relationRbacRepo.save(entityToSave); + Validate.validate("anchor, anchor.uuid").exactlyOne(body.getHolder(), body.getHolderUuid()); + if ( body.getHolderUuid() != null) { + entityToSave.setHolder(realPersonRepo.findByUuid(body.getHolderUuid()).orElseThrow( + () -> new NoSuchElementException("cannot find Person by holderUuid: " + body.getHolderUuid()) + )); + } else { + entityToSave.setHolder(realPersonRepo.save( + mapper.map(body.getHolder(), HsOfficePersonRealEntity.class) + ) ); + } + + Validate.validate("contact, contact.uuid").exactlyOne(body.getContact(), body.getContactUuid()); + if ( body.getContactUuid() != null) { + entityToSave.setContact(realContactRepo.findByUuid(body.getContactUuid()).orElseThrow( + () -> new NoSuchElementException("cannot find Contact by contactUuid: " + body.getContactUuid()) + )); + } else { + entityToSave.setContact(realContactRepo.save( + mapper.map(body.getContact(), HsOfficeContactRealEntity.class, CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER) + ) ); + } + + final var saved = rbacRelationRepo.save(entityToSave); final var uri = MvcUriComponentsBuilder.fromController(getClass()) @@ -110,7 +131,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { context.define(currentSubject, assumedRoles); - final var result = relationRbacRepo.findByUuid(relationUuid); + final var result = rbacRelationRepo.findByUuid(relationUuid); if (result.isEmpty()) { return ResponseEntity.notFound().build(); } @@ -126,7 +147,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { final UUID relationUuid) { context.define(currentSubject, assumedRoles); - final var result = relationRbacRepo.deleteByUuid(relationUuid); + final var result = rbacRelationRepo.deleteByUuid(relationUuid); if (result == 0) { return ResponseEntity.notFound().build(); } @@ -145,11 +166,11 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { context.define(currentSubject, assumedRoles); - final var current = relationRbacRepo.findByUuid(relationUuid).orElseThrow(); + final var current = rbacRelationRepo.findByUuid(relationUuid).orElseThrow(); new HsOfficeRelationEntityPatcher(em, current).apply(body); - final var saved = relationRbacRepo.save(current); + final var saved = rbacRelationRepo.save(current); final var mapped = mapper.map(saved, HsOfficeRelationResource.class); return ResponseEntity.ok(mapped); } @@ -159,4 +180,11 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.class)); resource.setContact(mapper.map(entity.getContact(), HsOfficeContactResource.class)); }; + + + @SuppressWarnings("unchecked") + final BiConsumer CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { + entity.putEmailAddresses(from(resource.getEmailAddresses())); + entity.putPhoneNumbers(from(resource.getPhoneNumbers())); + }; } diff --git a/src/main/resources/api-definition/hs-office/hs-office-relation-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-relation-schemas.yaml index 8cf1f03f..9b8d2e46 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-relation-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-relation-schemas.yaml @@ -52,6 +52,8 @@ components: holder.uuid: type: string format: uuid + holder: + $ref: 'hs-office-person-schemas.yaml#/components/schemas/HsOfficePersonInsert' type: type: string nullable: true @@ -61,11 +63,17 @@ components: contact.uuid: type: string format: uuid + contact: + $ref: 'hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContactInsert' required: - - anchor.uuid - - holder.uuid - - type - - contact.uuid + - anchor.uuid + - type + # soon we might need to be able to use this: + # https://community.smartbear.com/discussions/swaggerostools/defining-conditional-attributes-in-openapi/222410 + # For now we just describe the conditionally required properties: + description: + Additionally to `type` and `anchor.uuid`, either `anchor.uuid` or `anchor` + and either `contact` or `contact.uuid` need to be given. # relation created as a sub-element with implicitly known type HsOfficeRelationSubInsert: diff --git a/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java b/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java index 0d212c90..97e73ec1 100644 --- a/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/errors/ValidateUnitTest.java @@ -1,5 +1,6 @@ package net.hostsharing.hsadminng.errors; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import jakarta.validation.ValidationException; @@ -9,23 +10,53 @@ import static org.assertj.core.api.Assertions.catchThrowable; class ValidateUnitTest { - @Test - void shouldFailValidationIfBothParametersAreNotNull() { - final var throwable = catchThrowable(() -> - Validate.validate("var1, var2").atMaxOneNonNull("val1", "val2") - ); - assertThat(throwable).isInstanceOf(ValidationException.class) - .hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)"); + @Nested + class AtMaxOne { + @Test + void shouldFailValidationIfBothParametersAreNotNull() { + final var throwable = catchThrowable(() -> + Validate.validate("var1, var2").atMaxOne("val1", "val2") + ); + assertThat(throwable).isInstanceOf(ValidationException.class) + .hasMessage("At maximum one of (var1, var2) must be non-null, but are (val1, val2)"); + } + + @Test + void shouldNotFailValidationIfBothParametersAreNull() { + Validate.validate("var1, var2").atMaxOne(null, null); + } + + @Test + void shouldNotFailValidationIfExactlyOneParameterIsNonNull() { + Validate.validate("var1, var2").atMaxOne("val1", null); + Validate.validate("var1, var2").atMaxOne(null, "val2"); + } } - @Test - void shouldNotFailValidationIfBothParametersAreull() { - Validate.validate("var1, var2").atMaxOneNonNull(null, null); - } + @Nested + class ExactlyOne { + @Test + void shouldFailValidationIfBothParametersAreNotNull() { + final var throwable = catchThrowable(() -> + Validate.validate("var1, var2").exactlyOne("val1", "val2") + ); + assertThat(throwable).isInstanceOf(ValidationException.class) + .hasMessage("Exactly one of (var1, var2) must be non-null, but are (val1, val2)"); + } - @Test - void shouldNotFailValidationIfExactlyOneParameterIsNonNull() { - Validate.validate("var1, var2").atMaxOneNonNull("val1", null); - Validate.validate("var1, var2").atMaxOneNonNull(null, "val2"); + @Test + void shouldFailValidationIfBothParametersAreNull() { + final var throwable = catchThrowable(() -> + Validate.validate("var1, var2").exactlyOne(null, null) + ); + assertThat(throwable).isInstanceOf(ValidationException.class) + .hasMessage("Exactly one of (var1, var2) must be non-null, but are (null, null)"); + } + + @Test + void shouldNotFailValidationIfExactlyOneParameterIsNonNull() { + Validate.validate("var1, var2").exactlyOne("val1", null); + Validate.validate("var1, var2").exactlyOne(null, "val2"); + } } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java index 53e1f591..d79ddbfa 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java @@ -223,7 +223,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean class AddRelation { @Test - void globalAdmin_withoutAssumedRole_canAddRelation() { + void globalAdmin_withoutAssumedRole_canAddRelationWithHolderUuidAndContactUuid() { context.define("superuser-alex@hostsharing.net"); final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); @@ -261,7 +261,62 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean .body("holder.givenName", is("Paul")) .body("contact.caption", is("second contact")) .header("Location", startsWith("http://localhost")) - .extract().header("Location"); // @formatter:on + .extract().header("Location"); // @formatter:on + + // finally, the new relation can be accessed under the generated UUID + final var newSubjectUuid = toCleanup(HsOfficeRelationRealEntity.class, UUID.fromString( + location.substring(location.lastIndexOf('/') + 1))); + assertThat(newSubjectUuid).isNotNull(); + } + + @Test + void globalAdmin_withoutAssumedRole_canAddRelationWithHolderAndContactData() { + + context.define("superuser-alex@hostsharing.net"); + final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0); + + final var location = RestAssured // @formatter:off + .given() + .header("current-subject", "superuser-alex@hostsharing.net") + .contentType(ContentType.JSON) + .body(""" + { + "type": "%s", + "mark": "%s", + "anchor.uuid": "%s", + "holder": { + "personType": "NATURAL_PERSON", + "familyName": "Person", + "givenName": "Temp" + }, + "contact": { + "caption": "Temp Contact", + "emailAddresses": { + "main": "test@example.org" + } + } + } + """.formatted( + HsOfficeRelationTypeResource.SUBSCRIBER, + "operations-discuss", + givenAnchorPerson.getUuid() + ) + ) + .port(port) + .when() + .post("http://localhost/api/hs/office/relations") + .then().log().all().assertThat() + .statusCode(201) + .contentType(ContentType.JSON) + .body("uuid", isUuidValid()) + .body("type", is("SUBSCRIBER")) + .body("mark", is("operations-discuss")) + .body("anchor.tradeName", is("Third OHG")) + .body("holder.givenName", is("Temp")) + .body("holder.familyName", is("Person")) + .body("contact.caption", is("Temp Contact")) + .header("Location", startsWith("http://localhost")) + .extract().header("Location"); // @formatter:on // finally, the new relation can be accessed under the generated UUID final var newSubjectUuid = toCleanup(HsOfficeRelationRealEntity.class, UUID.fromString( @@ -277,7 +332,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Smith").get(0); final var givenContact = contactrealRepo.findContactByOptionalCaptionLike("fourth").get(0); - final var location = RestAssured // @formatter:off + RestAssured // @formatter:off .given() .header("current-subject", "superuser-alex@hostsharing.net") .contentType(ContentType.JSON) -- 2.39.5 From fb70e212a62c084a3e478b656982ae339541a05c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 11 Dec 2024 16:55:30 +0100 Subject: [PATCH 22/30] create Partner-Relation: Erbengemeinschaft with embedded holder and contact data --- .../config/JsonObjectMapperConfiguration.java | 5 ++ .../scenarios/HsOfficeScenarioTests.java | 4 +- ...ceDeceasedPartnerWithCommunityOfHeirs.java | 71 +++++++++++++++++-- .../hsadminng/hs/scenarios/TestReport.java | 3 +- 4 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java b/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java index d5ff80d9..87276f34 100644 --- a/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java +++ b/src/main/java/net/hostsharing/hsadminng/config/JsonObjectMapperConfiguration.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.config; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.openapitools.jackson.nullable.JsonNullableModule; @@ -14,6 +15,10 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @Configuration public class JsonObjectMapperConfiguration { + public static ObjectMapper build() { + return new JsonObjectMapperConfiguration().customObjectMapper().build(); + } + @Bean @Primary public Jackson2ObjectMapperBuilder customObjectMapper() { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 7f797c14..15045cf7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -605,10 +605,12 @@ class HsOfficeScenarioTests extends ScenarioTest { .given("partnerNumber", "P-31011") .given("nameOfDeceasedPerson", "Michelle Matthieu") // FIXME: redundant .given("dateOfDeath", "2024-11-15") + .given("representativeGivenName", "Lena") + .given("representativeFamilyName", "Stadland") .given( // "name": "Erbengemeinschaft Michelle Matthieu", // FIXME: automatic? "communityOfHeirsPostalAddress", """ - "co": "Lena Stadland", + "co": "Lena Stadland", // FIXME: take this form representative...? "street": "Im Wischer 14", "zipcode": "22987", "city": "Hamburg", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index a59da0d4..e9fb8d7b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase; import org.springframework.http.HttpStatus; import static io.restassured.http.ContentType.JSON; +import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.OK; public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase { @@ -44,9 +45,36 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase - httpPost("/api/hs/office/contacts", usingJsonBody(""" - { +// obtain("Contact: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> +// httpPost("/api/hs/office/contacts", usingJsonBody(""" +// { +// "caption": "Erbengemeinschaft %{nameOfDeceasedPerson}", +// "postalAddress": { +// "name": "Erbengemeinschaft %{nameOfDeceasedPerson}", +// %{communityOfHeirsPostalAddress} +// }, +// "phoneNumbers": { +// "office": ${communityOfHeirsOfficePhoneNumber} +// }, +// "emailAddresses": { +// "main": ${communityOfHeirsEmailAddress} +// } +// } +// """)) +// .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) +// ); + + obtain("Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> + httpPost("/api/hs/office/relations", usingJsonBody(""" + { + "type": "PARTNER", + "anchor.uuid": ${Person: Hostsharing eG}, + "holder": { + "personType": "NATURAL_PERSON", + "givenName": ${representativeGivenName}, + "familyName": ${representativeFamilyName} + }, + "contact": { "caption": "Erbengemeinschaft %{nameOfDeceasedPerson}", "postalAddress": { "name": "Erbengemeinschaft %{nameOfDeceasedPerson}", @@ -59,15 +87,44 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase +// httpPost("/api/hs/office/persons", usingJsonBody(""" +// { +// "personType": "NATURAL_PERSON", +// "givenName": ${representativeGivenName}, +// "familyName": ${representativeFamilyName} +// } +// """)) +// .expecting(HttpStatus.CREATED).expecting(ContentType.JSON), +// "A community of heirs needs at least one representative.", +// "If there are more than one, each needs their own contact" +// ); + + obtain("Representative-Relation: %{subscriberGivenName} %{subscriberFamilyName} for Erbengemeinschaft %{nameOfDeceasedPerson}", () -> + httpPost("/api/hs/office/relations", usingJsonBody(""" + { + "type": "REPRESENTATIVE", + "anchor.uuid": ${Person: %{partnerPersonTradeName}}, + "holder.uuid": { + "personType": "NATURAL_PERSON", + "givenName": ${representativeGivenName}, + "familyName": ${representativeFamilyName} + }, + "contact.uuid": ${Contact: %{subscriberGivenName} %{subscriberFamilyName}} + } + """)) + .expecting(CREATED).expecting(JSON), + "" + ); + obtain("Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> httpPost("/api/hs/office/relations", usingJsonBody(""" { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index c55bddce..57728f8a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.scenarios; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; +import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; import net.hostsharing.hsadminng.system.SystemProcess; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Order; @@ -28,7 +29,7 @@ public class TestReport { public static final SimpleDateFormat MM_DD_YYYY_HH_MM_SS = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss"); private static final File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = JsonObjectMapperConfiguration.build(); private final Map aliases; private final PrintWriter markdownLog; // records everything for debugging purposes -- 2.39.5 From 1623e72534b95d67b59beda141001e3c6dd19c06 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 16 Dec 2024 12:36:01 +0100 Subject: [PATCH 23/30] merging aftermaths --- .../hs/office/relation/HsOfficeRelationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java index cf712640..a4f17edf 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationController.java @@ -60,7 +60,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { context.define(currentSubject, assumedRoles); final List entities = - rbacRelationRepo.findRelationRelatedToPersonUuidRelationTypePersonAndContactData( + rbacRelationRepo.findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData( personUuid, relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name()), mark, personData, contactData); -- 2.39.5 From 2971f4196d9acb1c4d5d4bbf215bcab9eeaeaac8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 17 Dec 2024 10:20:48 +0100 Subject: [PATCH 24/30] extract feaure for obtain --- ...placeDeceasedPartnerWithCommunityOfHeirs.java | 16 ++++++++-------- .../hsadminng/hs/scenarios/ScenarioTest.java | 7 +++++-- .../hsadminng/hs/scenarios/TestReport.java | 10 +++++----- .../hsadminng/hs/scenarios/UseCase.java | 11 +++++++++-- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index e9fb8d7b..d37337df 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -90,7 +90,9 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase + obtain("Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{nameOfDeceasedPerson}", () -> httpPost("/api/hs/office/relations", usingJsonBody(""" { "type": "REPRESENTATIVE", - "anchor.uuid": ${Person: %{partnerPersonTradeName}}, - "holder.uuid": { + "anchor.uuid": ${Person: Erbengemeinschaft %{nameOfDeceasedPerson}}, + "holder": { "personType": "NATURAL_PERSON", "givenName": ${representativeGivenName}, "familyName": ${representativeFamilyName} }, - "contact.uuid": ${Contact: %{subscriberGivenName} %{subscriberFamilyName}} + "contact.uuid": ${Contact: Erbengemeinschaft %{nameOfDeceasedPerson}} } """)) .expecting(CREATED).expecting(JSON), @@ -137,12 +139,10 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody(""" { - "partnerRel": ${Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}} + "partnerRel.uuid": ${Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}} } """)) .expecting(HttpStatus.OK) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java index 75ea20e7..b65c8ca7 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java @@ -50,13 +50,16 @@ public abstract class ScenarioTest extends ContextBasedTest { return Optional.of(currentTestMethodProduces.pop()); } - record Alias>(Class useCase, UUID uuid) { + public record Alias>(Class useCase, UUID uuid) { @Override public String toString() { return Objects.toString(uuid); } + public boolean hasUuid() { + return uuid != null; + } } private final static Map> aliases = new HashMap<>(); @@ -111,7 +114,7 @@ public abstract class ScenarioTest extends ContextBasedTest { jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - aliases.put( + putAlias( "Person: Hostsharing eG", new Alias<>( null, diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index 57728f8a..d4c11c36 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; +import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.Alias; import net.hostsharing.hsadminng.system.SystemProcess; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Order; @@ -31,7 +32,7 @@ public class TestReport { private static final File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); private static final ObjectMapper objectMapper = JsonObjectMapperConfiguration.build(); - private final Map aliases; + private final Map> aliases; private final PrintWriter markdownLog; // records everything for debugging purposes private File markdownReportFile; private PrintWriter markdownReport; // records only the use-case under test, without its pre-requisites @@ -43,7 +44,7 @@ public class TestReport { } @SneakyThrows - public TestReport(final Map aliases) { + public TestReport(final Map> aliases) { this.aliases = aliases; this.markdownLog = new PrintWriter(new FileWriter(markdownLogFile)); } @@ -131,9 +132,8 @@ public class TestReport { final var result = new StringBuilder(); for (String line : lines) { - for (Map.Entry entry : aliases.entrySet()) { - final var uuidString = entry.getValue().toString(); - if (line.contains(uuidString)) { + for (Map.Entry> entry : aliases.entrySet()) { + if (entry.getValue().hasUuid() && line.contains(entry.getValue().toString())) { line = line + " // " + entry.getKey(); break; // only add comment for one UUID per row (in our case, there is only one per row) } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index a64052d8..b4f38fa1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -134,8 +134,8 @@ public abstract class UseCase> { }); } - public final void obtain(final String alias, final Supplier http, final String... extraInfo) { - withTitle(alias, () -> { + public final HttpResponse obtain(final String alias, final Supplier http, final String... extraInfo) { + return withTitle(alias, () -> { final var response = http.get().keep(); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; @@ -396,6 +396,13 @@ public abstract class UseCase> { final var onlyVisibleInGeneratedMarkdownNotInSource = new String(new char[]{'F', 'I', 'X', 'M', 'E'}); return alias == null ? "unknown alias -- " + onlyVisibleInGeneratedMarkdownNotInSource : alias; } + + public HttpResponse extract(final String jsonPath, final String resolvableName) { + final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); + final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenAsString(); + ScenarioTest.putProperty(resolvedName, resolvedJsonPath); + return this; + } } protected T self() { -- 2.39.5 From 9fa5bd96d65453e9d54a7a84c161279cbe38d36d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 17 Dec 2024 10:37:16 +0100 Subject: [PATCH 25/30] remove UseCase from Alias --- .../net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java | 3 +-- .../java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java index b65c8ca7..0b33ccec 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java @@ -50,7 +50,7 @@ public abstract class ScenarioTest extends ContextBasedTest { return Optional.of(currentTestMethodProduces.pop()); } - public record Alias>(Class useCase, UUID uuid) { + public record Alias>(UUID uuid) { @Override public String toString() { @@ -117,7 +117,6 @@ public abstract class ScenarioTest extends ContextBasedTest { putAlias( "Person: Hostsharing eG", new Alias<>( - null, personRepo.findPersonByOptionalNameLike("Hostsharing eG") .stream() .map(HsOfficePersonRbacEntity::getUuid) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index b4f38fa1..095fe8b5 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -306,14 +306,14 @@ public abstract class UseCase> { final var value = extractor.apply(this); ScenarioTest.putAlias( alias, - new ScenarioTest.Alias<>(UseCase.this.getClass(), UUID.fromString(value))); + new ScenarioTest.Alias<>(UUID.fromString(value))); return this; } public HttpResponse keepAs(final String alias) { ScenarioTest.putAlias( nonNullAlias(alias), - new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid)); + new ScenarioTest.Alias<>(locationUuid)); return this; } -- 2.39.5 From 921893a3501dc33dd7f6c37873a904a766f60a45 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 17 Dec 2024 10:45:46 +0100 Subject: [PATCH 26/30] remove Alias wrapper class --- .../hsadminng/hs/scenarios/ScenarioTest.java | 25 ++++--------------- .../hsadminng/hs/scenarios/TestReport.java | 10 ++++---- .../hsadminng/hs/scenarios/UseCase.java | 8 ++---- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java index 0b33ccec..f1cf3f96 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/ScenarioTest.java @@ -21,7 +21,6 @@ import java.math.BigDecimal; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Stack; import java.util.UUID; @@ -50,19 +49,7 @@ public abstract class ScenarioTest extends ContextBasedTest { return Optional.of(currentTestMethodProduces.pop()); } - public record Alias>(UUID uuid) { - - @Override - public String toString() { - return Objects.toString(uuid); - } - - public boolean hasUuid() { - return uuid != null; - } - } - - private final static Map> aliases = new HashMap<>(); + private final static Map aliases = new HashMap<>(); private final static Map properties = new HashMap<>(); public final TestReport testReport = new TestReport(aliases); @@ -116,11 +103,9 @@ public abstract class ScenarioTest extends ContextBasedTest { context.define("superuser-alex@hostsharing.net"); putAlias( "Person: Hostsharing eG", - new Alias<>( - personRepo.findPersonByOptionalNameLike("Hostsharing eG") - .stream() + personRepo.findPersonByOptionalNameLike("Hostsharing eG").stream() .map(HsOfficePersonRbacEntity::getUuid) - .reduce(Reducer::toSingleElement).orElseThrow()) + .reduce(Reducer::toSingleElement).orElseThrow() ); } ); @@ -212,7 +197,7 @@ public abstract class ScenarioTest extends ContextBasedTest { return alias; } - static void putAlias(final String name, final Alias value) { + static void putAlias(final String name, final UUID value) { aliases.put(name, value); } @@ -226,7 +211,7 @@ public abstract class ScenarioTest extends ContextBasedTest { static Map knowVariables() { final var map = new LinkedHashMap(); - ScenarioTest.aliases.forEach((key, value) -> map.put(key, value.uuid())); + map.putAll(ScenarioTest.aliases); map.putAll(ScenarioTest.properties); return map; } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index d4c11c36..a3199da6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; -import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.Alias; import net.hostsharing.hsadminng.system.SystemProcess; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Order; @@ -19,6 +18,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Map; +import java.util.UUID; import java.util.regex.Pattern; import static java.lang.String.join; @@ -32,7 +32,7 @@ public class TestReport { private static final File markdownLogFile = new File(BUILD_DOC_SCENARIOS, ".last-debug-log.md"); private static final ObjectMapper objectMapper = JsonObjectMapperConfiguration.build(); - private final Map> aliases; + private final Map aliases; private final PrintWriter markdownLog; // records everything for debugging purposes private File markdownReportFile; private PrintWriter markdownReport; // records only the use-case under test, without its pre-requisites @@ -44,7 +44,7 @@ public class TestReport { } @SneakyThrows - public TestReport(final Map> aliases) { + public TestReport(final Map aliases) { this.aliases = aliases; this.markdownLog = new PrintWriter(new FileWriter(markdownLogFile)); } @@ -132,8 +132,8 @@ public class TestReport { final var result = new StringBuilder(); for (String line : lines) { - for (Map.Entry> entry : aliases.entrySet()) { - if (entry.getValue().hasUuid() && line.contains(entry.getValue().toString())) { + for (Map.Entry entry : aliases.entrySet()) { + if ( entry.getValue() != null && line.contains(entry.getValue().toString())) { line = line + " // " + entry.getKey(); break; // only add comment for one UUID per row (in our case, there is only one per row) } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index 095fe8b5..f8b1da80 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -304,16 +304,12 @@ public abstract class UseCase> { assertThat(alias).as("cannot keep result, no alias found").isNotNull(); final var value = extractor.apply(this); - ScenarioTest.putAlias( - alias, - new ScenarioTest.Alias<>(UUID.fromString(value))); + ScenarioTest.putAlias(alias, UUID.fromString(value)); return this; } public HttpResponse keepAs(final String alias) { - ScenarioTest.putAlias( - nonNullAlias(alias), - new ScenarioTest.Alias<>(locationUuid)); + ScenarioTest.putAlias(nonNullAlias(alias), locationUuid); return this; } -- 2.39.5 From 9b58997fb78f7b76650644a6f961f8df7a8e1e0a Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 18 Dec 2024 09:16:35 +0100 Subject: [PATCH 27/30] ignore missing title in silent section --- .../scenarios/HsOfficeScenarioTests.java | 3 +- ...ceDeceasedPartnerWithCommunityOfHeirs.java | 79 ++++++------------- .../hsadminng/hs/scenarios/JsonOptional.java | 10 +++ .../hsadminng/hs/scenarios/TestReport.java | 4 + .../hsadminng/hs/scenarios/UseCase.java | 61 +++++++++++--- 5 files changed, 90 insertions(+), 67 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 124d3731..735d51a6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -634,8 +634,7 @@ class HsOfficeScenarioTests extends ScenarioTest { """) .given("communityOfHeirsOfficePhoneNumber", "+49 40 666666") .given("communityOfHeirsEmailAddress", "lena.stadland@example.org") - .doRun() - .keep(); + .doRun(); } } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index d37337df..7e62d535 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -31,38 +31,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase response.getFromBody("uuid"), "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? - ); - - obtain("Person: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> - httpPost("/api/hs/office/persons", usingJsonBody(""" - { - "personType": "UNINCORPORATED_FIRM", - "tradeName": "Erbengemeinschaft %{nameOfDeceasedPerson}", - "givenName": ${givenName???}, - "familyName": ${familyName???} - } - """)) - .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) - ); - -// obtain("Contact: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> -// httpPost("/api/hs/office/contacts", usingJsonBody(""" -// { -// "caption": "Erbengemeinschaft %{nameOfDeceasedPerson}", -// "postalAddress": { -// "name": "Erbengemeinschaft %{nameOfDeceasedPerson}", -// %{communityOfHeirsPostalAddress} -// }, -// "phoneNumbers": { -// "office": ${communityOfHeirsOfficePhoneNumber} -// }, -// "emailAddresses": { -// "main": ${communityOfHeirsEmailAddress} -// } -// } -// """)) -// .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) -// ); + ).extractValue("partnerRel.holder.uuid", "Person: %{nameOfDeceasedPerson}"); obtain("Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> httpPost("/api/hs/office/relations", usingJsonBody(""" @@ -70,9 +39,10 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase -// httpPost("/api/hs/office/persons", usingJsonBody(""" -// { -// "personType": "NATURAL_PERSON", -// "givenName": ${representativeGivenName}, -// "familyName": ${representativeFamilyName} -// } -// """)) -// .expecting(HttpStatus.CREATED).expecting(ContentType.JSON), -// "A community of heirs needs at least one representative.", -// "If there are more than one, each needs their own contact" -// ); + .extractUuidAlias("contact.uuid", "Contact: Erbengemeinschaft %{nameOfDeceasedPerson}") + .extractUuidAlias("holder.uuid", "Person: Erbengemeinschaft %{nameOfDeceasedPerson}"); obtain("Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{nameOfDeceasedPerson}", () -> httpPost("/api/hs/office/relations", usingJsonBody(""" @@ -125,7 +79,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpPost("/api/hs/office/relations", usingJsonBody(""" @@ -148,6 +102,8 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpGet("/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: %{representativeGivenName} %{representativeFamilyName}}") + .expecting(OK).expecting(JSON).expectArrayElements(1), + path("[0].anchor.tradeName").contains("Erbengemeinschaft %{nameOfDeceasedPerson}"), + path("[0].holder.familyName").contains("%{representativeFamilyName}") + ); + + // TODO.test: Verify Debitor, Membership, Coop-Shares and Coop-Assets once implemented } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java index f5c829c6..6fff42df 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/JsonOptional.java @@ -1,5 +1,7 @@ package net.hostsharing.hsadminng.hs.scenarios; +import java.util.Objects; +import java.util.UUID; public final class JsonOptional { @@ -41,4 +43,12 @@ public final class JsonOptional { } return jsonValue == null ? null : jsonValue.toString(); } + + public UUID givenUUID() { + try { + return UUID.fromString(Objects.toString(jsonValue)); + } catch(final IllegalArgumentException e) { + throw new ClassCastException("expected a UUID, but got '" + jsonValue + "'"); + } + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java index a3199da6..028e679a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/TestReport.java @@ -169,4 +169,8 @@ public class TestReport { return "unknown"; } } + + public boolean isSilent() { + return silent > 0; + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index f8b1da80..095a4ff5 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -122,12 +122,12 @@ public abstract class UseCase> { return new JsonTemplate(jsonTemplate); } - public final void obtain( + public final HttpResponse obtain( final String title, final Supplier http, final Function extractor, final String... extraInfo) { - withTitle(title, () -> { + return withTitle(title, () -> { final var response = http.get().keep(extractor); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; @@ -261,6 +261,10 @@ public abstract class UseCase> { public final class HttpResponse { + private final HttpMethod httpMethod; + private final String uri; + private final String requestBody; + @Getter private final java.net.http.HttpResponse response; @@ -270,6 +274,8 @@ public abstract class UseCase> { @Getter private UUID locationUuid; + private boolean reported = false; + @SneakyThrows public HttpResponse( final HttpMethod httpMethod, @@ -277,6 +283,9 @@ public abstract class UseCase> { final String requestBody, final java.net.http.HttpResponse response ) { + this.httpMethod = httpMethod; + this.uri = uri; + this.requestBody = requestBody; this.response = response; this.status = HttpStatus.valueOf(response.statusCode()); if (this.status == HttpStatus.CREATED) { @@ -284,22 +293,24 @@ public abstract class UseCase> { assertThat(location).startsWith("http://localhost:"); locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1)); } - - reportRequestAndResponse(httpMethod, uri, requestBody); } public HttpResponse expecting(final HttpStatus httpStatus) { + optionallyReportRequestAndResponse(); assertThat(HttpStatus.valueOf(response.statusCode())).isEqualTo(httpStatus); return this; } public HttpResponse expecting(final ContentType contentType) { + optionallyReportRequestAndResponse(); assertThat(response.headers().firstValue("content-type")) .contains(contentType.toString()); return this; } public HttpResponse keep(final Function extractor) { + optionallyReportRequestAndResponse(); + final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias; assertThat(alias).as("cannot keep result, no alias found").isNotNull(); @@ -309,11 +320,15 @@ public abstract class UseCase> { } public HttpResponse keepAs(final String alias) { + optionallyReportRequestAndResponse(); + ScenarioTest.putAlias(nonNullAlias(alias), locationUuid); return this; } public HttpResponse keep() { + optionallyReportRequestAndResponse(); + final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias; assertThat(alias).as("cannot keep result, no title or alias found for locationUuid: " + locationUuid).isNotNull(); @@ -322,6 +337,8 @@ public abstract class UseCase> { @SneakyThrows public HttpResponse expectArrayElements(final int expectedElementCount) { + optionallyReportRequestAndResponse(); + final var rootNode = objectMapper.readTree(response.body()); assertThat(rootNode.isArray()).as("array expected, but got: " + response.body()).isTrue(); @@ -333,6 +350,8 @@ public abstract class UseCase> { @SneakyThrows public HttpResponse expectObject() { + optionallyReportRequestAndResponse(); + final var rootNode = objectMapper.readTree(response.body()); assertThat(rootNode.isArray()).as("object expected, but got array: " + response.body()).isFalse(); return this; @@ -360,14 +379,23 @@ public abstract class UseCase> { return assertThat(getFromBodyAsOptional(path).givenAsString()); } + public HttpResponse reportWithResponse() { + return reportRequestAndResponse(true); + } + @SneakyThrows - private void reportRequestAndResponse(final HttpMethod httpMethod, final String uri, final String requestBody) { + private HttpResponse reportRequestAndResponse(final boolean unconditionallyWithResponse) { + if (reported) { + throw new IllegalStateException("request report already generated"); + } // the title if (nextTitle != null) { - testReport.printLine("\n### " + ScenarioTest.resolve(nextTitle, KEEP_COMMENTS) + "\n"); + testReport.printPara("### " + ScenarioTest.resolve(nextTitle, KEEP_COMMENTS)); } else if (resultAlias != null) { - testReport.printLine("\n### Create " + resultAlias + "\n"); + testReport.printPara("### Create " + resultAlias); + } else if (testReport.isSilent()) { + testReport.printPara("### Untitled Section"); } else { fail("please wrap the http...-call in the UseCase using `withTitle(...)`"); } @@ -379,11 +407,19 @@ public abstract class UseCase> { // the response testReport.printLine("=> status: " + status + " " + (locationUuid != null ? locationUuid : "")); - if (httpMethod == HttpMethod.GET || status.isError()) { + if (unconditionallyWithResponse || httpMethod == HttpMethod.GET || status.isError()) { testReport.printJson(response.body()); } testReport.printLine("```"); testReport.printLine(""); + return this; + } + + @SneakyThrows + private void optionallyReportRequestAndResponse() { + if (!reported) { + reportRequestAndResponse(false); + } } private String nonNullAlias(final String alias) { @@ -393,7 +429,14 @@ public abstract class UseCase> { return alias == null ? "unknown alias -- " + onlyVisibleInGeneratedMarkdownNotInSource : alias; } - public HttpResponse extract(final String jsonPath, final String resolvableName) { + public HttpResponse extractUuidAlias(final String jsonPath, final String resolvableName) { + final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); + final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenUUID(); + ScenarioTest.putAlias(resolvedName, resolvedJsonPath); + return this; + } + + public HttpResponse extractValue(final String jsonPath, final String resolvableName) { final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenAsString(); ScenarioTest.putProperty(resolvedName, resolvedJsonPath); -- 2.39.5 From 83ab12f3e09d8d1fea55698ce0359477676386e0 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 18 Dec 2024 09:21:59 +0100 Subject: [PATCH 28/30] take co from representative...Name --- .../scenarios/HsOfficeScenarioTests.java | 34 +++++++++---------- ...ceDeceasedPartnerWithCommunityOfHeirs.java | 1 + .../hsadminng/hs/scenarios/ScenarioTest.java | 27 ++++++++------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 735d51a6..220dc0dd 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -227,16 +227,17 @@ class HsOfficeScenarioTests extends ScenarioTest { new ReplaceContactData(scenarioTest) .given("partnerName", "Test AG") .given("newContactCaption", "Test AG - China") - .given("newPostalAddress", """ - "firm": "Test AG", - "name": "Fi Zhong-Kha", - "building": "Thi Chi Koh Building", - "street": "No.2 Commercial Second Street", - "district": "Niushan Wei Wu", - "city": "Dongguan City", - "province": "Guangdong Province", - "country": "China" - """) + .given( + "newPostalAddress", """ + "firm": "Test AG", + "name": "Fi Zhong-Kha", + "building": "Thi Chi Koh Building", + "street": "No.2 Commercial Second Street", + "district": "Niushan Wei Wu", + "city": "Dongguan City", + "province": "Guangdong Province", + "country": "China" + """) .given("newOfficePhoneNumber", "++15 999 654321") .given("newEmailAddress", "norden@test-ag.example.org") .doRun(); @@ -263,6 +264,7 @@ class HsOfficeScenarioTests extends ScenarioTest { @Order(20) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class DebitorScenarios { + @Test @Order(2010) @Requires("Partner: P-31010 - Test AG") @@ -624,14 +626,12 @@ class HsOfficeScenarioTests extends ScenarioTest { .given("representativeGivenName", "Lena") .given("representativeFamilyName", "Stadland") .given( - // "name": "Erbengemeinschaft Michelle Matthieu", // FIXME: automatic? "communityOfHeirsPostalAddress", """ - "co": "Lena Stadland", // FIXME: take this form representative...? - "street": "Im Wischer 14", - "zipcode": "22987", - "city": "Hamburg", - "country": "Germany" - """) + "street": "Im Wischer 14", + "zipcode": "22987", + "city": "Hamburg", + "country": "Germany" + """) .given("communityOfHeirsOfficePhoneNumber", "+49 40 666666") .given("communityOfHeirsEmailAddress", "lena.stadland@example.org") .doRun(); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index 7e62d535..8d386abe 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -48,6 +48,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase { - // FIXME: extract to method - final var producesAnnot = currentTestMethod.getAnnotation(Produces.class); - if (producesAnnot != null && producesAnnot.permanent()) { - final var testMethodProduces = producedAliases(producesAnnot); - testMethodProduces.forEach(declaredAlias -> - assertThat(knowVariables().containsKey(declaredAlias)) - .as("@Producer method " + currentTestMethod.getName() + - " did declare but not produce \"" + declaredAlias + "\"") - .isTrue() ); - } - }); + verifyProduceDeclaration(testInfo); properties.clear(); testReport.close(); @@ -182,6 +171,20 @@ public abstract class ScenarioTest extends ContextBasedTest { } } + private static void verifyProduceDeclaration(final TestInfo testInfo) { + testInfo.getTestMethod().ifPresent(currentTestMethod -> { + final var producesAnnot = currentTestMethod.getAnnotation(Produces.class); + if (producesAnnot != null && producesAnnot.permanent()) { + final var testMethodProduces = producedAliases(producesAnnot); + testMethodProduces.forEach(declaredAlias -> + assertThat(knowVariables().containsKey(declaredAlias)) + .as("@Producer method " + currentTestMethod.getName() + + " did declare but not produce \"" + declaredAlias + "\"") + .isTrue() ); + } + }); + } + static boolean containsAlias(final String alias) { return aliases.containsKey(alias); } -- 2.39.5 From 091454b6abaad788219cc75ce0f9cf960b7329b4 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 18 Dec 2024 10:22:30 +0100 Subject: [PATCH 29/30] amendments according to code review --- ...ceDeceasedPartnerWithCommunityOfHeirs.java | 20 ++--------- .../hsadminng/hs/scenarios/UseCase.java | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index 8d386abe..8aa535ca 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -1,6 +1,5 @@ package net.hostsharing.hsadminng.hs.office.scenarios.partner; -import io.restassured.http.ContentType; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.UseCase; import org.springframework.http.HttpStatus; @@ -28,7 +27,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpGet("/api/hs/office/partners/%{partnerNumber}") - .expecting(OK).expecting(JSON), + .reportWithResponse().expecting(OK).expecting(JSON), response -> response.getFromBody("uuid"), "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? ).extractValue("partnerRel.holder.uuid", "Person: %{nameOfDeceasedPerson}"); @@ -41,8 +40,6 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase - httpPost("/api/hs/office/relations", usingJsonBody(""" - { - "type": "PARTNER", - "anchor.uuid": ${Person: Hostsharing eG}, - "holder.uuid": ${Person: Erbengemeinschaft %{nameOfDeceasedPerson}}, - "contact.uuid": ${Contact: Erbengemeinschaft %{nameOfDeceasedPerson}} - } - """)) - .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) - ); - obtain("Partner: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody(""" { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java index 095a4ff5..01c5dede 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/scenarios/UseCase.java @@ -129,22 +129,26 @@ public abstract class UseCase> { final String... extraInfo) { return withTitle(title, () -> { final var response = http.get().keep(extractor); + response.optionallyReportRequestAndResponse(); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; }); } - public final HttpResponse obtain(final String alias, final Supplier http, final String... extraInfo) { + public final HttpResponse obtain(final String alias, final Supplier httpCall, final String... extraInfo) { return withTitle(alias, () -> { - final var response = http.get().keep(); + final var response = httpCall.get().keep(); + response.optionallyReportRequestAndResponse(); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; }); } - public HttpResponse withTitle(final String resolvableTitle, final Supplier code) { + public HttpResponse withTitle(final String resolvableTitle, final Supplier httpCall, final String... extraInfo) { this.nextTitle = resolvableTitle; - final var response = code.get(); + final var response = httpCall.get(); + response.optionallyReportRequestAndResponse(); + Arrays.stream(extraInfo).forEach(testReport::printPara); this.nextTitle = null; return response; } @@ -274,7 +278,8 @@ public abstract class UseCase> { @Getter private UUID locationUuid; - private boolean reported = false; + private boolean reportGenerated = false; + private boolean reportGeneratedWithResponse = false; @SneakyThrows public HttpResponse( @@ -385,7 +390,7 @@ public abstract class UseCase> { @SneakyThrows private HttpResponse reportRequestAndResponse(final boolean unconditionallyWithResponse) { - if (reported) { + if (reportGenerated) { throw new IllegalStateException("request report already generated"); } @@ -409,19 +414,30 @@ public abstract class UseCase> { testReport.printLine("=> status: " + status + " " + (locationUuid != null ? locationUuid : "")); if (unconditionallyWithResponse || httpMethod == HttpMethod.GET || status.isError()) { testReport.printJson(response.body()); + this.reportGeneratedWithResponse = true; } testReport.printLine("```"); testReport.printLine(""); + this.reportGenerated = true; return this; } @SneakyThrows private void optionallyReportRequestAndResponse() { - if (!reported) { + if (!reportGenerated) { reportRequestAndResponse(false); } } + private void verifyResponseReported(final String action) { + if (!reportGenerated) { + throw new IllegalStateException("report not generated yet, but expected for `" + action + "`"); + } + if (!reportGeneratedWithResponse) { + throw new IllegalStateException("report without response, but response report required for `" + action + "`"); + } + } + private String nonNullAlias(final String alias) { // This marker tag should not appear in the source-code, as here is nothing to fix. // But if it appears in generated Markdown files, it should show up when that marker tag is searched. @@ -430,6 +446,8 @@ public abstract class UseCase> { } public HttpResponse extractUuidAlias(final String jsonPath, final String resolvableName) { + verifyResponseReported("extractUuidAlias"); + final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenUUID(); ScenarioTest.putAlias(resolvedName, resolvedJsonPath); @@ -437,6 +455,8 @@ public abstract class UseCase> { } public HttpResponse extractValue(final String jsonPath, final String resolvableName) { + verifyResponseReported("extractValue"); + final var resolvedName = ScenarioTest.resolve(resolvableName, DROP_COMMENTS); final var resolvedJsonPath = getFromBodyAsOptional(jsonPath).givenAsString(); ScenarioTest.putProperty(resolvedName, resolvedJsonPath); -- 2.39.5 From 966ac662cd62bc069f9129b0983e64602ca84a5d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 18 Dec 2024 10:26:19 +0100 Subject: [PATCH 30/30] fetch nameOfDeceasedPerson from partnerrel.holder-person --- .../scenarios/HsOfficeScenarioTests.java | 1 - ...ceDeceasedPartnerWithCommunityOfHeirs.java | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java index 220dc0dd..f3037336 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/HsOfficeScenarioTests.java @@ -621,7 +621,6 @@ class HsOfficeScenarioTests extends ScenarioTest { void shouldReplaceDeceasedPartnerByCommunityOfHeirs() { new ReplaceDeceasedPartnerWithCommunityOfHeirs(scenarioTest) .given("partnerNumber", "P-31011") - .given("nameOfDeceasedPerson", "Michelle Matthieu") // FIXME: redundant .given("dateOfDeath", "2024-11-15") .given("representativeGivenName", "Lena") .given("representativeFamilyName", "Stadland") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java index 8aa535ca..c706e8e6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/ReplaceDeceasedPartnerWithCommunityOfHeirs.java @@ -30,21 +30,24 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase response.getFromBody("uuid"), "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint? - ).extractValue("partnerRel.holder.uuid", "Person: %{nameOfDeceasedPerson}"); + ) + .extractValue("partnerRel.holder.familyName", "familyNameOfDeceasedPerson") + .extractValue("partnerRel.holder.givenName", "givenNameOfDeceasedPerson") + .extractUuidAlias("partnerRel.holder.uuid", "Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"); - obtain("Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> + obtain("Partner-Relation: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> httpPost("/api/hs/office/relations", usingJsonBody(""" { "type": "PARTNER", "anchor.uuid": ${Person: Hostsharing eG}, "holder": { "personType": "UNINCORPORATED_FIRM", - "tradeName": "Erbengemeinschaft %{nameOfDeceasedPerson}", + "tradeName": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", }, "contact": { - "caption": "Erbengemeinschaft %{nameOfDeceasedPerson}", + "caption": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", "postalAddress": { - "name": "Erbengemeinschaft %{nameOfDeceasedPerson}", + "name": "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", "co": "%{representativeGivenName} %{representativeFamilyName}", %{communityOfHeirsPostalAddress} }, @@ -59,29 +62,29 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase + obtain("Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> httpPost("/api/hs/office/relations", usingJsonBody(""" { "type": "REPRESENTATIVE", - "anchor.uuid": ${Person: Erbengemeinschaft %{nameOfDeceasedPerson}}, + "anchor.uuid": ${Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}, "holder": { "personType": "NATURAL_PERSON", "givenName": ${representativeGivenName}, "familyName": ${representativeFamilyName} }, - "contact.uuid": ${Contact: Erbengemeinschaft %{nameOfDeceasedPerson}} + "contact.uuid": ${Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}} } """)) .reportWithResponse().expecting(CREATED).expecting(JSON) ).extractUuidAlias("holder.uuid", "Person: %{representativeGivenName} %{representativeFamilyName}"); - obtain("Partner: Erbengemeinschaft %{nameOfDeceasedPerson}", () -> + obtain("Partner: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", () -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", usingJsonBody(""" { - "partnerRel.uuid": ${Partner-Relation: Erbengemeinschaft %{nameOfDeceasedPerson}} + "partnerRel.uuid": ${Partner-Relation: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}} } """)) .expecting(HttpStatus.OK) @@ -117,7 +120,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpGet("/api/hs/office/partners/%{partnerNumber}") .expecting(OK).expecting(JSON).expectObject(), - path("partnerRel.holder.tradeName").contains("Erbengemeinschaft %{nameOfDeceasedPerson}") + path("partnerRel.holder.tradeName").contains("Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}") ); // TODO.test: Verify the EX_PARTNER-Relation, once we fixed the anchor problem, see HsOfficePartnerController @@ -127,7 +130,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase httpGet("/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: %{representativeGivenName} %{representativeFamilyName}}") .expecting(OK).expecting(JSON).expectArrayElements(1), - path("[0].anchor.tradeName").contains("Erbengemeinschaft %{nameOfDeceasedPerson}"), + path("[0].anchor.tradeName").contains("Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"), path("[0].holder.familyName").contains("%{representativeFamilyName}") ); -- 2.39.5