Compare commits
5 Commits
2b21c742f0
...
a1e04508ed
Author | SHA1 | Date | |
---|---|---|---|
|
a1e04508ed | ||
|
4911ca54c0 | ||
|
887ec588b6 | ||
|
4d29c4643b | ||
|
1a52895cda |
@ -1,51 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
|
||||||
|
|
||||||
import io.restassured.http.ContentType;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
|
|
||||||
class HsOfficeCreatePartnerUseCase extends UseCase {
|
|
||||||
|
|
||||||
public HsOfficeCreatePartnerUseCase(final UseCaseTest testSuite) {
|
|
||||||
super(testSuite);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
HttpResponse run() {
|
|
||||||
|
|
||||||
httpPost("/api/hs/office/persons", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"personType": "LEGAL_PERSON",
|
|
||||||
"tradeName": "Test AG"
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
|
||||||
.keepingAs("person:Test AG.uuid");
|
|
||||||
|
|
||||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"caption": "Test AG - Bord of Directors",
|
|
||||||
"emailAddresses": {
|
|
||||||
"main": "bord-of-directors@test-ag.example.org"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
|
||||||
.keepingAs("contact:Test AG - Bord of Directors.uuid");
|
|
||||||
|
|
||||||
return httpPost("/api/hs/office/partners", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"partnerNumber": "30003",
|
|
||||||
"partnerRel": {
|
|
||||||
"anchorUuid": "${person:Hostsharing eG.uuid}",
|
|
||||||
"holderUuid": "${person:Test AG.uuid}",
|
|
||||||
"contactUuid": "${contact:Test AG - Bord of Directors.uuid}"
|
|
||||||
},
|
|
||||||
"details": {
|
|
||||||
"registrationOffice": "Registergericht Hamburg",
|
|
||||||
"registrationNumber": "1234567"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
|
||||||
|
|
||||||
import static io.restassured.http.ContentType.JSON;
|
|
||||||
import static org.springframework.http.HttpStatus.CREATED;
|
|
||||||
|
|
||||||
class HsOfficeDebitorUseCase extends UseCase {
|
|
||||||
|
|
||||||
public HsOfficeDebitorUseCase(final UseCaseTest testSuite) {
|
|
||||||
super(testSuite);
|
|
||||||
|
|
||||||
requires("person:Test AG.uuid", () -> new HsOfficeCreatePartnerUseCase(testSuite));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
HttpResponse run() {
|
|
||||||
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"holder": "Test AG - refund bank account",
|
|
||||||
"iban": "DE88100900001234567892",
|
|
||||||
"bic": "BEVODEBB"
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(CREATED).expecting(JSON)
|
|
||||||
.keepingAs("bankaccount:Test AG - refund bank account.uuid");
|
|
||||||
|
|
||||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"caption": "Test AG - billing department",
|
|
||||||
"emailAddresses": {
|
|
||||||
"main": "billing@test-ag.example.org"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(CREATED).expecting(JSON)
|
|
||||||
.keepingAs("contact:Test AG - billing department.uuid");
|
|
||||||
|
|
||||||
httpPost("/api/hs/office/debitors", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"debitorRel": {
|
|
||||||
"type": "DEBITOR", // FIXME: should be defaulted to DEBITOR
|
|
||||||
"anchorUuid": "${person:Test AG.uuid}",
|
|
||||||
"holderUuid": "${person:Test AG.uuid}",
|
|
||||||
"contactUuid": "${contact:Test AG - billing department.uuid}"
|
|
||||||
},
|
|
||||||
"debitorNumberSuffix": "00",
|
|
||||||
"billable": "true",
|
|
||||||
"vatId": "VAT123456",
|
|
||||||
"vatCountryCode": "DE",
|
|
||||||
"vatBusiness": true,
|
|
||||||
"vatReverseCharge": "false",
|
|
||||||
"refundBankAccountUuid": "${bankaccount:Test AG - refund bank account.uuid}",
|
|
||||||
"defaultPrefix": "tst"
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(CREATED).expecting(JSON)
|
|
||||||
.keepingAs("debitor:Test AG - Hauptdebitor.uuid");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
|
|
||||||
class HsOfficeDeletePartnerUseCase extends UseCase {
|
|
||||||
|
|
||||||
public HsOfficeDeletePartnerUseCase(final UseCaseTest testSuite) {
|
|
||||||
super(testSuite);
|
|
||||||
|
|
||||||
requires("partner:Test AG for deletetion:uuid", () -> new HsOfficeCreatePartnerUseCase(testSuite));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
HttpResponse run() {
|
|
||||||
httpDelete("/api/hs/office/partners/" + uuid("partner:Test AG.uuid"))
|
|
||||||
.expecting(HttpStatus.NO_CONTENT);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
|
||||||
|
|
||||||
import io.restassured.http.ContentType;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
|
|
||||||
class HsOfficeMembershipUseCase extends UseCase {
|
|
||||||
|
|
||||||
public HsOfficeMembershipUseCase(final UseCaseTest testSuite) {
|
|
||||||
super(testSuite);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
HttpResponse run() {
|
|
||||||
httpPost("/api/hs/office/memberships", usingJsonBody("""
|
|
||||||
{
|
|
||||||
"partnerUuid": "${partner:Test AG.uuid}",
|
|
||||||
"memberNumberSuffix": "00",
|
|
||||||
"validFrom": "2024-10-15",
|
|
||||||
"membershipFeeBillable": "true"
|
|
||||||
}
|
|
||||||
"""))
|
|
||||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
|
||||||
.keepingAs("membership:Test AG 00.uuid");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,16 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
package net.hostsharing.hsadminng.hs.office.usecases;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.debitor.CreateSelfDebitorForPartner;
|
||||||
|
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 net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
@SpringBootTest(
|
@SpringBootTest(
|
||||||
@ -16,31 +19,50 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||||||
)
|
)
|
||||||
@Tag("useCaseTest")
|
@Tag("useCaseTest")
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
@ExtendWith(OrderedDependedTestsExtension.class)
|
|
||||||
class HsOfficeUseCasesTest extends UseCaseTest {
|
class HsOfficeUseCasesTest extends UseCaseTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1010)
|
@Order(1010)
|
||||||
void shouldCreatePartner() {
|
void shouldCreatePartner() {
|
||||||
new HsOfficeCreatePartnerUseCase(this).run()
|
new CreatePartner(this, "Partner: Test AG")
|
||||||
.keepingAs("partner:Test AG.uuid");
|
.given("partnerNumber", 30001)
|
||||||
}
|
.given("personType", "LEGAL_PERSON")
|
||||||
|
.given("tradeName", "Test AG")
|
||||||
@Test
|
.given("contactCaption", "Test AG - Bord of Directors")
|
||||||
@Order(1011)
|
.given("emailAddress", "bord-of-directors@test-ag.example.org")
|
||||||
void shouldDeletePartner() {
|
.doRun()
|
||||||
new HsOfficeDeletePartnerUseCase(this).run();
|
.keep();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1020)
|
@Order(1020)
|
||||||
void shouldCreateSelfDebitorForPartner() {
|
void shouldDeletePartner() {
|
||||||
new HsOfficeDebitorUseCase(this).run();
|
new DeletePartner(this)
|
||||||
|
.given("partnerNumber", 31020)
|
||||||
|
.doRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1030)
|
@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)
|
||||||
void shouldCreateMembershipForPartner() {
|
void shouldCreateMembershipForPartner() {
|
||||||
new HsOfficeMembershipUseCase(this).run();
|
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());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -4,58 +4,102 @@ import io.restassured.RestAssured;
|
|||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
import io.restassured.response.Response;
|
import io.restassured.response.Response;
|
||||||
import io.restassured.response.ValidatableResponse;
|
import io.restassured.response.ValidatableResponse;
|
||||||
|
import org.apache.commons.collections4.map.LinkedMap;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public abstract class UseCase {
|
public abstract class UseCase<T extends UseCase<?>> {
|
||||||
|
|
||||||
private final UseCaseTest testSuite;
|
private final UseCaseTest testSuite;
|
||||||
|
private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>();
|
||||||
|
private final String resultAlias;
|
||||||
|
private String nextTitle; // FIXME: ugly
|
||||||
|
|
||||||
public UseCase(final UseCaseTest testSuite) {
|
public UseCase(final UseCaseTest testSuite) {
|
||||||
|
this(testSuite, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UseCase(final UseCaseTest testSuite, final String resultAlias) {
|
||||||
this.testSuite = testSuite;
|
this.testSuite = testSuite;
|
||||||
}
|
this.resultAlias = resultAlias;
|
||||||
|
if (resultAlias != null) {
|
||||||
void requires(final String alias, final Supplier<UseCase> useCaseSupplier) {
|
log("### UseCase " + resultAlias);
|
||||||
if ( !UseCaseTest.aliases.containsKey(alias) ) {
|
log("");
|
||||||
useCaseSupplier.get().run().keepingAs(alias);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract HttpResponse run();
|
public final void requires(final String alias, final Function<String, UseCase<?>> useCaseFactory) {
|
||||||
|
if (!UseCaseTest.containsAlias(alias)) {
|
||||||
|
requirements.put(alias, useCaseFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JsonTemplate usingJsonBody(final String jsonTemplate) {
|
public void requires(final String alias) {
|
||||||
|
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)");
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract HttpResponse run();
|
||||||
|
|
||||||
|
public final UseCase<T> given(final String propName, final Object propValue) {
|
||||||
|
UseCaseTest.putProperty(propName, propValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final JsonTemplate usingJsonBody(final String jsonTemplate) {
|
||||||
return new JsonTemplate(jsonTemplate);
|
return new JsonTemplate(jsonTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
public final void keep(final String alias, final Supplier<HttpResponse> http) {
|
||||||
|
this.nextTitle = UseCaseTest.resolve(alias);
|
||||||
|
http.get().keep();
|
||||||
|
this.nextTitle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
||||||
|
final var body = bodyJsonTemplate.resolvePlaceholders();
|
||||||
|
final var uri = "http://localhost" + uriPath;
|
||||||
final var response = RestAssured.given()
|
final var response = RestAssured.given()
|
||||||
.header("current-subject", UseCaseTest.RUN_AS_USER)
|
.header("current-subject", UseCaseTest.RUN_AS_USER)
|
||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body(bodyJsonTemplate.with(UseCaseTest.aliases))
|
.body(body)
|
||||||
.port(testSuite.port)
|
.port(testSuite.port)
|
||||||
.when().post("http://localhost" + uriPath);
|
.when().post(uri);
|
||||||
return new HttpResponse(response);
|
return new HttpResponse(HttpMethod.POST, uriPath, body, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse httpDelete(final String uriPath) {
|
public final HttpResponse httpDelete(final String uriPath) {
|
||||||
final var response = RestAssured.given()
|
final var response = RestAssured.given()
|
||||||
.header("current-subject", UseCaseTest.RUN_AS_USER)
|
.header("current-subject", UseCaseTest.RUN_AS_USER)
|
||||||
.port(testSuite.port)
|
.port(testSuite.port)
|
||||||
.when().delete("http://localhost" + uriPath);
|
.when().delete("http://localhost" + uriPath);
|
||||||
return new HttpResponse(response);
|
return new HttpResponse(HttpMethod.DELETE, uriPath, null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID uuid(final String alias) {
|
public final UUID uuid(final String alias) {
|
||||||
return testSuite.aliases.get(alias);
|
return UseCaseTest.getAlias(alias).uuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class JsonTemplate {
|
static class JsonTemplate {
|
||||||
@ -66,52 +110,71 @@ public abstract class UseCase {
|
|||||||
this.template = jsonTemplate;
|
this.template = jsonTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
String with(final Map<String, UUID> aliases) {
|
String resolvePlaceholders() {
|
||||||
var partiallyResolved = new AtomicReference<>(template);
|
return UseCaseTest.resolve(template);
|
||||||
aliases.forEach((k, v) ->
|
|
||||||
partiallyResolved.set(partiallyResolved.get().replace("${" + k + "}", v.toString())));
|
|
||||||
verifyAllPlaceholdersResolved(partiallyResolved.get());
|
|
||||||
return partiallyResolved.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(unresolvedPlaceholders).as("unresolved placeholders").hasSize(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HttpResponse {
|
public class HttpResponse {
|
||||||
|
|
||||||
private final ValidatableResponse response;
|
private final ValidatableResponse response;
|
||||||
|
private final HttpStatus status;
|
||||||
|
private UUID locationUuid;
|
||||||
|
|
||||||
public HttpResponse(final Response response) {
|
public HttpResponse(
|
||||||
this.response = response.then().log().all().assertThat();
|
final HttpMethod httpMethod,
|
||||||
|
final String uri,
|
||||||
|
final String requestBody,
|
||||||
|
final Response response
|
||||||
|
) {
|
||||||
|
final var validatableResponse = response.then();
|
||||||
|
this.response = validatableResponse.log().all().assertThat();
|
||||||
|
this.status = HttpStatus.valueOf(response.statusCode());
|
||||||
|
if (response.statusCode() == HttpStatus.CREATED.value()) {
|
||||||
|
final var location = validatableResponse.header("Location", startsWith("http://localhost"))
|
||||||
|
.extract().header("Location");
|
||||||
|
locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse expecting(final HttpStatus httpStatus) {
|
if (nextTitle != null) {
|
||||||
|
log("\n### " + nextTitle + "\n");
|
||||||
|
} else if (resultAlias != null) {
|
||||||
|
log("\n### " + resultAlias + "\n");
|
||||||
|
}
|
||||||
|
log("```");
|
||||||
|
log(httpMethod.name() + " " + uri);
|
||||||
|
log(requestBody + "=> status: " + status + " " +
|
||||||
|
(locationUuid != null ? locationUuid : ""));
|
||||||
|
if (!status.is2xxSuccessful()) {
|
||||||
|
log(response.getBody().prettyPrint());
|
||||||
|
}
|
||||||
|
log("```");
|
||||||
|
log("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpResponse expecting(final HttpStatus httpStatus) {
|
||||||
response.statusCode(httpStatus.value());
|
response.statusCode(httpStatus.value());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse expecting(final ContentType contentType) {
|
public HttpResponse expecting(final ContentType contentType) {
|
||||||
response.contentType(contentType);
|
response.contentType(contentType);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void keepingAs(final String uuidAliasName) {
|
public void keep() {
|
||||||
final var location = response.header("Location", startsWith("http://localhost"))
|
UseCaseTest.putAlias(
|
||||||
.extract().header("Location");
|
nextTitle != null ? nextTitle : resultAlias,
|
||||||
final var newSubjectUuid = UUID.fromString(
|
new UseCaseTest.Alias<>(UseCase.this.getClass(), locationUuid));
|
||||||
location.substring(location.lastIndexOf('/') + 1));
|
|
||||||
assertThat(newSubjectUuid).isNotNull();
|
|
||||||
UseCaseTest.aliases.put(uuidAliasName, newSubjectUuid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void log(final String output) {
|
||||||
|
testSuite.log(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T self() {
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,43 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.usecases;
|
package net.hostsharing.hsadminng.hs.office.usecases;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
||||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
|
||||||
import org.junit.jupiter.api.extension.TestWatcher;
|
|
||||||
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 java.io.FileWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public abstract class UseCaseTest extends ContextBasedTest {
|
public abstract class UseCaseTest extends ContextBasedTest {
|
||||||
|
|
||||||
final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented
|
final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented
|
||||||
|
|
||||||
final static Map<String, UUID> aliases = new HashMap<>();
|
@Getter
|
||||||
|
public static TestInfo currentTestInfo;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private PrintWriter markdownFile;
|
||||||
|
|
||||||
|
record Alias<T extends UseCase<T>>(Class<T> useCase, UUID uuid) {}
|
||||||
|
|
||||||
|
private final static Map<String, Alias<?>> aliases = new HashMap<>();
|
||||||
|
private final static Map<String, Object> properties = new HashMap<>();
|
||||||
|
|
||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
Integer port;
|
Integer port;
|
||||||
@ -33,33 +48,75 @@ public abstract class UseCaseTest extends ContextBasedTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
JpaAttempt jpaAttempt;
|
JpaAttempt jpaAttempt;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void init() {
|
void init(final TestInfo testInfo) {
|
||||||
jpaAttempt.transacted(() ->
|
jpaAttempt.transacted(() ->
|
||||||
{
|
{
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
aliases.put(
|
aliases.put(
|
||||||
"person:Hostsharing eG.uuid",
|
"Person: Hostsharing eG",
|
||||||
|
new Alias<>(
|
||||||
|
null,
|
||||||
personRepo.findPersonByOptionalNameLike("Hostsharing eG")
|
personRepo.findPersonByOptionalNameLike("Hostsharing eG")
|
||||||
.stream()
|
.stream()
|
||||||
.map(HsOfficePersonEntity::getUuid)
|
.map(HsOfficePersonEntity::getUuid)
|
||||||
.reduce(Reducer::toSingleElement).orElseThrow());
|
.reduce(Reducer::toSingleElement).orElseThrow())
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow();
|
||||||
|
markdownFile = new PrintWriter(new FileWriter(testMethodName + ".md"));
|
||||||
|
log("## Testcase " + testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2"));
|
||||||
|
currentTestInfo = testInfo; // FIXME: remove?
|
||||||
}
|
}
|
||||||
|
|
||||||
class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
|
@AfterEach
|
||||||
|
void cleanup() {
|
||||||
private static boolean previousTestsPassed = true;
|
properties.clear();
|
||||||
|
markdownFile.close();
|
||||||
@Override
|
|
||||||
public void testFailed(final ExtensionContext context, final Throwable cause) {
|
|
||||||
previousTestsPassed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@SneakyThrows
|
||||||
public void beforeEach(final ExtensionContext extensionContext) {
|
void log(final String output) {
|
||||||
assumeThat(previousTestsPassed).isTrue();
|
markdownFile.println(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean containsAlias(final String alias) {
|
||||||
|
return aliases.containsKey(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Alias<?> getAlias(final String name) {
|
||||||
|
final var alias = aliases.get(name);
|
||||||
|
assertThat(alias).as("alias '" + name + "' not found in aliases [" +
|
||||||
|
aliases.keySet().stream().map(v -> "'" + v + "'").collect(Collectors.joining(", ")) + "]"
|
||||||
|
).isNotNull();
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void putAlias(final String name, final Alias<?> value) {
|
||||||
|
aliases.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void putProperty(final String name, final Object value) {
|
||||||
|
properties.put(name, (value instanceof String string) ? resolve(string) : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.usecases.debitor;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCase;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCaseTest;
|
||||||
|
|
||||||
|
import static io.restassured.http.ContentType.JSON;
|
||||||
|
import static org.springframework.http.HttpStatus.CREATED;
|
||||||
|
|
||||||
|
public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPartner> {
|
||||||
|
|
||||||
|
public CreateSelfDebitorForPartner(final UseCaseTest testSuite, final String resultAlias) {
|
||||||
|
super(testSuite, resultAlias);
|
||||||
|
|
||||||
|
requires("Person: Test AG");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpResponse run() {
|
||||||
|
keep("BankAccount: Test AG - refund bank account", () ->
|
||||||
|
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"holder": "Test AG - refund bank account",
|
||||||
|
"iban": "DE88100900001234567892",
|
||||||
|
"bic": "BEVODEBB"
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(CREATED).expecting(JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
keep("Contact: Test AG - billing department", () ->
|
||||||
|
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"caption": ${billingContactCaption},
|
||||||
|
"emailAddresses": {
|
||||||
|
"main": ${billingContactEmailAddress}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(CREATED).expecting(JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
return httpPost("/api/hs/office/debitors", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"debitorRel": {
|
||||||
|
"type": "DEBITOR", // FIXME: should be defaulted to DEBITOR
|
||||||
|
"anchorUuid": ${partnerPersonUuid},
|
||||||
|
"holderUuid": ${partnerPersonUuid},
|
||||||
|
"contactUuid": ${Contact: Test AG - billing department}
|
||||||
|
},
|
||||||
|
"debitorNumberSuffix": ${debitorNumberSuffix},
|
||||||
|
"billable": ${billable},
|
||||||
|
"vatId": ${vatId},
|
||||||
|
"vatCountryCode": ${vatCountryCode},
|
||||||
|
"vatBusiness": ${vatBusiness},
|
||||||
|
"vatReverseCharge": ${vatReverseCharge},
|
||||||
|
"refundBankAccountUuid": ${BankAccount: Test AG - refund bank account},
|
||||||
|
"defaultPrefix": ${defaultPrefix}
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(CREATED).expecting(JSON);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.usecases.membership;
|
||||||
|
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCase;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCaseTest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
public class CreateMembership extends UseCase<CreateMembership> {
|
||||||
|
|
||||||
|
public CreateMembership(final UseCaseTest testSuite) {
|
||||||
|
super(testSuite);
|
||||||
|
|
||||||
|
requires("Partner: Test AG");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpResponse run() {
|
||||||
|
keep("Membership: Test AG 00", () ->
|
||||||
|
httpPost("/api/hs/office/memberships", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"partnerUuid": ${Partner: Test AG},
|
||||||
|
"memberNumberSuffix": "00",
|
||||||
|
"validFrom": "2024-10-15",
|
||||||
|
"membershipFeeBillable": "true"
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.usecases.partner;
|
||||||
|
|
||||||
|
import io.restassured.http.ContentType;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCase;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCaseTest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
public class CreatePartner extends UseCase<CreatePartner> {
|
||||||
|
|
||||||
|
public CreatePartner(final UseCaseTest testSuite, final String resultAlias) {
|
||||||
|
super(testSuite, resultAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpResponse run() {
|
||||||
|
|
||||||
|
keep("Person: %{tradeName}", () ->
|
||||||
|
httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"personType": ${personType},
|
||||||
|
"tradeName": ${tradeName}
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
keep("Contact: %{tradeName} - Bord of Directors", () ->
|
||||||
|
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"caption": ${contactCaption},
|
||||||
|
"emailAddresses": {
|
||||||
|
"main": ${emailAddress}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
return httpPost("/api/hs/office/partners", usingJsonBody("""
|
||||||
|
{
|
||||||
|
"partnerNumber": ${partnerNumber},
|
||||||
|
"partnerRel": {
|
||||||
|
"anchorUuid": ${Person: Hostsharing eG},
|
||||||
|
"holderUuid": ${Person: %{tradeName}},
|
||||||
|
"contactUuid": ${Contact: %{tradeName} - Bord of Directors}
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"registrationOffice": "Registergericht Hamburg",
|
||||||
|
"registrationNumber": "1234567"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""))
|
||||||
|
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.office.usecases.partner;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCase;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.usecases.UseCaseTest;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
public class DeletePartner extends UseCase<DeletePartner> {
|
||||||
|
|
||||||
|
public DeletePartner(final UseCaseTest testSuite) {
|
||||||
|
super(testSuite);
|
||||||
|
|
||||||
|
requires("Partner: Delete AG", alias -> new CreatePartner(testSuite, alias)
|
||||||
|
.given("personType", "LEGAL_PERSON")
|
||||||
|
.given("tradeName", "Delete AG")
|
||||||
|
.given("contactCaption", "Delete AG - Bord of Directors")
|
||||||
|
.given("emailAddress", "bord-of-directors@delete-ag.example.org"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpResponse run() {
|
||||||
|
httpDelete("/api/hs/office/partners/" + uuid("Partner: Delete AG"))
|
||||||
|
.expecting(HttpStatus.NO_CONTENT);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user