|
|
|
@ -1,6 +1,9 @@
|
|
|
|
|
package net.hostsharing.hsadminng.hs.office.scenarios;
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import io.restassured.http.ContentType;
|
|
|
|
|
import lombok.Getter;
|
|
|
|
|
import lombok.SneakyThrows;
|
|
|
|
|
import net.hostsharing.hsadminng.reflection.AnnotationFinder;
|
|
|
|
|
import org.apache.commons.collections4.map.LinkedMap;
|
|
|
|
@ -14,6 +17,7 @@ import java.net.URI;
|
|
|
|
|
import java.net.http.HttpClient;
|
|
|
|
|
import java.net.http.HttpRequest;
|
|
|
|
|
import java.net.http.HttpRequest.BodyPublishers;
|
|
|
|
|
import java.net.http.HttpResponse;
|
|
|
|
|
import java.net.http.HttpResponse.BodyHandlers;
|
|
|
|
|
import java.time.Duration;
|
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
@ -22,6 +26,7 @@ import java.util.UUID;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
|
|
|
|
|
import static java.net.URLEncoder.encode;
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
import static org.junit.platform.commons.util.StringUtils.isBlank;
|
|
|
|
|
import static org.junit.platform.commons.util.StringUtils.isNotBlank;
|
|
|
|
@ -29,8 +34,9 @@ import static org.junit.platform.commons.util.StringUtils.isNotBlank;
|
|
|
|
|
public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
|
|
|
|
|
private static final HttpClient client = HttpClient.newHttpClient();
|
|
|
|
|
final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
|
|
|
|
private final ScenarioTest testSuite;
|
|
|
|
|
protected final ScenarioTest testSuite;
|
|
|
|
|
private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>();
|
|
|
|
|
private final String resultAlias;
|
|
|
|
|
private final Map<String, Object> givenProperties = new LinkedHashMap<>();
|
|
|
|
@ -76,17 +82,35 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
return new JsonTemplate(jsonTemplate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public final void keep(final String alias, final Supplier<HttpResponse> http, final Function<HttpResponse, String> extractor) {
|
|
|
|
|
this.nextTitle = ScenarioTest.resolve(alias);
|
|
|
|
|
http.get().keep(extractor);
|
|
|
|
|
this.nextTitle = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public final void keep(final String alias, final Supplier<HttpResponse> http) {
|
|
|
|
|
this.nextTitle = ScenarioTest.resolve(alias);
|
|
|
|
|
http.get().keep();
|
|
|
|
|
this.nextTitle = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SneakyThrows
|
|
|
|
|
public final HttpResponse httpGet(final String uriPath) {
|
|
|
|
|
final var request = HttpRequest.newBuilder()
|
|
|
|
|
.GET()
|
|
|
|
|
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
|
|
|
|
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
|
|
|
|
.timeout(Duration.ofSeconds(10))
|
|
|
|
|
.build();
|
|
|
|
|
final var response = client.send(request, BodyHandlers.ofString());
|
|
|
|
|
return new HttpResponse(HttpMethod.POST, uriPath, null, response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SneakyThrows
|
|
|
|
|
public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
|
|
|
|
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
|
|
|
|
final var request = HttpRequest.newBuilder()
|
|
|
|
|
.method(HttpMethod.POST.toString(), BodyPublishers.ofString(requestBody))
|
|
|
|
|
.POST(BodyPublishers.ofString(requestBody))
|
|
|
|
|
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
|
|
|
|
.header("Content-Type", "application/json")
|
|
|
|
|
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
|
|
|
@ -127,6 +151,10 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
return ScenarioTest.uuid(alias);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String uriEncoded(final String text) {
|
|
|
|
|
return encode(ScenarioTest.resolve(text));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class JsonTemplate {
|
|
|
|
|
|
|
|
|
|
private final String template;
|
|
|
|
@ -142,8 +170,12 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
|
|
|
|
|
public class HttpResponse {
|
|
|
|
|
|
|
|
|
|
@Getter
|
|
|
|
|
private final java.net.http.HttpResponse<String> response;
|
|
|
|
|
|
|
|
|
|
@Getter
|
|
|
|
|
private final HttpStatus status;
|
|
|
|
|
|
|
|
|
|
private UUID locationUuid;
|
|
|
|
|
|
|
|
|
|
public HttpResponse(
|
|
|
|
@ -187,6 +219,16 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void keep(final Function<HttpResponse, String> extractor) {
|
|
|
|
|
final var alias = nextTitle != null ? nextTitle : resultAlias;
|
|
|
|
|
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
|
|
|
|
|
|
|
|
|
|
final var value = extractor.apply(this);
|
|
|
|
|
ScenarioTest.putAlias(
|
|
|
|
|
alias,
|
|
|
|
|
new ScenarioTest.Alias<>(UseCase.this.getClass(), UUID.fromString(value)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void keep() {
|
|
|
|
|
final var alias = nextTitle != null ? nextTitle : resultAlias;
|
|
|
|
|
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
|
|
|
|
@ -194,6 +236,12 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
alias,
|
|
|
|
|
new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SneakyThrows
|
|
|
|
|
public String getFromBody(final String path) {
|
|
|
|
|
final var rootNode = objectMapper.readTree(response.body());
|
|
|
|
|
return getPropertyFromJson(rootNode, path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void print(final String output) {
|
|
|
|
@ -231,4 +279,55 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|
|
|
|
private final String title(String resultAlias) {
|
|
|
|
|
return getClass().getSimpleName().replaceAll("([a-z])([A-Z]+)", "$1 $2") + " => " + resultAlias;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: refactor to own class
|
|
|
|
|
/**
|
|
|
|
|
* Extracts a property from a JsonNode based on a dotted path.
|
|
|
|
|
* Supports array notation like "users[0].address.city" and root arrays like "[0].user.address.city".
|
|
|
|
|
*
|
|
|
|
|
* @param rootNode the root JsonNode
|
|
|
|
|
* @param propertyPath the property path in dot notation (e.g., "[0].user.address.city")
|
|
|
|
|
* @return the extracted property value as a String
|
|
|
|
|
*/
|
|
|
|
|
public static String getPropertyFromJson(final JsonNode rootNode, final String propertyPath) {
|
|
|
|
|
final var pathParts = propertyPath.split("\\.");
|
|
|
|
|
var currentNode = rootNode;
|
|
|
|
|
|
|
|
|
|
// Traverse the JSON structure based on the path parts
|
|
|
|
|
for (final var part : pathParts) {
|
|
|
|
|
// Check if the part contains array notation like "[0]"
|
|
|
|
|
if (part.contains("[")) {
|
|
|
|
|
String arrayName;
|
|
|
|
|
final var arrayIndex = Integer.parseInt(part.substring(part.indexOf("[") + 1, part.indexOf("]")));
|
|
|
|
|
|
|
|
|
|
if (part.startsWith("[")) {
|
|
|
|
|
// This is a root-level array access (e.g., "[0]")
|
|
|
|
|
arrayName = null;
|
|
|
|
|
} else {
|
|
|
|
|
// This is a nested array access (e.g., "users[0]")
|
|
|
|
|
arrayName = part.substring(0, part.indexOf("["));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there's an array name, traverse to it
|
|
|
|
|
if (arrayName != null) {
|
|
|
|
|
currentNode = currentNode.path(arrayName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure the current node is an array, then access the element at the index
|
|
|
|
|
if (currentNode.isArray()) {
|
|
|
|
|
currentNode = currentNode.get(arrayIndex);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Traverse as a normal field
|
|
|
|
|
currentNode = currentNode.path(part);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If at any point, the node is missing, return null
|
|
|
|
|
if (currentNode.isMissingNode()) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return currentNode.asText(); // Return the final value as a String
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|