feature/use-case-acceptance-tests-2 #117

Merged
hsh-michaelhoennig merged 38 commits from feature/use-case-acceptance-tests-2 into master 2024-11-05 13:58:39 +01:00
4 changed files with 129 additions and 36 deletions
Showing only changes of commit 1cb0ea1018 - Show all commits

View File

@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import org.testcontainers.shaded.org.apache.commons.lang3.ObjectUtils;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
@ -35,7 +36,7 @@ public abstract class ScenarioTest extends ContextBasedTest {
@Override @Override
public String toString() { public String toString() {
return uuid.toString(); return ObjectUtils.toString(uuid);
} }
} }

View File

@ -1,9 +1,49 @@
package net.hostsharing.hsadminng.hs.office.scenarios; package net.hostsharing.hsadminng.hs.office.scenarios;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
public class TemplateResolver { public class TemplateResolver {
enum PlaceholderPrefix {
RAW('%') {
@Override
String convert(final Object value) {
return value.toString();
}
},
JSON_QUOTED('$'){
@Override
String convert(final Object value) {
return jsonQuoted(value);
}
},
URI_ENCODED('&'){
@Override
String convert(final Object value) {
return URLEncoder.encode(value.toString(), StandardCharsets.UTF_8);
}
};
private final char prefixChar;
PlaceholderPrefix(final char prefixChar) {
this.prefixChar = prefixChar;
}
static boolean contains(final char 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);
}
private final String template; private final String template;
private final Map<String, Object> properties; private final Map<String, Object> properties;
private final StringBuilder resolved = new StringBuilder(); private final StringBuilder resolved = new StringBuilder();
@ -21,7 +61,7 @@ public class TemplateResolver {
private void copy() { private void copy() {
while (hasMoreChars()) { while (hasMoreChars()) {
if ((currentChar() == '$' || currentChar() == '%') && nextChar() == '{') { if (PlaceholderPrefix.contains(currentChar()) && nextChar() == '{') {
startPlaceholder(currentChar()); startPlaceholder(currentChar());
} else { } else {
resolved.append(fetchChar()); resolved.append(fetchChar());
@ -41,7 +81,7 @@ public class TemplateResolver {
if (currentChar() == '}') { if (currentChar() == '}') {
--nested; --nested;
placeholder.append(fetchChar()); placeholder.append(fetchChar());
} else if ((currentChar() == '$' || currentChar() == '%') && nextChar() == '{') { } else if (PlaceholderPrefix.contains (currentChar()) && nextChar() == '{') {
++nested; ++nested;
placeholder.append(fetchChar()); placeholder.append(fetchChar());
} else { } else {
@ -50,11 +90,9 @@ public class TemplateResolver {
} }
final var name = new TemplateResolver(placeholder.toString(), properties).resolve(); final var name = new TemplateResolver(placeholder.toString(), properties).resolve();
final var value = propVal(name); final var value = propVal(name);
if ( intro == '%') { resolved.append(
resolved.append(value); PlaceholderPrefix.ofPrefixChar(intro).convert(value)
} else { );
resolved.append(optionallyQuoted(value));
}
skipChar('}'); skipChar('}');
} }
@ -104,7 +142,7 @@ public class TemplateResolver {
return template.charAt(position+1); return template.charAt(position+1);
} }
private static String optionallyQuoted(final Object value) { private static String jsonQuoted(final Object value) {
return switch (value) { return switch (value) {
case Boolean bool -> bool.toString(); case Boolean bool -> bool.toString();
case Number number -> number.toString(); case Number number -> number.toString();
@ -112,27 +150,4 @@ public class TemplateResolver {
default -> "\"" + value + "\""; default -> "\"" + value + "\"";
}; };
} }
public static void main(String[] args) {
System.out.println(
new TemplateResolver("""
etwas davor,
${einfacher Platzhalter},
${verschachtelter %{Name}},
und nochmal ohne Quotes:
%{einfacher Platzhalter},
%{verschachtelter %{Name}},
etwas danach.
""",
Map.ofEntries(
Map.entry("Name", "placeholder"),
Map.entry("einfacher Platzhalter", "simple placeholder"),
Map.entry("verschachtelter placeholder", "nested placeholder")
)).resolve());
}
} }

View File

@ -0,0 +1,73 @@
package net.hostsharing.hsadminng.hs.office.scenarios;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class TemplateResolverUnitTest {
@Test
void resolveTemplate() {
final var resolved = new TemplateResolver("""
with optional JSON quotes:
${boolean},
${numeric},
${simple placeholder},
${nested %{name}},
${with-special-chars}
and without quotes:
%{boolean},
%{numeric},
%{simple placeholder},
%{nested %{name}},
%{with-special-chars}
and uri-encoded:
&{boolean},
&{numeric},
&{simple placeholder},
&{nested %{name}},
&{with-special-chars}
""",
Map.ofEntries(
Map.entry("name", "placeholder"),
Map.entry("boolean", true),
Map.entry("numeric", 42),
Map.entry("simple placeholder", "einfach"),
Map.entry("nested placeholder", "verschachtelt"),
Map.entry("with-special-chars", "3&3 AG")
)).resolve();
assertThat(resolved).isEqualTo("""
with optional JSON quotes:
true,
42,
"einfach",
"verschachtelt",
"3&3 AG"
and without quotes:
true,
42,
einfach,
verschachtelt,
3&3 AG
and uri-encoded:
true,
42,
einfach,
verschachtelt,
3%263+AG
""");
}
}

View File

@ -120,7 +120,8 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpGet(final String uriPath) { public final HttpResponse httpGet(final String uriPathWithPlaceholders) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.GET() .GET()
.uri(new URI("http://localhost:" + testSuite.port + uriPath)) .uri(new URI("http://localhost:" + testSuite.port + uriPath))
@ -132,7 +133,8 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) { public final HttpResponse httpPost(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var requestBody = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.POST(BodyPublishers.ofString(requestBody)) .POST(BodyPublishers.ofString(requestBody))
@ -146,7 +148,8 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpPatch(final String uriPath, final JsonTemplate bodyJsonTemplate) { public final HttpResponse httpPatch(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var requestBody = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody)) .method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody))
@ -160,7 +163,8 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpDelete(final String uriPath) { public final HttpResponse httpDelete(final String uriPathWithPlaceholders) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.DELETE() .DELETE()
.uri(new URI("http://localhost:" + testSuite.port + uriPath)) .uri(new URI("http://localhost:" + testSuite.port + uriPath))