diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java index 6712fefd..ed84b7b6 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionController.java @@ -57,7 +57,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar @Override @Transactional - public ResponseEntity addCoopSharesTransaction( + public ResponseEntity postCoopSharesTransaction( final String currentSubject, final String assumedRoles, final HsOfficeCoopSharesTransactionInsertResource requestBody) { @@ -131,9 +131,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar } final BiConsumer RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { - if ( resource.getAdjustedShareTxUuid() != null ) { - entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getAdjustedShareTxUuid()) - .orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getAdjustedShareTxUuid())))); + if ( resource.getRevertedShareTxUuid() != null ) { + entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid()) + .orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid())))); } }; } 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 c61a863e..282bfaf6 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 @@ -14,14 +14,16 @@ public interface HsOfficeMembershipRepository extends Repository findAll(); @Query(""" SELECT membership FROM HsOfficeMembershipEntity membership WHERE ( CAST(:partnerUuid as org.hibernate.type.UUIDCharType) IS NULL OR membership.partner.uuid = :partnerUuid ) ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix - """) + """) List findMembershipsByOptionalPartnerUuid(UUID partnerUuid); + @Query(""" SELECT membership FROM HsOfficeMembershipEntity membership WHERE (:partnerNumber = membership.partner.partnerNumber) @@ -31,10 +33,18 @@ public interface HsOfficeMembershipRepository extends Repository aliases; - private final StringBuilder markdownLog = new StringBuilder(); // records everything for debugging purposes + private final static File markdownLogFile = new File("doc/scenarios/last-debug-log.md"); - private PrintWriter markdownReport; + 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 private int silent; // do not print anything to test-report if >0 + @SneakyThrows public TestReport(final Map aliases) { this.aliases = aliases; + this.markdownLog = new PrintWriter(new FileWriter(markdownLogFile)); } public void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException { final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow(); final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow(); assertThat(new File("doc/scenarios/").isDirectory() || new File("doc/scenarios/").mkdirs()).as("mkdir doc/scenarios/").isTrue(); - markdownReport = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md")); - print("## Scenario #" + testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " + - testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2")); + markdownReportFile = new File("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md"); + markdownReport = new PrintWriter(new FileWriter(markdownReportFile)); + print("## Scenario #" + determineScenarioTitle(testInfo)); } @SneakyThrows @@ -45,7 +50,7 @@ public class TestReport { } // but the debugLog should contain all output, even if silent - markdownLog.append(outputWithCommentsForUuids); + markdownLog.print(outputWithCommentsForUuids); } public void printLine(final String output) { @@ -59,7 +64,21 @@ public class TestReport { public void close() { if (markdownReport != null) { markdownReport.close(); + System.out.println("SCENARIO REPORT: " + asClickableLink(markdownReportFile)); } + markdownLog.close(); + System.out.println("DEBUG LOG: " + asClickableLink(markdownLogFile)); + } + + private static @NotNull String determineScenarioTitle(final TestInfo testInfo) { + final var convertedTestMethodName = + testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " + + testInfo.getTestMethod().map(Method::getName).map(t -> t.replaceAll("([a-z])([A-Z]+)", "$1 $2")).orElseThrow(); + return convertedTestMethodName.replaceAll(": should ", ": "); + } + + private String asClickableLink(final File file) { + return file.toURI().toString().replace("file:/", "file:///"); } private static Object orderNumber(final Method method) { @@ -88,5 +107,4 @@ public class TestReport { code.run(); silent--; } - } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java index ccd4200f..2111349c 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UseCase.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.scenarios; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; import io.restassured.http.ContentType; import lombok.Getter; import lombok.SneakyThrows; @@ -81,7 +82,7 @@ public abstract class UseCase> { testReport.silent(() -> requirements.forEach((alias, factory) -> { if (!ScenarioTest.containsAlias(alias)) { - factory.apply(alias).run().keep(); + factory.apply(alias).run().keepAs(alias); } }) ); @@ -126,7 +127,7 @@ public abstract class UseCase> { } public HttpResponse withTitle(final String title, final Supplier code) { - this.nextTitle = title; + this.nextTitle = ScenarioTest.resolve(title); final var response = code.get(); this.nextTitle = null; return response; @@ -276,15 +277,20 @@ public abstract class UseCase> { return this; } - public HttpResponse keep() { - final var alias = nextTitle != null ? nextTitle : resultAlias; - assertThat(alias).as("cannot keep result, no alias found").isNotNull(); + public HttpResponse keepAs(final String alias) { ScenarioTest.putAlias( - alias, + alias == null ? "unknown alias" : alias, // FIXME new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid)); return this; } + public HttpResponse keep() { + final var alias = nextTitle != null ? nextTitle : resultAlias; + // FIXME assertThat(alias).as("cannot keep result, no title or alias found for locationUuid: " + locationUuid).isNotNull(); + + return keepAs(alias); + } + @SneakyThrows public HttpResponse expectArrayElements(final int expectedElementCount) { final var rootNode = objectMapper.readTree(response.body()); @@ -305,8 +311,7 @@ public abstract class UseCase> { public Optional getFromBodyAsOptional(final String path) { try { return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path))); - } catch (final Exception e) { - // FIXME: catch more precise exception class + } catch (final PathNotFoundException e) { return null; // means the property did not exist at all, not that it was there with value null } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesCancellationTransaction.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesCancellationTransaction.java new file mode 100644 index 00000000..200ae07c --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesCancellationTransaction.java @@ -0,0 +1,17 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.membership; + +import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; + +public class CreateCoopSharesCancellationTransaction extends CreateCoopSharesTransaction { + + public CreateCoopSharesCancellationTransaction(final ScenarioTest testSuite) { + super(testSuite); + } + + @Override + protected HttpResponse run() { + given("transactionType", "CANCELLATION"); + given("shareCount", "-%{sharesToCancel}"); + return super.run(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesRevertTransaction.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesRevertTransaction.java new file mode 100644 index 00000000..fc7fb6b9 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesRevertTransaction.java @@ -0,0 +1,27 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.membership; + +import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; + +public class CreateCoopSharesRevertTransaction extends CreateCoopSharesTransaction { + + public CreateCoopSharesRevertTransaction(final ScenarioTest testSuite) { + super(testSuite); + + requires("CoopShares-Transaction with incorrect shareCount", alias -> + new CreateCoopSharesSubscriptionTransaction(testSuite) + .given("memberNumber", "3101000") + .given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedShareTx + .given("shareCount", 100) + .given("comment", "reverting subscription transaction with wrong share count") + .given("transactionDate", "%{dateOfIncorrectTransaction}") + ); + } + + @Override + protected HttpResponse run() { + given("transactionType", "ADJUSTMENT"); + given("shareCount", -100); + given("revertedShareTx", uuid("CoopShares-Transaction with incorrect shareCount")); + return super.run(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesSubscriptionTransaction.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesSubscriptionTransaction.java new file mode 100644 index 00000000..3aba4f29 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesSubscriptionTransaction.java @@ -0,0 +1,12 @@ +package net.hostsharing.hsadminng.hs.office.scenarios.membership; + +import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; + +public class CreateCoopSharesSubscriptionTransaction extends CreateCoopSharesTransaction { + + public CreateCoopSharesSubscriptionTransaction(final ScenarioTest testSuite) { + super(testSuite); + + given("transactionType", "SUBSCRIPTION"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CoopSharesTransactionUseCase.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java similarity index 74% rename from src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CoopSharesTransactionUseCase.java rename to src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java index 589469da..0ed75435 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CoopSharesTransactionUseCase.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java @@ -8,9 +8,9 @@ import org.springframework.http.HttpStatus; import static io.restassured.http.ContentType.JSON; import static org.springframework.http.HttpStatus.OK; -public class CoopSharesTransactionUseCase extends UseCase { +public abstract class CreateCoopSharesTransaction extends UseCase { - public CoopSharesTransactionUseCase(final ScenarioTest testSuite) { + public CreateCoopSharesTransaction(final ScenarioTest testSuite) { super(testSuite); } @@ -23,17 +23,20 @@ public class CoopSharesTransactionUseCase extends UseCase response.getFromBody("$[0].uuid") ); - return httpPost("/api/hs/office/coopsharestransactions", usingJsonBody(""" + return withTitle("Create the CoopShares-%{transactionType} Transaction", () -> + httpPost("/api/hs/office/coopsharestransactions", usingJsonBody(""" { "membership.uuid": ${membershipUuid}, "transactionType": ${transactionType}, "reference": ${reference}, "shareCount": ${shareCount}, "comment": ${comment}, - "valueDate": ${transactionDate} + "valueDate": ${transactionDate}, + "revertedShareTx.uuid": ${revertedShareTx???} } """)) - .expecting(HttpStatus.CREATED).expecting(ContentType.JSON); + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) + ); } @Override @@ -41,8 +44,7 @@ public class CoopSharesTransactionUseCase extends UseCase httpGet("/api/hs/office/coopsharestransactions/" + response.getLocationUuid()) .expecting(HttpStatus.OK).expecting(ContentType.JSON), -// path("transactionType").contains("%{transactionType}"), -// path("memberNumber").contains("%{memberNumber}"), + path("transactionType").contains("%{transactionType}"), path("shareCount").contains("%{shareCount}"), path("comment").contains("%{comment}"), path("valueDate").contains("%{transactionDate}")