diff --git a/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java b/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java index 8b302098..c53c814d 100644 --- a/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java +++ b/src/main/java/net/hostsharing/hsadminng/system/SystemProcess.java @@ -14,6 +14,7 @@ public class SystemProcess { @Getter private String stdOut; + @Getter private String stdErr; @@ -21,7 +22,6 @@ public class SystemProcess { this.processBuilder = new ProcessBuilder(command); } - public String getCommand() { return processBuilder.command().toString(); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java index a358280d..6c189db6 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/PathAssertion.java @@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.UseCase.HttpResponse; import java.util.function.Consumer; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; import static org.junit.jupiter.api.Assertions.fail; public class PathAssertion { @@ -18,7 +19,7 @@ public class PathAssertion { public Consumer contains(final String resolvableValue) { return response -> { try { - response.path(path).map(Object::toString).contains(ScenarioTest.resolve(resolvableValue)); + response.path(path).map(Object::toString).contains(ScenarioTest.resolve(resolvableValue, DROP_COMMENTS)); } catch (final AssertionError e) { // without this, the error message is often lacking important context fail(e.getMessage() + " in `path(\"" + path + "\").contains(\"" + resolvableValue + "\")`" ); 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 4600584a..530eba74 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 @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.scenarios; import lombok.SneakyThrows; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; +import net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver; import net.hostsharing.hsadminng.lambda.Reducer; import net.hostsharing.hsadminng.rbac.context.ContextBasedTest; import net.hostsharing.hsadminng.rbac.test.JpaAttempt; @@ -26,6 +27,8 @@ import java.util.stream.Collectors; import static java.util.Arrays.asList; import static java.util.Optional.ofNullable; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; import static org.assertj.core.api.Assertions.assertThat; public abstract class ScenarioTest extends ContextBasedTest { @@ -38,11 +41,11 @@ public abstract class ScenarioTest extends ContextBasedTest { public String toString() { return ObjectUtils.toString(uuid); } + } - private final static Map> aliases = new HashMap<>(); - private final static Map properties = new HashMap<>(); + private final static Map properties = new HashMap<>(); public final TestReport testReport = new TestReport(aliases); @LocalServerPort @@ -139,9 +142,9 @@ public abstract class ScenarioTest extends ContextBasedTest { } static UUID uuid(final String nameWithPlaceholders) { - final var resoledName = resolve(nameWithPlaceholders); - final UUID alias = ofNullable(knowVariables().get(resoledName)).filter(v -> v instanceof UUID).map(UUID.class::cast).orElse(null); - assertThat(alias).as("alias '" + resoledName + "' not found in aliases nor in properties [" + + final var resolvedName = resolve(nameWithPlaceholders, DROP_COMMENTS); + final UUID alias = ofNullable(knowVariables().get(resolvedName)).filter(v -> v instanceof UUID).map(UUID.class::cast).orElse(null); + assertThat(alias).as("alias '" + resolvedName + "' not found in aliases nor in properties [" + knowVariables().keySet().stream().map(v -> "'" + v + "'").collect(Collectors.joining(", ")) + "]" ).isNotNull(); return alias; @@ -162,13 +165,13 @@ public abstract class ScenarioTest extends ContextBasedTest { return map; } - public static String resolve(final String text) { - final var resolved = new TemplateResolver(text, ScenarioTest.knowVariables()).resolve(); + public static String resolve(final String text, final Resolver resolver) { + final var resolved = new TemplateResolver(text, ScenarioTest.knowVariables()).resolve(resolver); return resolved; } public static Object resolveTyped(final String text) { - final var resolved = resolve(text); + final var resolved = resolve(text, DROP_COMMENTS); try { return UUID.fromString(resolved); } catch (final IllegalArgumentException e) { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolver.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolver.java index aaa8855a..b6fac263 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolver.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolver.java @@ -10,29 +10,39 @@ import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; + public class TemplateResolver { - private final static Pattern pattern = Pattern.compile(",(\\s*})", Pattern.MULTILINE); - private static final String IF_NOT_FOUND_SYMBOL = "???"; + public enum Resolver { + DROP_COMMENTS, // deletes comments ('#{whatever}' -> '') + KEEP_COMMENTS // deletes comments ('#{whatever}' -> '') + } enum PlaceholderPrefix { RAW('%') { @Override - String convert(final Object value) { + String convert(final Object value, final Resolver resolver) { return value != null ? value.toString() : ""; } }, JSON_QUOTED('$'){ @Override - String convert(final Object value) { + String convert(final Object value, final Resolver resolver) { return jsonQuoted(value); } }, URI_ENCODED('&'){ @Override - String convert(final Object value) { + String convert(final Object value, final Resolver resolver) { return value != null ? URLEncoder.encode(value.toString(), StandardCharsets.UTF_8) : ""; } + }, + COMMENT('#'){ + @Override + String convert(final Object value, final Resolver resolver) { + return resolver == DROP_COMMENTS ? "" : value.toString(); + } }; private final char prefixChar; @@ -42,19 +52,24 @@ public class TemplateResolver { } static boolean contains(final char givenChar) { - return Arrays.stream(values()).anyMatch(p -> p.prefixChar == givenChar); + return Arrays.stream(values()).anyMatch(p -> p.prefixChar == givenChar); } static PlaceholderPrefix ofPrefixChar(final char givenChar) { return Arrays.stream(values()).filter(p -> p.prefixChar == givenChar).findFirst().orElseThrow(); } - abstract String convert(final Object value); + abstract String convert(final Object value, final Resolver resolver); // FIXME: why Object and not String? } + private final static Pattern pattern = Pattern.compile(",(\\s*})", Pattern.MULTILINE); + private static final String IF_NOT_FOUND_SYMBOL = "???"; + private final String template; private final Map properties; private final StringBuilder resolved = new StringBuilder(); + + private Resolver resolver; private int position = 0; public TemplateResolver(final String template, final Map properties) { @@ -62,7 +77,8 @@ public class TemplateResolver { this.properties = properties; } - String resolve() { + String resolve(final Resolver resolver) { + this.resolver = resolver; final var resolved = copy(); final var withoutDroppedLines = dropLinesWithNullProperties(resolved); final var result = removeDanglingCommas(withoutDroppedLines); @@ -119,10 +135,10 @@ public class TemplateResolver { placeholder.append(fetchChar()); } } - final var name = new TemplateResolver(placeholder.toString(), properties).resolve(); - final var value = propVal(name); + final var content = new TemplateResolver(placeholder.toString(), properties).resolve(resolver); + final var value = intro != '#' ? propVal(content) : content; resolved.append( - PlaceholderPrefix.ofPrefixChar(intro).convert(value) + PlaceholderPrefix.ofPrefixChar(intro).convert(value, resolver) ); skipChar('}'); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolverUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolverUnitTest.java index 2d63e204..435d44d3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolverUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TemplateResolverUnitTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import java.util.Map; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; import static org.assertj.core.api.Assertions.assertThat; class TemplateResolverUnitTest { @@ -42,7 +43,7 @@ class TemplateResolverUnitTest { Map.entry("simple placeholder", "einfach"), Map.entry("nested placeholder", "verschachtelt"), Map.entry("with-special-chars", "3&3 AG") - )).resolve(); + )).resolve(DROP_COMMENTS); assertThat(resolved).isEqualTo(""" with optional JSON quotes: 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 index 5b89e6f1..007e4512 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TestReport.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/TestReport.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.hs.office.scenarios; import lombok.SneakyThrows; +import net.hostsharing.hsadminng.system.SystemProcess; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestInfo; @@ -10,6 +11,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -17,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestReport { private final static File markdownLogFile = new File("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 final Map aliases; private final PrintWriter markdownLog; // records everything for debugging purposes @@ -61,8 +65,16 @@ public class TestReport { printLine("\n" +output + "\n"); } + void silent(final Runnable code) { + silent++; + code.run(); + silent--; + } + public void close() { if (markdownReport != null) { + printPara("---"); + printPara("generated on " + MM_DD_YYYY_HH_MM_SS.format(new Date()) + " for branch " + currentGitBranch()); markdownReport.close(); System.out.println("SCENARIO REPORT: " + asClickableLink(markdownReportFile)); } @@ -102,9 +114,10 @@ public class TestReport { return result.toString(); } - void silent(final Runnable code) { - silent++; - code.run(); - silent--; + @SneakyThrows + private String currentGitBranch() { + final var gitRevParse = new SystemProcess("git", "rev-parse", "--abbrev-ref", "HEAD"); + gitRevParse.execute(); + return gitRevParse.getStdOut().split("\\R", 2)[0]; } } 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 5867146a..23244d64 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 @@ -34,6 +34,8 @@ import java.util.function.Function; import java.util.function.Supplier; import static java.net.URLEncoder.encode; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; +import static net.hostsharing.hsadminng.hs.office.scenarios.TemplateResolver.Resolver.KEEP_COMMENTS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.platform.commons.util.StringUtils.isBlank; @@ -116,11 +118,11 @@ public abstract class UseCase> { } public final void obtain( - final String alias, + final String title, final Supplier http, final Function extractor, final String... extraInfo) { - withTitle(ScenarioTest.resolve(alias), () -> { + withTitle(title, () -> { final var response = http.get().keep(extractor); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; @@ -128,15 +130,15 @@ public abstract class UseCase> { } public final void obtain(final String alias, final Supplier http, final String... extraInfo) { - withTitle(ScenarioTest.resolve(alias), () -> { + withTitle(alias, () -> { final var response = http.get().keep(); Arrays.stream(extraInfo).forEach(testReport::printPara); return response; }); } - public HttpResponse withTitle(final String title, final Supplier code) { - this.nextTitle = ScenarioTest.resolve(title); + public HttpResponse withTitle(final String resolvableTitle, final Supplier code) { + this.nextTitle = resolvableTitle; final var response = code.get(); this.nextTitle = null; return response; @@ -144,7 +146,7 @@ public abstract class UseCase> { @SneakyThrows public final HttpResponse httpGet(final String uriPathWithPlaceholders) { - final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders); + final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var request = HttpRequest.newBuilder() .GET() .uri(new URI("http://localhost:" + testSuite.port + uriPath)) @@ -157,7 +159,7 @@ public abstract class UseCase> { @SneakyThrows public final HttpResponse httpPost(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) { - final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders); + final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var request = HttpRequest.newBuilder() .POST(BodyPublishers.ofString(requestBody)) @@ -172,7 +174,7 @@ public abstract class UseCase> { @SneakyThrows public final HttpResponse httpPatch(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) { - final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders); + final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var request = HttpRequest.newBuilder() .method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody)) @@ -187,7 +189,7 @@ public abstract class UseCase> { @SneakyThrows public final HttpResponse httpDelete(final String uriPathWithPlaceholders) { - final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders); + final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var request = HttpRequest.newBuilder() .DELETE() .uri(new URI("http://localhost:" + testSuite.port + uriPath)) @@ -207,7 +209,7 @@ public abstract class UseCase> { final String title, final Supplier http, final Consumer... assertions) { - withTitle(ScenarioTest.resolve(title), () -> { + withTitle(title, () -> { final var response = http.get(); Arrays.stream(assertions).forEach(assertion -> assertion.accept(response)); return response; @@ -219,7 +221,7 @@ public abstract class UseCase> { } public String uriEncoded(final String text) { - return encode(ScenarioTest.resolve(text), StandardCharsets.UTF_8); + return encode(ScenarioTest.resolve(text, DROP_COMMENTS), StandardCharsets.UTF_8); } public static class JsonTemplate { @@ -231,7 +233,7 @@ public abstract class UseCase> { } String resolvePlaceholders() { - return ScenarioTest.resolve(template); + return ScenarioTest.resolve(template, DROP_COMMENTS); } } @@ -276,7 +278,7 @@ public abstract class UseCase> { } public HttpResponse keep(final Function extractor) { - final var alias = nextTitle != null ? nextTitle : resultAlias; + final var alias = nextTitle != null ? ScenarioTest.resolve(nextTitle, DROP_COMMENTS) : resultAlias; assertThat(alias).as("cannot keep result, no alias found").isNotNull(); final var value = extractor.apply(this); @@ -294,7 +296,7 @@ public abstract class UseCase> { } public HttpResponse keep() { - final var alias = nextTitle != null ? nextTitle : resultAlias; + 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(); return keepAs(alias); @@ -313,13 +315,13 @@ public abstract class UseCase> { @SneakyThrows public String getFromBody(final String path) { - return JsonPath.parse(response.body()).read(ScenarioTest.resolve(path)); + return JsonPath.parse(response.body()).read(ScenarioTest.resolve(path, DROP_COMMENTS)); } @SneakyThrows public Optional getFromBodyAsOptional(final String path) { try { - return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path))); + return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path, DROP_COMMENTS))); } catch (final PathNotFoundException e) { return null; // means the property did not exist at all, not that it was there with value null } @@ -335,9 +337,9 @@ public abstract class UseCase> { // the title if (nextTitle != null) { - testReport.printLine("\n### " + nextTitle + "\n"); + testReport.printLine("\n### " + ScenarioTest.resolve(nextTitle, KEEP_COMMENTS) + "\n"); } else if (resultAlias != null) { - testReport.printLine("\n### " + resultAlias + "\n"); + testReport.printLine("\n### Create " + resultAlias + "\n"); } else { fail("please wrap the http...-call in the UseCase using `withTitle(...)`"); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java index 0ed75435..2618b14d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/membership/CreateCoopSharesTransaction.java @@ -17,7 +17,7 @@ public abstract class CreateCoopSharesTransaction extends UseCase + obtain("#{Find }membershipUuid", () -> httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}") .expecting(OK).expecting(JSON).expectArrayElements(1), response -> response.getFromBody("$[0].uuid")