From ccb810f314a5915164b19a66faa4608ad32b0d5d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 29 Oct 2024 16:47:26 +0100 Subject: [PATCH] refacoring to TestReport --- .../hs/office/scenarios/ScenarioTest.java | 58 +++--------- .../hs/office/scenarios/TestReport.java | 84 +++++++++++++++++ .../hs/office/scenarios/UUIDAppender.java | 34 ------- .../hs/office/scenarios/UseCase.java | 90 ++++++++++--------- .../debitor/CreateSelfDebitorForPartner.java | 6 +- .../AddOperationsContactToPartner.java | 51 +++++------ .../partner/AddRepresentativeToPartner.java | 10 +-- 7 files changed, 179 insertions(+), 154 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TestReport.java delete mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UUIDAppender.java diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java index 1e7b11d0..43c1e399 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/ScenarioTest.java @@ -1,6 +1,5 @@ package net.hostsharing.hsadminng.hs.office.scenarios; -import lombok.Getter; import lombok.SneakyThrows; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; @@ -10,14 +9,10 @@ import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import org.apache.commons.collections4.SetUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.server.LocalServerPort; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; @@ -36,16 +31,19 @@ public abstract class ScenarioTest extends ContextBasedTest { final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented - @Getter - private PrintWriter markdownFile; + record Alias>(Class useCase, UUID uuid) { - private StringBuilder debugLog = new StringBuilder(); - - record Alias>(Class useCase, UUID uuid) {} + @Override + public String toString() { + return uuid.toString(); + } + } private final static Map> aliases = new HashMap<>(); private final static Map properties = new HashMap<>(); + public final TestReport testReport = new TestReport(aliases); + @LocalServerPort Integer port; @@ -61,43 +59,16 @@ public abstract class ScenarioTest extends ContextBasedTest { createHostsharingPerson(); try { testInfo.getTestMethod().ifPresent(this::callRequiredProducers); - createTestLogMarkdownFile(testInfo); + testReport.createTestLogMarkdownFile(testInfo); } catch (Exception exc) { throw exc; } } @AfterEach - void cleanup() { + void cleanup() { // final TestInfo testInfo properties.clear(); - if (markdownFile != null) { - markdownFile.close(); - } else { - toString(); - } - debugLog = new StringBuilder(); - } - - @SneakyThrows - void print(final String output) { - - final var outputWithCommentsForUuids = UUIDAppender.appendUUIDKey(aliases, output.replace("+", "\\+")); - - // for tests executed due to @Requires/@Produces there is no markdownFile yet - if (markdownFile != null) { - markdownFile.print(outputWithCommentsForUuids); - } - - // but the debugLog should contain all output - debugLog.append(outputWithCommentsForUuids); - } - - void printLine(final String output) { - print(output + "\n"); - } - - void printPara(final String output) { - printLine("\n" +output + "\n"); + testReport.close(); } private void createHostsharingPerson() { @@ -117,13 +88,6 @@ public abstract class ScenarioTest extends ContextBasedTest { ); } - private 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(); - markdownFile = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md")); - print("## Scenario: " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2")); - } - @SneakyThrows private void callRequiredProducers(final Method currentTestMethod) { final var testMethodRequired = Optional.of(currentTestMethod) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TestReport.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TestReport.java new file mode 100644 index 00000000..33d3577d --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TestReport.java @@ -0,0 +1,84 @@ +package net.hostsharing.hsadminng.hs.office.scenarios; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestInfo; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestReport { + + private final Map aliases; + private final StringBuilder debugLog = new StringBuilder(); // records everything for debugging purposes + + private PrintWriter markdownFile; + private int silent; // do not print anything to test-report if >0 + + public TestReport(final Map aliases) { + this.aliases = aliases; + } + + 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(); + markdownFile = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md")); + print("## Scenario: " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2")); + } + + @SneakyThrows + public void print(final String output) { + + final var outputWithCommentsForUuids = appendUUIDKey(output.replace("+", "\\+")); + + // for tests executed due to @Requires/@Produces there is no markdownFile yet + if (markdownFile != null && silent == 0) { // FIXME: check if markdownFile can even be null + markdownFile.print(outputWithCommentsForUuids); + } + + // but the debugLog should contain all output, even if silent + debugLog.append(outputWithCommentsForUuids); + } + + public void printLine(final String output) { + print(output + "\n"); + } + + public void printPara(final String output) { + printLine("\n" +output + "\n"); + } + + public void close() { + markdownFile.close(); + } + + private String appendUUIDKey(String multilineText) { + final var lines = multilineText.split("\\r?\\n"); + 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)) { + line = line + " // " + entry.getKey(); + break; // only add comment for one UUID per row (in our case, there is only one per row) + } + } + result.append(line).append("\n"); + } + return result.toString(); + } + + void silent(final Runnable code) { + silent++; + code.run(); + silent--; + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UUIDAppender.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UUIDAppender.java deleted file mode 100644 index 187d2857..00000000 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/UUIDAppender.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.hostsharing.hsadminng.hs.office.scenarios; - -import java.util.Map; - -public class UUIDAppender { - public static String appendUUIDKey(Map> uuidMap, String multilineText) { - // Split the multiline text into lines - String[] lines = multilineText.split("\\r?\\n"); - - // StringBuilder to build the resulting multiline text - StringBuilder result = new StringBuilder(); - - // Iterate over each line - for (String line : lines) { - - // Iterate over the map to find if the line contains a UUID - for (Map.Entry> entry : uuidMap.entrySet()) { - String uuidString = entry.getValue().uuid().toString(); - - // If the line contains the UUID, append the key at the end - if (line.contains(uuidString)) { - line = line + " // " + entry.getKey(); - break; // Exit once we've matched one UUID - } - } - - // Append the (possibly modified) line to the result - result.append(line).append("\n"); - } - - // Return the final modified text - return result.toString(); - } -} 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 751a027e..7b7e1fcc 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 @@ -20,6 +20,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse.BodyHandlers; import java.time.Duration; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -35,13 +36,15 @@ import static org.junit.platform.commons.util.StringUtils.isNotBlank; public abstract class UseCase> { private static final HttpClient client = HttpClient.newHttpClient(); - final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); protected final ScenarioTest testSuite; + private final TestReport testReport; private final Map>> requirements = new LinkedMap<>(); private final String resultAlias; private final Map givenProperties = new LinkedHashMap<>(); - private String nextTitle; // FIXME: ugly + + private String nextTitle; // just temporary public UseCase(final ScenarioTest testSuite) { this(testSuite, getResultAliasFromProducesAnnotationInCallStack()); @@ -49,9 +52,10 @@ public abstract class UseCase> { public UseCase(final ScenarioTest testSuite, final String resultAlias) { this.testSuite = testSuite; + this.testReport = testSuite.testReport; this.resultAlias = resultAlias; if (resultAlias != null) { - printPara("### UseCase " + title(resultAlias)); + testReport.printPara("### UseCase " + title(resultAlias)); } } @@ -62,19 +66,23 @@ public abstract class UseCase> { } public final HttpResponse doRun() { - printPara("### Given Properties"); - printLine(""" + testReport.printPara("### Given Properties"); + testReport.printLine(""" | name | value | |------|-------|"""); - givenProperties.forEach((key, value) -> printLine("| " + key + " | " + value.toString().replace("\n", "
") + " |")); - printLine(""); - requirements.forEach((alias, factory) -> { - if (!ScenarioTest.containsAlias(alias)) { - factory.apply(alias).run().keep(); - } - }); + givenProperties.forEach((key, value) -> + testReport.printLine("| " + key + " | " + value.toString().replace("\n", "
") + " |")); + testReport.printLine(""); + testReport.silent(() -> + requirements.forEach((alias, factory) -> { + if (!ScenarioTest.containsAlias(alias)) { + factory.apply(alias).run().keep(); + } + }) + ); return run(); } + protected abstract HttpResponse run(); public final UseCase given(final String propName, final Object propValue) { @@ -87,15 +95,27 @@ public abstract class UseCase> { return new JsonTemplate(jsonTemplate); } - public final void keep(final String alias, final Supplier http, final Function extractor) { - this.nextTitle = ScenarioTest.resolve(alias); - http.get().keep(extractor); - this.nextTitle = null; + public final void keep( + final String alias, + final Supplier http, + final Function extractor, + final String... extraInfo) { + withTitle(ScenarioTest.resolve(alias), () -> { + http.get().keep(extractor); + Arrays.stream(extraInfo).forEach(testReport::printPara); + }); } - public final void keep(final String alias, final Supplier http) { - this.nextTitle = ScenarioTest.resolve(alias); - http.get().keep(); + public final void keep(final String alias, final Supplier http, final String... extraInfo) { + withTitle(ScenarioTest.resolve(alias), () -> { + http.get().keep(); + Arrays.stream(extraInfo).forEach(testReport::printPara); + }); + } + + private void withTitle(final String title, final Runnable code) { + this.nextTitle = title; + code.run(); this.nextTitle = null; } @@ -199,21 +219,23 @@ public abstract class UseCase> { } if (nextTitle != null) { - printLine("\n### " + nextTitle + "\n"); + testReport.printLine("\n### " + nextTitle + "\n"); } else if (resultAlias != null) { - printLine("\n### " + resultAlias + "\n"); + testReport.printLine("\n### " + resultAlias + "\n"); } - printLine("```"); - printLine(httpMethod.name() + " " + uri); - printLine((requestBody != null ? requestBody : "") + "=> status: " + status + " " + + + // FIXME: maybe refactor into TestReport? + testReport.printLine("```"); + testReport.printLine(httpMethod.name() + " " + uri); + testReport.printLine((requestBody != null ? requestBody : "") + "=> status: " + status + " " + (locationUuid != null ? locationUuid : "")); if (httpMethod == HttpMethod.GET || !status.is2xxSuccessful()) { final var jsonNode = objectMapper.readTree(response.body()); final var prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); - printLine(prettyJson); + testReport.printLine(prettyJson); } - printLine("```"); - printLine(""); + testReport.printLine("```"); + testReport.printLine(""); } public HttpResponse expecting(final HttpStatus httpStatus) { @@ -262,18 +284,6 @@ public abstract class UseCase> { } } - public void print(final String output) { - testSuite.print(output); - } - - public void printLine(final String output) { - testSuite.printLine(output); - } - - public void printPara(final String output) { - testSuite.printPara(output); - } - protected T self() { //noinspection unchecked return (T) this; @@ -288,7 +298,7 @@ public abstract class UseCase> { private static String oneOf(final String one, final String another) { if (isNotBlank(one) && isBlank(another)) { return one; - } else if ( isBlank(one) && isNotBlank(another)) { + } else if (isBlank(one) && isNotBlank(another)) { return another; } throw new AssertionFailure("exactly one value required, but got '" + one + "' and '" + another + "'"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSelfDebitorForPartner.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSelfDebitorForPartner.java index 5df672a3..9d2a1b6e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSelfDebitorForPartner.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/debitor/CreateSelfDebitorForPartner.java @@ -18,10 +18,10 @@ public class CreateSelfDebitorForPartner extends UseCase httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerPersonTradeName}")) .expecting(OK).expecting(JSON), - response -> response.expectArrayElements(1).getFromBody("[0].holder.uuid") + response -> response.expectArrayElements(1).getFromBody("[0].holder.uuid"), + "From that output above, we're taking the UUID of the holder of the first result element.", + "**HINT**: With production data, you might get multiple results and have to decide which is the right one." ); - printPara("From that output above, we're taking the UUID of the holder of the first result element."); - printPara("**HINT**: With production data, you might get multiple results and have to decide which is the right one."); keep("BankAccount: Test AG - refund bank account", () -> httpPost("/api/hs/office/bankaccounts", usingJsonBody(""" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddOperationsContactToPartner.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddOperationsContactToPartner.java index 69876ba0..b5267196 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddOperationsContactToPartner.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddOperationsContactToPartner.java @@ -1,8 +1,8 @@ package net.hostsharing.hsadminng.hs.office.scenarios.partner; import io.restassured.http.ContentType; -import net.hostsharing.hsadminng.hs.office.scenarios.UseCase; import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; +import net.hostsharing.hsadminng.hs.office.scenarios.UseCase; import org.springframework.http.HttpStatus; import static io.restassured.http.ContentType.JSON; @@ -16,34 +16,35 @@ public class AddOperationsContactToPartner extends UseCase - httpPost("/api/hs/office/persons", usingJsonBody(""" - { - "personType": "NATURAL_PERSON", - "familyName": ${operationsContactFamilyName}, - "givenName": ${operationsContactGivenName} - } - """)) - .expecting(HttpStatus.CREATED).expecting(ContentType.JSON) + keep("Person: %{operationsContactGivenName} %{operationsContactFamilyName}", + () -> + httpPost("/api/hs/office/persons", usingJsonBody(""" + { + "personType": "NATURAL_PERSON", + "familyName": ${operationsContactFamilyName}, + "givenName": ${operationsContactGivenName} + } + """)) + .expecting(HttpStatus.CREATED).expecting(ContentType.JSON), + "Please check first if that person already exists, if so, use it's UUID below.", + "**HINT**: operations contacts are always connected to a partner-person, thus a person which is a holder of a partner-relation." ); - printPara("Please check first if that person already exists, if so, use it's UUID below."); - printPara("**HINT**: operations contacts are always connected to a partner-person, thus a person which is a holder of a partner-relation."); keep("Contact: %{operationsContactGivenName} %{operationsContactFamilyName}", () -> - httpPost("/api/hs/office/contacts", usingJsonBody(""" - { - "caption": "%{operationsContactGivenName} %{operationsContactFamilyName}", - "phoneNumbers": { - "main": ${operationsContactPhoneNumber} - }, - "emailAddresses": { - "main": ${operationsContactEMailAddress} - } - } - """)) - .expecting(CREATED).expecting(JSON) + httpPost("/api/hs/office/contacts", usingJsonBody(""" + { + "caption": "%{operationsContactGivenName} %{operationsContactFamilyName}", + "phoneNumbers": { + "main": ${operationsContactPhoneNumber} + }, + "emailAddresses": { + "main": ${operationsContactEMailAddress} + } + } + """)) + .expecting(CREATED).expecting(JSON), + "Please check first if that contact already exists, if so, use it's UUID below." ); - printPara("Please check first if that contact already exists, if so, use it's UUID below."); return httpPost("/api/hs/office/relations", usingJsonBody(""" { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddRepresentativeToPartner.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddRepresentativeToPartner.java index 9c64dc41..e4de631e 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddRepresentativeToPartner.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/partner/AddRepresentativeToPartner.java @@ -24,10 +24,10 @@ public class AddRepresentativeToPartner extends UseCase httpPost("/api/hs/office/contacts", usingJsonBody(""" @@ -42,9 +42,9 @@ public class AddRepresentativeToPartner extends UseCase