all 4 tests green and printing test report
This commit is contained in:
parent
4911ca54c0
commit
a1e04508ed
@ -6,7 +6,6 @@ import net.hostsharing.hsadminng.hs.office.usecases.membership.CreateMembership;
|
||||
import net.hostsharing.hsadminng.hs.office.usecases.partner.CreatePartner;
|
||||
import net.hostsharing.hsadminng.hs.office.usecases.partner.DeletePartner;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
@ -47,13 +46,22 @@ class HsOfficeUseCasesTest extends UseCaseTest {
|
||||
@Order(2000)
|
||||
void shouldCreateSelfDebitorForPartner() {
|
||||
new CreateSelfDebitorForPartner(this, "Debitor: Test AG - main debitor")
|
||||
.given("partnerPersonUuid", "%{Person: 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
|
||||
.given("billable", true)
|
||||
.given("vatId", "VAT123456")
|
||||
.given("vatCountryCode", "DE")
|
||||
.given("vatBusiness", true)
|
||||
.given("vatReverseCharge", false)
|
||||
.given("defaultPrefix", "tst")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3000)
|
||||
@Disabled
|
||||
void shouldCreateMembershipForPartner() {
|
||||
new CreateMembership(this).doRun();
|
||||
}
|
||||
|
@ -0,0 +1,138 @@
|
||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class TemplateResolver {
|
||||
|
||||
private final String template;
|
||||
private final Map<String, Object> properties;
|
||||
private final StringBuilder resolved = new StringBuilder();
|
||||
private int position = 0;
|
||||
|
||||
public TemplateResolver(final String template, final Map<String, Object> properties) {
|
||||
this.template = template;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
String resolve() {
|
||||
copy();
|
||||
return resolved.toString();
|
||||
}
|
||||
|
||||
private void copy() {
|
||||
while (hasMoreChars()) {
|
||||
if ((currentChar() == '$' || currentChar() == '%') && nextChar() == '{') {
|
||||
startPlaceholder(currentChar());
|
||||
} else {
|
||||
resolved.append(fetchChar());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMoreChars() {
|
||||
return position < template.length();
|
||||
}
|
||||
|
||||
private void startPlaceholder(final char intro) {
|
||||
skipChars(intro + "{");
|
||||
int nested = 0;
|
||||
final var placeholder = new StringBuilder();
|
||||
while (nested > 0 || currentChar() != '}') {
|
||||
if (currentChar() == '}') {
|
||||
--nested;
|
||||
placeholder.append(fetchChar());
|
||||
} else if ((currentChar() == '$' || currentChar() == '%') && nextChar() == '{') {
|
||||
++nested;
|
||||
placeholder.append(fetchChar());
|
||||
} else {
|
||||
placeholder.append(fetchChar());
|
||||
}
|
||||
}
|
||||
final var name = new TemplateResolver(placeholder.toString(), properties).resolve();
|
||||
final var value = propVal(name);
|
||||
if ( intro == '%') {
|
||||
resolved.append(value);
|
||||
} else {
|
||||
resolved.append(optionallyQuoted(value));
|
||||
}
|
||||
skipChar('}');
|
||||
}
|
||||
|
||||
private Object propVal(final String name) {
|
||||
final var val = properties.get(name);
|
||||
if (val == null) {
|
||||
throw new IllegalStateException("Missing required property: " + name);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private void skipChar(final char expectedChar) {
|
||||
if (currentChar() != expectedChar) {
|
||||
throw new IllegalStateException("expected '" + expectedChar + "' but got '" + currentChar() + "'");
|
||||
}
|
||||
++position;
|
||||
}
|
||||
|
||||
private void skipChars(final String expectedChars) {
|
||||
final var nextChars = template.substring(position, position + expectedChars.length());
|
||||
if ( !nextChars.equals(expectedChars) ) {
|
||||
throw new IllegalStateException("expected '" + expectedChars + "' but got '" + nextChars + "'");
|
||||
}
|
||||
position += expectedChars.length();
|
||||
}
|
||||
|
||||
private char fetchChar() {
|
||||
if ((position+1) > template.length()) {
|
||||
throw new IllegalStateException("no more characters. resolved so far: " + resolved);
|
||||
}
|
||||
final var currentChar = currentChar();
|
||||
//System.out.println("fetched #" + position + ": " + currentChar);
|
||||
++position;
|
||||
return currentChar;
|
||||
}
|
||||
|
||||
private char currentChar() {
|
||||
if (position >= template.length()) {
|
||||
throw new IllegalStateException("no more characters. resolved so far: " + resolved);
|
||||
}
|
||||
return template.charAt(position);
|
||||
}
|
||||
|
||||
private char nextChar() {
|
||||
if ((position+1) >= template.length()) {
|
||||
throw new IllegalStateException("no more characters. resolved so far: " + resolved);
|
||||
}
|
||||
return template.charAt(position+1);
|
||||
}
|
||||
|
||||
private static String optionallyQuoted(final Object value) {
|
||||
return switch (value) {
|
||||
case Boolean bool -> bool.toString();
|
||||
case Number number -> number.toString();
|
||||
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());
|
||||
|
||||
}
|
||||
}
|
@ -8,16 +8,11 @@ import org.apache.commons.collections4.map.LinkedMap;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
@ -26,7 +21,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
private final UseCaseTest testSuite;
|
||||
private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>();
|
||||
private final String resultAlias;
|
||||
private String nextTitle;
|
||||
private String nextTitle; // FIXME: ugly
|
||||
|
||||
public UseCase(final UseCaseTest testSuite) {
|
||||
this(testSuite, null);
|
||||
@ -42,7 +37,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public final void requires(final String alias, final Function<String, UseCase<?>> useCaseFactory) {
|
||||
if ( !UseCaseTest.containsAlias(alias) ) {
|
||||
if (!UseCaseTest.containsAlias(alias)) {
|
||||
requirements.put(alias, useCaseFactory);
|
||||
}
|
||||
}
|
||||
@ -51,10 +46,17 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
assumeThat(UseCaseTest.containsAlias(alias))
|
||||
.as("skipping because alias '" + alias + "' not found, maybe the other test failed?")
|
||||
.isTrue();
|
||||
log("depends on ["+alias+"]("+UseCaseTest.getAlias(alias).useCase().getSimpleName()+".md)");
|
||||
log("depends on [" + alias + "](" + UseCaseTest.getAlias(alias).useCase().getSimpleName() + ".md)");
|
||||
}
|
||||
|
||||
public final HttpResponse doRun() {
|
||||
log("### Given Properties\n");
|
||||
log("""
|
||||
| name | value |
|
||||
|------|-------|
|
||||
""".trim());
|
||||
UseCaseTest.properties().forEach((key, value) -> log("| " + key + " | " + value + " |"));
|
||||
log("");
|
||||
requirements.forEach((alias, factory) -> factory.apply(alias).run().keep());
|
||||
return run();
|
||||
}
|
||||
@ -71,7 +73,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
public final void keep(final String alias, final Supplier<HttpResponse> http) {
|
||||
this.nextTitle = alias; // FIXME: ugly
|
||||
this.nextTitle = UseCaseTest.resolve(alias);
|
||||
http.get().keep();
|
||||
this.nextTitle = null;
|
||||
}
|
||||
@ -109,37 +111,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
String resolvePlaceholders() {
|
||||
var partiallyResolved = new AtomicReference<>(template);
|
||||
UseCaseTest.knowVariables().forEach(entry -> partiallyResolved.set(
|
||||
partiallyResolved.get().replace("${" + entry.getKey() + "}", optionallyQuoted(entry.getValue())))
|
||||
);
|
||||
verifyAllPlaceholdersResolved(partiallyResolved.get());
|
||||
return partiallyResolved.get();
|
||||
}
|
||||
|
||||
private String optionallyQuoted(final Object value) {
|
||||
return switch (value) {
|
||||
case Boolean bool -> bool.toString();
|
||||
case Number number -> number.toString();
|
||||
default -> "\"" + value + "\"";
|
||||
};
|
||||
}
|
||||
|
||||
private void verifyAllPlaceholdersResolved(final String remainingTemplate) {
|
||||
final var pattern = Pattern.compile("\\$\\{[^}]+\\}");
|
||||
final var matcher = pattern.matcher(remainingTemplate);
|
||||
|
||||
final var unresolvedPlaceholders = new ArrayList<>();
|
||||
while (matcher.find()) {
|
||||
unresolvedPlaceholders.add(matcher.group());
|
||||
}
|
||||
|
||||
final var knownVariables = UseCaseTest.knowVariables()
|
||||
.map(e -> '"' + e.getKey() + "\": \"" + e.getValue() + '"')
|
||||
.collect(Collectors.joining("\n "));
|
||||
assertThat(unresolvedPlaceholders)
|
||||
.as("known variables: [\n " + knownVariables + "\n]\nunresolved placeholders:")
|
||||
.isEmpty();
|
||||
return UseCaseTest.resolve(template);
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,6 +145,9 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
log(httpMethod.name() + " " + uri);
|
||||
log(requestBody + "=> status: " + status + " " +
|
||||
(locationUuid != null ? locationUuid : ""));
|
||||
if (!status.is2xxSuccessful()) {
|
||||
log(response.getBody().prettyPrint());
|
||||
}
|
||||
log("```");
|
||||
log("");
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
||||
|
||||
import com.tngtech.archunit.thirdparty.com.google.common.collect.Streams;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
@ -18,10 +17,10 @@ import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ -101,13 +100,23 @@ public abstract class UseCaseTest extends ContextBasedTest {
|
||||
}
|
||||
|
||||
static void putProperty(final String name, final Object value) {
|
||||
properties.put(name, value);
|
||||
properties.put(name, (value instanceof String string) ? resolve(string) : value);
|
||||
}
|
||||
|
||||
static Stream<Map.Entry<String,?>> knowVariables() {
|
||||
return Streams.concat(
|
||||
UseCaseTest.aliases.entrySet().stream().map(e -> Map.entry(e.getKey(), e.getValue().uuid())),
|
||||
UseCaseTest.properties.entrySet().stream()
|
||||
);
|
||||
static LinkedHashMap<String, Object> properties() {
|
||||
return new LinkedHashMap<>(properties);
|
||||
}
|
||||
|
||||
static Map<String, Object> knowVariables() {
|
||||
final var map = new LinkedHashMap<String, Object>();
|
||||
UseCaseTest.aliases.forEach((key, value) -> map.put(key, value.uuid()));
|
||||
map.putAll(UseCaseTest.properties);
|
||||
return map;
|
||||
}
|
||||
|
||||
static String resolve(final String text) {
|
||||
final var resolved = new TemplateResolver(text, UseCaseTest.knowVariables()).resolve();
|
||||
return resolved;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
|
||||
keep("Contact: Test AG - billing department", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": "Test AG - billing department",
|
||||
"caption": ${billingContactCaption},
|
||||
"emailAddresses": {
|
||||
"main": "billing@test-ag.example.org"
|
||||
"main": ${billingContactEmailAddress}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
@ -43,18 +43,18 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
|
||||
{
|
||||
"debitorRel": {
|
||||
"type": "DEBITOR", // FIXME: should be defaulted to DEBITOR
|
||||
"anchorUuid": ${Person: Test AG},
|
||||
"holderUuid": ${Person: Test AG},
|
||||
"anchorUuid": ${partnerPersonUuid},
|
||||
"holderUuid": ${partnerPersonUuid},
|
||||
"contactUuid": ${Contact: Test AG - billing department}
|
||||
},
|
||||
"debitorNumberSuffix": "00",
|
||||
"billable": true,
|
||||
"vatId": "VAT123456",
|
||||
"vatCountryCode": "DE",
|
||||
"vatBusiness": true,
|
||||
"vatReverseCharge": false,
|
||||
"debitorNumberSuffix": ${debitorNumberSuffix},
|
||||
"billable": ${billable},
|
||||
"vatId": ${vatId},
|
||||
"vatCountryCode": ${vatCountryCode},
|
||||
"vatBusiness": ${vatBusiness},
|
||||
"vatReverseCharge": ${vatReverseCharge},
|
||||
"refundBankAccountUuid": ${BankAccount: Test AG - refund bank account},
|
||||
"defaultPrefix": "tst"
|
||||
"defaultPrefix": ${defaultPrefix}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON);
|
||||
|
@ -10,15 +10,15 @@ public class CreateMembership extends UseCase<CreateMembership> {
|
||||
public CreateMembership(final UseCaseTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
requires("partner: Test AG.uuid");
|
||||
requires("Partner: Test AG");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
keep("membership:Test AG 00.uuid", () ->
|
||||
keep("Membership: Test AG 00", () ->
|
||||
httpPost("/api/hs/office/memberships", usingJsonBody("""
|
||||
{
|
||||
"partnerUuid": "${partner:Test AG.uuid}",
|
||||
"partnerUuid": ${Partner: Test AG},
|
||||
"memberNumberSuffix": "00",
|
||||
"validFrom": "2024-10-15",
|
||||
"membershipFeeBillable": "true"
|
||||
|
@ -7,10 +7,6 @@ import org.springframework.http.HttpStatus;
|
||||
|
||||
public class CreatePartner extends UseCase<CreatePartner> {
|
||||
|
||||
public CreatePartner(final UseCaseTest testSuite) {
|
||||
super(testSuite, null);
|
||||
}
|
||||
|
||||
public CreatePartner(final UseCaseTest testSuite, final String resultAlias) {
|
||||
super(testSuite, resultAlias);
|
||||
}
|
||||
@ -18,17 +14,17 @@ public class CreatePartner extends UseCase<CreatePartner> {
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
keep("Person: Test AG", () ->
|
||||
keep("Person: %{tradeName}", () ->
|
||||
httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||
{
|
||||
"personType": ${personType},
|
||||
"tradeName": ${tradeName}
|
||||
},
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||
);
|
||||
|
||||
keep("Contact: Test AG - Bord of Directors", () ->
|
||||
keep("Contact: %{tradeName} - Bord of Directors", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": ${contactCaption},
|
||||
@ -45,8 +41,8 @@ public class CreatePartner extends UseCase<CreatePartner> {
|
||||
"partnerNumber": ${partnerNumber},
|
||||
"partnerRel": {
|
||||
"anchorUuid": ${Person: Hostsharing eG},
|
||||
"holderUuid": ${Person: Test AG},
|
||||
"contactUuid": ${Contact: Test AG - Bord of Directors}
|
||||
"holderUuid": ${Person: %{tradeName}},
|
||||
"contactUuid": ${Contact: %{tradeName} - Bord of Directors}
|
||||
},
|
||||
"details": {
|
||||
"registrationOffice": "Registergericht Hamburg",
|
||||
|
Loading…
Reference in New Issue
Block a user