feature/use-case-acceptance-tests #116

Merged
hsh-michaelhoennig merged 49 commits from feature/use-case-acceptance-tests into master 2024-10-30 11:40:46 +01:00
4 changed files with 112 additions and 5 deletions
Showing only changes of commit e12d0b0ba2 - Show all commits

View File

@ -112,7 +112,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Produces("Debitor: Test AG - main debitor")
void shouldCreateSelfDebitorForPartner() {
new CreateSelfDebitorForPartner(this, "Debitor: Test AG - main debitor")
.given("partnerPersonUuid", "%{Person: Test AG}")
.given("partnerPersonTradeName", "Test AG")
.given("billingContactCaption", "Test AG - billing department")
.given("billingContactEmailAddress", "billing@test-ag.example.org")
.given("debitorNumberSuffix", "00") // TODO.impl: could be assigned automatically, but is not yet

View File

@ -179,12 +179,12 @@ public abstract class ScenarioTest extends ContextBasedTest {
return map;
}
static String resolve(final String text) {
public static String resolve(final String text) {
final var resolved = new TemplateResolver(text, ScenarioTest.knowVariables()).resolve();
return resolved;
}
static Object resolveTyped(final String text) {
public static Object resolveTyped(final String text) {
final var resolved = resolve(text);
try {
return UUID.fromString(resolved);

View File

@ -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
}
}

View File

@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPartner> {
@ -14,6 +15,12 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
@Override
protected HttpResponse run() {
keep("partnerPersonUuid", () ->
httpGet("/api/hs/office/relations?personData=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.getFromBody("[0].holder.uuid")
);
keep("BankAccount: Test AG - refund bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
{
@ -57,4 +64,5 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
"""))
.expecting(CREATED).expecting(JSON);
}
}