feature/use-case-acceptance-tests (#116)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #116 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
c181500a1d
commit
3b94f117fb
@ -90,6 +90,20 @@ Acceptance-tests, are blackbox-tests and do <u>not</u> count into test-code-cove
|
||||
TODO.test: Complete the Acceptance-Tests test concept.
|
||||
|
||||
|
||||
#### Scenario-Tests
|
||||
|
||||
Our Scenario-tests are induced by business use-cases.
|
||||
They test from the REST API all the way down to the database.
|
||||
|
||||
Most scenario-tests are positive tests, they test if business scenarios do work.
|
||||
But few might be negative tests, which test if specific forbidden data gets rejected.
|
||||
|
||||
Our scenario tests also generate test-reports which contain the REST-API calls needed for each scenario.
|
||||
These reports can be used as examples for the API usage from a business perspective.
|
||||
|
||||
There is an extra document regarding scenario-test, see [Scenario-Tests README](../src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/README.md).
|
||||
|
||||
|
||||
#### Performance-Tests
|
||||
|
||||
Performance-critical scenarios have to be identified and a special performance-test has to be implemented.
|
||||
|
@ -77,16 +77,13 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both");
|
||||
Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
|
||||
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none");
|
||||
Validate.isTrue(body.getDebitorRel() == null ||
|
||||
body.getDebitorRel().getType() == null || DEBITOR.name().equals(body.getDebitorRel().getType()),
|
||||
"ERROR: [400] debitorRel.type must be '"+DEBITOR.name()+"' or null for default");
|
||||
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
|
||||
"ERROR: [400] debitorRel.mark must be null");
|
||||
|
||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
|
||||
if ( body.getDebitorRel() != null ) {
|
||||
body.getDebitorRel().setType(DEBITOR.name());
|
||||
if (body.getDebitorRel() != null) {
|
||||
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
|
||||
debitorRel.setType(DEBITOR);
|
||||
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
|
||||
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
|
||||
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
|
||||
@ -95,7 +92,10 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
|
||||
debitorRelOptional.ifPresentOrElse(
|
||||
debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));},
|
||||
() -> { throw new ValidationException("Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());});
|
||||
() -> {
|
||||
throw new ValidationException(
|
||||
"Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());
|
||||
});
|
||||
}
|
||||
|
||||
final var savedEntity = debitorRepo.save(entityToSave);
|
||||
|
@ -0,0 +1,44 @@
|
||||
package net.hostsharing.hsadminng.reflection;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
|
||||
@UtilityClass
|
||||
public class AnnotationFinder {
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends Annotation> Optional<T> findCallerAnnotation(
|
||||
final Class<T> annotationClassToFind,
|
||||
final Class<? extends Annotation> annotationClassToStopLookup
|
||||
) {
|
||||
for (var element : Thread.currentThread().getStackTrace()) {
|
||||
final var clazz = Class.forName(element.getClassName());
|
||||
final var method = getMethodFromStackElement(clazz, element);
|
||||
|
||||
// Check if the method is annotated with the desired annotation
|
||||
if (method != null) {
|
||||
if (method.isAnnotationPresent(annotationClassToFind)) {
|
||||
return Optional.of(method.getAnnotation(annotationClassToFind));
|
||||
} else if (method.isAnnotationPresent(annotationClassToStopLookup)) {
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
return empty();
|
||||
}
|
||||
|
||||
private static Method getMethodFromStackElement(Class<?> clazz, StackTraceElement element) {
|
||||
for (var method : clazz.getDeclaredMethods()) {
|
||||
if (method.getName().equals(element.getMethodName())) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -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:
|
||||
@ -76,15 +74,13 @@ components:
|
||||
type: object
|
||||
properties:
|
||||
debitorRel:
|
||||
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
|
||||
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationSubInsert'
|
||||
debitorRelUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
debitorNumberSuffix:
|
||||
type: integer
|
||||
format: int8
|
||||
minimum: 00
|
||||
maximum: 99
|
||||
type: string
|
||||
pattern: '^[0-9][0-9]$'
|
||||
billable:
|
||||
type: boolean
|
||||
vatId:
|
||||
|
@ -41,6 +41,7 @@ components:
|
||||
format: uuid
|
||||
nullable: true
|
||||
|
||||
# arbitrary relation with explicit type
|
||||
HsOfficeRelationInsert:
|
||||
type: object
|
||||
properties:
|
||||
@ -64,3 +65,24 @@ components:
|
||||
- holderUuid
|
||||
- type
|
||||
- contactUuid
|
||||
|
||||
# relation created as a sub-element with implicitly known type
|
||||
HsOfficeRelationSubInsert:
|
||||
type: object
|
||||
properties:
|
||||
anchorUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
holderUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
mark:
|
||||
type: string
|
||||
nullable: true
|
||||
contactUuid:
|
||||
type: string
|
||||
format: uuid
|
||||
required:
|
||||
- anchorUuid
|
||||
- holderUuid
|
||||
- contactUuid
|
||||
|
@ -36,7 +36,7 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
|
||||
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
|
||||
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s of package', NEW.customerUuid);
|
||||
|
||||
|
||||
perform rbac.defineRoleWithGrants(
|
||||
@ -102,10 +102,10 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM rbactest.customer WHERE uuid = OLD.customerUuid INTO oldCustomer;
|
||||
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid);
|
||||
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s of package', OLD.customerUuid);
|
||||
|
||||
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
|
||||
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
|
||||
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s of package', NEW.customerUuid);
|
||||
|
||||
|
||||
if NEW.customerUuid <> OLD.customerUuid then
|
||||
|
@ -36,7 +36,7 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage;
|
||||
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
|
||||
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s of domain', NEW.packageUuid);
|
||||
|
||||
|
||||
perform rbac.defineRoleWithGrants(
|
||||
@ -98,10 +98,10 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM rbactest.package WHERE uuid = OLD.packageUuid INTO oldPackage;
|
||||
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid);
|
||||
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s of domain', OLD.packageUuid);
|
||||
|
||||
SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage;
|
||||
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
|
||||
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s of domain', NEW.packageUuid);
|
||||
|
||||
|
||||
if NEW.packageUuid <> OLD.packageUuid then
|
||||
|
@ -38,13 +38,13 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.person WHERE uuid = NEW.holderUuid INTO newHolderPerson;
|
||||
assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid);
|
||||
assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s of relation', NEW.holderUuid);
|
||||
|
||||
SELECT * FROM hs_office.person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson;
|
||||
assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid);
|
||||
assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s of relation', NEW.anchorUuid);
|
||||
|
||||
SELECT * FROM hs_office.contact WHERE uuid = NEW.contactUuid INTO newContact;
|
||||
assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid);
|
||||
assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s of relation', NEW.contactUuid);
|
||||
|
||||
|
||||
perform rbac.defineRoleWithGrants(
|
||||
|
@ -37,10 +37,10 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s of partner', NEW.partnerRelUuid);
|
||||
|
||||
SELECT * FROM hs_office.partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
|
||||
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
|
||||
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s of partner', NEW.detailsUuid);
|
||||
|
||||
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'DELETE'), hs_office.relation_OWNER(newPartnerRel));
|
||||
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.relation_TENANT(newPartnerRel));
|
||||
@ -96,16 +96,16 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.relation WHERE uuid = OLD.partnerRelUuid INTO oldPartnerRel;
|
||||
assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid);
|
||||
assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s of partner', OLD.partnerRelUuid);
|
||||
|
||||
SELECT * FROM hs_office.relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s of partner', NEW.partnerRelUuid);
|
||||
|
||||
SELECT * FROM hs_office.partner_details WHERE uuid = OLD.detailsUuid INTO oldPartnerDetails;
|
||||
assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid);
|
||||
assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s of partner', OLD.detailsUuid);
|
||||
|
||||
SELECT * FROM hs_office.partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
|
||||
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
|
||||
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s of partner', NEW.detailsUuid);
|
||||
|
||||
|
||||
if NEW.partnerRelUuid <> OLD.partnerRelUuid then
|
||||
|
@ -44,10 +44,10 @@ begin
|
||||
WHERE partnerRel.type = 'PARTNER'
|
||||
AND NEW.debitorRelUuid = debitorRel.uuid
|
||||
INTO newPartnerRel;
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.debitorRelUuid = %s of debitor', NEW.debitorRelUuid);
|
||||
|
||||
SELECT * FROM hs_office.relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel;
|
||||
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
|
||||
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s of debitor', NEW.debitorRelUuid);
|
||||
|
||||
SELECT * FROM hs_office.bankaccount WHERE uuid = NEW.refundBankAccountUuid INTO newRefundBankAccount;
|
||||
|
||||
|
@ -37,14 +37,14 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount;
|
||||
assert newBankAccount.uuid is not null, format('newBankAccount must not be null for NEW.bankAccountUuid = %s', NEW.bankAccountUuid);
|
||||
assert newBankAccount.uuid is not null, format('newBankAccount must not be null for NEW.bankAccountUuid = %s of sepamandate', NEW.bankAccountUuid);
|
||||
|
||||
SELECT debitorRel.*
|
||||
FROM hs_office.relation debitorRel
|
||||
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
||||
WHERE debitor.uuid = NEW.debitorUuid
|
||||
INTO newDebitorRel;
|
||||
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
|
||||
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s of sepamandate', NEW.debitorUuid);
|
||||
|
||||
|
||||
perform rbac.defineRoleWithGrants(
|
||||
|
@ -40,7 +40,7 @@ begin
|
||||
JOIN hs_office.relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid
|
||||
WHERE partner.uuid = NEW.partnerUuid
|
||||
INTO newPartnerRel;
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerUuid = %s', NEW.partnerUuid);
|
||||
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerUuid = %s of membership', NEW.partnerUuid);
|
||||
|
||||
|
||||
perform rbac.defineRoleWithGrants(
|
||||
|
@ -36,7 +36,7 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.membership WHERE uuid = NEW.membershipUuid INTO newMembership;
|
||||
assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s', NEW.membershipUuid);
|
||||
assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s of coopshares', NEW.membershipUuid);
|
||||
|
||||
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.membership_AGENT(newMembership));
|
||||
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'UPDATE'), hs_office.membership_ADMIN(newMembership));
|
||||
|
@ -36,7 +36,7 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.membership WHERE uuid = NEW.membershipUuid INTO newMembership;
|
||||
assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s', NEW.membershipUuid);
|
||||
assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s of coopasset', NEW.membershipUuid);
|
||||
|
||||
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.membership_AGENT(newMembership));
|
||||
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'UPDATE'), hs_office.membership_ADMIN(newMembership));
|
||||
|
@ -37,14 +37,14 @@ begin
|
||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||
|
||||
SELECT * FROM hs_office.debitor WHERE uuid = NEW.debitorUuid INTO newDebitor;
|
||||
assert newDebitor.uuid is not null, format('newDebitor must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
|
||||
assert newDebitor.uuid is not null, format('newDebitor must not be null for NEW.debitorUuid = %s of project', NEW.debitorUuid);
|
||||
|
||||
SELECT debitorRel.*
|
||||
FROM hs_office.relation debitorRel
|
||||
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
||||
WHERE debitor.uuid = NEW.debitorUuid
|
||||
INTO newDebitorRel;
|
||||
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
|
||||
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s or project', NEW.debitorUuid);
|
||||
|
||||
|
||||
perform rbac.defineRoleWithGrants(
|
||||
|
@ -46,6 +46,7 @@ public class ArchitectureTest {
|
||||
"..lambda",
|
||||
"..generated..",
|
||||
"..persistence..",
|
||||
"..reflection",
|
||||
"..system..",
|
||||
"..validation..",
|
||||
"..hs.office.bankaccount",
|
||||
@ -54,6 +55,7 @@ public class ArchitectureTest {
|
||||
"..hs.office.coopshares",
|
||||
"..hs.office.debitor",
|
||||
"..hs.office.membership",
|
||||
"..hs.office.scenarios..",
|
||||
"..hs.migration",
|
||||
"..hs.office.partner",
|
||||
"..hs.office.person",
|
||||
@ -96,7 +98,7 @@ public class ArchitectureTest {
|
||||
public static final ArchRule testClassesAreProperlyNamed = classes()
|
||||
.that().haveSimpleNameEndingWith("Test")
|
||||
.and().doNotHaveModifier(ABSTRACT)
|
||||
.should().haveNameMatching(".*(UnitTest|RestTest|IntegrationTest|AcceptanceTest|ArchitectureTest)$");
|
||||
.should().haveNameMatching(".*(UnitTest|RestTest|IntegrationTest|AcceptanceTest|ScenarioTest|ArchitectureTest)$");
|
||||
|
||||
@ArchTest
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -12,7 +12,6 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
@ -76,7 +75,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
class ListDebitors {
|
||||
|
||||
@Test
|
||||
void globalAdmin_withoutAssumedRoles_canViewAllDebitors_ifNoCriteriaGiven() throws JSONException {
|
||||
void globalAdmin_withoutAssumedRoles_canViewAllDebitors_ifNoCriteriaGiven() {
|
||||
|
||||
RestAssured // @formatter:off
|
||||
.given()
|
||||
@ -112,7 +111,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
}
|
||||
},
|
||||
"debitorNumber": 1000111,
|
||||
"debitorNumberSuffix": 11,
|
||||
"debitorNumberSuffix": "11",
|
||||
"partner": {
|
||||
"partnerNumber": 10001,
|
||||
"partnerRel": {
|
||||
@ -167,7 +166,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
}
|
||||
},
|
||||
"debitorNumber": 1000212,
|
||||
"debitorNumberSuffix": 12,
|
||||
"debitorNumberSuffix": "12",
|
||||
"partner": {
|
||||
"partnerNumber": 10002,
|
||||
"partnerRel": {
|
||||
@ -201,7 +200,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
}
|
||||
},
|
||||
"debitorNumber": 1000313,
|
||||
"debitorNumberSuffix": 13,
|
||||
"debitorNumberSuffix": "13",
|
||||
"partner": {
|
||||
"partnerNumber": 10003,
|
||||
"partnerRel": {
|
||||
@ -334,7 +333,6 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.body("""
|
||||
{
|
||||
"debitorRel": {
|
||||
"type": "DEBITOR",
|
||||
"anchorUuid": "%s",
|
||||
"holderUuid": "%s",
|
||||
"contactUuid": "%s"
|
||||
@ -386,7 +384,6 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.body("""
|
||||
{
|
||||
"debitorRel": {
|
||||
"type": "DEBITOR",
|
||||
"anchorUuid": "%s",
|
||||
"holderUuid": "%s",
|
||||
"contactUuid": "%s"
|
||||
@ -469,7 +466,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
}
|
||||
},
|
||||
"debitorNumber": 1000111,
|
||||
"debitorNumberSuffix": 11,
|
||||
"debitorNumberSuffix": "11",
|
||||
"partner": {
|
||||
"partnerNumber": 10001,
|
||||
"partnerRel": {
|
||||
@ -581,7 +578,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
"contact": { "caption": "fourth contact" }
|
||||
},
|
||||
"debitorNumber": 10004${debitorNumberSuffix},
|
||||
"debitorNumberSuffix": ${debitorNumberSuffix},
|
||||
"debitorNumberSuffix": "${debitorNumberSuffix}",
|
||||
"partner": {
|
||||
"partnerNumber": 10004,
|
||||
"partnerRel": {
|
||||
|
@ -0,0 +1,240 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateExternalDebitorForPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSelfDebitorForPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.partner.DeletePartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddRepresentativeToPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.RemoveOperationsContactFromPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.SubscribeToMailinglist;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.subscription.UnsubscribeFromMailinglist;
|
||||
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;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
|
||||
@Tag("scenarioTest")
|
||||
@SpringBootTest(
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = { HsadminNgApplication.class, JpaAttempt.class },
|
||||
properties = {
|
||||
"spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///scenariosTC}",
|
||||
"spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}",
|
||||
"spring.datasource.password=${HSADMINNG_POSTGRES_ADMIN_PASSWORD:password}",
|
||||
"hsadminng.superuser=${HSADMINNG_SUPERUSER:superuser-alex@hostsharing.net}"
|
||||
}
|
||||
)
|
||||
@DirtiesContext
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1010)
|
||||
@Produces(explicitly = "Partner: Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Board of Directors"})
|
||||
void shouldCreatePartner() {
|
||||
new CreatePartner(this)
|
||||
.given("partnerNumber", 31010)
|
||||
.given("personType", "LEGAL_PERSON")
|
||||
.given("tradeName", "Test AG")
|
||||
.given("contactCaption", "Test AG - Board of Directors")
|
||||
.given("emailAddress", "board-of-directors@test-ag.example.org")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1020)
|
||||
@Requires("Person: Test AG")
|
||||
@Produces("Representative: Tracy Trust for Test AG")
|
||||
void shouldAddRepresentativeToPartner() {
|
||||
new AddRepresentativeToPartner(this)
|
||||
.given("partnerPersonTradeName", "Test AG")
|
||||
.given("representativeFamilyName", "Trust")
|
||||
.given("representativeGivenName", "Tracy")
|
||||
.given("representativePostalAddress", """
|
||||
An der Alster 100
|
||||
20000 Hamburg
|
||||
""")
|
||||
.given("representativePhoneNumber", "+49 40 123456")
|
||||
.given("representativeEMailAddress", "tracy.trust@example.org")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1030)
|
||||
@Requires("Person: Test AG")
|
||||
@Produces("Operations-Contact: Dennis Krause for Test AG")
|
||||
void shouldAddOperationsContactToPartner() {
|
||||
new AddOperationsContactToPartner(this)
|
||||
.given("partnerPersonTradeName", "Test AG")
|
||||
.given("operationsContactFamilyName", "Krause")
|
||||
.given("operationsContactGivenName", "Dennis")
|
||||
.given("operationsContactPhoneNumber", "+49 9932 587741")
|
||||
.given("operationsContactEMailAddress", "dennis.krause@example.org")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1039)
|
||||
@Requires("Operations-Contact: Dennis Krause for Test AG")
|
||||
void shouldRemoveOperationsContactFromPartner() {
|
||||
new RemoveOperationsContactFromPartner(this)
|
||||
.given("operationsContactPerson", "Dennis Krause")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1090)
|
||||
void shouldDeletePartner() {
|
||||
new DeletePartner(this)
|
||||
.given("partnerNumber", 31020)
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2010)
|
||||
@Requires("Partner: Test AG")
|
||||
@Produces("Debitor: Test AG - main debitor")
|
||||
void shouldCreateSelfDebitorForPartner() {
|
||||
new CreateSelfDebitorForPartner(this, "Debitor: Test AG - main debitor")
|
||||
.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
|
||||
.given("billable", true)
|
||||
.given("vatId", "VAT123456")
|
||||
.given("vatCountryCode", "DE")
|
||||
.given("vatBusiness", true)
|
||||
.given("vatReverseCharge", false)
|
||||
.given("defaultPrefix", "tst")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2011)
|
||||
@Requires("Person: Test AG")
|
||||
@Produces("Debitor: Billing GmbH")
|
||||
void shouldCreateExternalDebitorForPartner() {
|
||||
new CreateExternalDebitorForPartner(this)
|
||||
.given("partnerPersonTradeName", "Test AG")
|
||||
.given("billingContactCaption", "Billing GmbH - billing department")
|
||||
.given("billingContactEmailAddress", "billing@test-ag.example.org")
|
||||
.given("debitorNumberSuffix", "01")
|
||||
.given("billable", true)
|
||||
.given("vatId", "VAT123456")
|
||||
.given("vatCountryCode", "DE")
|
||||
.given("vatBusiness", true)
|
||||
.given("vatReverseCharge", false)
|
||||
.given("defaultPrefix", "tsx")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2020)
|
||||
@Requires("Person: Test AG")
|
||||
void shouldDeleteDebitor() {
|
||||
new DeleteDebitor(this)
|
||||
.given("partnerNumber", 31020)
|
||||
.given("debitorSuffix", "02")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2020)
|
||||
@Requires("Debitor: Test AG - main debitor")
|
||||
@Disabled("see TODO.spec in DontDeleteDefaultDebitor")
|
||||
void shouldNotDeleteDefaultDebitor() {
|
||||
new DontDeleteDefaultDebitor(this)
|
||||
.given("partnerNumber", 31020)
|
||||
.given("debitorSuffix", "00")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3100)
|
||||
@Requires("Debitor: Test AG - main debitor")
|
||||
@Produces("SEPA-Mandate: Test AG")
|
||||
void shouldCreateSepaMandateForDebitor() {
|
||||
new CreateSepaMandateForDebitor(this)
|
||||
.given("debitor", "Test AG")
|
||||
.given("memberNumberSuffix", "00")
|
||||
.given("validFrom", "2024-10-15")
|
||||
.given("membershipFeeBillable", "true")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3108)
|
||||
@Requires("SEPA-Mandate: Test AG")
|
||||
void shouldInvalidateSepaMandateForDebitor() {
|
||||
new InvalidateSepaMandateForDebitor(this)
|
||||
.given("sepaMandateUuid", "%{SEPA-Mandate: Test AG}")
|
||||
.given("validUntil", "2025-09-30")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3109)
|
||||
@Requires("SEPA-Mandate: Test AG")
|
||||
void shouldDeleteSepaMandateForDebitor() {
|
||||
new DeleteSepaMandateForDebitor(this)
|
||||
.given("sepaMandateUuid", "%{SEPA-Mandate: Test AG}")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4000)
|
||||
@Requires("Partner: Test AG")
|
||||
void shouldCreateMembershipForPartner() {
|
||||
new CreateMembership(this)
|
||||
.given("partnerName", "Test AG")
|
||||
.given("memberNumberSuffix", "00")
|
||||
.given("validFrom", "2024-10-15")
|
||||
.given("membershipFeeBillable", "true")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5000)
|
||||
@Requires("Person: Test AG")
|
||||
@Produces("Subscription: Michael Miller to operations-announce")
|
||||
void shouldSubscribeNewPersonAndContactToMailinglist() {
|
||||
new SubscribeToMailinglist(this)
|
||||
// TODO.spec: do we need the personType? or is an operational contact always a natural person? what about distribution lists?
|
||||
.given("partnerPersonTradeName", "Test AG")
|
||||
.given("subscriberFamilyName", "Miller")
|
||||
.given("subscriberGivenName", "Michael")
|
||||
.given("subscriberEMailAddress", "michael.miller@example.org")
|
||||
.given("mailingList", "operations-announce")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5001)
|
||||
@Requires("Subscription: Michael Miller to operations-announce")
|
||||
void shouldUnsubscribeNewPersonAndContactToMailinglist() {
|
||||
new UnsubscribeFromMailinglist(this)
|
||||
.given("mailingList", "operations-announce")
|
||||
.given("subscriberEMailAddress", "michael.miller@example.org")
|
||||
.doRun();
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Target(METHOD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface Produces {
|
||||
String value() default ""; // same as explicitly, makes it possible to omit the property name
|
||||
String explicitly() default ""; // same as value
|
||||
String[] implicitly() default {};
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
# UseCase-Tests
|
||||
|
||||
We define UseCase-tests as test for business-scenarios.
|
||||
They test positive (successful) scenarios by using the REST-API.
|
||||
|
||||
Running these tests also creates test-reports which can be used as documentation about the necessary REST-calls for each scenario.
|
||||
|
||||
Clarification: Acceptance tests also test at the REST-API level but are more technical and also test negative (error-) scenarios.
|
||||
|
||||
## ... extends ScenarioTest
|
||||
|
||||
Each test-method in subclasses of ScenarioTest describes a business-scenario,
|
||||
each utilizing a main-use-case and given example data for the scenario.
|
||||
|
||||
To reduce the number of API-calls, intermediate results can be re-used.
|
||||
This is controlled by two annotations:
|
||||
|
||||
### @Produces(....)
|
||||
|
||||
This annotation tells the test-runner that this scenario produces certain business object for re-use.
|
||||
The UUID of the new business objects are stored in a key-value map using the provided keys.
|
||||
|
||||
There are two variants of this annotation:
|
||||
|
||||
#### A Single Business Object
|
||||
```
|
||||
@Produces("key")
|
||||
```
|
||||
|
||||
This variant is used when there is just a single business-object produced by the use-case.
|
||||
|
||||
#### Multiple Business Objects
|
||||
|
||||
```
|
||||
@Produces(explicitly = "main-key", implicitly = {"other-key", ...})
|
||||
```
|
||||
|
||||
This variant is used when multiple business-objects are produced by the use-case,
|
||||
e.g. a Relation, a Person and a Contact.
|
||||
The UUID of the business-object produced by the main-use-case gets stored as the key after "explicitly",
|
||||
the others are listed after "implicitly";
|
||||
if there is just one, leave out the surrounding braces.
|
||||
|
||||
### @Requires(...)
|
||||
|
||||
This annotation tells the test-runner that which business objects are required before this scenario can run.
|
||||
|
||||
Each subset must be produced by the same producer-method.
|
||||
|
||||
|
||||
## ... extends UseCase
|
||||
|
||||
These classes consist of two parts:
|
||||
|
||||
### Prerequisites of the Use-Case
|
||||
|
||||
The constructor may create prerequisites via `required(...)`.
|
||||
These do not really belong to the use-case itself,
|
||||
e.g. create business objects which, in the context of that use-case, would already exist.
|
||||
|
||||
This is similar to @Requires(...) just that no other test scenario produces this prerequisite.
|
||||
Here, use-cases can be re-used, usually with different data.
|
||||
|
||||
### The Use-Case Itself
|
||||
|
||||
The use-case
|
@ -0,0 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Target(METHOD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface Requires {
|
||||
String value();
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
|
||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||
import org.apache.commons.collections4.SetUtils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public abstract class ScenarioTest extends ContextBasedTest {
|
||||
|
||||
final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented
|
||||
|
||||
record Alias<T extends UseCase<T>>(Class<T> useCase, UUID uuid) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uuid.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final static Map<String, Alias<?>> aliases = new HashMap<>();
|
||||
private final static Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
public final TestReport testReport = new TestReport(aliases);
|
||||
|
||||
@LocalServerPort
|
||||
Integer port;
|
||||
|
||||
@Autowired
|
||||
HsOfficePersonRepository personRepo;
|
||||
|
||||
@Autowired
|
||||
JpaAttempt jpaAttempt;
|
||||
|
||||
@SneakyThrows
|
||||
@BeforeEach
|
||||
void init(final TestInfo testInfo) {
|
||||
createHostsharingPerson();
|
||||
try {
|
||||
testInfo.getTestMethod().ifPresent(this::callRequiredProducers);
|
||||
testReport.createTestLogMarkdownFile(testInfo);
|
||||
} catch (Exception exc) {
|
||||
throw exc;
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() { // final TestInfo testInfo
|
||||
properties.clear();
|
||||
// FIXME: Delete all aliases as well to force HTTP GET queries in each scenario?
|
||||
testReport.close();
|
||||
}
|
||||
|
||||
private void createHostsharingPerson() {
|
||||
jpaAttempt.transacted(() ->
|
||||
{
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
aliases.put(
|
||||
"Person: Hostsharing eG",
|
||||
new Alias<>(
|
||||
null,
|
||||
personRepo.findPersonByOptionalNameLike("Hostsharing eG")
|
||||
.stream()
|
||||
.map(HsOfficePersonEntity::getUuid)
|
||||
.reduce(Reducer::toSingleElement).orElseThrow())
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void callRequiredProducers(final Method currentTestMethod) {
|
||||
final var testMethodRequired = Optional.of(currentTestMethod)
|
||||
.map(m -> m.getAnnotation(Requires.class))
|
||||
.map(Requires::value)
|
||||
.orElse(null);
|
||||
if (testMethodRequired != null) {
|
||||
for (Method potentialProducerMethod : getClass().getDeclaredMethods()) {
|
||||
final var producesAnnot = potentialProducerMethod.getAnnotation(Produces.class);
|
||||
if (producesAnnot != null) {
|
||||
final var testMethodProduces = allOf(
|
||||
producesAnnot.value(),
|
||||
producesAnnot.explicitly(),
|
||||
producesAnnot.implicitly());
|
||||
// @formatter:off
|
||||
if ( // that method can produce something required
|
||||
testMethodProduces.contains(testMethodRequired) &&
|
||||
|
||||
// and it does not produce anything we already have (would cause errors)
|
||||
SetUtils.intersection(testMethodProduces, knowVariables().keySet()).isEmpty()
|
||||
) {
|
||||
// then we recursively produce the pre-requisites of the producer method
|
||||
callRequiredProducers(potentialProducerMethod);
|
||||
|
||||
// and finally we call the producer method
|
||||
potentialProducerMethod.invoke(this);
|
||||
}
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> allOf(final String value, final String explicitly, final String[] implicitly) {
|
||||
final var all = new HashSet<String>();
|
||||
if (!value.isEmpty()) {
|
||||
all.add(value);
|
||||
}
|
||||
if (!explicitly.isEmpty()) {
|
||||
all.add(explicitly);
|
||||
}
|
||||
all.addAll(asList(implicitly));
|
||||
return all;
|
||||
}
|
||||
|
||||
static boolean containsAlias(final String alias) {
|
||||
return aliases.containsKey(alias);
|
||||
}
|
||||
|
||||
static UUID uuid(final String nameWithPlaceholders) {
|
||||
final var resoledName = resolve(nameWithPlaceholders);
|
||||
final UUID alias = ofNullable(knowVariables().get(resoledName)).filter(v -> v instanceof UUID).map(UUID.class::cast).orElse(null);
|
||||
assertThat(alias).as("alias '" + resoledName + "' not found in aliases nor in properties [" +
|
||||
knowVariables().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) ? resolveTyped(string) : value);
|
||||
}
|
||||
|
||||
static Map<String, Object> knowVariables() {
|
||||
final var map = new LinkedHashMap<String, Object>();
|
||||
ScenarioTest.aliases.forEach((key, value) -> map.put(key, value.uuid()));
|
||||
map.putAll(ScenarioTest.properties);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static String resolve(final String text) {
|
||||
final var resolved = new TemplateResolver(text, ScenarioTest.knowVariables()).resolve();
|
||||
return resolved;
|
||||
}
|
||||
|
||||
public static Object resolveTyped(final String text) {
|
||||
final var resolved = resolve(text);
|
||||
try {
|
||||
return UUID.fromString(resolved);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
// ignore and just use the String value
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
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();
|
||||
++position;
|
||||
return currentChar;
|
||||
}
|
||||
|
||||
private char currentChar() {
|
||||
if (position >= template.length()) {
|
||||
throw new IllegalStateException("no more characters, maybe closing bracelet missing in template: '''\n" + template + "\n'''");
|
||||
}
|
||||
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();
|
||||
case String string -> "\"" + string.replace("\n", "\\n") + "\"";
|
||||
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());
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestReport {
|
||||
|
||||
private final Map<String, ?> aliases;
|
||||
private final StringBuilder markdownLog = new StringBuilder(); // records everything for debugging purposes
|
||||
|
||||
private PrintWriter markdownReport;
|
||||
private int silent; // do not print anything to test-report if >0
|
||||
|
||||
public TestReport(final Map<String, ?> aliases) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
|
||||
public void createTestLogMarkdownFile(final TestInfo testInfo) throws IOException {
|
||||
final var testMethodName = testInfo.getTestMethod().map(Method::getName).orElseThrow();
|
||||
final var testMethodOrder = testInfo.getTestMethod().map(m -> m.getAnnotation(Order.class).value()).orElseThrow();
|
||||
assertThat(new File("doc/scenarios/").isDirectory() || new File("doc/scenarios/").mkdirs()).as("mkdir doc/scenarios/").isTrue();
|
||||
markdownReport = new PrintWriter(new FileWriter("doc/scenarios/" + testMethodOrder + "-" + testMethodName + ".md"));
|
||||
print("## Scenario #" + testInfo.getTestMethod().map(TestReport::orderNumber).orElseThrow() + ": " +
|
||||
testMethodName.replaceAll("([a-z])([A-Z]+)", "$1 $2"));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void print(final String output) {
|
||||
|
||||
final var outputWithCommentsForUuids = appendUUIDKey(output);
|
||||
|
||||
// for tests executed due to @Requires/@Produces there is no markdownFile yet
|
||||
if (markdownReport != null && silent == 0) {
|
||||
markdownReport.print(outputWithCommentsForUuids);
|
||||
}
|
||||
|
||||
// but the debugLog should contain all output, even if silent
|
||||
markdownLog.append(outputWithCommentsForUuids);
|
||||
}
|
||||
|
||||
public void printLine(final String output) {
|
||||
print(output + "\n");
|
||||
}
|
||||
|
||||
public void printPara(final String output) {
|
||||
printLine("\n" +output + "\n");
|
||||
}
|
||||
|
||||
public void close() {
|
||||
markdownReport.close();
|
||||
}
|
||||
|
||||
private static Object orderNumber(final Method method) {
|
||||
return method.getAnnotation(Order.class).value();
|
||||
}
|
||||
|
||||
private String appendUUIDKey(String multilineText) {
|
||||
final var lines = multilineText.split("\\r?\\n");
|
||||
final var result = new StringBuilder();
|
||||
|
||||
for (String line : lines) {
|
||||
for (Map.Entry<String, ?> entry : aliases.entrySet()) {
|
||||
final var uuidString = entry.getValue().toString();
|
||||
if (line.contains(uuidString)) {
|
||||
line = line + " // " + entry.getKey();
|
||||
break; // only add comment for one UUID per row (in our case, there is only one per row)
|
||||
}
|
||||
}
|
||||
result.append(line).append("\n");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
void silent(final Runnable code) {
|
||||
silent++;
|
||||
code.run();
|
||||
silent--;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import io.restassured.http.ContentType;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.reflection.AnnotationFinder;
|
||||
import org.apache.commons.collections4.map.LinkedMap;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
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.BodyHandlers;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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;
|
||||
|
||||
public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
private static final HttpClient client = HttpClient.newHttpClient();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
protected final ScenarioTest testSuite;
|
||||
private final TestReport testReport;
|
||||
private final Map<String, Function<String, UseCase<?>>> requirements = new LinkedMap<>();
|
||||
private final String resultAlias;
|
||||
private final Map<String, Object> givenProperties = new LinkedHashMap<>();
|
||||
|
||||
private String nextTitle; // just temporary to override resultAlias for sub-use-cases
|
||||
|
||||
public UseCase(final ScenarioTest testSuite) {
|
||||
this(testSuite, getResultAliasFromProducesAnnotationInCallStack());
|
||||
}
|
||||
|
||||
public UseCase(final ScenarioTest testSuite, final String resultAlias) {
|
||||
this.testSuite = testSuite;
|
||||
this.testReport = testSuite.testReport;
|
||||
this.resultAlias = resultAlias;
|
||||
if (resultAlias != null) {
|
||||
testReport.printPara("### UseCase " + title(resultAlias));
|
||||
}
|
||||
}
|
||||
|
||||
public final void requires(final String alias, final Function<String, UseCase<?>> useCaseFactory) {
|
||||
if (!ScenarioTest.containsAlias(alias)) {
|
||||
requirements.put(alias, useCaseFactory);
|
||||
}
|
||||
}
|
||||
|
||||
public final HttpResponse doRun() {
|
||||
testReport.printPara("### Given Properties");
|
||||
testReport.printLine("""
|
||||
| name | value |
|
||||
|------|-------|""");
|
||||
givenProperties.forEach((key, value) ->
|
||||
testReport.printLine("| " + key + " | " + value.toString().replace("\n", "<br>") + " |"));
|
||||
testReport.printLine("");
|
||||
testReport.silent(() ->
|
||||
requirements.forEach((alias, factory) -> {
|
||||
if (!ScenarioTest.containsAlias(alias)) {
|
||||
factory.apply(alias).run().keep();
|
||||
}
|
||||
})
|
||||
);
|
||||
return run();
|
||||
}
|
||||
|
||||
protected abstract HttpResponse run();
|
||||
|
||||
public final UseCase<T> given(final String propName, final Object propValue) {
|
||||
givenProperties.put(propName, propValue);
|
||||
ScenarioTest.putProperty(propName, propValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
public final JsonTemplate usingJsonBody(final String jsonTemplate) {
|
||||
return new JsonTemplate(jsonTemplate);
|
||||
}
|
||||
|
||||
public final void obtain(
|
||||
final String alias,
|
||||
final Supplier<HttpResponse> http,
|
||||
final Function<HttpResponse, String> extractor,
|
||||
final String... extraInfo) {
|
||||
withTitle(ScenarioTest.resolve(alias), () -> {
|
||||
http.get().keep(extractor);
|
||||
Arrays.stream(extraInfo).forEach(testReport::printPara);
|
||||
});
|
||||
}
|
||||
|
||||
public final void obtain(final String alias, final Supplier<HttpResponse> http, final String... extraInfo) {
|
||||
withTitle(ScenarioTest.resolve(alias), () -> {
|
||||
http.get().keep();
|
||||
Arrays.stream(extraInfo).forEach(testReport::printPara);
|
||||
});
|
||||
}
|
||||
|
||||
private void withTitle(final String title, final Runnable code) {
|
||||
this.nextTitle = title;
|
||||
code.run();
|
||||
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.GET, uriPath, null, response);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
||||
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.POST(BodyPublishers.ofString(requestBody))
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Content-Type", "application/json")
|
||||
.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, requestBody, response);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPatch(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
||||
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody))
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
return new HttpResponse(HttpMethod.PATCH, uriPath, requestBody, response);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpDelete(final String uriPath) {
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.DELETE()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
return new HttpResponse(HttpMethod.DELETE, uriPath, null, response);
|
||||
}
|
||||
|
||||
public final UUID uuid(final String alias) {
|
||||
return ScenarioTest.uuid(alias);
|
||||
}
|
||||
|
||||
public String uriEncoded(final String text) {
|
||||
return encode(ScenarioTest.resolve(text));
|
||||
}
|
||||
|
||||
public static class JsonTemplate {
|
||||
|
||||
private final String template;
|
||||
|
||||
private JsonTemplate(final String jsonTemplate) {
|
||||
this.template = jsonTemplate;
|
||||
}
|
||||
|
||||
String resolvePlaceholders() {
|
||||
return ScenarioTest.resolve(template);
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpResponse {
|
||||
|
||||
@Getter
|
||||
private final java.net.http.HttpResponse<String> response;
|
||||
|
||||
@Getter
|
||||
private final HttpStatus status;
|
||||
|
||||
private UUID locationUuid;
|
||||
|
||||
@SneakyThrows
|
||||
public HttpResponse(
|
||||
final HttpMethod httpMethod,
|
||||
final String uri,
|
||||
final String requestBody,
|
||||
final java.net.http.HttpResponse<String> response
|
||||
) {
|
||||
this.response = response;
|
||||
this.status = HttpStatus.valueOf(response.statusCode());
|
||||
if (this.status == HttpStatus.CREATED) {
|
||||
final var location = response.headers().firstValue("Location").orElseThrow();
|
||||
assertThat(location).startsWith("http://localhost:");
|
||||
locationUuid = UUID.fromString(location.substring(location.lastIndexOf('/') + 1));
|
||||
}
|
||||
|
||||
reportRequestAndResponse(httpMethod, uri, requestBody);
|
||||
}
|
||||
|
||||
public HttpResponse expecting(final HttpStatus httpStatus) {
|
||||
assertThat(HttpStatus.valueOf(response.statusCode())).isEqualTo(httpStatus);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse expecting(final ContentType contentType) {
|
||||
assertThat(response.headers().firstValue("content-type"))
|
||||
.contains(contentType.toString());
|
||||
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();
|
||||
ScenarioTest.putAlias(
|
||||
alias,
|
||||
new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public HttpResponse expectArrayElements(final int expectedElementCount) {
|
||||
final var rootNode = objectMapper.readTree(response.body());
|
||||
assertThat(rootNode.isArray()).as("array expected, but got: " + response.body()).isTrue();
|
||||
|
||||
final var root = (List<?>) objectMapper.readValue(response.body(), new TypeReference<List<Object>>() {
|
||||
});
|
||||
assertThat(root.size()).as("unexpected number of array elements").isEqualTo(expectedElementCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public String getFromBody(final String path) {
|
||||
return JsonPath.parse(response.body()).read(path);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void reportRequestAndResponse(final HttpMethod httpMethod, final String uri, final String requestBody) {
|
||||
|
||||
// the title
|
||||
if (nextTitle != null) {
|
||||
testReport.printLine("\n### " + nextTitle + "\n");
|
||||
} else if (resultAlias != null) {
|
||||
testReport.printLine("\n### " + resultAlias + "\n");
|
||||
}
|
||||
|
||||
// the request
|
||||
testReport.printLine("```");
|
||||
testReport.printLine(httpMethod.name() + " " + uri);
|
||||
testReport.printLine((requestBody != null ? requestBody.trim() : ""));
|
||||
|
||||
// the response
|
||||
testReport.printLine("=> status: " + status + " " + (locationUuid != null ? locationUuid : ""));
|
||||
if (httpMethod == HttpMethod.GET || status.isError()) {
|
||||
final var jsonNode = objectMapper.readTree(response.body());
|
||||
final var prettyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
|
||||
testReport.printLine(prettyJson);
|
||||
}
|
||||
testReport.printLine("```");
|
||||
testReport.printLine("");
|
||||
}
|
||||
}
|
||||
|
||||
protected T self() {
|
||||
//noinspection unchecked
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
private static @Nullable String getResultAliasFromProducesAnnotationInCallStack() {
|
||||
return AnnotationFinder.findCallerAnnotation(Produces.class, Test.class)
|
||||
.map(produces -> oneOf(produces.value(), produces.explicitly()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private static String oneOf(final String one, final String another) {
|
||||
if (isNotBlank(one) && isBlank(another)) {
|
||||
return one;
|
||||
} else if (isBlank(one) && isNotBlank(another)) {
|
||||
return another;
|
||||
}
|
||||
throw new AssertionFailure("exactly one value required, but got '" + one + "' and '" + another + "'");
|
||||
}
|
||||
|
||||
private String title(String resultAlias) {
|
||||
return getClass().getSimpleName().replaceAll("([a-z])([A-Z]+)", "$1 $2") + " => " + resultAlias;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.person.CreatePerson;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebitorForPartner> {
|
||||
|
||||
public CreateExternalDebitorForPartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
requires("Person: Billing GmbH", alias -> new CreatePerson(testSuite, alias)
|
||||
.given("personType", "LEGAL_PERSON")
|
||||
.given("tradeName", "Billing GmbH")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Person: %{partnerPersonTradeName}", () ->
|
||||
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("BankAccount: Billing GmbH - refund bank account", () ->
|
||||
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
|
||||
{
|
||||
"holder": "Billing GmbH - refund bank account",
|
||||
"iban": "DE02120300000000202051",
|
||||
"bic": "BYLADEM1001"
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON)
|
||||
);
|
||||
|
||||
obtain("Contact: Billing GmbH - Test AG billing", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": "Billing GmbH, billing for Test AG",
|
||||
"emailAddresses": {
|
||||
"main": "test-ag@billing-GmbH.example.com"
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON)
|
||||
);
|
||||
|
||||
return httpPost("/api/hs/office/debitors", usingJsonBody("""
|
||||
{
|
||||
"debitorRel": {
|
||||
"anchorUuid": ${Person: %{partnerPersonTradeName}},
|
||||
"holderUuid": ${Person: Billing GmbH},
|
||||
"contactUuid": ${Contact: Billing GmbH - Test AG billing}
|
||||
},
|
||||
"debitorNumberSuffix": ${debitorNumberSuffix},
|
||||
"billable": ${billable},
|
||||
"vatId": ${vatId},
|
||||
"vatCountryCode": ${vatCountryCode},
|
||||
"vatBusiness": ${vatBusiness},
|
||||
"vatReverseCharge": ${vatReverseCharge},
|
||||
"refundBankAccountUuid": ${BankAccount: Billing GmbH - refund bank account},
|
||||
"defaultPrefix": ${defaultPrefix}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
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> {
|
||||
|
||||
public CreateSelfDebitorForPartner(final ScenarioTest testSuite, final String resultAlias) {
|
||||
super(testSuite, resultAlias);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
obtain("partnerPersonUuid", () ->
|
||||
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].holder.uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one.",
|
||||
"**HINT**: With production data, you might get multiple results and have to decide which is the right one."
|
||||
);
|
||||
|
||||
obtain("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)
|
||||
);
|
||||
|
||||
obtain("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": {
|
||||
"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,39 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
|
||||
public class CreateSepaMandateForDebitor extends UseCase<CreateSepaMandateForDebitor> {
|
||||
|
||||
public CreateSepaMandateForDebitor(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
obtain("BankAccount: Test AG - debit bank account", () ->
|
||||
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
|
||||
{
|
||||
"holder": "Test AG - debit bank account",
|
||||
"iban": "DE02701500000000594937",
|
||||
"bic": "SSKMDEMM"
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON)
|
||||
);
|
||||
|
||||
return httpPost("/api/hs/office/sepamandates", usingJsonBody("""
|
||||
{
|
||||
"debitorUuid": ${Debitor: Test AG - main debitor},
|
||||
"bankAccountUuid": ${BankAccount: Test AG - debit bank account},
|
||||
"reference": "Test AG - main debitor",
|
||||
"agreement": "2022-10-12",
|
||||
"validFrom": "2022-10-13"
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class DeleteDebitor extends UseCase<DeleteDebitor> {
|
||||
|
||||
public DeleteDebitor(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
requires("Debitor: Test AG - delete debitor", alias -> new CreateSelfDebitorForPartner(testSuite, alias)
|
||||
.given("partnerPersonTradeName", "Test AG")
|
||||
.given("billingContactCaption", "Test AG - billing department")
|
||||
.given("billingContactEmailAddress", "billing@test-ag.example.org")
|
||||
.given("debitorNumberSuffix", "%{debitorSuffix}")
|
||||
.given("billable", true)
|
||||
.given("vatId", "VAT123456")
|
||||
.given("vatCountryCode", "DE")
|
||||
.given("vatBusiness", true)
|
||||
.given("vatReverseCharge", false)
|
||||
.given("defaultPrefix", "tsy"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
httpDelete("/api/hs/office/debitors/" + uuid("Debitor: Test AG - delete debitor"))
|
||||
.expecting(HttpStatus.NO_CONTENT);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
|
||||
public class DeleteSepaMandateForDebitor extends UseCase<DeleteSepaMandateForDebitor> {
|
||||
|
||||
public DeleteSepaMandateForDebitor(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
httpDelete("/api/hs/office/sepamandates/" + uuid("SEPA-Mandate: Test AG"))
|
||||
.expecting(HttpStatus.NO_CONTENT);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class DontDeleteDefaultDebitor extends UseCase<DontDeleteDefaultDebitor> {
|
||||
|
||||
public DontDeleteDefaultDebitor(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
httpDelete("/api/hs/office/debitors/" + uuid("Debitor: Test AG - main debitor"))
|
||||
// TODO.spec: should be CONFLICT or CLIENT_ERROR for Debitor "00" - but how to delete Partners?
|
||||
.expecting(HttpStatus.NO_CONTENT);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaMandateForDebitor> {
|
||||
|
||||
public InvalidateSepaMandateForDebitor(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
return httpPatch("/api/hs/office/sepamandates/" + uuid("SEPA-Mandate: Test AG"), usingJsonBody("""
|
||||
{
|
||||
"validUntil": ${validUntil}
|
||||
}
|
||||
"""))
|
||||
.expecting(OK).expecting(JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.membership;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class CreateMembership extends UseCase<CreateMembership> {
|
||||
|
||||
public CreateMembership(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
obtain("Membership: %{partnerName} 00", () ->
|
||||
httpPost("/api/hs/office/memberships", usingJsonBody("""
|
||||
{
|
||||
"partnerUuid": ${Partner: Test AG},
|
||||
"memberNumberSuffix": ${memberNumberSuffix},
|
||||
"validFrom": ${validFrom},
|
||||
"membershipFeeBillable": ${membershipFeeBillable}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.partner;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class AddOperationsContactToPartner extends UseCase<AddOperationsContactToPartner> {
|
||||
|
||||
public AddOperationsContactToPartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Person: %{partnerPersonTradeName}", () ->
|
||||
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("Person: %{operationsContactGivenName} %{operationsContactFamilyName}", () ->
|
||||
httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||
{
|
||||
"personType": "NATURAL_PERSON",
|
||||
"familyName": ${operationsContactFamilyName},
|
||||
"givenName": ${operationsContactGivenName}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON),
|
||||
"Please check first if that person already exists, if so, use it's UUID below.",
|
||||
"**HINT**: operations contacts are always connected to a partner-person, thus a person which is a holder of a partner-relation."
|
||||
);
|
||||
|
||||
obtain("Contact: %{operationsContactGivenName} %{operationsContactFamilyName}", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": "%{operationsContactGivenName} %{operationsContactFamilyName}",
|
||||
"phoneNumbers": {
|
||||
"main": ${operationsContactPhoneNumber}
|
||||
},
|
||||
"emailAddresses": {
|
||||
"main": ${operationsContactEMailAddress}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON),
|
||||
"Please check first if that contact already exists, if so, use it's UUID below."
|
||||
);
|
||||
|
||||
return httpPost("/api/hs/office/relations", usingJsonBody("""
|
||||
{
|
||||
"type": "OPERATIONS",
|
||||
"anchorUuid": ${Person: %{partnerPersonTradeName}},
|
||||
"holderUuid": ${Person: %{operationsContactGivenName} %{operationsContactFamilyName}},
|
||||
"contactUuid": ${Contact: %{operationsContactGivenName} %{operationsContactFamilyName}}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.partner;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartner> {
|
||||
|
||||
public AddRepresentativeToPartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Person: %{partnerPersonTradeName}", () ->
|
||||
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("Person: %{representativeGivenName} %{representativeFamilyName}", () ->
|
||||
httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||
{
|
||||
"personType": "NATURAL_PERSON",
|
||||
"familyName": ${representativeFamilyName},
|
||||
"givenName": ${representativeGivenName}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON),
|
||||
"Please check first if that person already exists, if so, use it's UUID below.",
|
||||
"**HINT**: A representative is always a natural person and represents a non-natural-person."
|
||||
);
|
||||
|
||||
obtain("Contact: %{representativeGivenName} %{representativeFamilyName}", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": "%{representativeGivenName} %{representativeFamilyName}",
|
||||
"postalAddress": ${representativePostalAddress},
|
||||
"phoneNumbers": {
|
||||
"main": ${representativePhoneNumber}
|
||||
},
|
||||
"emailAddresses": {
|
||||
"main": ${representativeEMailAddress}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON),
|
||||
"Please check first if that contact already exists, if so, use it's UUID below."
|
||||
);
|
||||
|
||||
return httpPost("/api/hs/office/relations", usingJsonBody("""
|
||||
{
|
||||
"type": "REPRESENTATIVE",
|
||||
"anchorUuid": ${Person: %{partnerPersonTradeName}},
|
||||
"holderUuid": ${Person: %{representativeGivenName} %{representativeFamilyName}},
|
||||
"contactUuid": ${Contact: %{representativeGivenName} %{representativeFamilyName}}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.partner;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class CreatePartner extends UseCase<CreatePartner> {
|
||||
|
||||
public CreatePartner(final ScenarioTest testSuite, final String resultAlias) {
|
||||
super(testSuite, resultAlias);
|
||||
}
|
||||
|
||||
public CreatePartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Person: Hostsharing eG", () ->
|
||||
httpGet("/api/hs/office/persons?name=Hostsharing+eG")
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"Even in production data we expect this query to return just a single result." // TODO.impl: add constraint?
|
||||
);
|
||||
|
||||
obtain("Person: %{tradeName}", () ->
|
||||
httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||
{
|
||||
"personType": ${personType},
|
||||
"tradeName": ${tradeName}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||
);
|
||||
|
||||
obtain("Contact: %{tradeName} - Board 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} - Board of Directors}
|
||||
},
|
||||
"details": {
|
||||
"registrationOffice": "Registergericht Hamburg",
|
||||
"registrationNumber": "1234567"
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.partner;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class DeletePartner extends UseCase<DeletePartner> {
|
||||
|
||||
public DeletePartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
|
||||
requires("Partner: Delete AG", alias -> new CreatePartner(testSuite, alias)
|
||||
.given("personType", "LEGAL_PERSON")
|
||||
.given("tradeName", "Delete AG")
|
||||
.given("contactCaption", "Delete AG - Board of Directors")
|
||||
.given("emailAddress", "board-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;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.person;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class CreatePerson extends UseCase<CreatePerson> {
|
||||
|
||||
public CreatePerson(final ScenarioTest testSuite, final String resultAlias) {
|
||||
super(testSuite, resultAlias);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
return httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||
{
|
||||
"personType": ${personType},
|
||||
"tradeName": ${tradeName}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.subscription;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperationsContactFromPartner> {
|
||||
|
||||
public RemoveOperationsContactFromPartner(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Operations-Contact: %{operationsContactPerson}", () ->
|
||||
httpGet("/api/hs/office/relations?relationType=OPERATIONS&name=" + uriEncoded("%{operationsContactPerson}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
return httpDelete("/api/hs/office/relations/" + uuid("Operations-Contact: %{operationsContactPerson}"))
|
||||
.expecting(NO_CONTENT);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.subscription;
|
||||
|
||||
import io.restassured.http.ContentType;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class SubscribeToMailinglist extends UseCase<SubscribeToMailinglist> {
|
||||
|
||||
public SubscribeToMailinglist(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Person: %{partnerPersonTradeName}", () ->
|
||||
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("Person: %{subscriberGivenName} %{subscriberFamilyName}", () ->
|
||||
httpPost("/api/hs/office/persons", usingJsonBody("""
|
||||
{
|
||||
"personType": "NATURAL_PERSON",
|
||||
"familyName": ${subscriberFamilyName},
|
||||
"givenName": ${subscriberGivenName}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
|
||||
);
|
||||
|
||||
obtain("Contact: %{subscriberGivenName} %{subscriberFamilyName}", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": "%{subscriberGivenName} %{subscriberFamilyName}",
|
||||
"emailAddresses": {
|
||||
"main": ${subscriberEMailAddress}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON)
|
||||
);
|
||||
|
||||
return httpPost("/api/hs/office/relations", usingJsonBody("""
|
||||
{
|
||||
"type": "SUBSCRIBER",
|
||||
"mark": ${mailingList},
|
||||
"anchorUuid": ${Person: %{partnerPersonTradeName}},
|
||||
"holderUuid": ${Person: %{subscriberGivenName} %{subscriberFamilyName}},
|
||||
"contactUuid": ${Contact: %{subscriberGivenName} %{subscriberFamilyName}}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.subscription;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class UnsubscribeFromMailinglist extends UseCase<UnsubscribeFromMailinglist> {
|
||||
|
||||
public UnsubscribeFromMailinglist(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("Subscription: %{subscriberEMailAddress}", () ->
|
||||
httpGet("/api/hs/office/relations?relationType=SUBSCRIBER" +
|
||||
"&mark=" + uriEncoded("%{mailingList}") +
|
||||
"&contactData=" + uriEncoded("%{subscriberEMailAddress}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
return httpDelete("/api/hs/office/relations/" + uuid("Subscription: %{subscriberEMailAddress}"))
|
||||
.expecting(NO_CONTENT);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user