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
7 changed files with 247 additions and 156 deletions
Showing only changes of commit 963bf89841 - Show all commits

View File

@ -17,10 +17,8 @@ components:
minimum: 1000000
maximum: 9999999
debitorNumberSuffix:
type: integer
format: int8
minimum: 00
maximum: 99
type: string
pattern: '^[0-9][0-9]$'
partner:
$ref: 'hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner'
billable:
@ -81,10 +79,8 @@ components:
type: string
format: uuid
debitorNumberSuffix:
type: integer
format: int8
minimum: 00
maximum: 99
type: string
pattern: '^[0-9][0-9]$'
billable:
type: boolean
vatId:

View File

@ -0,0 +1,57 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import org.springframework.http.HttpStatus;
import static io.restassured.http.Method.POST;
class HsOfficeDebitorUseCase extends UseCase {
public HsOfficeDebitorUseCase(final UseCaseTest testSuite) {
super(testSuite);
}
void shouldCreateSelfDebitorForPartner() {
http(POST, "/api/hs/office/bankaccounts", usingJsonBody("""
{
"holder": "Test AG - refund bank account",
"iban": "DE88100900001234567892",
"bic": "BEVODEBB"
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("bankaccount:Test AG - refund bank account.uuid");
http(POST, "/api/hs/office/contacts", usingJsonBody("""
{
"caption": "Test AG - billing department",
"emailAddresses": {
"main": "billing@test-ag.example.org"
}
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("contact:Test AG - billing department.uuid");
http(POST, "/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(HttpStatus.CREATED)
.keepingAs("debitor:Test AG - Hauptdebitor.uuid");
}
}

View File

@ -0,0 +1,52 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import org.springframework.http.HttpStatus;
import static io.restassured.http.Method.POST;
class HsOfficePartnerUseCase extends UseCase {
public HsOfficePartnerUseCase(final UseCaseTest testSuite) {
super(testSuite);
}
void shouldCreatePartner() {
http(POST, "/api/hs/office/persons", usingJsonBody("""
{
"personType": "LEGAL_PERSON",
"tradeName": "Test AG"
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("person:Test AG.uuid");
http(POST, "/api/hs/office/contacts", usingJsonBody("""
{
"caption": "Test AG - Bord of Directors",
"emailAddresses": {
"main": "bord-of-directors@test-ag.example.org"
}
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("contact:Test AG - Bord of Directors.uuid");
http(POST, "/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)
.keepingAs("partner:Test AG.uuid");
}
}

View File

@ -1,62 +0,0 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import static io.restassured.http.Method.POST;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { HsadminNgApplication.class, JpaAttempt.class }
)
// @Tag("useCaseTest") FIXME
@Order(1)
class HsOfficePartnerUseCaseTest extends UseCaseTest {
@Test
void shouldCreatePartner() {
http(POST, "/api/hs/office/persons", usingJsonBody("""
{
"personType": "NATURAL_PERSON",
"familyName": "Tester",
"givenName": "Temp Testi"
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("partnerPerson.uuid");
http(POST, "/api/hs/office/contacts", usingJsonBody("""
{
"caption": "Temp Contact",
"emailAddresses": {
"main": "test@example.org"
}
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("contact.uuid");
http(POST, "/api/hs/office/partners", usingJsonBody("""
{
"partnerNumber": "20002",
"partnerRel": {
"anchorUuid": "${hostsharingPerson.uuid}",
"holderUuid": "${partnerPerson.uuid}",
"contactUuid": "${contact.uuid}"
},
"details": {
"registrationOffice": "Registergericht Hamburg",
"registrationNumber": "1234567"
}
}
"""))
.expecting(HttpStatus.CREATED)
.keepingAs("partner20002.uuid");
}
}

View File

@ -0,0 +1,31 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@Tag("useCaseTest")
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class HsOfficeUseCasesTest extends UseCaseTest {
@Test
@Order(1010)
void shouldCreatePartner() {
new HsOfficePartnerUseCase(this).shouldCreatePartner();
}
@Test
@Order(1020)
void shouldCreateSelfDebitorForPartner() {
new HsOfficeDebitorUseCase(this).shouldCreateSelfDebitorForPartner();
}
}

View File

@ -0,0 +1,97 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.http.Method;
import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse;
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.regex.Pattern;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.startsWith;
public class UseCase {
private final UseCaseTest testSuite;
public UseCase(final UseCaseTest testSuite) {
this.testSuite = testSuite;
}
JsonTemplate usingJsonBody(final String jsonTemplate) {
return new JsonTemplate(jsonTemplate);
}
HttpResponse http(final Method method, final String uriPath, final JsonTemplate bodyJsonTemplate) {
final var request = RestAssured.given()
.header("current-subject", UseCaseTest.RUN_AS_USER)
.contentType(ContentType.JSON)
.body(bodyJsonTemplate.with(UseCaseTest.aliases))
.port(testSuite.port);
final var response =
switch (method) {
case POST -> request.when().post("http://localhost" + uriPath);
default -> throw new IllegalStateException("HTTP method not implemented yet: " + method);
};
return new HttpResponse(response);
}
static class JsonTemplate {
private final String template;
private JsonTemplate(final String jsonTemplate) {
this.template = jsonTemplate;
}
String with(final Map<String, String> aliases) {
var partiallyResolved = new AtomicReference<>(template);
aliases.forEach((k, v) ->
partiallyResolved.set(partiallyResolved.get().replace("${" + k + "}", v)));
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 {
private final ValidatableResponse response;
public HttpResponse(final Response response) {
this.response = response.then().log().all().assertThat();
}
HttpResponse expecting(final HttpStatus httpStatus) {
response.statusCode(httpStatus.value())
.contentType(ContentType.JSON);
return this;
}
void keepingAs(final String uuidAliasName) {
final var location = response.header("Location", startsWith("http://localhost"))
.extract().header("Location");
final var newSubjectUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newSubjectUuid).isNotNull();
UseCaseTest.aliases.put(uuidAliasName, newSubjectUuid.toString());
}
}
}

View File

@ -1,37 +1,25 @@
package net.hostsharing.hsadminng.hs.office.usecases;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.http.Method;
import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.startsWith;
public abstract class UseCaseTest extends ContextBasedTestWithCleanup {
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 Map<String, String> aliases = new HashMap<>();
@LocalServerPort
private Integer port;
Integer port;
@Autowired
HsOfficePersonRepository personRepo;
@ -39,15 +27,13 @@ public abstract class UseCaseTest extends ContextBasedTestWithCleanup {
@Autowired
JpaAttempt jpaAttempt;
Map<String, String> aliases = new HashMap<>();
@BeforeEach
void init() {
jpaAttempt.transacted(() ->
{
context.define("superuser-alex@hostsharing.net");
aliases.put(
"hostsharingPerson.uuid",
"person:Hostsharing eG.uuid",
personRepo.findPersonByOptionalNameLike("Hostsharing eG")
.stream()
.map(HsOfficePersonEntity::getUuid)
@ -56,70 +42,4 @@ public abstract class UseCaseTest extends ContextBasedTestWithCleanup {
}
);
}
@AfterEach
void cleanup() {
cleanupAllNew(HsOfficePartnerEntity.class);
// TODO: should not be necessary anymore, once it's deleted via after delete trigger
cleanupAllNew(HsOfficeRelationRealEntity.class);
}
JsonTemplate usingJsonBody(final String jsonTemplate) {
return new JsonTemplate(jsonTemplate);
}
HttpResponse http(final Method method, final String uriPath, final JsonTemplate bodyJsonTemplate) {
final var request = RestAssured.given()
.header("current-subject", RUN_AS_USER)
.contentType(ContentType.JSON)
.body(bodyJsonTemplate.with(aliases))
.port(port);
final var response =
switch (method) {
case POST -> request.when().post("http://localhost" + uriPath);
default -> throw new IllegalStateException("HTTP method not implemented yet: " + method);
};
return new HttpResponse(response);
}
static class JsonTemplate {
private final String template;
private JsonTemplate(final String jsonTemplate) {
this.template = jsonTemplate;
}
String with(final Map<String, String> aliases) {
var resolved = new AtomicReference<String>(template);
aliases.forEach((k, v) ->
resolved.set(resolved.get().replace("${" + k + "}", v)));
return resolved.get();
}
}
class HttpResponse {
private final ValidatableResponse response;
public HttpResponse(final Response response) {
this.response = response.then().log().all().assertThat();
}
HttpResponse expecting(final HttpStatus httpStatus) {
response.statusCode(httpStatus.value())
.contentType(ContentType.JSON);
return this;
}
void keepingAs(final String uuidAliasName) {
final var location = response.header("Location", startsWith("http://localhost"))
.extract().header("Location");
final var newSubjectUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newSubjectUuid).isNotNull();
aliases.put(uuidAliasName, newSubjectUuid.toString());
}
}
}