1
0

Story #374 [ACCOUNTS] create initial hsadminng-profile for (new/existing) person (#208)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/208
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
Co-authored-by: Michael Hoennig <michael.hoennig@hostsharing.net>
Co-committed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
This commit is contained in:
Michael Hoennig
2025-11-10 11:03:26 +01:00
committed by Timotheus Pokorra
parent 6d92d80e44
commit d282885cc9
44 changed files with 598 additions and 342 deletions
+15 -1
View File
@@ -285,6 +285,7 @@ tasks.compileJava {
configure<SpotlessExtension> { configure<SpotlessExtension> {
java { java {
// Configure formatting steps // Configure formatting steps
removeUnusedImports() removeUnusedImports()
leadingTabsToSpaces(4) leadingTabsToSpaces(4)
endWithNewline() endWithNewline()
@@ -429,6 +430,20 @@ tasks.named<JacocoCoverageVerification>("jacocoTestCoverageVerification") {
} }
} }
// HOWTO: run all unit-tests - this is useful in an IDE: gw-test anyTest
tasks.register<Test>("anyTest") {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed", "standardOut", "standardError")
showStandardStreams = true
}
group = "verification"
description = "runs all unit-tests which do not need a database"
mustRunAfter(tasks.named("spotlessJava"))
}
// HOWTO: run all unit-tests which don't need a database: gw-test unitTest // HOWTO: run all unit-tests which don't need a database: gw-test unitTest
tasks.register<Test>("unitTest") { tasks.register<Test>("unitTest") {
@@ -591,7 +606,6 @@ tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
} }
} }
// Generate HTML from Markdown scenario-test-reports using Pandoc: // Generate HTML from Markdown scenario-test-reports using Pandoc:
tasks.register("convertMarkdownToHtml") { tasks.register("convertMarkdownToHtml") {
description = "Generates HTML from Markdown scenario-test-reports using Pandoc." description = "Generates HTML from Markdown scenario-test-reports using Pandoc."
@@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.accounts;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.AllArgsConstructor;
import lombok.val; import lombok.val;
import net.hostsharing.hsadminng.accounts.generated.api.v1.api.ProfileApi; import net.hostsharing.hsadminng.accounts.generated.api.v1.api.ProfileApi;
import net.hostsharing.hsadminng.accounts.generated.api.v1.model.CurrentLoginUserResource; import net.hostsharing.hsadminng.accounts.generated.api.v1.model.CurrentLoginUserResource;
@@ -13,7 +14,9 @@ import net.hostsharing.hsadminng.accounts.generated.api.v1.model.RbacSubjectReso
import net.hostsharing.hsadminng.accounts.generated.api.v1.model.ScopeResource; import net.hostsharing.hsadminng.accounts.generated.api.v1.model.ScopeResource;
import net.hostsharing.hsadminng.config.MessageTranslator; import net.hostsharing.hsadminng.config.MessageTranslator;
import net.hostsharing.hsadminng.errors.ForbiddenException; import net.hostsharing.hsadminng.errors.ForbiddenException;
import net.hostsharing.hsadminng.errors.Validate;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson; import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.mapper.StrictMapper; import net.hostsharing.hsadminng.mapper.StrictMapper;
@@ -23,15 +26,18 @@ import net.hostsharing.hsadminng.rbac.subject.RbacSubjectEntity;
import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository; import net.hostsharing.hsadminng.rbac.subject.RbacSubjectRepository;
import net.hostsharing.hsadminng.rbac.subject.RealSubjectEntity; import net.hostsharing.hsadminng.rbac.subject.RealSubjectEntity;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -107,17 +113,19 @@ public class HsProfileController implements ProfileApi {
final ProfileInsertResource body final ProfileInsertResource body
) { ) {
context.define(); // without assumed roles, otherwise we cannot access the subject anymore context.define(); // without assumed roles, otherwise we cannot access the subject anymore
final LoginContext originalLoginContext = new LoginContext(context);
// first create and save the subject to get its UUID // first create and save the subject to get its UUID
val newlySavedSubject = createSubject(body.getNickname()); val newlySavedSubject = createSubject(body.getNickname());
// switch to the new subject to get access to its own subject RBAC object
context.define("activate newly created self-service subject", null, body.getNickname(), null);
// afterward, create and save the profile entity with the subject's UUID // afterward, create and save the profile entity with the subject's UUID
val newProfileEntity = mapper.map( val newProfileEntity = mapper.map(
body, HsProfileEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); body, HsProfileEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
validateOnCreate(newProfileEntity); validateOnCreate(originalLoginContext, newProfileEntity);
// switch to the new subject to get access to its own subject RBAC object
context.define("activate newly created self-service subject", null, body.getNickname(), null);
newProfileEntity.setSubject(em.merge(newlySavedSubject)); // attached to EM by the new subject newProfileEntity.setSubject(em.merge(newlySavedSubject)); // attached to EM by the new subject
em.persist(newProfileEntity); // newProfileEntity.uuid == newlySavedSubject.uuid => do not use repository! em.persist(newProfileEntity); // newProfileEntity.uuid == newlySavedSubject.uuid => do not use repository!
@@ -154,10 +162,11 @@ public class HsProfileController implements ProfileApi {
final ProfilePatchResource body final ProfilePatchResource body
) { ) {
context.define(); // without assumed roles, otherwise we cannot access the subject anymore context.define(); // without assumed roles, otherwise we cannot access the subject anymore
final LoginContext originalLoginContext = new LoginContext(context);
val current = profileRepo.findByUuid(profileUuid).orElseThrow(); val current = profileRepo.findByUuid(profileUuid).orElseThrow();
validateBeforePatch(current, body); validateBeforePatch(originalLoginContext, current, body);
new HsProfileEntityPatcher(scopeMapper, current).apply(body); new HsProfileEntityPatcher(scopeMapper, current).apply(body);
validateOnUpdate(current); validateOnUpdate(current);
@@ -187,13 +196,15 @@ public class HsProfileController implements ProfileApi {
return ResponseEntity.ok(result); return ResponseEntity.ok(result);
} }
private void validateBeforePatch(final HsProfileEntity current, final ProfilePatchResource body) { private void validateBeforePatch(final LoginContext originalLoginContext, final HsProfileEntity current, final ProfilePatchResource body) {
validateReferencedPersonToBeRepresentedByLoginUserPerson(originalLoginContext, current);
if (!context.isGlobalAdmin() && !current.isActive() && body.getActive()) if (!context.isGlobalAdmin() && !current.isActive() && body.getActive())
throw new ForbiddenException("Only global admins are allowed to activate an inactive profile"); throw new ForbiddenException("Only global admins are allowed to activate an inactive profile");
} }
private void validateOnCreate(final HsProfileEntity newProfileEntity) { private void validateOnCreate(final LoginContext originalLoginContext, final HsProfileEntity newProfileEntity) {
validateReferencedPersonToBeRepresentedByLoginUserPerson(newProfileEntity); validateReferencedPersonToBeRepresentedByLoginUserPerson(originalLoginContext, newProfileEntity);
validateNormalUsersOnlyAccessPublicScopes(newProfileEntity); validateNormalUsersOnlyAccessPublicScopes(newProfileEntity);
validateNaturalPersonRequirementOfScopes(newProfileEntity); validateNaturalPersonRequirementOfScopes(newProfileEntity);
} }
@@ -208,20 +219,16 @@ public class HsProfileController implements ProfileApi {
validateOwnHsadminProfileMustNotBeRemoved(profileEntity); validateOwnHsadminProfileMustNotBeRemoved(profileEntity);
} }
private void validateReferencedPersonToBeRepresentedByLoginUserPerson(final HsProfileEntity newProfileEntity) { private void validateReferencedPersonToBeRepresentedByLoginUserPerson(final LoginContext originalLoginContext, final HsProfileEntity profileEntity) {
if (context.isGlobalAdmin()) { if (originalLoginContext.isGlobalAdmin) {
return; return;
} }
val referredPersonUuid = newProfileEntity.getPerson().getUuid(); val referredPersonUuid = profileEntity.getPerson().getUuid();
val currentSubjectUuid = context.fetchCurrentSubjectUuid(); val loginPersonUuid = originalLoginContext.profile.getPerson().getUuid();
val loginPersonUuid = profileRepo.findByUuid(currentSubjectUuid)
.map(HsProfileEntity::getPerson)
.map(HsOfficePerson::getUuid)
.orElseThrow();
val representedPersonUuids = realPersonRepo.findPersonsRepresentedByPersonWithUuid(loginPersonUuid) val representedPersonUuids = realPersonRepo.findPersonsRepresentedByPersonWithUuid(loginPersonUuid)
.stream().map(HsOfficePerson::getUuid).toList(); .stream().map(HsOfficePerson::getUuid).toList();
if ( !representedPersonUuids.contains(referredPersonUuid)) { if ( !representedPersonUuids.contains(referredPersonUuid)) {
throw new ValidationException( throw new ForbiddenException(
messageTranslator.translate( messageTranslator.translate(
"profile.access-denied-to-person-with-uuid-{0}-not-represented-by-currently-logged-in-person", "profile.access-denied-to-person-with-uuid-{0}-not-represented-by-currently-logged-in-person",
loginPersonUuid)); loginPersonUuid));
@@ -233,7 +240,7 @@ public class HsProfileController implements ProfileApi {
.filter(c -> !c.isPublicAccess() && !context.isGlobalAdmin() ) .filter(c -> !c.isPublicAccess() && !context.isGlobalAdmin() )
.toList(); .toList();
if (!forbiddenScopes.isEmpty()) { if (!forbiddenScopes.isEmpty()) {
throw new ValidationException( throw new ForbiddenException(
messageTranslator.translate( messageTranslator.translate(
"profile.access-denied-for-scopes-{0}", "profile.access-denied-for-scopes-{0}",
toDisplay(forbiddenScopes) toDisplay(forbiddenScopes)
@@ -330,7 +337,19 @@ public class HsProfileController implements ProfileApi {
} }
final BiConsumer<ProfileInsertResource, HsProfileEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<ProfileInsertResource, HsProfileEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
val person = realPersonRepo.findByUuid(resource.getPersonUuid()).orElseThrow(
Validate.validate("person, person.uuid").exactlyOne(resource.getPerson(), resource.getPersonUuid());
if ( resource.getPersonUuid() != null) {
entity.setPerson(realPersonRepo.findByUuid(resource.getPersonUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find Person by 'person.uuid': " + resource.getPersonUuid())
));
} else {
entity.setPerson(realPersonRepo.save(
mapper.map(resource.getPerson(), HsOfficePersonRealEntity.class)
) );
}
val person = realPersonRepo.findByUuid(entity.getPerson().getUuid()).orElseThrow(
() -> new EntityNotFoundException( () -> new EntityNotFoundException(
messageTranslator.translate("general.{0}-{1}-not-found-or-not-accessible", "personUuid", resource.getPersonUuid()) messageTranslator.translate("general.{0}-{1}-not-found-or-not-accessible", "personUuid", resource.getPersonUuid())
) )
@@ -339,4 +358,18 @@ public class HsProfileController implements ProfileApi {
entity.setScopes(scopeMapper.mapProfileToScopeEntities(resource.getScopes())); entity.setScopes(scopeMapper.mapProfileToScopeEntities(resource.getScopes()));
entity.setPassword(resource.getPassword()); entity.setPassword(resource.getPassword());
}; };
@AllArgsConstructor
private class LoginContext {
final HsProfileEntity profile;
final boolean isGlobalAdmin;
public LoginContext(final Context context) {
val subjectUuid = context.fetchCurrentSubjectUuid();
profile = profileRepo.findByUuid(subjectUuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,
"subject " + context.fetchCurrentSubject() + " has no profile"));
isGlobalAdmin = context.isGlobalAdmin();
}
}
} }
@@ -45,6 +45,9 @@ public class HsProfileEntity implements BaseEntity<HsProfileEntity>, Stringifyab
@MapsId @MapsId
@OneToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @OneToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "uuid", nullable = false, updatable = false, referencedColumnName = "uuid") @JoinColumn(name = "uuid", nullable = false, updatable = false, referencedColumnName = "uuid")
// Must be the real subject, so that representative persons can access profiles+subjects of represented persons.
// Otherwise, we would also need to allow RBAC grants to subject roles.
// This also means that each access has to be checked explicitly (same subject or represented subject).
private RealSubjectEntity subject; private RealSubjectEntity subject;
@ManyToOne(optional = false, fetch = FetchType.EAGER) @ManyToOne(optional = false, fetch = FetchType.EAGER)
@@ -84,6 +84,8 @@ components:
person.uuid: person.uuid:
type: string type: string
format: uuid format: uuid
person:
$ref: '../hs-office/hs-office-person-schemas.yaml#/components/schemas/HsOfficePersonInsert'
nickname: nickname:
type: string type: string
pattern: '^[a-z][a-z0-9]{1,8}-[a-z0-9]{1,10}$' # TODO.spec: pattern for login nickname pattern: '^[a-z][a-z0-9]{1,8}-[a-z0-9]{1,10}$' # TODO.spec: pattern for login nickname
@@ -112,8 +114,11 @@ components:
items: items:
$ref: 'scope-schemas.yaml#/components/schemas/Scope' $ref: 'scope-schemas.yaml#/components/schemas/Scope'
required: required:
- person.uuid
- nickname - nickname
- active - active
# soon we might need to be able to use this:
# https://community.smartbear.com/discussions/swaggerostools/defining-conditional-attributes-in-openapi/222410
# For now we just describe the conditionally required properties:
description:
Either `person.uuid` or `person` need to be given.
additionalProperties: false additionalProperties: false
@@ -202,7 +202,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.when() .when()
.post("http://localhost/api/hs/accounts/profiles") .post("http://localhost/api/hs/accounts/profiles")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(400) .statusCode(403)
.contentType("application/json") .contentType("application/json")
.body("message", containsString("wird von der eingeloggten Person nicht repräsentiert")); .body("message", containsString("wird von der eingeloggten Person nicht repräsentiert"));
// @formatter:on // @formatter:on
@@ -246,7 +246,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.when() .when()
.post("http://localhost/api/hs/accounts/profiles") .post("http://localhost/api/hs/accounts/profiles")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(400) .statusCode(403)
.contentType("application/json") .contentType("application/json")
.body("message", containsString("Zugriff auf Geltungsbereich verweigert: 'MATRIX:internal', 'SSH:internal'")); .body("message", containsString("Zugriff auf Geltungsbereich verweigert: 'MATRIX:internal', 'SSH:internal'"));
// @formatter:on // @formatter:on
@@ -326,7 +326,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.when() .when()
.patch("http://localhost/api/hs/accounts/profiles/" + drewProfileUuid) .patch("http://localhost/api/hs/accounts/profiles/" + drewProfileUuid)
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(400) .statusCode(403)
.contentType("application/json") .contentType("application/json")
.body("message", containsString("Zugriff auf Geltungsbereich verweigert: 'MATRIX:internal', 'SSH:internal'")); .body("message", containsString("Zugriff auf Geltungsbereich verweigert: 'MATRIX:internal', 'SSH:internal'"));
// @formatter:on // @formatter:on
@@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.accounts.scenarios;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.hostsharing.hsadminng.accounts.generated.api.v1.model.ScopeResource; import net.hostsharing.hsadminng.accounts.generated.api.v1.model.ScopeResource;
import net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -9,19 +10,23 @@ import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays; import java.util.Arrays;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public abstract class BaseProfileUseCase<T extends UseCase<?>> extends UseCase<T> { public abstract class BaseProfileUseCase<T extends UseCase<?>> extends UseCase<T> {
public BaseProfileUseCase(final ScenarioTest testSuite) { protected final FakeLoginUser asLoginUser;
public BaseProfileUseCase(final ScenarioTest testSuite, final FakeLoginUser asLoginUser) {
super(testSuite); super(testSuite);
this.asLoginUser = asLoginUser;
} }
@SneakyThrows @SneakyThrows
protected ScopeResource[] fetchScopeResourcesByDescriptorPairs(final String descriptPairsVarName) { protected ScopeResource[] fetchScopeResourcesByDescriptorPairs(final String descriptPairsVarName) {
final var requestedScopes = ScenarioTest.getTypedVariable("scopes", Pair[].class); final var requestedScopes = ScenarioTest.getTypedVariable("scopes", Pair[].class);
final var existingScopesJson = withTitle("Fetch Available Account Scopes", () -> final var existingScopesJson = withTitle("Fetch Available Account Scopes", () ->
httpGet("/api/hs/accounts/scopes").expecting(OK).expecting(JSON) httpGet(asGlobalAgent(), "/api/hs/accounts/scopes").expecting(OK).expecting(JSON)
).getResponse().body(); ).getResponse().body();
final var existingScopes = objectMapper.readValue(existingScopesJson, ScopeResource[].class); final var existingScopes = objectMapper.readValue(existingScopesJson, ScopeResource[].class);
return Arrays.stream(requestedScopes) return Arrays.stream(requestedScopes)
@@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.accounts.scenarios; package net.hostsharing.hsadminng.hs.accounts.scenarios;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -8,10 +9,10 @@ import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class CreateProfile extends BaseProfileUseCase<CreateProfile> { public class CreateProfileForExistingPerson extends BaseProfileUseCase<CreateProfileForExistingPerson> {
public CreateProfile(final ScenarioTest testSuite) { public CreateProfileForExistingPerson(final ScenarioTest testSuite, final FakeLoginUser asLoginUser) {
super(testSuite); super(testSuite, asLoginUser);
introduction("A set of profile contains the login data for an RBAC subject."); introduction("A set of profile contains the login data for an RBAC subject.");
} }
@@ -20,7 +21,7 @@ public class CreateProfile extends BaseProfileUseCase<CreateProfile> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{personGivenName} %{personFamilyName}", () -> obtain("Person: %{personGivenName} %{personFamilyName}", () ->
httpGet("/api/hs/office/persons?name=%{personFamilyName}") httpGet(asLoginUser, "/api/hs/office/persons?name=%{personFamilyName}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In real situations we have more precise measures to find the related person." "In real situations we have more precise measures to find the related person."
@@ -31,7 +32,7 @@ public class CreateProfile extends BaseProfileUseCase<CreateProfile> {
); );
return obtain("newProfile", () -> return obtain("newProfile", () ->
httpPost("/api/hs/accounts/profiles", usingJsonBody(""" httpPost(asLoginUser, "/api/hs/accounts/profiles", usingJsonBody("""
{ {
"person.uuid": ${Person: %{personGivenName} %{personFamilyName}}, "person.uuid": ${Person: %{personGivenName} %{personFamilyName}},
"nickname": ${nickname}, "nickname": ${nickname},
@@ -51,10 +52,10 @@ public class CreateProfile extends BaseProfileUseCase<CreateProfile> {
} }
@Override @Override
protected void verify(final UseCase<CreateProfile>.HttpResponse response) { protected void verify(final UseCase<CreateProfileForExistingPerson>.HttpResponse response) {
verify( verify(
"Verify the new Profile", "Verify the new Profile",
() -> httpGet("/api/hs/accounts/profiles/%{newProfile}") () -> httpGet(asLoginUser, "/api/hs/accounts/profiles/%{newProfile}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
path("uuid").contains("%{newProfile}"), path("uuid").contains("%{newProfile}"),
path("nickname").contains("%{nickname}"), path("nickname").contains("%{nickname}"),
@@ -0,0 +1,72 @@
package net.hostsharing.hsadminng.hs.accounts.scenarios;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.OK;
public class CreateProfileForNewPerson extends BaseProfileUseCase<CreateProfileForNewPerson> {
public CreateProfileForNewPerson(final ScenarioTest testSuite, final FakeLoginUser asLoginUser) {
super(testSuite, asLoginUser);
introduction("A set of profile contains the login data for an RBAC subject.");
}
@Override
protected HttpResponse run() {
given("resolvedScopes",
fetchScopeResourcesByDescriptorPairs("scopes")
);
return obtain("newProfile", () ->
httpPost(asLoginUser, "/api/hs/accounts/profiles", usingJsonBody("""
{
"person": {
"personType": "NATURAL_PERSON",
"salutation": "Hallo",
"title": null,
"givenName": ${personGivenName},
"familyName": ${personFamilyName}
},
"nickname": ${nickname},
"emailAddress": ${emailAddress},
"smsNumber": ${smsNumber},
"password": ${password},
"totpSecrets": @{totpSecrets},
"phonePassword": ${phonePassword},
"globalUid": %{globalUid},
"globalGid": %{globalGid},
"active": %{active},
"scopes": @{resolvedScopes}
}
"""))
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
);
}
@Override
protected void verify(final UseCase<CreateProfileForNewPerson>.HttpResponse response) {
obtain("Person: %{personGivenName} %{personFamilyName}", () ->
httpGet(asLoginUser, "/api/hs/office/persons?name=%{personFamilyName}")
.expecting(OK).expecting(JSON),
personResponse -> personResponse.expectArrayElements(1).getFromBody("[0].uuid"),
"In real situations we have more precise measures to find the related person."
);
verify(
"Verify the new Profile",
() -> httpGet(asLoginUser, "/api/hs/accounts/profiles/%{newProfile}")
.expecting(OK).expecting(JSON),
path("uuid").contains("%{newProfile}"),
path("nickname").contains("%{nickname}"),
path("person.uuid").contains("%{Person: %{personGivenName} %{personFamilyName}}"),
path("totpSecrets").contains("@{totpSecrets}")
);
}
}
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.bearerTemplate; import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.bearerTemplate;
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolve; import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolve;
import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
@@ -24,7 +25,7 @@ public class CurrentLoginUser extends UseCase<CurrentLoginUser> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{personGivenName}", () -> obtain("Person: %{personGivenName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{personGivenName}")) httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{personGivenName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
@@ -13,6 +13,10 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.as;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
class ProfileScenarioTests extends ScenarioTest { class ProfileScenarioTests extends ScenarioTest {
@@ -35,7 +39,7 @@ class ProfileScenarioTests extends ScenarioTest {
.given("subjectName", "superuser-fran@hostsharing.net") .given("subjectName", "superuser-fran@hostsharing.net")
.given("assumedRoles", "rbactest.package#xxx00:ADMIN;rbactest.package#yyy00:ADMIN") .given("assumedRoles", "rbactest.package#xxx00:ADMIN;rbactest.package#yyy00:ADMIN")
.given("expectedToBeGlobalAdmin", true) .given("expectedToBeGlobalAdmin", true)
.doRun() .thenExpect(HttpStatus.OK)
.keep(); .keep();
} }
} }
@@ -53,7 +57,7 @@ class ProfileScenarioTests extends ScenarioTest {
.given("subjectName", "superuser-fran@hostsharing.net") .given("subjectName", "superuser-fran@hostsharing.net")
.given("personGivenName", "Fran") .given("personGivenName", "Fran")
.given("expectedToBeGlobalAdmin", true) .given("expectedToBeGlobalAdmin", true)
.doRun() .thenExpect(HttpStatus.OK)
.keep(); .keep();
} }
} }
@@ -65,9 +69,11 @@ class ProfileScenarioTests extends ScenarioTest {
@Test @Test
@Order(1010) @Order(1010)
@Produces(explicitly = "Profile: firby-susan", implicitly = { "Person: Susan Firby" }) @Produces(
explicitly = "Profile: susan-firby",
implicitly = {"Person: Susan Firby"})
void shouldCreateInitialProfileForExistingNaturalPerson() { void shouldCreateInitialProfileForExistingNaturalPerson() {
new CreateProfile(scenarioTest) new CreateProfileForExistingPerson(scenarioTest, asGlobalAgent())
// to find a specific existing person // to find a specific existing person
.given("personFamilyName", "Firby") .given("personFamilyName", "Firby")
.given("personGivenName", "Susan") .given("personGivenName", "Susan")
@@ -86,30 +92,95 @@ class ProfileScenarioTests extends ScenarioTest {
"scopes", Array.of( "scopes", Array.of(
Pair.of("HSADMIN", "prod") Pair.of("HSADMIN", "prod")
)) ))
.doRun() .thenExpect(HttpStatus.OK)
.keep(); .keep();
} }
@Test @Test
@Order(1020) @Order(1020)
@Requires("Profile: firby-susan") @Requires("Profile: susan-firby")
void shouldUpdateProfile() { void naturalPersonShouldBeAbleToUpdateTheirOwnProfile() {
new UpdateProfile(scenarioTest) new UpdateProfile(scenarioTest, as("firby-susan"))
// the profile to update // the profile to update
.given("profileUuid", "%{Profile: firby-susan}") .given("profileUuid", "%{Profile: susan-firby}")
// updated profile // updated profile
.given("active", false) .given("active", false)
.given("totpSecrets", Array.of("initialSecret", "additionalSecret")) .given("totpSecrets", Array.of("initialSecret", "additionalSecret"))
.given("emailAddress", "susan.firby@example.org") .given("emailAddress", "susan.firby@example.org")
.given("password", "my new raw password") .given("password", "my new raw password")
.given("phonePassword", "securePass987") .given("phonePassword", "securePass987")
.given("smsNumber", "+49987654321")
.given("scopes", Array.of(Pair.of("HSADMIN", "prod"), Pair.of("SSH", "external")))
.thenExpect(HttpStatus.OK);
}
@Test
@Order(1100)
@Produces(
explicitly = "Profile: peter-newman",
implicitly = {"Person: Peter Newman"})
void shouldCreateInitialProfileForNewNaturalPerson() {
new CreateProfileForNewPerson(scenarioTest, asGlobalAgent())
// to find a specific existing person
.given("personFamilyName", "Newman")
.given("personGivenName", "Peter")
// a login name, to be stored in the new RBAC subject
.given("nickname", "newman-peter")
// initial profile
.given("emailAddress", "peter.newman@example.com")
.given("smsNumber", "+49123456789")
.given("password", "my raw password")
.given("totpSecrets", Array.of("initialSecret"))
.given("phonePassword", "securePass123")
.given("globalUid", 21012)
.given("globalGid", 21012)
.given("active", true)
.given("scopes", Array.of(Pair.of("HSADMIN", "prod")))
.thenExpect(HttpStatus.OK)
.keep();
}
@Test
@Order(1110)
@Requires("Profile: peter-newman")
void newNaturalPersonShouldBeAbleToUpdateTheirOwnProfile() {
new UpdateProfile(scenarioTest, as("newman-peter"))
// the profile to update
.given("profileUuid", "%{Profile: peter-newman}")
// updated profile
.given("active", false)
.given("totpSecrets", Array.of("initialSecret", "additionalSecret"))
.given("emailAddress", "peter.newman@example.org")
.given("password", "my new raw password")
.given("phonePassword", "securePass987")
.given("smsNumber", "+49987654321") .given("smsNumber", "+49987654321")
.given( .given(
"scopes", Array.of( "scopes", Array.of(
Pair.of("HSADMIN", "prod"), Pair.of("HSADMIN", "prod"),
Pair.of("SSH", "internal") Pair.of("SSH", "external")
)) ))
.doRun(); .thenExpect(HttpStatus.OK);
}
@Test
@Order(1120)
@Requires({"Profile: peter-newman", "Profile: susan-firby"})
// Usually, scenario tests just test positive cases, but in the case of account profiles, security is extra important,
// thus I've added also some negative cases like this one for documentation reasons.
// More negative cases are tested in "so-called" Acceptance and in RestTests.
void anotherNaturalPersonShouldNotBeAbleToUpdateOthersProfile() {
new UpdateProfile(scenarioTest, as("firby-susan"))
// the profile to update
.given("profileUuid", "%{Profile: peter-newman}")
// updated profile
.given("active", false)
.given("totpSecrets", Array.of("initialSecret", "additionalSecret"))
.given("emailAddress", "peter.newman@example.org")
.given("password", "my new raw password")
.given("phonePassword", "securePass987")
.given("smsNumber", "+49987654321")
.given("scopes", Array.of(Pair.of("HSADMIN", "prod"), Pair.of("SSH", "external")))
.thenExpect(HttpStatus.FORBIDDEN);
} }
} }
} }
@@ -1,30 +1,34 @@
package net.hostsharing.hsadminng.hs.accounts.scenarios; package net.hostsharing.hsadminng.hs.accounts.scenarios;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import lombok.val;
import net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static org.assertj.core.api.Fail.fail;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class UpdateProfile extends BaseProfileUseCase<UpdateProfile> { public class UpdateProfile extends BaseProfileUseCase<UpdateProfile> {
public UpdateProfile(final ScenarioTest testSuite) { public UpdateProfile(final ScenarioTest testSuite, final FakeLoginUser asLoginUser) {
super(testSuite); super(testSuite, asLoginUser);
introduction("A set of profile contains the login data for an RBAC subject."); introduction("A set of profile contains the login data for an RBAC subject.");
} }
@Override @Override
protected HttpResponse run() { protected HttpResponse run(final HttpStatus expectedStatus) {
given("resolvedScopes", given("resolvedScopes",
fetchScopeResourcesByDescriptorPairs("scopes") fetchScopeResourcesByDescriptorPairs("scopes")
); );
withTitle("Patch the Changes to the existing Profile", () -> return withTitle("Patch the Changes to the existing Profile", () -> {
httpPatch("/api/hs/accounts/profiles/%{profileUuid}", usingJsonBody(""" val response = httpPatch(
asLoginUser, "/api/hs/accounts/profiles/%{profileUuid}", usingJsonBody("""
{ {
"active": %{active}, "active": %{active},
"totpSecrets": @{totpSecrets}, "totpSecrets": @{totpSecrets},
@@ -34,19 +38,24 @@ public class UpdateProfile extends BaseProfileUseCase<UpdateProfile> {
"scopes": @{resolvedScopes} "scopes": @{resolvedScopes}
} }
""")) """))
.reportWithResponse().expecting(HttpStatus.OK).expecting(ContentType.JSON) .reportWithResponse().expecting(expectedStatus);
.extractValue("nickname", "nickname")
.extractValue("totpSecrets", "totpSecrets")
);
return null; return switch (expectedStatus) {
case OK -> response.expecting(ContentType.JSON)
.extractValue("nickname", "nickname")
.extractValue("totpSecrets", "totpSecrets");
case FORBIDDEN -> response.expecting(ContentType.JSON);
default -> fail("unexpected response: " + response);
};
}
);
} }
@Override @Override
protected void verify(final UseCase<UpdateProfile>.HttpResponse response) { protected void verify(final UseCase<UpdateProfile>.HttpResponse response) {
verify( verify(
"Verify the Patched Profile", "Verify the Patched Profile",
() -> httpGet("/api/hs/accounts/profiles/%{profileUuid}") () -> httpGet(asLoginUser, "/api/hs/accounts/profiles/%{profileUuid}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
path("uuid").contains("%{newProfile}"), path("uuid").contains("%{newProfile}"),
path("nickname").contains("%{nickname}"), path("nickname").contains("%{nickname}"),
@@ -51,6 +51,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
class HsOfficeScenarioTests extends ScenarioTest { class HsOfficeScenarioTests extends ScenarioTest {
@@ -104,8 +105,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("officePhoneNumber", "+49 40 654321-0") .given("officePhoneNumber", "+49 40 654321-0")
.given("emailAddress", "hamburg@test-ag.example.org") .given("emailAddress", "hamburg@test-ag.example.org")
.given("registrationOffice", "Registergericht Hamburg") .given("registrationOffice", "Registergericht Hamburg")
.given("registrationNumber", "1234567") .given("registrationNumber", "1234567").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -132,8 +132,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("emailAddress", "michelle.matthieu@example.org") .given("emailAddress", "michelle.matthieu@example.org")
.given("birthday", "1951-03-25") .given("birthday", "1951-03-25")
.given("birthPlace", "Neustadt a.d.R.") .given("birthPlace", "Neustadt a.d.R.")
.given("birthName", "Eichbaum") .given("birthName", "Eichbaum").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -155,8 +154,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
"country": "Germany" "country": "Germany"
""") """)
.given("representativePhoneNumber", "+49 40 123456") .given("representativePhoneNumber", "+49 40 123456")
.given("representativeEMailAddress", "tracy.trust@example.org") .given("representativeEMailAddress", "tracy.trust@example.org").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -170,8 +168,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("operationsContactFamilyName", "Krause") .given("operationsContactFamilyName", "Krause")
.given("operationsContactGivenName", "Dennis") .given("operationsContactGivenName", "Dennis")
.given("operationsContactPhoneNumber", "+49 9932 587741") .given("operationsContactPhoneNumber", "+49 9932 587741")
.given("operationsContactEMailAddress", "dennis.krause@example.org") .given("operationsContactEMailAddress", "dennis.krause@example.org").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -180,16 +177,14 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Operations-Contact: Dennis Krause for Test AG") @Requires("Operations-Contact: Dennis Krause for Test AG")
void shouldRemoveOperationsContactFromPartner() { void shouldRemoveOperationsContactFromPartner() {
new RemoveOperationsContactFromPartner(scenarioTest) new RemoveOperationsContactFromPartner(scenarioTest)
.given("operationsContactPerson", "Dennis Krause") .given("operationsContactPerson", "Dennis Krause").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@Order(1090) @Order(1090)
void shouldDeletePartner() { void shouldDeletePartner() {
new DeletePartner(scenarioTest) new DeletePartner(scenarioTest)
.given("partnerNumber", "P-31020") .given("partnerNumber", "P-31020").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -204,8 +199,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldAmendContactData() { void shouldAmendContactData() {
new AmendContactData(scenarioTest) new AmendContactData(scenarioTest)
.given("partnerName", "Matthieu") .given("partnerName", "Matthieu")
.given("newEmailAddress", "michelle@matthieu.example.org") .given("newEmailAddress", "michelle@matthieu.example.org").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -215,8 +209,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new AddPhoneNumberToContactData(scenarioTest) new AddPhoneNumberToContactData(scenarioTest)
.given("partnerName", "Matthieu") .given("partnerName", "Matthieu")
.given("phoneNumberKeyToAdd", "mobile") .given("phoneNumberKeyToAdd", "mobile")
.given("phoneNumberToAdd", "+49 152 1234567") .given("phoneNumberToAdd", "+49 152 1234567").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -225,8 +218,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldRemovePhoneNumberFromContactData() { void shouldRemovePhoneNumberFromContactData() {
new RemovePhoneNumberFromContactData(scenarioTest) new RemovePhoneNumberFromContactData(scenarioTest)
.given("partnerName", "Matthieu") .given("partnerName", "Matthieu")
.given("phoneNumberKeyToRemove", "office") .given("phoneNumberKeyToRemove", "office").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -247,8 +239,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
"country": "China" "country": "China"
""") """)
.given("newOfficePhoneNumber", "++15 999 654321") .given("newOfficePhoneNumber", "++15 999 654321")
.given("newEmailAddress", "norden@test-ag.example.org") .given("newEmailAddress", "norden@test-ag.example.org").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -263,8 +254,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldUpdatePersonData() { void shouldUpdatePersonData() {
new ShouldUpdatePersonData(scenarioTest) new ShouldUpdatePersonData(scenarioTest)
.given("oldFamilyName", "Matthieu") .given("oldFamilyName", "Matthieu")
.given("newFamilyName", "Matthieu-Zhang") .given("newFamilyName", "Matthieu-Zhang").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -278,14 +268,14 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Partner: P-31011 - Michelle Matthieu") @Requires("Partner: P-31011 - Michelle Matthieu")
@Produces("Debitor: D-3101100 - Michelle Matthieu") @Produces("Debitor: D-3101100 - Michelle Matthieu")
void shouldCreateSelfDebitorForPartnerWithIdenticalContactData() { void shouldCreateSelfDebitorForPartnerWithIdenticalContactData() {
// TODO.impl: could be assigned automatically but is not yet
new CreateSelfDebitorForPartnerWithIdenticalContactData(scenarioTest) new CreateSelfDebitorForPartnerWithIdenticalContactData(scenarioTest)
.given("partnerNumber", "P-31011") .given("partnerNumber", "P-31011")
.given("debitorNumberSuffix", "00") // TODO.impl: could be assigned automatically but is not yet .given("debitorNumberSuffix", "00") // TODO.impl: could be assigned automatically but is not yet
.given("billable", true) .given("billable", true)
.given("vatBusiness", false) .given("vatBusiness", false)
.given("vatReverseCharge", false) .given("vatReverseCharge", false)
.given("defaultPrefix", "mim") .given("defaultPrefix", "mim").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -294,6 +284,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Partner: P-31010 - Test AG") @Requires("Partner: P-31010 - Test AG")
@Produces("Debitor: D-3101000 - Test AG - main debitor") @Produces("Debitor: D-3101000 - Test AG - main debitor")
void shouldCreateSelfDebitorForPartnerWithDistinctContactData() { void shouldCreateSelfDebitorForPartnerWithDistinctContactData() {
// TODO.impl: could be assigned automaticallybut is not yet
new CreateSelfDebitorForPartner(scenarioTest) new CreateSelfDebitorForPartner(scenarioTest)
.given("partnerPersonTradeName", "Test AG") .given("partnerPersonTradeName", "Test AG")
.given("billingContactCaption", "Test AG - billing department") .given("billingContactCaption", "Test AG - billing department")
@@ -304,8 +295,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("vatCountryCode", "DE") .given("vatCountryCode", "DE")
.given("vatBusiness", true) .given("vatBusiness", true)
.given("vatReverseCharge", false) .given("vatReverseCharge", false)
.given("defaultPrefix", "tst") .given("defaultPrefix", "tst").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -324,8 +314,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("vatCountryCode", "DE") .given("vatCountryCode", "DE")
.given("vatBusiness", true) .given("vatBusiness", true)
.given("vatReverseCharge", false) .given("vatReverseCharge", false)
.given("defaultPrefix", "tsx") .given("defaultPrefix", "tsx").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -344,8 +333,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("vatCountryCode", "DE") .given("vatCountryCode", "DE")
.given("vatBusiness", true) .given("vatBusiness", true)
.given("vatReverseCharge", false) .given("vatReverseCharge", false)
.given("defaultPrefix", "tsy") .given("defaultPrefix", "tsy").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -356,8 +344,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldDeleteDebitor() { void shouldDeleteDebitor() {
new DeleteDebitor(scenarioTest) new DeleteDebitor(scenarioTest)
.given("partnerNumber", "P-31020") .given("partnerNumber", "P-31020")
.given("debitorSuffix", "02") .given("debitorSuffix", "02").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -367,8 +354,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldNotDeleteDefaultDebitor() { void shouldNotDeleteDefaultDebitor() {
new DontDeleteDefaultDebitor(scenarioTest) new DontDeleteDefaultDebitor(scenarioTest)
.given("partnerNumber", "P-31010") .given("partnerNumber", "P-31010")
.given("debitorSuffix", "00") .given("debitorSuffix", "00").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -382,6 +368,9 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Debitor: D-3101000 - Test AG - main debitor") @Requires("Debitor: D-3101000 - Test AG - main debitor")
@Produces("SEPA-Mandate: Test AG") @Produces("SEPA-Mandate: Test AG")
void shouldCreateSepaMandateForDebitor() { void shouldCreateSepaMandateForDebitor() {
// existing debitor
// new sepa-mandate
// new bank-account
new CreateSepaMandateForDebitor(scenarioTest) new CreateSepaMandateForDebitor(scenarioTest)
// existing debitor // existing debitor
.given("debitorNumber", "D-3101000") .given("debitorNumber", "D-3101000")
@@ -394,8 +383,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
// new bank-account // new bank-account
.given("bankAccountHolder", "Test AG - debit bank account") .given("bankAccountHolder", "Test AG - debit bank account")
.given("bankAccountIBAN", "DE02701500000000594937") .given("bankAccountIBAN", "DE02701500000000594937")
.given("bankAccountBIC", "SSKMDEMM") .given("bankAccountBIC", "SSKMDEMM").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -405,8 +393,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldInvalidateSepaMandateForDebitor() { void shouldInvalidateSepaMandateForDebitor() {
new InvalidateSepaMandateForDebitor(scenarioTest) new InvalidateSepaMandateForDebitor(scenarioTest)
.given("bankAccountIBAN", "DE02701500000000594937") .given("bankAccountIBAN", "DE02701500000000594937")
.given("mandateValidTo", "2025-09-30") .given("mandateValidTo", "2025-09-30").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -414,8 +401,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("SEPA-Mandate: Test AG") @Requires("SEPA-Mandate: Test AG")
void shouldFinallyDeleteSepaMandateForDebitor() { void shouldFinallyDeleteSepaMandateForDebitor() {
new FinallyDeleteSepaMandateForDebitor(scenarioTest) new FinallyDeleteSepaMandateForDebitor(scenarioTest)
.given("bankAccountIBAN", "DE02701500000000594937") .given("bankAccountIBAN", "DE02701500000000594937").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -433,8 +419,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("partnerName", "Test AG") .given("partnerName", "Test AG")
.given("validFrom", "2020-10-15") .given("validFrom", "2020-10-15")
.given("newStatus", "ACTIVE") .given("newStatus", "ACTIVE")
.given("membershipFeeBillable", "true") .given("membershipFeeBillable", "true").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -446,8 +431,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new CancelMembership(scenarioTest) new CancelMembership(scenarioTest)
.given("memberNumber", "M-3101000") .given("memberNumber", "M-3101000")
.given("validTo", "2023-12-31") .given("validTo", "2023-12-31")
.given("newStatus", "CANCELLED") .given("newStatus", "CANCELLED").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -461,8 +445,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("memberNumberSuffix", "01") .given("memberNumberSuffix", "01")
.given("validFrom", "2025-02-24") .given("validFrom", "2025-02-24")
.given("newStatus", "ACTIVE") .given("newStatus", "ACTIVE")
.given("membershipFeeBillable", "true") .given("membershipFeeBillable", "true").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
} }
@@ -482,8 +465,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "sign 2024-01-15") .given("reference", "sign 2024-01-15")
.given("shareCount", 100) .given("shareCount", 100)
.given("comment", "Signing the Membership") .given("comment", "Signing the Membership")
.given("transactionDate", "2024-01-15") .given("transactionDate", "2024-01-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -493,8 +475,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new CreateCoopSharesRevertTransaction(scenarioTest) new CreateCoopSharesRevertTransaction(scenarioTest)
.given("memberNumber", "M-3101000") .given("memberNumber", "M-3101000")
.given("comment", "reverting some incorrect transaction") .given("comment", "reverting some incorrect transaction")
.given("dateOfIncorrectTransaction", "2024-02-15") .given("dateOfIncorrectTransaction", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -507,8 +488,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15") .given("reference", "cancel 2024-01-15")
.given("sharesToCancel", 8) .given("sharesToCancel", 8)
.given("comment", "Cancelling 8 Shares") .given("comment", "Cancelling 8 Shares")
.given("transactionDate", "2024-02-15") .given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -527,8 +507,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "sign 2024-01-15") .given("reference", "sign 2024-01-15")
.given("assetValue", 100 * 64) .given("assetValue", 100 * 64)
.given("comment", "disposal for initial shares") .given("comment", "disposal for initial shares")
.given("transactionDate", "2024-01-15") .given("transactionDate", "2024-01-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -538,8 +517,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new CreateCoopAssetsRevertSimpleTransaction(scenarioTest) new CreateCoopAssetsRevertSimpleTransaction(scenarioTest)
.given("memberNumber", "M-3101000") .given("memberNumber", "M-3101000")
.given("comment", "reverting some incorrect transaction") .given("comment", "reverting some incorrect transaction")
.given("dateOfIncorrectTransaction", "2024-02-15") .given("dateOfIncorrectTransaction", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -552,8 +530,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15") .given("reference", "cancel 2024-01-15")
.given("valueToDisburse", 8 * 64) .given("valueToDisburse", 8 * 64)
.given("comment", "disbursal according to shares cancellation") .given("comment", "disbursal according to shares cancellation")
.given("transactionDate", "2024-02-15") .given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -567,8 +544,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "transfer 2024-12-31") .given("reference", "transfer 2024-12-31")
.given("valueToTransfer", 2 * 64) .given("valueToTransfer", 2 * 64)
.given("comment", "transfer assets from M-3101000 to M-4303000") .given("comment", "transfer assets from M-3101000 to M-4303000")
.given("transactionDate", "2024-12-31") .given("transactionDate", "2024-12-31").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -580,8 +556,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("adoptingMemberNumber", "M-4303000") .given("adoptingMemberNumber", "M-4303000")
.given("transferredValue", 2 * 64) .given("transferredValue", 2 * 64)
.given("comment", "reverting some incorrect transfer transaction") .given("comment", "reverting some incorrect transfer transaction")
.given("dateOfIncorrectTransaction", "2024-02-15") .given("dateOfIncorrectTransaction", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -593,8 +568,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15") .given("reference", "cancel 2024-01-15")
.given("valueToClear", 2 * 64) .given("valueToClear", 2 * 64)
.given("comment", "clearing according to members debt") .given("comment", "clearing according to members debt")
.given("transactionDate", "2024-02-15") .given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -606,8 +580,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15") .given("reference", "cancel 2024-01-15")
.given("valueLost", 2 * 64) .given("valueLost", 2 * 64)
.given("comment", "assign balance sheet loss") .given("comment", "assign balance sheet loss")
.given("transactionDate", "2024-02-15") .given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
@Test @Test
@@ -619,8 +592,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15") .given("reference", "cancel 2024-01-15")
.given("valueForLimitation", 2 * 64) .given("valueForLimitation", 2 * 64)
.given("comment", "adjust coop ") .given("comment", "adjust coop ")
.given("transactionDate", "2024-02-15") .given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -634,14 +606,14 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Person: Test AG") @Requires("Person: Test AG")
@Produces("Subscription: Michael Miller to operations-announce") @Produces("Subscription: Michael Miller to operations-announce")
void shouldSubscribeNewPersonAndContactToMailinglist() { void shouldSubscribeNewPersonAndContactToMailinglist() {
// TODO.spec: do we need the personType? or is an operational contact always a natural person? what about distribution lists?
new SubscribeNewPersonAndContactToMailinglist(scenarioTest) new SubscribeNewPersonAndContactToMailinglist(scenarioTest)
// TODO.spec: do we need the personType? or is an operational contact always a natural person? what about distribution lists? // 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("partnerPersonTradeName", "Test AG")
.given("subscriberFamilyName", "Miller") .given("subscriberFamilyName", "Miller")
.given("subscriberGivenName", "Michael") .given("subscriberGivenName", "Michael")
.given("subscriberEMailAddress", "michael.miller@example.org") .given("subscriberEMailAddress", "michael.miller@example.org")
.given("mailingList", "operations-announce") .given("mailingList", "operations-announce").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -655,8 +627,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("subscriberFamilyName", "Miller") .given("subscriberFamilyName", "Miller")
.given("subscriberGivenName", "Michael") .given("subscriberGivenName", "Michael")
.given("subscriberEMailAddress", "michael.miller@example.org") .given("subscriberEMailAddress", "michael.miller@example.org")
.given("mailingList", "operations-discussion") .given("mailingList", "operations-discussion").thenExpect(HttpStatus.OK)
.doRun()
.keep(); .keep();
} }
@@ -666,8 +637,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldUnsubscribePersonAndContactFromMailinglist() { void shouldUnsubscribePersonAndContactFromMailinglist() {
new UnsubscribeFromMailinglist(scenarioTest) new UnsubscribeFromMailinglist(scenarioTest)
.given("mailingList", "operations-announce") .given("mailingList", "operations-announce")
.given("subscriberEMailAddress", "michael.miller@example.org") .given("subscriberEMailAddress", "michael.miller@example.org").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
@@ -694,8 +664,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
"country": "Germany" "country": "Germany"
""") """)
.given("communityOfHeirsOfficePhoneNumber", "+49 40 666666") .given("communityOfHeirsOfficePhoneNumber", "+49 40 666666")
.given("communityOfHeirsEmailAddress", "lena.stadland@example.org") .given("communityOfHeirsEmailAddress", "lena.stadland@example.org").thenExpect(HttpStatus.OK);
.doRun();
} }
} }
} }
@@ -6,6 +6,7 @@ import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContactData> { public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContactData> {
@@ -19,14 +20,14 @@ public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContact
obtain( obtain(
"partnerContactUuid", "partnerContactUuid",
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"), response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
withTitle("Patch the Additional Phone-Number into the Contact", () -> return withTitle("Patch the Additional Phone-Number into the Contact", () ->
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody(""" httpPatch(asGlobalAgent(), "/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
{ {
"phoneNumbers": { "phoneNumbers": {
${phoneNumberKeyToAdd}: ${phoneNumberToAdd} ${phoneNumberKeyToAdd}: ${phoneNumberToAdd}
@@ -35,15 +36,13 @@ public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContact
""")) """))
.expecting(HttpStatus.OK) .expecting(HttpStatus.OK)
); );
return null;
} }
@Override @Override
protected void verify(final UseCase<AddPhoneNumberToContactData>.HttpResponse response) { protected void verify(final UseCase<AddPhoneNumberToContactData>.HttpResponse response) {
verify( verify(
"Verify if the New Phone Number Got Added", "Verify if the New Phone Number Got Added",
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].contact.phoneNumbers.%{phoneNumberKeyToAdd}").contains("%{phoneNumberToAdd}") path("[0].contact.phoneNumbers.%{phoneNumberKeyToAdd}").contains("%{phoneNumberToAdd}")
); );
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class AmendContactData extends UseCase<AmendContactData> { public class AmendContactData extends UseCase<AmendContactData> {
@@ -17,14 +18,14 @@ public class AmendContactData extends UseCase<AmendContactData> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("partnerContactUuid", () -> obtain("partnerContactUuid", () ->
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"), response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
withTitle("Patch the New Phone Number Into the Contact", () -> return withTitle("Patch the New Phone Number Into the Contact", () ->
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody(""" httpPatch(asGlobalAgent(), "/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
{ {
"caption": ${newContactCaption???}, "caption": ${newContactCaption???},
"postalAddress": { "postalAddress": {
@@ -40,7 +41,5 @@ public class AmendContactData extends UseCase<AmendContactData> {
""")) """))
.expecting(HttpStatus.OK) .expecting(HttpStatus.OK)
); );
return null;
} }
} }
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberFromContactData> { public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberFromContactData> {
@@ -18,14 +19,14 @@ public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberF
obtain( obtain(
"partnerContactUuid", "partnerContactUuid",
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"), response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
withTitle("Patch the Additional Phone-Number into the Contact", () -> return withTitle("Patch the Additional Phone-Number into the Contact", () ->
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody(""" httpPatch(asGlobalAgent(), "/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
{ {
"phoneNumbers": { "phoneNumbers": {
${phoneNumberKeyToRemove}: NULL ${phoneNumberKeyToRemove}: NULL
@@ -34,15 +35,13 @@ public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberF
""")) """))
.expecting(HttpStatus.OK) .expecting(HttpStatus.OK)
); );
return null;
} }
@Override @Override
protected void verify(final UseCase<RemovePhoneNumberFromContactData>.HttpResponse response) { protected void verify(final UseCase<RemovePhoneNumberFromContactData>.HttpResponse response) {
verify( verify(
"Verify if the New Phone Number Got Added", "Verify if the New Phone Number Got Added",
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].contact.phoneNumbers.%{phoneNumberKeyToRemove}").doesNotExist() path("[0].contact.phoneNumbers.%{phoneNumberKeyToRemove}").doesNotExist()
); );
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -17,14 +18,14 @@ public class ReplaceContactData extends UseCase<ReplaceContactData> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("partnerRelationUuid", () -> obtain("partnerRelationUuid", () ->
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
obtain("Contact: %{newContactCaption}", () -> obtain("Contact: %{newContactCaption}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": ${newContactCaption}, "caption": ${newContactCaption},
"postalAddress": { "postalAddress": {
@@ -44,23 +45,21 @@ public class ReplaceContactData extends UseCase<ReplaceContactData> {
"(currently `firm`, `name`, `co`, `street`, `zipcode`, `city`, `country`) " + "(currently `firm`, `name`, `co`, `street`, `zipcode`, `city`, `country`) " +
"its values might not appear in external systems."); "its values might not appear in external systems.");
withTitle("Replace the Contact-Reference in the Partner-Relation", () -> return withTitle("Replace the Contact-Reference in the Partner-Relation", () ->
httpPatch("/api/hs/office/relations/%{partnerRelationUuid}", usingJsonBody(""" httpPatch(asGlobalAgent(), "/api/hs/office/relations/%{partnerRelationUuid}", usingJsonBody("""
{ {
"contact.uuid": ${Contact: %{newContactCaption}} "contact.uuid": ${Contact: %{newContactCaption}}
} }
""")) """))
.expecting(OK) .expecting(OK)
); );
return null;
} }
@Override @Override
protected void verify(final UseCase<ReplaceContactData>.HttpResponse response) { protected void verify(final UseCase<ReplaceContactData>.HttpResponse response) {
verify( verify(
"Verify if the Contact-Relation Got Replaced in the Partner-Relation", "Verify if the Contact-Relation Got Replaced in the Partner-Relation",
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].contact.caption").contains("%{newContactCaption}") path("[0].contact.caption").contains("%{newContactCaption}")
); );
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.office.scenarios.person.CreatePerson; import net.hostsharing.hsadminng.hs.office.scenarios.person.CreatePerson;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -23,14 +24,14 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () -> obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}")) httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "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", () -> obtain("BankAccount: Billing GmbH - refund bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/bankaccounts", usingJsonBody("""
{ {
"holder": "Billing GmbH - refund bank account", "holder": "Billing GmbH - refund bank account",
"iban": "DE02120300000000202051", "iban": "DE02120300000000202051",
@@ -41,7 +42,7 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
); );
obtain("Contact: Billing GmbH - Test AG billing", () -> obtain("Contact: Billing GmbH - Test AG billing", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": "Billing GmbH, billing for Test AG", "caption": "Billing GmbH, billing for Test AG",
"emailAddresses": { "emailAddresses": {
@@ -52,7 +53,7 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
.expecting(CREATED).expecting(JSON) .expecting(CREATED).expecting(JSON)
); );
return httpPost("/api/hs/office/debitors", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/debitors", usingJsonBody("""
{ {
"debitorRel": { "debitorRel": {
"anchor.uuid": ${Person: %{partnerPersonTradeName}}, "anchor.uuid": ${Person: %{partnerPersonTradeName}},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -16,14 +17,14 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
obtain("partnerPersonUuid", () -> obtain("partnerPersonUuid", () ->
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerPersonTradeName}")) httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].holder.uuid"), 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." "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: Test AG - refund bank account", () -> obtain("BankAccount: Test AG - refund bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/bankaccounts", usingJsonBody("""
{ {
"holder": "Test AG - refund bank account", "holder": "Test AG - refund bank account",
"iban": "DE88100900001234567892", "iban": "DE88100900001234567892",
@@ -34,7 +35,7 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
); );
obtain("Contact: Test AG - billing department", () -> obtain("Contact: Test AG - billing department", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": ${billingContactCaption}, "caption": ${billingContactCaption},
"emailAddresses": { "emailAddresses": {
@@ -45,7 +46,7 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
.expecting(CREATED).expecting(JSON) .expecting(CREATED).expecting(JSON)
); );
return httpPost("/api/hs/office/debitors", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/debitors", usingJsonBody("""
{ {
"debitorRel": { "debitorRel": {
"anchor.uuid": ${partnerPersonUuid}, "anchor.uuid": ${partnerPersonUuid},
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
public class CreateSelfDebitorForPartnerWithIdenticalContactData public class CreateSelfDebitorForPartnerWithIdenticalContactData
@@ -17,13 +18,13 @@ public class CreateSelfDebitorForPartnerWithIdenticalContactData
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
withTitle("Determine Partner-Person UUID", () -> withTitle("Determine Partner-Person UUID", () ->
httpGet("/api/hs/office/partners/" + uriEncoded("%{partnerNumber}")) httpGet(asGlobalAgent(), "/api/hs/office/partners/" + uriEncoded("%{partnerNumber}"))
.reportWithResponse().expecting(HttpStatus.OK).expecting(JSON) .reportWithResponse().expecting(HttpStatus.OK).expecting(JSON)
.extractUuidAlias("partnerRel.holder.uuid", "partnerPersonUuid") .extractUuidAlias("partnerRel.holder.uuid", "partnerPersonUuid")
.extractUuidAlias("partnerRel.contact.uuid", "partnerContactUuid") .extractUuidAlias("partnerRel.contact.uuid", "partnerContactUuid")
); );
return httpPost("/api/hs/office/debitors", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/debitors", usingJsonBody("""
{ {
"debitorRel": { "debitorRel": {
"anchor.uuid": ${partnerPersonUuid}, "anchor.uuid": ${partnerPersonUuid},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -17,13 +18,13 @@ public class CreateSepaMandateForDebitor extends UseCase<CreateSepaMandateForDeb
protected HttpResponse run() { protected HttpResponse run() {
obtain("Debitor: Test AG - main debitor", () -> obtain("Debitor: Test AG - main debitor", () ->
httpGet("/api/hs/office/debitors/%{debitorNumber}") httpGet(asGlobalAgent(), "/api/hs/office/debitors/%{debitorNumber}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid") response -> response.getFromBody("uuid")
); );
obtain("BankAccount: Test AG - debit bank account", () -> obtain("BankAccount: Test AG - debit bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/bankaccounts", usingJsonBody("""
{ {
"holder": ${bankAccountHolder}, "holder": ${bankAccountHolder},
"iban": ${bankAccountIBAN}, "iban": ${bankAccountIBAN},
@@ -33,7 +34,7 @@ public class CreateSepaMandateForDebitor extends UseCase<CreateSepaMandateForDeb
.expecting(CREATED).expecting(JSON) .expecting(CREATED).expecting(JSON)
); );
return httpPost("/api/hs/office/sepamandates", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/sepamandates", usingJsonBody("""
{ {
"debitor.uuid": ${Debitor: Test AG - main debitor}, "debitor.uuid": ${Debitor: Test AG - main debitor},
"bankAccount.uuid": ${BankAccount: Test AG - debit bank account}, "bankAccount.uuid": ${BankAccount: Test AG - debit bank account},
@@ -4,6 +4,8 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class DeleteDebitor extends UseCase<DeleteDebitor> { public class DeleteDebitor extends UseCase<DeleteDebitor> {
public DeleteDebitor(final ScenarioTest testSuite) { public DeleteDebitor(final ScenarioTest testSuite) {
@@ -24,10 +26,9 @@ public class DeleteDebitor extends UseCase<DeleteDebitor> {
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
withTitle("Delete the Debitor using its UUID", () -> return withTitle("Delete the Debitor using its UUID", () ->
httpDelete("/api/hs/office/debitors/&{Debitor: Test AG - delete debitor}") httpDelete(asGlobalAgent(), "/api/hs/office/debitors/&{Debitor: Test AG - delete debitor}")
.expecting(HttpStatus.NO_CONTENT) .expecting(HttpStatus.NO_CONTENT)
); );
return null;
} }
} }
@@ -4,6 +4,8 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class DontDeleteDefaultDebitor extends UseCase<DontDeleteDefaultDebitor> { public class DontDeleteDefaultDebitor extends UseCase<DontDeleteDefaultDebitor> {
public DontDeleteDefaultDebitor(final ScenarioTest testSuite) { public DontDeleteDefaultDebitor(final ScenarioTest testSuite) {
@@ -12,7 +14,7 @@ public class DontDeleteDefaultDebitor extends UseCase<DontDeleteDefaultDebitor>
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
httpDelete("/api/hs/office/debitors/&{Debitor: Test AG - main debitor}") httpDelete(asGlobalAgent(), "/api/hs/office/debitors/&{Debitor: Test AG - main debitor}")
// TODO.spec: should be CONFLICT or CLIENT_ERROR for Debitor "00" - but how to delete Partners? // TODO.spec: should be CONFLICT or CLIENT_ERROR for Debitor "00" - but how to delete Partners?
.expecting(HttpStatus.NO_CONTENT); .expecting(HttpStatus.NO_CONTENT);
return null; return null;
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class FinallyDeleteSepaMandateForDebitor extends UseCase<FinallyDeleteSepaMandateForDebitor> { public class FinallyDeleteSepaMandateForDebitor extends UseCase<FinallyDeleteSepaMandateForDebitor> {
@@ -17,14 +18,15 @@ public class FinallyDeleteSepaMandateForDebitor extends UseCase<FinallyDeleteSep
protected HttpResponse run() { protected HttpResponse run() {
obtain("SEPA-Mandate: %{bankAccountIBAN}", () -> obtain("SEPA-Mandate: %{bankAccountIBAN}", () ->
httpGet("/api/hs/office/sepamandates?iban=&{bankAccountIBAN}") httpGet(asGlobalAgent(), "/api/hs/office/sepamandates?iban=&{bankAccountIBAN}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"With production data, the bank-account could be used in multiple SEPA-mandates, make sure to use the right one!" "With production data, the bank-account could be used in multiple SEPA-mandates, make sure to use the right one!"
); );
// TODO.spec: When to allow actual deletion of SEPA-mandates? Add constraint accordingly. // TODO.spec: When to allow actual deletion of SEPA-mandates? Add constraint accordingly.
return withTitle("Delete the SEPA-Mandate by its UUID", () -> httpDelete("/api/hs/office/sepamandates/&{SEPA-Mandate: %{bankAccountIBAN}}") return withTitle("Delete the SEPA-Mandate by its UUID", () ->
httpDelete(asGlobalAgent(), "/api/hs/office/sepamandates/&{SEPA-Mandate: %{bankAccountIBAN}}")
.expecting(HttpStatus.NO_CONTENT) .expecting(HttpStatus.NO_CONTENT)
); );
} }
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaMandateForDebitor> { public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaMandateForDebitor> {
@@ -16,14 +17,14 @@ public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaManda
protected HttpResponse run() { protected HttpResponse run() {
obtain("SEPA-Mandate: %{bankAccountIBAN}", () -> obtain("SEPA-Mandate: %{bankAccountIBAN}", () ->
httpGet("/api/hs/office/sepamandates?iban=&{bankAccountIBAN}") httpGet(asGlobalAgent(), "/api/hs/office/sepamandates?iban=&{bankAccountIBAN}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"With production data, the bank-account could be used in multiple SEPA-mandates, make sure to use the right one!" "With production data, the bank-account could be used in multiple SEPA-mandates, make sure to use the right one!"
); );
return withTitle("Patch the End of the Mandate into the SEPA-Mandate", () -> return withTitle("Patch the End of the Mandate into the SEPA-Mandate", () ->
httpPatch("/api/hs/office/sepamandates/&{SEPA-Mandate: %{bankAccountIBAN}}", usingJsonBody(""" httpPatch(asGlobalAgent(), "/api/hs/office/sepamandates/&{SEPA-Mandate: %{bankAccountIBAN}}", usingJsonBody("""
{ {
"validTo": ${mandateValidTo} "validTo": ${mandateValidTo}
} }
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class CancelMembership extends UseCase<CancelMembership> { public class CancelMembership extends UseCase<CancelMembership> {
@@ -18,12 +19,12 @@ public class CancelMembership extends UseCase<CancelMembership> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("Membership: %{memberNumber}", () -> obtain("Membership: %{memberNumber}", () ->
httpGet("/api/hs/office/memberships/%{memberNumber}"), httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{memberNumber}"),
response -> response.getFromBody("uuid") response -> response.getFromBody("uuid")
); );
return withTitle("Patch the New Status Into the Membership", () -> return withTitle("Patch the New Status Into the Membership", () ->
httpPatch("/api/hs/office/memberships/%{Membership: %{memberNumber}}", usingJsonBody(""" httpPatch(asGlobalAgent(), "/api/hs/office/memberships/%{Membership: %{memberNumber}}", usingJsonBody("""
{ {
"validTo": ${validTo}, "validTo": ${validTo},
"status": ${newStatus} "status": ${newStatus}
@@ -37,7 +38,7 @@ public class CancelMembership extends UseCase<CancelMembership> {
protected void verify(final UseCase<CancelMembership>.HttpResponse response) { protected void verify(final UseCase<CancelMembership>.HttpResponse response) {
verify( verify(
"Verify That the Membership Got Cancelled", "Verify That the Membership Got Cancelled",
() -> httpGet("/api/hs/office/memberships/%{Membership: %{memberNumber}}") () -> httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{Membership: %{memberNumber}}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
path("validTo").contains("%{validTo}"), path("validTo").contains("%{validTo}"),
path("status").contains("CANCELLED") path("status").contains("CANCELLED")
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class CreateMembership extends UseCase<CreateMembership> { public class CreateMembership extends UseCase<CreateMembership> {
@@ -18,13 +19,13 @@ public class CreateMembership extends UseCase<CreateMembership> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("Partner: %{partnerName}", () -> obtain("Partner: %{partnerName}", () ->
httpGet("/api/hs/office/partners?name=&{partnerName}") httpGet(asGlobalAgent(), "/api/hs/office/partners?name=&{partnerName}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
return httpPost("/api/hs/office/memberships", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/memberships", usingJsonBody("""
{ {
"partner.uuid": ${Partner: %{partnerName}}, "partner.uuid": ${Partner: %{partnerName}},
"memberNumberSuffix": ${%{memberNumberSuffix???}???00}, "memberNumberSuffix": ${%{memberNumberSuffix???}???00},
@@ -40,7 +41,7 @@ public class CreateMembership extends UseCase<CreateMembership> {
protected void verify(final UseCase<CreateMembership>.HttpResponse response) { protected void verify(final UseCase<CreateMembership>.HttpResponse response) {
verify( verify(
"Verify That the Membership Got Created", "Verify That the Membership Got Created",
() -> httpGet("/api/hs/office/memberships/" + response.getLocationUuid()) () -> httpGet(asGlobalAgent(), "/api/hs/office/memberships/" + response.getLocationUuid())
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
path("validFrom").contains("%{validFrom}"), path("validFrom").contains("%{validFrom}"),
path("status").contains("ACTIVE") path("status").contains("ACTIVE")
@@ -7,6 +7,7 @@ import org.springframework.http.HttpStatus;
import java.math.BigDecimal; import java.math.BigDecimal;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolveTyped; import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolveTyped;
public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsTransaction { public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsTransaction {
@@ -41,7 +42,7 @@ public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsT
given("negativeAssetValue", resolveTyped("%{transferredValue}", BigDecimal.class).negate()); given("negativeAssetValue", resolveTyped("%{transferredValue}", BigDecimal.class).negate());
verify("Verify Reverted Coop-Assets TRANSFER-Transaction", verify("Verify Reverted Coop-Assets TRANSFER-Transaction",
() -> httpGet("/api/hs/office/coopassetstransactions/" + revertedAssetTxUuid) () -> httpGet(asGlobalAgent(), "/api/hs/office/coopassetstransactions/" + revertedAssetTxUuid)
.expecting(HttpStatus.OK).expecting(ContentType.JSON), .expecting(HttpStatus.OK).expecting(ContentType.JSON),
path("assetValue").contains("%{negativeAssetValue}"), path("assetValue").contains("%{negativeAssetValue}"),
path("comment").contains("%{comment}"), path("comment").contains("%{comment}"),
@@ -51,7 +52,7 @@ public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsT
final var adoptionAssetTxUuid = response.getFromBody("revertedAssetTx.['adoptionAssetTx.uuid']"); final var adoptionAssetTxUuid = response.getFromBody("revertedAssetTx.['adoptionAssetTx.uuid']");
verify("Verify Related Coop-Assets ADOPTION-Transaction Also Got Reverted", verify("Verify Related Coop-Assets ADOPTION-Transaction Also Got Reverted",
() -> httpGet("/api/hs/office/coopassetstransactions/" + adoptionAssetTxUuid) () -> httpGet(asGlobalAgent(), "/api/hs/office/coopassetstransactions/" + adoptionAssetTxUuid)
.expecting(HttpStatus.OK).expecting(ContentType.JSON), .expecting(HttpStatus.OK).expecting(ContentType.JSON),
path("reversalAssetTx.['transferAssetTx.uuid']").contains(revertedAssetTxUuid.toString()) path("reversalAssetTx.['transferAssetTx.uuid']").contains(revertedAssetTxUuid.toString())
); );
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAssetsTransaction> { public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAssetsTransaction> {
@@ -18,13 +19,13 @@ public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAsse
protected HttpResponse run() { protected HttpResponse run() {
obtain("#{Find }membershipUuid", () -> obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships/%{memberNumber}") httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{memberNumber}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid") response -> response.getFromBody("uuid")
); );
return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () -> return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopassetstransactions", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/coopassetstransactions", usingJsonBody("""
{ {
"membership.uuid": ${membershipUuid}, "membership.uuid": ${membershipUuid},
"transactionType": ${transactionType}, "transactionType": ${transactionType},
@@ -43,7 +44,7 @@ public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAsse
@Override @Override
protected void verify(final HttpResponse response) { protected void verify(final HttpResponse response) {
verify("Verify Coop-Assets %{transactionType}-Transaction", verify("Verify Coop-Assets %{transactionType}-Transaction",
() -> httpGet("/api/hs/office/coopassetstransactions/" + response.getLocationUuid()) () -> httpGet(asGlobalAgent(), "/api/hs/office/coopassetstransactions/" + response.getLocationUuid())
.expecting(HttpStatus.OK).expecting(ContentType.JSON), .expecting(HttpStatus.OK).expecting(ContentType.JSON),
path("transactionType").contains("%{transactionType}"), path("transactionType").contains("%{transactionType}"),
path("assetValue").contains("%{assetValue}"), path("assetValue").contains("%{assetValue}"),
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopSharesTransaction> { public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopSharesTransaction> {
@@ -18,13 +19,13 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
protected HttpResponse run() { protected HttpResponse run() {
obtain("#{Find }membershipUuid", () -> obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships/%{memberNumber}") httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{memberNumber}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid") response -> response.getFromBody("uuid")
); );
return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () -> return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopsharestransactions", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/coopsharestransactions", usingJsonBody("""
{ {
"membership.uuid": ${membershipUuid}, "membership.uuid": ${membershipUuid},
"transactionType": ${transactionType}, "transactionType": ${transactionType},
@@ -42,7 +43,7 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
@Override @Override
protected void verify(final HttpResponse response) { protected void verify(final HttpResponse response) {
verify("Verify Coop-Shares %{transactionType}-Transaction", verify("Verify Coop-Shares %{transactionType}-Transaction",
() -> httpGet("/api/hs/office/coopsharestransactions/" + response.getLocationUuid()) () -> httpGet(asGlobalAgent(), "/api/hs/office/coopsharestransactions/" + response.getLocationUuid())
.expecting(HttpStatus.OK).expecting(ContentType.JSON), .expecting(HttpStatus.OK).expecting(ContentType.JSON),
path("transactionType").contains("%{transactionType}"), path("transactionType").contains("%{transactionType}"),
path("shareCount").contains("%{shareCount}"), path("shareCount").contains("%{shareCount}"),
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -19,14 +20,14 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () -> obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}")) httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "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}", () -> obtain("Person: %{operationsContactGivenName} %{operationsContactFamilyName}", () ->
httpPost("/api/hs/office/persons", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{ {
"personType": "NATURAL_PERSON", "personType": "NATURAL_PERSON",
"familyName": ${operationsContactFamilyName}, "familyName": ${operationsContactFamilyName},
@@ -39,7 +40,7 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
); );
obtain("Contact: %{operationsContactGivenName} %{operationsContactFamilyName}", () -> obtain("Contact: %{operationsContactGivenName} %{operationsContactFamilyName}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": "%{operationsContactGivenName} %{operationsContactFamilyName}", "caption": "%{operationsContactGivenName} %{operationsContactFamilyName}",
"phoneNumbers": { "phoneNumbers": {
@@ -54,7 +55,7 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
"Please check first if that contact already exists, if so, use it's UUID below." "Please check first if that contact already exists, if so, use it's UUID below."
); );
return httpPost("/api/hs/office/relations", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{ {
"type": "OPERATIONS", "type": "OPERATIONS",
"anchor.uuid": ${Person: %{partnerPersonTradeName}}, "anchor.uuid": ${Person: %{partnerPersonTradeName}},
@@ -69,7 +70,7 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
protected void verify(final UseCase<AddOperationsContactToPartner>.HttpResponse response) { protected void verify(final UseCase<AddOperationsContactToPartner>.HttpResponse response) {
verify( verify(
"Verify the New OPERATIONS Relation", "Verify the New OPERATIONS Relation",
() -> httpGet("/api/hs/office/relations?relationType=OPERATIONS&personData=" + uriEncoded( () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=OPERATIONS&personData=" + uriEncoded(
"%{operationsContactFamilyName}")) "%{operationsContactFamilyName}"))
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].contact.caption").contains("%{operationsContactGivenName} %{operationsContactFamilyName}") path("[0].contact.caption").contains("%{operationsContactGivenName} %{operationsContactFamilyName}")
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -19,14 +20,14 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () -> obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}")) httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "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}", () -> obtain("Person: %{representativeGivenName} %{representativeFamilyName}", () ->
httpPost("/api/hs/office/persons", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{ {
"personType": "NATURAL_PERSON", "personType": "NATURAL_PERSON",
"familyName": ${representativeFamilyName}, "familyName": ${representativeFamilyName},
@@ -39,7 +40,7 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
); );
obtain("Contact: %{representativeGivenName} %{representativeFamilyName}", () -> obtain("Contact: %{representativeGivenName} %{representativeFamilyName}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": "%{representativeGivenName} %{representativeFamilyName}", "caption": "%{representativeGivenName} %{representativeFamilyName}",
"postalAddress": { "postalAddress": {
@@ -57,7 +58,7 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
"Please check first if that contact already exists, if so, use it's UUID below." "Please check first if that contact already exists, if so, use it's UUID below."
); );
return httpPost("/api/hs/office/relations", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{ {
"type": "REPRESENTATIVE", "type": "REPRESENTATIVE",
"anchor.uuid": ${Person: %{partnerPersonTradeName}}, "anchor.uuid": ${Person: %{partnerPersonTradeName}},
@@ -72,7 +73,7 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
protected void verify(final UseCase<AddRepresentativeToPartner>.HttpResponse response) { protected void verify(final UseCase<AddRepresentativeToPartner>.HttpResponse response) {
verify( verify(
"Verify the REPRESENTATIVE Relation Got Removed", "Verify the REPRESENTATIVE Relation Got Removed",
() -> httpGet("/api/hs/office/relations?relationType=REPRESENTATIVE&personData=" + uriEncoded("%{representativeFamilyName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=REPRESENTATIVE&personData=" + uriEncoded("%{representativeFamilyName}"))
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].contact.caption").contains("%{representativeGivenName} %{representativeFamilyName}") path("[0].contact.caption").contains("%{representativeGivenName} %{representativeFamilyName}")
); );
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class CreatePartner extends UseCase<CreatePartner> { public class CreatePartner extends UseCase<CreatePartner> {
@@ -24,14 +25,14 @@ public class CreatePartner extends UseCase<CreatePartner> {
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: Hostsharing eG", () -> obtain("Person: Hostsharing eG", () ->
httpGet("/api/hs/office/persons?name=Hostsharing+eG") httpGet(asGlobalAgent(), "/api/hs/office/persons?name=Hostsharing+eG")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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? "Even in production data we expect this query to return just a single result." // TODO.impl: add constraint?
); );
obtain("Person: %{%{tradeName???}???%{givenName???} %{familyName???}}", () -> obtain("Person: %{%{tradeName???}???%{givenName???} %{familyName???}}", () ->
httpPost("/api/hs/office/persons", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{ {
"personType": ${personType???}, "personType": ${personType???},
"tradeName": ${tradeName???}, "tradeName": ${tradeName???},
@@ -43,7 +44,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
); );
obtain("Contact: %{contactCaption}", () -> obtain("Contact: %{contactCaption}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{ {
"caption": ${contactCaption}, "caption": ${contactCaption},
"postalAddress": { "postalAddress": {
@@ -60,7 +61,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON) .expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
); );
return httpPost("/api/hs/office/partners", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/partners", usingJsonBody("""
{ {
"partnerNumber": ${partnerNumber}, "partnerNumber": ${partnerNumber},
"partnerRel": { "partnerRel": {
@@ -84,7 +85,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
protected void verify(final UseCase<CreatePartner>.HttpResponse response) { protected void verify(final UseCase<CreatePartner>.HttpResponse response) {
verify( verify(
"Verify the New Partner Relation", "Verify the New Partner Relation",
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&contactData=&{contactCaption}") () -> httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=PARTNER&contactData=&{contactCaption}")
.expecting(OK).expecting(JSON).expectArrayElements(1) .expecting(OK).expecting(JSON).expectArrayElements(1)
); );
} }
@@ -4,6 +4,8 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class DeletePartner extends UseCase<DeletePartner> { public class DeletePartner extends UseCase<DeletePartner> {
public DeletePartner(final ScenarioTest testSuite) { public DeletePartner(final ScenarioTest testSuite) {
@@ -18,10 +20,9 @@ public class DeletePartner extends UseCase<DeletePartner> {
@Override @Override
protected HttpResponse run() { protected HttpResponse run() {
withTitle("Delete Partner by its UUID", () -> return withTitle("Delete Partner by its UUID", () ->
httpDelete("/api/hs/office/partners/&{Partner: Delete AG}") httpDelete(asGlobalAgent(), "/api/hs/office/partners/&{Partner: Delete AG}")
.expecting(HttpStatus.NO_CONTENT) .expecting(HttpStatus.NO_CONTENT)
); );
return null;
} }
} }
@@ -1,10 +1,12 @@
package net.hostsharing.hsadminng.hs.office.scenarios.partner; package net.hostsharing.hsadminng.hs.office.scenarios.partner;
import lombok.val;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -18,7 +20,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
protected HttpResponse run() { protected HttpResponse run() {
obtain("Partner: %{partnerNumber}", obtain("Partner: %{partnerNumber}",
() -> httpGet("/api/hs/office/partners/%{partnerNumber}") () -> httpGet(asGlobalAgent(), "/api/hs/office/partners/%{partnerNumber}")
.reportWithResponse().expecting(OK).expecting(JSON), .reportWithResponse().expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid"), response -> response.getFromBody("uuid"),
"Even in production data we expect this query to return just a single result." "Even in production data we expect this query to return just a single result."
@@ -30,8 +32,8 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
"partnerRel.holder.uuid", "partnerRel.holder.uuid",
"Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"); "Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}");
withTitle("New Partner-Person+Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", val result = withTitle("New Partner-Person+Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
() -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}", () -> httpPatch(asGlobalAgent(), "/api/hs/office/partners/%{Partner: %{partnerNumber}}",
usingJsonBody(""" usingJsonBody("""
{ {
"partnerRel": { "partnerRel": {
@@ -68,7 +70,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
obtain( obtain(
"Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}", "Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
() -> httpPost("/api/hs/office/relations", () -> httpPost(asGlobalAgent(), "/api/hs/office/relations",
usingJsonBody(""" usingJsonBody("""
{ {
"type": "REPRESENTATIVE", "type": "REPRESENTATIVE",
@@ -88,14 +90,14 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
// outro: die Erbengemeinschaft hat eine Frist von 6 Monaten, um die Mitgliedschaft einer Person zu übertragen // outro: die Erbengemeinschaft hat eine Frist von 6 Monaten, um die Mitgliedschaft einer Person zu übertragen
// nächster "Drecksfall" // nächster "Drecksfall"
return null; return result;
} }
@Override @Override
protected void verify(final UseCase<ReplaceDeceasedPartnerWithCommunityOfHeirs>.HttpResponse response) { protected void verify(final UseCase<ReplaceDeceasedPartnerWithCommunityOfHeirs>.HttpResponse response) {
verify( verify(
"Verify the Updated Partner", "Verify the Updated Partner",
() -> httpGet("/api/hs/office/partners/%{partnerNumber}") () -> httpGet(asGlobalAgent(), "/api/hs/office/partners/%{partnerNumber}")
.expecting(OK).expecting(JSON).expectObject(), .expecting(OK).expecting(JSON).expectObject(),
path("partnerRel.holder.tradeName").contains( path("partnerRel.holder.tradeName").contains(
"Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"), "Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"),
@@ -106,7 +108,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify( verify(
"Verify the Ex-Partner-Relation", "Verify the Ex-Partner-Relation",
() -> httpGet( () -> httpGet(asGlobalAgent(),
"/api/hs/office/relations?relationType=EX_PARTNER&personUuid=%{Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}") "/api/hs/office/relations?relationType=EX_PARTNER&personUuid=%{Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}")
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].anchor.tradeName").contains( path("[0].anchor.tradeName").contains(
@@ -115,7 +117,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify( verify(
"Verify the Representative-Relation", "Verify the Representative-Relation",
() -> httpGet( () -> httpGet(asGlobalAgent(),
"/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}") "/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}")
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].anchor.tradeName").contains( path("[0].anchor.tradeName").contains(
@@ -128,7 +130,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify( verify(
"Verify the Debitor-Relation", "Verify the Debitor-Relation",
() -> httpGet( () -> httpGet(asGlobalAgent(),
"/api/hs/office/debitors?partnerNumber=%{partnerNumber}") "/api/hs/office/debitors?partnerNumber=%{partnerNumber}")
.expecting(OK).expecting(JSON).expectArrayElements(1), .expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].debitorRel.anchor.tradeName").contains( path("[0].debitorRel.anchor.tradeName").contains(
@@ -5,6 +5,8 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class CreatePerson extends UseCase<CreatePerson> { public class CreatePerson extends UseCase<CreatePerson> {
public CreatePerson(final ScenarioTest testSuite, final String resultAlias) { public CreatePerson(final ScenarioTest testSuite, final String resultAlias) {
@@ -15,7 +17,7 @@ public class CreatePerson extends UseCase<CreatePerson> {
protected HttpResponse run() { protected HttpResponse run() {
return withTitle("Create the Person", () -> return withTitle("Create the Person", () ->
httpPost("/api/hs/office/persons", usingJsonBody(""" httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{ {
"personType": ${personType}, "personType": ${personType},
"tradeName": ${tradeName} "tradeName": ${tradeName}
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
public class ShouldUpdatePersonData extends UseCase<ShouldUpdatePersonData> { public class ShouldUpdatePersonData extends UseCase<ShouldUpdatePersonData> {
@@ -18,29 +19,28 @@ public class ShouldUpdatePersonData extends UseCase<ShouldUpdatePersonData> {
obtain( obtain(
"personUuid", "personUuid",
() -> httpGet("/api/hs/office/persons?name=" + uriEncoded("%{oldFamilyName}")) () -> httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{oldFamilyName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
withTitle("Patch the Additional Phone-Number into the Person", () -> return withTitle("Patch the Additional Phone-Number into the Person", () ->
httpPatch("/api/hs/office/persons/%{personUuid}", usingJsonBody(""" httpPatch(
asGlobalAgent(), "/api/hs/office/persons/%{personUuid}", usingJsonBody("""
{ {
"familyName": ${newFamilyName} "familyName": ${newFamilyName}
} }
""")) """))
.expecting(HttpStatus.OK) .expecting(HttpStatus.OK)
); );
return null;
} }
@Override @Override
protected void verify(final UseCase<ShouldUpdatePersonData>.HttpResponse response) { protected void verify(final UseCase<ShouldUpdatePersonData>.HttpResponse response) {
verify( verify(
"Verify that the Family Name Got Amended", "Verify that the Family Name Got Amended",
() -> httpGet("/api/hs/office/persons/%{personUuid}") () -> httpGet(asGlobalAgent(), "/api/hs/office/persons/%{personUuid}")
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
path("familyName").contains("%{newFamilyName}") path("familyName").contains("%{newFamilyName}")
); );
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase; import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -19,7 +20,7 @@ public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperations
obtain("Operations-Contact: %{operationsContactPerson}", obtain("Operations-Contact: %{operationsContactPerson}",
() -> () ->
httpGet("/api/hs/office/relations?relationType=OPERATIONS&name=" + uriEncoded( httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=OPERATIONS&name=" + uriEncoded(
"%{operationsContactPerson}")) "%{operationsContactPerson}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
@@ -27,7 +28,7 @@ public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperations
); );
return withTitle("Delete the Contact", () -> return withTitle("Delete the Contact", () ->
httpDelete("/api/hs/office/relations/&{Operations-Contact: %{operationsContactPerson}}") httpDelete(asGlobalAgent(), "/api/hs/office/relations/&{Operations-Contact: %{operationsContactPerson}}")
.expecting(NO_CONTENT) .expecting(NO_CONTENT)
); );
} }
@@ -36,7 +37,7 @@ public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperations
protected void verify(final UseCase<RemoveOperationsContactFromPartner>.HttpResponse response) { protected void verify(final UseCase<RemoveOperationsContactFromPartner>.HttpResponse response) {
verify( verify(
"Verify the New OPERATIONS Relation", "Verify the New OPERATIONS Relation",
() -> httpGet("/api/hs/office/relations/&{Operations-Contact: %{operationsContactPerson}}") () -> httpGet(asGlobalAgent(), "/api/hs/office/relations/&{Operations-Contact: %{operationsContactPerson}}")
.expecting(NOT_FOUND) .expecting(NOT_FOUND)
); );
} }
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -19,7 +20,7 @@ public class SubscribeExistingPersonAndContactToMailinglist extends UseCase<Subs
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () -> obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}")) httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
@@ -27,20 +28,20 @@ public class SubscribeExistingPersonAndContactToMailinglist extends UseCase<Subs
obtain( obtain(
"Person: %{subscriberGivenName} %{subscriberFamilyName}", () -> "Person: %{subscriberGivenName} %{subscriberFamilyName}", () ->
httpGet("/api/hs/office/persons?name=%{subscriberFamilyName}") httpGet(asGlobalAgent(), "/api/hs/office/persons?name=%{subscriberFamilyName}")
.expecting(HttpStatus.OK).expecting(ContentType.JSON), .expecting(HttpStatus.OK).expecting(ContentType.JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In real scenarios there are most likely multiple results and you have to choose the right one." "In real scenarios there are most likely multiple results and you have to choose the right one."
); );
obtain("Contact: %{subscriberEMailAddress}", () -> obtain("Contact: %{subscriberEMailAddress}", () ->
httpGet("/api/hs/office/contacts?emailAddress=%{subscriberEMailAddress}") httpGet(asGlobalAgent(), "/api/hs/office/contacts?emailAddress=%{subscriberEMailAddress}")
.expecting(HttpStatus.OK).expecting(ContentType.JSON), .expecting(HttpStatus.OK).expecting(ContentType.JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In real scenarios there are most likely multiple results and you have to choose the right one." "In real scenarios there are most likely multiple results and you have to choose the right one."
); );
return httpPost("/api/hs/office/relations", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{ {
"type": "SUBSCRIBER", "type": "SUBSCRIBER",
"mark": ${mailingList}, "mark": ${mailingList},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -17,13 +18,13 @@ public class SubscribeNewPersonAndContactToMailinglist extends UseCase<Subscribe
protected HttpResponse run() { protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () -> obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}")) httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"), 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." "In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
); );
return httpPost("/api/hs/office/relations", usingJsonBody(""" return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{ {
"type": "SUBSCRIBER", "type": "SUBSCRIBER",
"mark": ${mailingList}, "mark": ${mailingList},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON; import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.OK;
@@ -17,7 +18,7 @@ public class UnsubscribeFromMailinglist extends UseCase<UnsubscribeFromMailingli
protected HttpResponse run() { protected HttpResponse run() {
obtain("Subscription: %{subscriberEMailAddress}", () -> obtain("Subscription: %{subscriberEMailAddress}", () ->
httpGet("/api/hs/office/relations?relationType=SUBSCRIBER" + httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=SUBSCRIBER" +
"&mark=" + uriEncoded("%{mailingList}") + "&mark=" + uriEncoded("%{mailingList}") +
"&contactData=" + uriEncoded("%{subscriberEMailAddress}")) "&contactData=" + uriEncoded("%{subscriberEMailAddress}"))
.expecting(OK).expecting(JSON), .expecting(OK).expecting(JSON),
@@ -26,7 +27,7 @@ public class UnsubscribeFromMailinglist extends UseCase<UnsubscribeFromMailingli
); );
return withTitle("Delete the Subscriber-Relation by its UUID", () -> return withTitle("Delete the Subscriber-Relation by its UUID", () ->
httpDelete("/api/hs/office/relations/&{Subscription: %{subscriberEMailAddress}}") httpDelete(asGlobalAgent(), "/api/hs/office/relations/&{Subscription: %{subscriberEMailAddress}}")
.expecting(NO_CONTENT) .expecting(NO_CONTENT)
); );
} }
@@ -0,0 +1,24 @@
package net.hostsharing.hsadminng.hs.scenarios;
import lombok.AllArgsConstructor;
import net.hostsharing.hsadminng.config.JwtFakeBearer;
@AllArgsConstructor
public class FakeLoginUser {
final static String GLOBAL_AGENT = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented
private String name;
public static FakeLoginUser as(final String name) {
return new FakeLoginUser(name);
}
public static FakeLoginUser asGlobalAgent() {
return new FakeLoginUser(GLOBAL_AGENT);
}
public String bearer() {
return JwtFakeBearer.bearer(name);
}
}
@@ -9,5 +9,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(METHOD) @Target(METHOD)
@Retention(RUNTIME) @Retention(RUNTIME)
public @interface Requires { public @interface Requires {
String value(); String[] value();
} }
@@ -31,7 +31,9 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -62,8 +64,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(IgnoreOnFailureExtension.class) @ExtendWith(IgnoreOnFailureExtension.class)
public abstract class ScenarioTest extends ContextBasedTest { public abstract class ScenarioTest extends ContextBasedTest {
final static String RUN_AS_USER = "superuser-alex@hostsharing.net"; // TODO.test: use global:AGENT when implemented
private final Stack<String> currentTestMethodProduces = new Stack<>(); private final Stack<String> currentTestMethodProduces = new Stack<>();
protected ScenarioTest scenarioTest = this; protected ScenarioTest scenarioTest = this;
@@ -116,20 +116,17 @@ public abstract class ScenarioTest extends ContextBasedTest {
@SneakyThrows @SneakyThrows
private void callRequiredProducers(final Method currentTestMethod) { private void callRequiredProducers(final Method currentTestMethod) {
final var testMethodRequires = Optional.of(currentTestMethod) final var testMethodRequires = Stream.of(currentTestMethod)
.map(m -> m.getAnnotation(Requires.class)) .map(m -> m.getAnnotation(Requires.class))
.map(Requires::value) .filter(Objects::nonNull)
.orElse(null); .flatMap(annotation -> Stream.of(annotation.value()))
if (testMethodRequires != null) { .collect(Collectors.toSet());
if (!testMethodRequires.isEmpty()) {
for (Method potentialProducerMethod : getPotentialProducerMethods()) { for (Method potentialProducerMethod : getPotentialProducerMethods()) {
final var producesAnnot = potentialProducerMethod.getAnnotation(Produces.class); final var producesAnnot = potentialProducerMethod.getAnnotation(Produces.class);
final var testMethodProduces = producedAliases(producesAnnot); final var testMethodProduces = producedAliases(producesAnnot);
// @formatter:off if ( thatMethodProducesSomethingRequired(testMethodProduces, testMethodRequires) &&
if ( // that method can produce something required thatMethodDoesNotProduceAnythingWeAlreadyHave(testMethodProduces)
testMethodProduces.contains(testMethodRequires) &&
// and it does not produce anything we already have (would cause errors)
SetUtils.intersection(testMethodProduces, knowVariables().keySet()).isEmpty()
) { ) {
assertThat(producesAnnot.permanent()).as("cannot depend on non-permanent producer: " + potentialProducerMethod); assertThat(producesAnnot.permanent()).as("cannot depend on non-permanent producer: " + potentialProducerMethod);
@@ -140,15 +137,30 @@ public abstract class ScenarioTest extends ContextBasedTest {
// and finally we call the producer method // and finally we call the producer method
invokeProducerMethod(this, potentialProducerMethod); invokeProducerMethod(this, potentialProducerMethod);
} }
// @formatter:on
} }
assertThat(knowVariables().containsKey(testMethodRequires)) assertThat(haveIntersection(knowVariables().keySet(), testMethodRequires))
.as("no @Producer for @Required(\"" + testMethodRequires + "\") found") .as("no @Producer for @Required(\"" + testMethodRequires + "\") found")
.isTrue(); .isTrue();
} }
} }
private static boolean haveIntersection(final Set<String> set1, final Set<String> set2) {
return !SetUtils.intersection(set1, set2).isEmpty();
}
private static boolean areDisjunct(final Set<String> set1, final Set<String> set2) {
return !haveIntersection(set1, set2);
}
private static boolean thatMethodProducesSomethingRequired(final Set<String> testMethodProduces, final Set<String> testMethodRequires) {
return haveIntersection(testMethodProduces, testMethodRequires);
}
private static boolean thatMethodDoesNotProduceAnythingWeAlreadyHave(final Set<String> testMethodProduces) {
return areDisjunct(testMethodProduces, knowVariables().keySet());
}
private void keepProducesAlias(final Method currentTestMethod) { private void keepProducesAlias(final Method currentTestMethod) {
final var producesAnnot = currentTestMethod.getAnnotation(Produces.class); final var producesAnnot = currentTestMethod.getAnnotation(Produces.class);
if (producesAnnot != null) { if (producesAnnot != null) {
@@ -37,7 +37,6 @@ import static java.net.URLEncoder.encode;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;
import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.DROP_COMMENTS; import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.DROP_COMMENTS;
import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.KEEP_COMMENTS; import static net.hostsharing.hsadminng.hs.scenarios.TemplateResolver.Resolver.KEEP_COMMENTS;
import static net.hostsharing.hsadminng.config.JwtFakeBearer.bearer;
import static net.hostsharing.hsadminng.test.DebuggerDetection.isDebuggerAttached; import static net.hostsharing.hsadminng.test.DebuggerDetection.isDebuggerAttached;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@@ -76,7 +75,7 @@ public abstract class UseCase<T extends UseCase<?>> {
requirements.put(alias, useCaseFactory); requirements.put(alias, useCaseFactory);
} }
public final HttpResponse doRun() { public final HttpResponse thenExpect(final HttpStatus expectedStatus) {
if (introduction != null) { if (introduction != null) {
testReport.printPara(introduction); testReport.printPara(introduction);
} }
@@ -95,8 +94,11 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
}) })
); );
final var response = run(); final var response = run(expectedStatus);
assertThat(response).as("use case implementation must return main response, never null").isNotNull();
if (!response.status.isError()) {
verify(response); verify(response);
}
keepInProduceAlias(response); keepInProduceAlias(response);
resetProperties(); resetProperties();
@@ -104,7 +106,14 @@ public abstract class UseCase<T extends UseCase<?>> {
return response; return response;
} }
protected abstract HttpResponse run(); // this method is called by the test framework, override, but do not call from subclass
protected HttpResponse run(final HttpStatus expectedStatus) {
assertThat(expectedStatus).as("legacy signature only defined for HttpStatus.OK").isEqualTo(HttpStatus.OK);
return run();
};
// legacy signature for backwards compatibility, only called by above method
protected HttpResponse run() {return null;}
protected void verify(final HttpResponse response) { protected void verify(final HttpResponse response) {
} }
@@ -171,20 +180,22 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpGet(final String uriPathWithPlaceholders) { public final HttpResponse httpGet(final FakeLoginUser loginUser, final String uriPathWithPlaceholders) {
return httpGet(uriPathWithPlaceholders, return httpGet(uriPathWithPlaceholders,
req -> req.header("Authorization", bearer(ScenarioTest.RUN_AS_USER))); req -> req.header("Authorization", loginUser.bearer()));
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpPost(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) { public final HttpResponse httpPost(
final FakeLoginUser loginUser, final String uriPathWithPlaceholders,
final JsonTemplate bodyJsonTemplate) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var requestBody = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.POST(BodyPublishers.ofString(requestBody)) .POST(BodyPublishers.ofString(requestBody))
.uri(new URI("http://localhost:" + testSuite.port + uriPath)) .uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", bearer(ScenarioTest.RUN_AS_USER)) .header("Authorization", loginUser.bearer())
.timeout(seconds(HTTP_TIMEOUT_SECONDS)) .timeout(seconds(HTTP_TIMEOUT_SECONDS))
.build(); .build();
final var response = client.send(request, BodyHandlers.ofString()); final var response = client.send(request, BodyHandlers.ofString());
@@ -192,14 +203,17 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpPatch(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) { public final HttpResponse httpPatch(
final FakeLoginUser loginUser, final String uriPathWithPlaceholders,
final JsonTemplate bodyJsonTemplate
) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
final var requestBody = bodyJsonTemplate.resolvePlaceholders(); final var requestBody = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody)) .method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody))
.uri(new URI("http://localhost:" + testSuite.port + uriPath)) .uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", bearer(ScenarioTest.RUN_AS_USER)) .header("Authorization", loginUser.bearer())
.timeout(seconds(HTTP_TIMEOUT_SECONDS)) .timeout(seconds(HTTP_TIMEOUT_SECONDS))
.build(); .build();
final var response = client.send(request, BodyHandlers.ofString()); final var response = client.send(request, BodyHandlers.ofString());
@@ -207,13 +221,13 @@ public abstract class UseCase<T extends UseCase<?>> {
} }
@SneakyThrows @SneakyThrows
public final HttpResponse httpDelete(final String uriPathWithPlaceholders) { public final HttpResponse httpDelete(final FakeLoginUser loginUser, final String uriPathWithPlaceholders) {
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS); final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders, DROP_COMMENTS);
final var request = HttpRequest.newBuilder() final var request = HttpRequest.newBuilder()
.DELETE() .DELETE()
.uri(new URI("http://localhost:" + testSuite.port + uriPath)) .uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", bearer(ScenarioTest.RUN_AS_USER)) .header("Authorization", loginUser.bearer())
.timeout(seconds(HTTP_TIMEOUT_SECONDS)) .timeout(seconds(HTTP_TIMEOUT_SECONDS))
.build(); .build();
final var response = client.send(request, BodyHandlers.ofString()); final var response = client.send(request, BodyHandlers.ofString());