Michael Hoennig
2022-10-25 e5ec8678194a2ef1afd843c97c63dc88e3cd4f21
get endpoints for coopassets+coopshares
2 files added
5 files modified
511 ■■■■■ changed files
src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java 16 ●●●●● patch | view | raw | blame | history
src/main/resources/api-definition/hs-office/hs-office-coopassets-with-uuid.yaml 27 ●●●●● patch | view | raw | blame | history
src/main/resources/api-definition/hs-office/hs-office-coopshares-with-uuid.yaml 27 ●●●●● patch | view | raw | blame | history
src/main/resources/api-definition/hs-office/hs-office.yaml 7 ●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java 48 ●●●●● patch | view | raw | blame | history
src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java 369 ●●●● patch | view | raw | blame | history
src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionController.java
@@ -4,6 +4,7 @@
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
@@ -78,6 +79,22 @@
        return ResponseEntity.created(uri).body(mapped);
    }
    @Override
    @Transactional(readOnly = true)
    public ResponseEntity<HsOfficeCoopAssetsTransactionResource> getCoopAssetTransactionByUuid(
        final String currentUser, final String assumedRoles, final UUID assetTransactionUuid) {
        context.define(currentUser, assumedRoles);
        final var result = coopAssetsTransactionRepo.findByUuid(assetTransactionUuid);
        if (result.isEmpty()) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(mapper.map(result.get(), HsOfficeCoopAssetsTransactionResource.class));
    }
    private void validate(final HsOfficeCoopAssetsTransactionInsertResource requestBody) {
        final var violations = new ArrayList<String>();
        validateDebitTransaction(requestBody, violations);
src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java
@@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.hs.office.coopshares;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactResource;
import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
@@ -79,6 +80,21 @@
        return ResponseEntity.created(uri).body(mapped);
    }
    @Override
    @Transactional(readOnly = true)
    public ResponseEntity<HsOfficeCoopSharesTransactionResource> getCoopShareTransactionByUuid(
        final String currentUser, final String assumedRoles, final UUID shareTransactionUuid) {
            context.define(currentUser, assumedRoles);
            final var result = coopSharesTransactionRepo.findByUuid(shareTransactionUuid);
            if (result.isEmpty()) {
                return ResponseEntity.notFound().build();
            }
            return ResponseEntity.ok(mapper.map(result.get(), HsOfficeCoopSharesTransactionResource.class));
    }
    private void validate(final HsOfficeCoopSharesTransactionInsertResource requestBody) {
        final var violations = new ArrayList<String>();
        validateSubscriptionTransaction(requestBody, violations);
src/main/resources/api-definition/hs-office/hs-office-coopassets-with-uuid.yaml
New file
@@ -0,0 +1,27 @@
get:
    tags:
        - hs-office-coopAssets
    description: 'Fetch a single asset transaction by its uuid, if visible for the current subject.'
    operationId: getCoopAssetTransactionByUuid
    parameters:
        - $ref: './auth.yaml#/components/parameters/currentUser'
        - $ref: './auth.yaml#/components/parameters/assumedRoles'
        - name: assetTransactionUUID
          in: path
          required: true
          schema:
              type: string
              format: uuid
          description: UUID of the asset transaction to fetch.
    responses:
        "200":
            description: OK
            content:
                'application/json':
                    schema:
                        $ref: './hs-office-coopassets-schemas.yaml#/components/schemas/HsOfficeCoopAssetsTransaction'
        "401":
            $ref: './error-responses.yaml#/components/responses/Unauthorized'
        "403":
            $ref: './error-responses.yaml#/components/responses/Forbidden'
src/main/resources/api-definition/hs-office/hs-office-coopshares-with-uuid.yaml
New file
@@ -0,0 +1,27 @@
get:
    tags:
        - hs-office-coopShares
    description: 'Fetch a single share transaction by its uuid, if visible for the current subject.'
    operationId: getCoopShareTransactionByUuid
    parameters:
        - $ref: './auth.yaml#/components/parameters/currentUser'
        - $ref: './auth.yaml#/components/parameters/assumedRoles'
        - name: shareTransactionUUID
          in: path
          required: true
          schema:
              type: string
              format: uuid
          description: UUID of the share transaction to fetch.
    responses:
        "200":
            description: OK
            content:
                'application/json':
                    schema:
                        $ref: './hs-office-coopshares-schemas.yaml#/components/schemas/HsOfficeCoopSharesTransaction'
        "401":
            $ref: './error-responses.yaml#/components/responses/Unauthorized'
        "403":
            $ref: './error-responses.yaml#/components/responses/Forbidden'
src/main/resources/api-definition/hs-office/hs-office.yaml
@@ -85,8 +85,13 @@
  /api/hs/office/coopsharestransactions:
    $ref: "./hs-office-coopshares.yaml"
  /api/hs/office/coopsharestransactions/{shareTransactionUUID}:
      $ref: "./hs-office-coopshares-with-uuid.yaml"
    # Coop Assets Transaction
  # Coop Assets Transaction
  /api/hs/office/coopassetstransactions:
      $ref: "./hs-office-coopassets.yaml"
  /api/hs/office/coopassetstransactions/{assetTransactionUUID}:
      $ref: "./hs-office-coopassets-with-uuid.yaml"
src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionControllerAcceptanceTest.java
@@ -5,6 +5,7 @@
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt;
@@ -18,6 +19,7 @@
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDate;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -38,6 +40,9 @@
    @Autowired
    Context context;
    @Autowired
    HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo;
    @Autowired
    HsOfficeMembershipRepository membershipRepo;
@@ -228,6 +233,49 @@
        }
    }
    @Nested
    @Accepts({"CoopAssetTransaction:R(Read)"})
    class GetCoopAssetTransaction {
        @Test
        void globalAdmin_withoutAssumedRole_canGetArbitraryCoopAssetTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenCoopAssetTransactionUuid = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
            RestAssured // @formatter:off
                .given().header("current-user", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopassetstransactions/" + givenCoopAssetTransactionUuid).then().log().body().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals("""
                    {
                        "transactionType": "DEPOSIT"
                    }
                    """)); // @formatter:on
        }
        @Test
        @Accepts({"CoopAssetTransaction:X(Access Control)"})
        void normalUser_canNotGetUnrelatedCoopAssetTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenCoopAssetTransactionUuid = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
            RestAssured // @formatter:off
                .given().header("current-user", "selfregistered-user-drew@hostsharing.org").port(port).when().get("http://localhost/api/hs/office/coopassetstransactions/" + givenCoopAssetTransactionUuid).then().log().body().assertThat().statusCode(404); // @formatter:on
        }
        @Test
        @Accepts({"CoopAssetTransaction:X(Access Control)"})
        void contactAdminUser_canGetRelatedCoopAssetTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenCoopAssetTransactionUuid = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
            RestAssured // @formatter:off
                .given().header("current-user", "contact-admin@firstcontact.example.com").port(port).when().get("http://localhost/api/hs/office/coopassetstransactions/" + givenCoopAssetTransactionUuid).then().log().body().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals("""
                    {
                         "transactionType": "DEPOSIT",
                         "assetValue": 320
                     }
                    """)); // @formatter:on
        }
    }
    @BeforeEach
    @AfterEach
    void cleanup() {
src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionControllerAcceptanceTest.java
@@ -17,6 +17,7 @@
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.time.LocalDate;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
@@ -25,213 +26,24 @@
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.startsWith;
@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {HsadminNgApplication.class, JpaAttempt.class})
@Transactional
class HsOfficeCoopSharesTransactionControllerAcceptanceTest {
    @LocalServerPort
    private Integer port;
    @Autowired
    Context context;
    @Autowired
    Context contextMock;
    @Autowired
    HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
    @Autowired
    HsOfficeMembershipRepository membershipRepo;
    @Autowired
    JpaAttempt jpaAttempt;
    @Autowired
    EntityManager em;
    @Nested
    @Accepts({ "CoopSharesTransaction:F(Find)" })
    class ListCoopSharesTransactions {
        @Test
        void globalAdmin_canViewAllCoopSharesTransactions() {
            RestAssured // @formatter:off
                .given()
                    .header("current-user", "superuser-alex@hostsharing.net")
                    .port(port)
                .when()
                    .get("http://localhost/api/hs/office/coopsharestransactions")
                .then().log().all().assertThat()
                    .statusCode(200)
                    .contentType("application/json")
                    .body("", hasSize(9));  // @formatter:on
        }
        @Test
        void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002)
                    .get(0);
            RestAssured // @formatter:off
                    .given()
                    .header("current-user", "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("""
                        [
                            {
                                "transactionType": "SUBSCRIPTION",
                                "shareCount": 4,
                                "valueDate": "2010-03-15",
                                "reference": "ref 10002-1",
                                "comment": "initial subscription"
                            },
                            {
                                "transactionType": "CANCELLATION",
                                "shareCount": -2,
                                "valueDate": "2021-09-01",
                                "reference": "ref 10002-2",
                                "comment": "cancelling some"
                            },
                            {
                                "transactionType": "ADJUSTMENT",
                                "shareCount": 2,
                                "valueDate": "2022-10-20",
                                "reference": "ref 10002-3",
                                "comment": "some adjustment"
                            }
                        ]
                        """)); // @formatter:on
        }
        @Test
        void globalAdmin_canFindCoopSharesTransactionsByMemberNumberAndDateRange() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002)
                    .get(0);
            RestAssured // @formatter:off
                .given()
                    .header("current-user", "superuser-alex@hostsharing.net")
                    .port(port)
                .when()
                    .get("http://localhost/api/hs/office/coopsharestransactions?membershipUuid="
                            + givenMembership.getUuid() + "&fromValueDate=2020-01-01&toValueDate=2021-12-31")
                .then().log().all().assertThat()
                    .statusCode(200)
                    .contentType("application/json")
                    .body("", lenientlyEquals("""
                        [
                            {
                                "transactionType": "CANCELLATION",
                                "shareCount": -2,
                                "valueDate": "2021-09-01",
                                "reference": "ref 10002-2",
                                "comment": "cancelling some"
                            }
                        ]
                        """)); // @formatter:on
        }
    }
    @Nested
    @Accepts({ "CoopSharesTransaction:C(Create)" })
    class AddCoopSharesTransaction {
        @Test
        void globalAdmin_canAddCoopSharesTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001)
                    .get(0);
            final var location = RestAssured // @formatter:off
                    .given()
                        .header("current-user", "superuser-alex@hostsharing.net")
                        .contentType(ContentType.JSON)
                        .body("""
                               {
                                   "membershipUuid": "%s",
                                   "transactionType": "SUBSCRIPTION",
                                   "shareCount": 8,
                                   "valueDate": "2022-10-13",
                                   "reference": "temp ref A",
                                   "comment": "just some test coop shares transaction"
                                 }
                            """.formatted(givenMembership.getUuid()))
                        .port(port)
                    .when()
                        .post("http://localhost/api/hs/office/coopsharestransactions")
                    .then().log().all().assertThat()
                        .statusCode(201)
                        .contentType(ContentType.JSON)
                        .body("uuid", isUuidValid())
                        .body("", lenientlyEquals("""
                            {
                                "transactionType": "SUBSCRIPTION",
                                "shareCount": 8,
                                "valueDate": "2022-10-13",
                                "reference": "temp ref A",
                                "comment": "just some test coop shares transaction"
                            }
                        """))
                        .header("Location", startsWith("http://localhost"))
                    .extract().header("Location");  // @formatter:on
            // finally, the new coopSharesTransaction can be accessed under the generated UUID
            final var newUserUuid = UUID.fromString(
                    location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
        @Test
        void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001)
                    .get(0);
            final var location = RestAssured // @formatter:off
                .given()
                    .header("current-user", "superuser-alex@hostsharing.net")
                    .contentType(ContentType.JSON)
                    .body("""
                           {
                               "membershipUuid": "%s",
                               "transactionType": "CANCELLATION",
                               "shareCount": -80,
                               "valueDate": "2022-10-13",
                               "reference": "temp ref X",
                               "comment": "just some test coop shares transaction"
                             }
                            """.formatted(givenMembership.getUuid()))
                    .port(port)
                .when()
                    .post("http://localhost/api/hs/office/coopsharestransactions")
                .then().log().all().assertThat()
                    .statusCode(400)
                    .contentType(ContentType.JSON)
                    .body("", lenientlyEquals("""
                            {
                                 "status": 400,
                                 "error": "Bad Request",
                                 "message": "ERROR: [400] coop shares transaction would result in a negative number of shares"
                             }
                        """));  // @formatter:on
        }
    }
    @LocalServerPort
    private Integer port;
    @BeforeEach
    @AfterEach
@@ -240,8 +52,177 @@
            context.define("superuser-alex@hostsharing.net", null);
            // HsOfficeCoopSharesTransactionEntity respectively hs_office_coopsharestransaction_rv
            // cannot be deleted at all, but the underlying table record can be deleted.
            em.createNativeQuery("delete from hs_office_coopsharestransaction where reference like 'temp %'")
                    .executeUpdate();
            em.createNativeQuery("delete from hs_office_coopsharestransaction where reference like 'temp %'").executeUpdate();
        }).assertSuccessful();
    }
    @Nested
    @Accepts({"CoopSharesTransaction:F(Find)"})
    class ListCoopSharesTransactions {
        @Test
        void globalAdmin_canViewAllCoopSharesTransactions() {
            RestAssured // @formatter:off
                .given().header("current-user", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions").then().log().all().assertThat().statusCode(200).contentType("application/json").body("", hasSize(9));  // @formatter:on
        }
        @Test
        void globalAdmin_canFindCoopSharesTransactionsByMemberNumber() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002).get(0);
            RestAssured // @formatter:off
                .given().header("current-user", "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("""
                    [
                        {
                            "transactionType": "SUBSCRIPTION",
                            "shareCount": 4,
                            "valueDate": "2010-03-15",
                            "reference": "ref 10002-1",
                            "comment": "initial subscription"
                        },
                        {
                            "transactionType": "CANCELLATION",
                            "shareCount": -2,
                            "valueDate": "2021-09-01",
                            "reference": "ref 10002-2",
                            "comment": "cancelling some"
                        },
                        {
                            "transactionType": "ADJUSTMENT",
                            "shareCount": 2,
                            "valueDate": "2022-10-20",
                            "reference": "ref 10002-3",
                            "comment": "some adjustment"
                        }
                    ]
                    """)); // @formatter:on
        }
        @Test
        void globalAdmin_canFindCoopSharesTransactionsByMemberNumberAndDateRange() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002).get(0);
            RestAssured // @formatter:off
                .given().header("current-user", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions?membershipUuid=" + givenMembership.getUuid() + "&fromValueDate=2020-01-01&toValueDate=2021-12-31").then().log().all().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals("""
                    [
                        {
                            "transactionType": "CANCELLATION",
                            "shareCount": -2,
                            "valueDate": "2021-09-01",
                            "reference": "ref 10002-2",
                            "comment": "cancelling some"
                        }
                    ]
                    """)); // @formatter:on
        }
    }
    @Nested
    @Accepts({"CoopSharesTransaction:C(Create)"})
    class AddCoopSharesTransaction {
        @Test
        void globalAdmin_canAddCoopSharesTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001).get(0);
            final var location = RestAssured // @formatter:off
                .given().header("current-user", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body("""
                       {
                           "membershipUuid": "%s",
                           "transactionType": "SUBSCRIPTION",
                           "shareCount": 8,
                           "valueDate": "2022-10-13",
                           "reference": "temp ref A",
                           "comment": "just some test coop shares transaction"
                         }
                    """.formatted(givenMembership.getUuid())).port(port).when().post("http://localhost/api/hs/office/coopsharestransactions").then().log().all().assertThat().statusCode(201).contentType(ContentType.JSON).body("uuid", isUuidValid()).body("", lenientlyEquals("""
                        {
                            "transactionType": "SUBSCRIPTION",
                            "shareCount": 8,
                            "valueDate": "2022-10-13",
                            "reference": "temp ref A",
                            "comment": "just some test coop shares transaction"
                        }
                    """)).header("Location", startsWith("http://localhost")).extract().header("Location");  // @formatter:on
            // finally, the new coopSharesTransaction can be accessed under the generated UUID
            final var newUserUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1));
            assertThat(newUserUuid).isNotNull();
        }
        @Test
        void globalAdmin_canNotCancelMoreSharesThanCurrentlySubscribed() {
            context.define("superuser-alex@hostsharing.net");
            final var givenMembership = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10001).get(0);
            final var location = RestAssured // @formatter:off
                .given().header("current-user", "superuser-alex@hostsharing.net").contentType(ContentType.JSON).body("""
                    {
                        "membershipUuid": "%s",
                        "transactionType": "CANCELLATION",
                        "shareCount": -80,
                        "valueDate": "2022-10-13",
                        "reference": "temp ref X",
                        "comment": "just some test coop shares transaction"
                      }
                     """.formatted(givenMembership.getUuid())).port(port).when().post("http://localhost/api/hs/office/coopsharestransactions").then().log().all().assertThat().statusCode(400).contentType(ContentType.JSON).body("", lenientlyEquals("""
                        {
                             "status": 400,
                             "error": "Bad Request",
                             "message": "ERROR: [400] coop shares transaction would result in a negative number of shares"
                         }
                    """));  // @formatter:on
        }
    }
    @Nested
    @Accepts({"CoopShareTransaction:R(Read)"})
    class GetCoopShareTransaction {
        @Test
        void globalAdmin_withoutAssumedRole_canGetArbitraryCoopShareTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenCoopShareTransactionUuid = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
            RestAssured // @formatter:off
                .given().header("current-user", "superuser-alex@hostsharing.net").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions/" + givenCoopShareTransactionUuid).then().log().body().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals("""
                    {
                        "transactionType": "SUBSCRIPTION"
                    }
                    """)); // @formatter:on
        }
        @Test
        @Accepts({"CoopShareTransaction:X(Access Control)"})
        void normalUser_canNotGetUnrelatedCoopShareTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenCoopShareTransactionUuid = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
            RestAssured // @formatter:off
                .given().header("current-user", "selfregistered-user-drew@hostsharing.org").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions/" + givenCoopShareTransactionUuid).then().log().body().assertThat().statusCode(404); // @formatter:on
        }
        @Test
        @Accepts({"CoopShareTransaction:X(Access Control)"})
        void contactAdminUser_canGetRelatedCoopShareTransaction() {
            context.define("superuser-alex@hostsharing.net");
            final var givenCoopShareTransactionUuid = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(null, LocalDate.of(2010, 3, 15), LocalDate.of(2010, 3, 15)).get(0).getUuid();
            RestAssured // @formatter:off
                .given().header("current-user", "contact-admin@firstcontact.example.com").port(port).when().get("http://localhost/api/hs/office/coopsharestransactions/" + givenCoopShareTransactionUuid).then().log().body().assertThat().statusCode(200).contentType("application/json").body("", lenientlyEquals("""
                    {
                         "transactionType": "SUBSCRIPTION",
                         "shareCount": 4
                     }
                    """)); // @formatter:on
        }
    }
}