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> {
java {
// Configure formatting steps
removeUnusedImports()
leadingTabsToSpaces(4)
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
tasks.register<Test>("unitTest") {
@@ -591,7 +606,6 @@ tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
}
}
// Generate HTML from Markdown scenario-test-reports using Pandoc:
tasks.register("convertMarkdownToHtml") {
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.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.AllArgsConstructor;
import lombok.val;
import net.hostsharing.hsadminng.accounts.generated.api.v1.api.ProfileApi;
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.config.MessageTranslator;
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.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
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.RealSubjectEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@@ -107,17 +113,19 @@ public class HsProfileController implements ProfileApi {
final ProfileInsertResource body
) {
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
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
val newProfileEntity = mapper.map(
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
em.persist(newProfileEntity); // newProfileEntity.uuid == newlySavedSubject.uuid => do not use repository!
@@ -154,10 +162,11 @@ public class HsProfileController implements ProfileApi {
final ProfilePatchResource body
) {
context.define(); // without assumed roles, otherwise we cannot access the subject anymore
final LoginContext originalLoginContext = new LoginContext(context);
val current = profileRepo.findByUuid(profileUuid).orElseThrow();
validateBeforePatch(current, body);
validateBeforePatch(originalLoginContext, current, body);
new HsProfileEntityPatcher(scopeMapper, current).apply(body);
validateOnUpdate(current);
@@ -187,13 +196,15 @@ public class HsProfileController implements ProfileApi {
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())
throw new ForbiddenException("Only global admins are allowed to activate an inactive profile");
}
private void validateOnCreate(final HsProfileEntity newProfileEntity) {
validateReferencedPersonToBeRepresentedByLoginUserPerson(newProfileEntity);
private void validateOnCreate(final LoginContext originalLoginContext, final HsProfileEntity newProfileEntity) {
validateReferencedPersonToBeRepresentedByLoginUserPerson(originalLoginContext, newProfileEntity);
validateNormalUsersOnlyAccessPublicScopes(newProfileEntity);
validateNaturalPersonRequirementOfScopes(newProfileEntity);
}
@@ -208,20 +219,16 @@ public class HsProfileController implements ProfileApi {
validateOwnHsadminProfileMustNotBeRemoved(profileEntity);
}
private void validateReferencedPersonToBeRepresentedByLoginUserPerson(final HsProfileEntity newProfileEntity) {
if (context.isGlobalAdmin()) {
private void validateReferencedPersonToBeRepresentedByLoginUserPerson(final LoginContext originalLoginContext, final HsProfileEntity profileEntity) {
if (originalLoginContext.isGlobalAdmin) {
return;
}
val referredPersonUuid = newProfileEntity.getPerson().getUuid();
val currentSubjectUuid = context.fetchCurrentSubjectUuid();
val loginPersonUuid = profileRepo.findByUuid(currentSubjectUuid)
.map(HsProfileEntity::getPerson)
.map(HsOfficePerson::getUuid)
.orElseThrow();
val referredPersonUuid = profileEntity.getPerson().getUuid();
val loginPersonUuid = originalLoginContext.profile.getPerson().getUuid();
val representedPersonUuids = realPersonRepo.findPersonsRepresentedByPersonWithUuid(loginPersonUuid)
.stream().map(HsOfficePerson::getUuid).toList();
if ( !representedPersonUuids.contains(referredPersonUuid)) {
throw new ValidationException(
throw new ForbiddenException(
messageTranslator.translate(
"profile.access-denied-to-person-with-uuid-{0}-not-represented-by-currently-logged-in-person",
loginPersonUuid));
@@ -233,7 +240,7 @@ public class HsProfileController implements ProfileApi {
.filter(c -> !c.isPublicAccess() && !context.isGlobalAdmin() )
.toList();
if (!forbiddenScopes.isEmpty()) {
throw new ValidationException(
throw new ForbiddenException(
messageTranslator.translate(
"profile.access-denied-for-scopes-{0}",
toDisplay(forbiddenScopes)
@@ -330,7 +337,19 @@ public class HsProfileController implements ProfileApi {
}
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(
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.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
@OneToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@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;
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@@ -84,6 +84,8 @@ components:
person.uuid:
type: string
format: uuid
person:
$ref: '../hs-office/hs-office-person-schemas.yaml#/components/schemas/HsOfficePersonInsert'
nickname:
type: string
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:
$ref: 'scope-schemas.yaml#/components/schemas/Scope'
required:
- person.uuid
- nickname
- 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
@@ -202,7 +202,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.when()
.post("http://localhost/api/hs/accounts/profiles")
.then().log().all().assertThat()
.statusCode(400)
.statusCode(403)
.contentType("application/json")
.body("message", containsString("wird von der eingeloggten Person nicht repräsentiert"));
// @formatter:on
@@ -246,7 +246,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.when()
.post("http://localhost/api/hs/accounts/profiles")
.then().log().all().assertThat()
.statusCode(400)
.statusCode(403)
.contentType("application/json")
.body("message", containsString("Zugriff auf Geltungsbereich verweigert: 'MATRIX:internal', 'SSH:internal'"));
// @formatter:on
@@ -326,7 +326,7 @@ class HsProfileControllerAcceptanceTest extends ContextBasedTestWithCleanup {
.when()
.patch("http://localhost/api/hs/accounts/profiles/" + drewProfileUuid)
.then().log().all().assertThat()
.statusCode(400)
.statusCode(403)
.contentType("application/json")
.body("message", containsString("Zugriff auf Geltungsbereich verweigert: 'MATRIX:internal', 'SSH:internal'"));
// @formatter:on
@@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.accounts.scenarios;
import lombok.SneakyThrows;
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.UseCase;
import org.apache.commons.lang3.tuple.Pair;
@@ -9,19 +10,23 @@ import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
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);
this.asLoginUser = asLoginUser;
}
@SneakyThrows
protected ScopeResource[] fetchScopeResourcesByDescriptorPairs(final String descriptPairsVarName) {
final var requestedScopes = ScenarioTest.getTypedVariable("scopes", Pair[].class);
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();
final var existingScopes = objectMapper.readValue(existingScopesJson, ScopeResource[].class);
return Arrays.stream(requestedScopes)
@@ -1,6 +1,7 @@
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;
@@ -8,10 +9,10 @@ import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.OK;
public class CreateProfile extends BaseProfileUseCase<CreateProfile> {
public class CreateProfileForExistingPerson extends BaseProfileUseCase<CreateProfileForExistingPerson> {
public CreateProfile(final ScenarioTest testSuite) {
super(testSuite);
public CreateProfileForExistingPerson(final ScenarioTest testSuite, final FakeLoginUser asLoginUser) {
super(testSuite, asLoginUser);
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() {
obtain("Person: %{personGivenName} %{personFamilyName}", () ->
httpGet("/api/hs/office/persons?name=%{personFamilyName}")
httpGet(asLoginUser, "/api/hs/office/persons?name=%{personFamilyName}")
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"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", () ->
httpPost("/api/hs/accounts/profiles", usingJsonBody("""
httpPost(asLoginUser, "/api/hs/accounts/profiles", usingJsonBody("""
{
"person.uuid": ${Person: %{personGivenName} %{personFamilyName}},
"nickname": ${nickname},
@@ -51,10 +52,10 @@ public class CreateProfile extends BaseProfileUseCase<CreateProfile> {
}
@Override
protected void verify(final UseCase<CreateProfile>.HttpResponse response) {
protected void verify(final UseCase<CreateProfileForExistingPerson>.HttpResponse response) {
verify(
"Verify the new Profile",
() -> httpGet("/api/hs/accounts/profiles/%{newProfile}")
() -> httpGet(asLoginUser, "/api/hs/accounts/profiles/%{newProfile}")
.expecting(OK).expecting(JSON),
path("uuid").contains("%{newProfile}"),
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 net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
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.TemplateResolver.Resolver.DROP_COMMENTS;
@@ -24,7 +25,7 @@ public class CurrentLoginUser extends UseCase<CurrentLoginUser> {
protected HttpResponse run() {
obtain("Person: %{personGivenName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{personGivenName}"))
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{personGivenName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
@@ -13,6 +13,10 @@ import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
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 {
@@ -35,7 +39,7 @@ class ProfileScenarioTests extends ScenarioTest {
.given("subjectName", "superuser-fran@hostsharing.net")
.given("assumedRoles", "rbactest.package#xxx00:ADMIN;rbactest.package#yyy00:ADMIN")
.given("expectedToBeGlobalAdmin", true)
.doRun()
.thenExpect(HttpStatus.OK)
.keep();
}
}
@@ -53,7 +57,7 @@ class ProfileScenarioTests extends ScenarioTest {
.given("subjectName", "superuser-fran@hostsharing.net")
.given("personGivenName", "Fran")
.given("expectedToBeGlobalAdmin", true)
.doRun()
.thenExpect(HttpStatus.OK)
.keep();
}
}
@@ -65,9 +69,11 @@ class ProfileScenarioTests extends ScenarioTest {
@Test
@Order(1010)
@Produces(explicitly = "Profile: firby-susan", implicitly = { "Person: Susan Firby" })
@Produces(
explicitly = "Profile: susan-firby",
implicitly = {"Person: Susan Firby"})
void shouldCreateInitialProfileForExistingNaturalPerson() {
new CreateProfile(scenarioTest)
new CreateProfileForExistingPerson(scenarioTest, asGlobalAgent())
// to find a specific existing person
.given("personFamilyName", "Firby")
.given("personGivenName", "Susan")
@@ -86,30 +92,95 @@ class ProfileScenarioTests extends ScenarioTest {
"scopes", Array.of(
Pair.of("HSADMIN", "prod")
))
.doRun()
.thenExpect(HttpStatus.OK)
.keep();
}
@Test
@Order(1020)
@Requires("Profile: firby-susan")
void shouldUpdateProfile() {
new UpdateProfile(scenarioTest)
@Requires("Profile: susan-firby")
void naturalPersonShouldBeAbleToUpdateTheirOwnProfile() {
new UpdateProfile(scenarioTest, as("firby-susan"))
// the profile to update
.given("profileUuid", "%{Profile: firby-susan}")
.given("profileUuid", "%{Profile: susan-firby}")
// updated profile
.given("active", false)
.given("totpSecrets", Array.of("initialSecret", "additionalSecret"))
.given("emailAddress", "susan.firby@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.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(
"scopes", Array.of(
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;
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.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.assertj.core.api.Fail.fail;
import static org.springframework.http.HttpStatus.OK;
public class UpdateProfile extends BaseProfileUseCase<UpdateProfile> {
public UpdateProfile(final ScenarioTest testSuite) {
super(testSuite);
public UpdateProfile(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() {
protected HttpResponse run(final HttpStatus expectedStatus) {
given("resolvedScopes",
fetchScopeResourcesByDescriptorPairs("scopes")
);
withTitle("Patch the Changes to the existing Profile", () ->
httpPatch("/api/hs/accounts/profiles/%{profileUuid}", usingJsonBody("""
return withTitle("Patch the Changes to the existing Profile", () -> {
val response = httpPatch(
asLoginUser, "/api/hs/accounts/profiles/%{profileUuid}", usingJsonBody("""
{
"active": %{active},
"totpSecrets": @{totpSecrets},
@@ -34,19 +38,24 @@ public class UpdateProfile extends BaseProfileUseCase<UpdateProfile> {
"scopes": @{resolvedScopes}
}
"""))
.reportWithResponse().expecting(HttpStatus.OK).expecting(ContentType.JSON)
.extractValue("nickname", "nickname")
.extractValue("totpSecrets", "totpSecrets")
);
.reportWithResponse().expecting(expectedStatus);
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
protected void verify(final UseCase<UpdateProfile>.HttpResponse response) {
verify(
"Verify the Patched Profile",
() -> httpGet("/api/hs/accounts/profiles/%{profileUuid}")
() -> httpGet(asLoginUser, "/api/hs/accounts/profiles/%{profileUuid}")
.expecting(OK).expecting(JSON),
path("uuid").contains("%{newProfile}"),
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.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
class HsOfficeScenarioTests extends ScenarioTest {
@@ -104,8 +105,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("officePhoneNumber", "+49 40 654321-0")
.given("emailAddress", "hamburg@test-ag.example.org")
.given("registrationOffice", "Registergericht Hamburg")
.given("registrationNumber", "1234567")
.doRun()
.given("registrationNumber", "1234567").thenExpect(HttpStatus.OK)
.keep();
}
@@ -132,8 +132,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("emailAddress", "michelle.matthieu@example.org")
.given("birthday", "1951-03-25")
.given("birthPlace", "Neustadt a.d.R.")
.given("birthName", "Eichbaum")
.doRun()
.given("birthName", "Eichbaum").thenExpect(HttpStatus.OK)
.keep();
}
@@ -155,8 +154,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
"country": "Germany"
""")
.given("representativePhoneNumber", "+49 40 123456")
.given("representativeEMailAddress", "tracy.trust@example.org")
.doRun()
.given("representativeEMailAddress", "tracy.trust@example.org").thenExpect(HttpStatus.OK)
.keep();
}
@@ -170,8 +168,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("operationsContactFamilyName", "Krause")
.given("operationsContactGivenName", "Dennis")
.given("operationsContactPhoneNumber", "+49 9932 587741")
.given("operationsContactEMailAddress", "dennis.krause@example.org")
.doRun()
.given("operationsContactEMailAddress", "dennis.krause@example.org").thenExpect(HttpStatus.OK)
.keep();
}
@@ -180,16 +177,14 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Operations-Contact: Dennis Krause for Test AG")
void shouldRemoveOperationsContactFromPartner() {
new RemoveOperationsContactFromPartner(scenarioTest)
.given("operationsContactPerson", "Dennis Krause")
.doRun();
.given("operationsContactPerson", "Dennis Krause").thenExpect(HttpStatus.OK);
}
@Test
@Order(1090)
void shouldDeletePartner() {
new DeletePartner(scenarioTest)
.given("partnerNumber", "P-31020")
.doRun();
.given("partnerNumber", "P-31020").thenExpect(HttpStatus.OK);
}
}
@@ -204,8 +199,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldAmendContactData() {
new AmendContactData(scenarioTest)
.given("partnerName", "Matthieu")
.given("newEmailAddress", "michelle@matthieu.example.org")
.doRun();
.given("newEmailAddress", "michelle@matthieu.example.org").thenExpect(HttpStatus.OK);
}
@Test
@@ -215,8 +209,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new AddPhoneNumberToContactData(scenarioTest)
.given("partnerName", "Matthieu")
.given("phoneNumberKeyToAdd", "mobile")
.given("phoneNumberToAdd", "+49 152 1234567")
.doRun();
.given("phoneNumberToAdd", "+49 152 1234567").thenExpect(HttpStatus.OK);
}
@Test
@@ -225,8 +218,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldRemovePhoneNumberFromContactData() {
new RemovePhoneNumberFromContactData(scenarioTest)
.given("partnerName", "Matthieu")
.given("phoneNumberKeyToRemove", "office")
.doRun();
.given("phoneNumberKeyToRemove", "office").thenExpect(HttpStatus.OK);
}
@Test
@@ -247,8 +239,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
"country": "China"
""")
.given("newOfficePhoneNumber", "++15 999 654321")
.given("newEmailAddress", "norden@test-ag.example.org")
.doRun();
.given("newEmailAddress", "norden@test-ag.example.org").thenExpect(HttpStatus.OK);
}
}
@@ -263,8 +254,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldUpdatePersonData() {
new ShouldUpdatePersonData(scenarioTest)
.given("oldFamilyName", "Matthieu")
.given("newFamilyName", "Matthieu-Zhang")
.doRun();
.given("newFamilyName", "Matthieu-Zhang").thenExpect(HttpStatus.OK);
}
}
@@ -278,14 +268,14 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Partner: P-31011 - Michelle Matthieu")
@Produces("Debitor: D-3101100 - Michelle Matthieu")
void shouldCreateSelfDebitorForPartnerWithIdenticalContactData() {
// TODO.impl: could be assigned automatically but is not yet
new CreateSelfDebitorForPartnerWithIdenticalContactData(scenarioTest)
.given("partnerNumber", "P-31011")
.given("debitorNumberSuffix", "00") // TODO.impl: could be assigned automatically but is not yet
.given("billable", true)
.given("vatBusiness", false)
.given("vatReverseCharge", false)
.given("defaultPrefix", "mim")
.doRun()
.given("defaultPrefix", "mim").thenExpect(HttpStatus.OK)
.keep();
}
@@ -294,6 +284,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Partner: P-31010 - Test AG")
@Produces("Debitor: D-3101000 - Test AG - main debitor")
void shouldCreateSelfDebitorForPartnerWithDistinctContactData() {
// TODO.impl: could be assigned automaticallybut is not yet
new CreateSelfDebitorForPartner(scenarioTest)
.given("partnerPersonTradeName", "Test AG")
.given("billingContactCaption", "Test AG - billing department")
@@ -304,8 +295,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("vatCountryCode", "DE")
.given("vatBusiness", true)
.given("vatReverseCharge", false)
.given("defaultPrefix", "tst")
.doRun()
.given("defaultPrefix", "tst").thenExpect(HttpStatus.OK)
.keep();
}
@@ -324,8 +314,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("vatCountryCode", "DE")
.given("vatBusiness", true)
.given("vatReverseCharge", false)
.given("defaultPrefix", "tsx")
.doRun()
.given("defaultPrefix", "tsx").thenExpect(HttpStatus.OK)
.keep();
}
@@ -344,8 +333,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("vatCountryCode", "DE")
.given("vatBusiness", true)
.given("vatReverseCharge", false)
.given("defaultPrefix", "tsy")
.doRun()
.given("defaultPrefix", "tsy").thenExpect(HttpStatus.OK)
.keep();
}
@@ -356,8 +344,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldDeleteDebitor() {
new DeleteDebitor(scenarioTest)
.given("partnerNumber", "P-31020")
.given("debitorSuffix", "02")
.doRun();
.given("debitorSuffix", "02").thenExpect(HttpStatus.OK);
}
@Test
@@ -367,8 +354,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldNotDeleteDefaultDebitor() {
new DontDeleteDefaultDebitor(scenarioTest)
.given("partnerNumber", "P-31010")
.given("debitorSuffix", "00")
.doRun();
.given("debitorSuffix", "00").thenExpect(HttpStatus.OK);
}
}
@@ -382,6 +368,9 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Debitor: D-3101000 - Test AG - main debitor")
@Produces("SEPA-Mandate: Test AG")
void shouldCreateSepaMandateForDebitor() {
// existing debitor
// new sepa-mandate
// new bank-account
new CreateSepaMandateForDebitor(scenarioTest)
// existing debitor
.given("debitorNumber", "D-3101000")
@@ -394,8 +383,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
// new bank-account
.given("bankAccountHolder", "Test AG - debit bank account")
.given("bankAccountIBAN", "DE02701500000000594937")
.given("bankAccountBIC", "SSKMDEMM")
.doRun()
.given("bankAccountBIC", "SSKMDEMM").thenExpect(HttpStatus.OK)
.keep();
}
@@ -405,8 +393,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldInvalidateSepaMandateForDebitor() {
new InvalidateSepaMandateForDebitor(scenarioTest)
.given("bankAccountIBAN", "DE02701500000000594937")
.given("mandateValidTo", "2025-09-30")
.doRun();
.given("mandateValidTo", "2025-09-30").thenExpect(HttpStatus.OK);
}
@Test
@@ -414,8 +401,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("SEPA-Mandate: Test AG")
void shouldFinallyDeleteSepaMandateForDebitor() {
new FinallyDeleteSepaMandateForDebitor(scenarioTest)
.given("bankAccountIBAN", "DE02701500000000594937")
.doRun();
.given("bankAccountIBAN", "DE02701500000000594937").thenExpect(HttpStatus.OK);
}
}
@@ -433,8 +419,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("partnerName", "Test AG")
.given("validFrom", "2020-10-15")
.given("newStatus", "ACTIVE")
.given("membershipFeeBillable", "true")
.doRun()
.given("membershipFeeBillable", "true").thenExpect(HttpStatus.OK)
.keep();
}
@@ -446,8 +431,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new CancelMembership(scenarioTest)
.given("memberNumber", "M-3101000")
.given("validTo", "2023-12-31")
.given("newStatus", "CANCELLED")
.doRun()
.given("newStatus", "CANCELLED").thenExpect(HttpStatus.OK)
.keep();
}
@@ -461,8 +445,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("memberNumberSuffix", "01")
.given("validFrom", "2025-02-24")
.given("newStatus", "ACTIVE")
.given("membershipFeeBillable", "true")
.doRun()
.given("membershipFeeBillable", "true").thenExpect(HttpStatus.OK)
.keep();
}
}
@@ -482,8 +465,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "sign 2024-01-15")
.given("shareCount", 100)
.given("comment", "Signing the Membership")
.given("transactionDate", "2024-01-15")
.doRun();
.given("transactionDate", "2024-01-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -493,8 +475,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new CreateCoopSharesRevertTransaction(scenarioTest)
.given("memberNumber", "M-3101000")
.given("comment", "reverting some incorrect transaction")
.given("dateOfIncorrectTransaction", "2024-02-15")
.doRun();
.given("dateOfIncorrectTransaction", "2024-02-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -507,8 +488,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15")
.given("sharesToCancel", 8)
.given("comment", "Cancelling 8 Shares")
.given("transactionDate", "2024-02-15")
.doRun();
.given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
}
}
@@ -527,8 +507,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "sign 2024-01-15")
.given("assetValue", 100 * 64)
.given("comment", "disposal for initial shares")
.given("transactionDate", "2024-01-15")
.doRun();
.given("transactionDate", "2024-01-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -538,8 +517,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
new CreateCoopAssetsRevertSimpleTransaction(scenarioTest)
.given("memberNumber", "M-3101000")
.given("comment", "reverting some incorrect transaction")
.given("dateOfIncorrectTransaction", "2024-02-15")
.doRun();
.given("dateOfIncorrectTransaction", "2024-02-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -552,8 +530,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15")
.given("valueToDisburse", 8 * 64)
.given("comment", "disbursal according to shares cancellation")
.given("transactionDate", "2024-02-15")
.doRun();
.given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -567,8 +544,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "transfer 2024-12-31")
.given("valueToTransfer", 2 * 64)
.given("comment", "transfer assets from M-3101000 to M-4303000")
.given("transactionDate", "2024-12-31")
.doRun();
.given("transactionDate", "2024-12-31").thenExpect(HttpStatus.OK);
}
@Test
@@ -580,8 +556,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("adoptingMemberNumber", "M-4303000")
.given("transferredValue", 2 * 64)
.given("comment", "reverting some incorrect transfer transaction")
.given("dateOfIncorrectTransaction", "2024-02-15")
.doRun();
.given("dateOfIncorrectTransaction", "2024-02-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -593,8 +568,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15")
.given("valueToClear", 2 * 64)
.given("comment", "clearing according to members debt")
.given("transactionDate", "2024-02-15")
.doRun();
.given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -606,8 +580,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15")
.given("valueLost", 2 * 64)
.given("comment", "assign balance sheet loss")
.given("transactionDate", "2024-02-15")
.doRun();
.given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
}
@Test
@@ -619,8 +592,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("reference", "cancel 2024-01-15")
.given("valueForLimitation", 2 * 64)
.given("comment", "adjust coop ")
.given("transactionDate", "2024-02-15")
.doRun();
.given("transactionDate", "2024-02-15").thenExpect(HttpStatus.OK);
}
}
@@ -634,14 +606,14 @@ class HsOfficeScenarioTests extends ScenarioTest {
@Requires("Person: Test AG")
@Produces("Subscription: Michael Miller to operations-announce")
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)
// TODO.spec: do we need the personType? or is an operational contact always a natural person? what about distribution lists?
.given("partnerPersonTradeName", "Test AG")
.given("subscriberFamilyName", "Miller")
.given("subscriberGivenName", "Michael")
.given("subscriberEMailAddress", "michael.miller@example.org")
.given("mailingList", "operations-announce")
.doRun()
.given("mailingList", "operations-announce").thenExpect(HttpStatus.OK)
.keep();
}
@@ -655,8 +627,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
.given("subscriberFamilyName", "Miller")
.given("subscriberGivenName", "Michael")
.given("subscriberEMailAddress", "michael.miller@example.org")
.given("mailingList", "operations-discussion")
.doRun()
.given("mailingList", "operations-discussion").thenExpect(HttpStatus.OK)
.keep();
}
@@ -666,8 +637,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldUnsubscribePersonAndContactFromMailinglist() {
new UnsubscribeFromMailinglist(scenarioTest)
.given("mailingList", "operations-announce")
.given("subscriberEMailAddress", "michael.miller@example.org")
.doRun();
.given("subscriberEMailAddress", "michael.miller@example.org").thenExpect(HttpStatus.OK);
}
}
@@ -694,8 +664,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
"country": "Germany"
""")
.given("communityOfHeirsOfficePhoneNumber", "+49 40 666666")
.given("communityOfHeirsEmailAddress", "lena.stadland@example.org")
.doRun();
.given("communityOfHeirsEmailAddress", "lena.stadland@example.org").thenExpect(HttpStatus.OK);
}
}
}
@@ -6,6 +6,7 @@ import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContactData> {
@@ -19,14 +20,14 @@ public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContact
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),
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."
);
withTitle("Patch the Additional Phone-Number into the Contact", () ->
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
return withTitle("Patch the Additional Phone-Number into the Contact", () ->
httpPatch(asGlobalAgent(), "/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
{
"phoneNumbers": {
${phoneNumberKeyToAdd}: ${phoneNumberToAdd}
@@ -35,15 +36,13 @@ public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContact
"""))
.expecting(HttpStatus.OK)
);
return null;
}
@Override
protected void verify(final UseCase<AddPhoneNumberToContactData>.HttpResponse response) {
verify(
"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),
path("[0].contact.phoneNumbers.%{phoneNumberKeyToAdd}").contains("%{phoneNumberToAdd}")
);
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class AmendContactData extends UseCase<AmendContactData> {
@@ -17,14 +18,14 @@ public class AmendContactData extends UseCase<AmendContactData> {
protected HttpResponse run() {
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),
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."
);
withTitle("Patch the New Phone Number Into the Contact", () ->
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
return withTitle("Patch the New Phone Number Into the Contact", () ->
httpPatch(asGlobalAgent(), "/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
{
"caption": ${newContactCaption???},
"postalAddress": {
@@ -40,7 +41,5 @@ public class AmendContactData extends UseCase<AmendContactData> {
"""))
.expecting(HttpStatus.OK)
);
return null;
}
}
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberFromContactData> {
@@ -18,14 +19,14 @@ public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberF
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),
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."
);
withTitle("Patch the Additional Phone-Number into the Contact", () ->
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
return withTitle("Patch the Additional Phone-Number into the Contact", () ->
httpPatch(asGlobalAgent(), "/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
{
"phoneNumbers": {
${phoneNumberKeyToRemove}: NULL
@@ -34,15 +35,13 @@ public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberF
"""))
.expecting(HttpStatus.OK)
);
return null;
}
@Override
protected void verify(final UseCase<RemovePhoneNumberFromContactData>.HttpResponse response) {
verify(
"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),
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 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.OK;
@@ -17,14 +18,14 @@ public class ReplaceContactData extends UseCase<ReplaceContactData> {
protected HttpResponse run() {
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),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
obtain("Contact: %{newContactCaption}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{
"caption": ${newContactCaption},
"postalAddress": {
@@ -44,23 +45,21 @@ public class ReplaceContactData extends UseCase<ReplaceContactData> {
"(currently `firm`, `name`, `co`, `street`, `zipcode`, `city`, `country`) " +
"its values might not appear in external systems.");
withTitle("Replace the Contact-Reference in the Partner-Relation", () ->
httpPatch("/api/hs/office/relations/%{partnerRelationUuid}", usingJsonBody("""
return withTitle("Replace the Contact-Reference in the Partner-Relation", () ->
httpPatch(asGlobalAgent(), "/api/hs/office/relations/%{partnerRelationUuid}", usingJsonBody("""
{
"contact.uuid": ${Contact: %{newContactCaption}}
}
"""))
.expecting(OK)
);
return null;
}
@Override
protected void verify(final UseCase<ReplaceContactData>.HttpResponse response) {
verify(
"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),
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 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.OK;
@@ -23,14 +24,14 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
obtain("BankAccount: Billing GmbH - refund bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/bankaccounts", usingJsonBody("""
{
"holder": "Billing GmbH - refund bank account",
"iban": "DE02120300000000202051",
@@ -41,7 +42,7 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
);
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",
"emailAddresses": {
@@ -52,7 +53,7 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
.expecting(CREATED).expecting(JSON)
);
return httpPost("/api/hs/office/debitors", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/debitors", usingJsonBody("""
{
"debitorRel": {
"anchor.uuid": ${Person: %{partnerPersonTradeName}},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
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.OK;
@@ -16,14 +17,14 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
@Override
protected HttpResponse run() {
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),
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."
);
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",
"iban": "DE88100900001234567892",
@@ -34,7 +35,7 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
);
obtain("Contact: Test AG - billing department", () ->
httpPost("/api/hs/office/contacts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{
"caption": ${billingContactCaption},
"emailAddresses": {
@@ -45,7 +46,7 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
.expecting(CREATED).expecting(JSON)
);
return httpPost("/api/hs/office/debitors", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/debitors", usingJsonBody("""
{
"debitorRel": {
"anchor.uuid": ${partnerPersonUuid},
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED;
public class CreateSelfDebitorForPartnerWithIdenticalContactData
@@ -17,13 +18,13 @@ public class CreateSelfDebitorForPartnerWithIdenticalContactData
@Override
protected HttpResponse run() {
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)
.extractUuidAlias("partnerRel.holder.uuid", "partnerPersonUuid")
.extractUuidAlias("partnerRel.contact.uuid", "partnerContactUuid")
);
return httpPost("/api/hs/office/debitors", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/debitors", usingJsonBody("""
{
"debitorRel": {
"anchor.uuid": ${partnerPersonUuid},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
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.OK;
@@ -17,13 +18,13 @@ public class CreateSepaMandateForDebitor extends UseCase<CreateSepaMandateForDeb
protected HttpResponse run() {
obtain("Debitor: Test AG - main debitor", () ->
httpGet("/api/hs/office/debitors/%{debitorNumber}")
httpGet(asGlobalAgent(), "/api/hs/office/debitors/%{debitorNumber}")
.expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid")
);
obtain("BankAccount: Test AG - debit bank account", () ->
httpPost("/api/hs/office/bankaccounts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/bankaccounts", usingJsonBody("""
{
"holder": ${bankAccountHolder},
"iban": ${bankAccountIBAN},
@@ -33,7 +34,7 @@ public class CreateSepaMandateForDebitor extends UseCase<CreateSepaMandateForDeb
.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},
"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 org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class DeleteDebitor extends UseCase<DeleteDebitor> {
public DeleteDebitor(final ScenarioTest testSuite) {
@@ -24,10 +26,9 @@ public class DeleteDebitor extends UseCase<DeleteDebitor> {
@Override
protected HttpResponse run() {
withTitle("Delete the Debitor using its UUID", () ->
httpDelete("/api/hs/office/debitors/&{Debitor: Test AG - delete debitor}")
return withTitle("Delete the Debitor using its UUID", () ->
httpDelete(asGlobalAgent(), "/api/hs/office/debitors/&{Debitor: Test AG - delete debitor}")
.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 org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class DontDeleteDefaultDebitor extends UseCase<DontDeleteDefaultDebitor> {
public DontDeleteDefaultDebitor(final ScenarioTest testSuite) {
@@ -12,7 +14,7 @@ public class DontDeleteDefaultDebitor extends UseCase<DontDeleteDefaultDebitor>
@Override
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?
.expecting(HttpStatus.NO_CONTENT);
return null;
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class FinallyDeleteSepaMandateForDebitor extends UseCase<FinallyDeleteSepaMandateForDebitor> {
@@ -17,14 +18,15 @@ public class FinallyDeleteSepaMandateForDebitor extends UseCase<FinallyDeleteSep
protected HttpResponse run() {
obtain("SEPA-Mandate: %{bankAccountIBAN}", () ->
httpGet("/api/hs/office/sepamandates?iban=&{bankAccountIBAN}")
httpGet(asGlobalAgent(), "/api/hs/office/sepamandates?iban=&{bankAccountIBAN}")
.expecting(OK).expecting(JSON),
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!"
);
// 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)
);
}
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaMandateForDebitor> {
@@ -16,14 +17,14 @@ public class InvalidateSepaMandateForDebitor extends UseCase<InvalidateSepaManda
protected HttpResponse run() {
obtain("SEPA-Mandate: %{bankAccountIBAN}", () ->
httpGet("/api/hs/office/sepamandates?iban=&{bankAccountIBAN}")
httpGet(asGlobalAgent(), "/api/hs/office/sepamandates?iban=&{bankAccountIBAN}")
.expecting(OK).expecting(JSON),
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!"
);
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}
}
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class CancelMembership extends UseCase<CancelMembership> {
@@ -18,12 +19,12 @@ public class CancelMembership extends UseCase<CancelMembership> {
protected HttpResponse run() {
obtain("Membership: %{memberNumber}", () ->
httpGet("/api/hs/office/memberships/%{memberNumber}"),
httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{memberNumber}"),
response -> response.getFromBody("uuid")
);
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},
"status": ${newStatus}
@@ -37,7 +38,7 @@ public class CancelMembership extends UseCase<CancelMembership> {
protected void verify(final UseCase<CancelMembership>.HttpResponse response) {
verify(
"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),
path("validTo").contains("%{validTo}"),
path("status").contains("CANCELLED")
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class CreateMembership extends UseCase<CreateMembership> {
@@ -18,13 +19,13 @@ public class CreateMembership extends UseCase<CreateMembership> {
protected HttpResponse run() {
obtain("Partner: %{partnerName}", () ->
httpGet("/api/hs/office/partners?name=&{partnerName}")
httpGet(asGlobalAgent(), "/api/hs/office/partners?name=&{partnerName}")
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
return httpPost("/api/hs/office/memberships", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/memberships", usingJsonBody("""
{
"partner.uuid": ${Partner: %{partnerName}},
"memberNumberSuffix": ${%{memberNumberSuffix???}???00},
@@ -40,7 +41,7 @@ public class CreateMembership extends UseCase<CreateMembership> {
protected void verify(final UseCase<CreateMembership>.HttpResponse response) {
verify(
"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),
path("validFrom").contains("%{validFrom}"),
path("status").contains("ACTIVE")
@@ -7,6 +7,7 @@ import org.springframework.http.HttpStatus;
import java.math.BigDecimal;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static net.hostsharing.hsadminng.hs.scenarios.ScenarioTest.resolveTyped;
public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsTransaction {
@@ -41,7 +42,7 @@ public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsT
given("negativeAssetValue", resolveTyped("%{transferredValue}", BigDecimal.class).negate());
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),
path("assetValue").contains("%{negativeAssetValue}"),
path("comment").contains("%{comment}"),
@@ -51,7 +52,7 @@ public class CreateCoopAssetsRevertTransferTransaction extends CreateCoopAssetsT
final var adoptionAssetTxUuid = response.getFromBody("revertedAssetTx.['adoptionAssetTx.uuid']");
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),
path("reversalAssetTx.['transferAssetTx.uuid']").contains(revertedAssetTxUuid.toString())
);
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAssetsTransaction> {
@@ -18,13 +19,13 @@ public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAsse
protected HttpResponse run() {
obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships/%{memberNumber}")
httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{memberNumber}")
.expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid")
);
return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopassetstransactions", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/coopassetstransactions", usingJsonBody("""
{
"membership.uuid": ${membershipUuid},
"transactionType": ${transactionType},
@@ -43,7 +44,7 @@ public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAsse
@Override
protected void verify(final HttpResponse response) {
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),
path("transactionType").contains("%{transactionType}"),
path("assetValue").contains("%{assetValue}"),
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopSharesTransaction> {
@@ -18,13 +19,13 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
protected HttpResponse run() {
obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships/%{memberNumber}")
httpGet(asGlobalAgent(), "/api/hs/office/memberships/%{memberNumber}")
.expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid")
);
return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopsharestransactions", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/coopsharestransactions", usingJsonBody("""
{
"membership.uuid": ${membershipUuid},
"transactionType": ${transactionType},
@@ -42,7 +43,7 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
@Override
protected void verify(final HttpResponse response) {
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),
path("transactionType").contains("%{transactionType}"),
path("shareCount").contains("%{shareCount}"),
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
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.OK;
@@ -19,14 +20,14 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
obtain("Person: %{operationsContactGivenName} %{operationsContactFamilyName}", () ->
httpPost("/api/hs/office/persons", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{
"personType": "NATURAL_PERSON",
"familyName": ${operationsContactFamilyName},
@@ -39,7 +40,7 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
);
obtain("Contact: %{operationsContactGivenName} %{operationsContactFamilyName}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{
"caption": "%{operationsContactGivenName} %{operationsContactFamilyName}",
"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."
);
return httpPost("/api/hs/office/relations", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{
"type": "OPERATIONS",
"anchor.uuid": ${Person: %{partnerPersonTradeName}},
@@ -69,7 +70,7 @@ public class AddOperationsContactToPartner extends UseCase<AddOperationsContactT
protected void verify(final UseCase<AddOperationsContactToPartner>.HttpResponse response) {
verify(
"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}"))
.expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].contact.caption").contains("%{operationsContactGivenName} %{operationsContactFamilyName}")
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
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.OK;
@@ -19,14 +20,14 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
obtain("Person: %{representativeGivenName} %{representativeFamilyName}", () ->
httpPost("/api/hs/office/persons", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{
"personType": "NATURAL_PERSON",
"familyName": ${representativeFamilyName},
@@ -39,7 +40,7 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
);
obtain("Contact: %{representativeGivenName} %{representativeFamilyName}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{
"caption": "%{representativeGivenName} %{representativeFamilyName}",
"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."
);
return httpPost("/api/hs/office/relations", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{
"type": "REPRESENTATIVE",
"anchor.uuid": ${Person: %{partnerPersonTradeName}},
@@ -72,7 +73,7 @@ public class AddRepresentativeToPartner extends UseCase<AddRepresentativeToPartn
protected void verify(final UseCase<AddRepresentativeToPartner>.HttpResponse response) {
verify(
"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),
path("[0].contact.caption").contains("%{representativeGivenName} %{representativeFamilyName}")
);
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class CreatePartner extends UseCase<CreatePartner> {
@@ -24,14 +25,14 @@ public class CreatePartner extends UseCase<CreatePartner> {
protected HttpResponse run() {
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),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"Even in production data we expect this query to return just a single result." // TODO.impl: add constraint?
);
obtain("Person: %{%{tradeName???}???%{givenName???} %{familyName???}}", () ->
httpPost("/api/hs/office/persons", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{
"personType": ${personType???},
"tradeName": ${tradeName???},
@@ -43,7 +44,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
);
obtain("Contact: %{contactCaption}", () ->
httpPost("/api/hs/office/contacts", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/contacts", usingJsonBody("""
{
"caption": ${contactCaption},
"postalAddress": {
@@ -60,7 +61,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
);
return httpPost("/api/hs/office/partners", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/partners", usingJsonBody("""
{
"partnerNumber": ${partnerNumber},
"partnerRel": {
@@ -84,7 +85,7 @@ public class CreatePartner extends UseCase<CreatePartner> {
protected void verify(final UseCase<CreatePartner>.HttpResponse response) {
verify(
"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)
);
}
@@ -4,6 +4,8 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class DeletePartner extends UseCase<DeletePartner> {
public DeletePartner(final ScenarioTest testSuite) {
@@ -18,10 +20,9 @@ public class DeletePartner extends UseCase<DeletePartner> {
@Override
protected HttpResponse run() {
withTitle("Delete Partner by its UUID", () ->
httpDelete("/api/hs/office/partners/&{Partner: Delete AG}")
return withTitle("Delete Partner by its UUID", () ->
httpDelete(asGlobalAgent(), "/api/hs/office/partners/&{Partner: Delete AG}")
.expecting(HttpStatus.NO_CONTENT)
);
return null;
}
}
@@ -1,10 +1,12 @@
package net.hostsharing.hsadminng.hs.office.scenarios.partner;
import lombok.val;
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 net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
@@ -18,7 +20,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
protected HttpResponse run() {
obtain("Partner: %{partnerNumber}",
() -> httpGet("/api/hs/office/partners/%{partnerNumber}")
() -> httpGet(asGlobalAgent(), "/api/hs/office/partners/%{partnerNumber}")
.reportWithResponse().expecting(OK).expecting(JSON),
response -> response.getFromBody("uuid"),
"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",
"Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}");
withTitle("New Partner-Person+Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
() -> httpPatch("/api/hs/office/partners/%{Partner: %{partnerNumber}}",
val result = withTitle("New Partner-Person+Contact: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
() -> httpPatch(asGlobalAgent(), "/api/hs/office/partners/%{Partner: %{partnerNumber}}",
usingJsonBody("""
{
"partnerRel": {
@@ -68,7 +70,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
obtain(
"Representative-Relation: %{representativeGivenName} %{representativeFamilyName} for Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}",
() -> httpPost("/api/hs/office/relations",
() -> httpPost(asGlobalAgent(), "/api/hs/office/relations",
usingJsonBody("""
{
"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
// nächster "Drecksfall"
return null;
return result;
}
@Override
protected void verify(final UseCase<ReplaceDeceasedPartnerWithCommunityOfHeirs>.HttpResponse response) {
verify(
"Verify the Updated Partner",
() -> httpGet("/api/hs/office/partners/%{partnerNumber}")
() -> httpGet(asGlobalAgent(), "/api/hs/office/partners/%{partnerNumber}")
.expecting(OK).expecting(JSON).expectObject(),
path("partnerRel.holder.tradeName").contains(
"Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}"),
@@ -106,7 +108,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify(
"Verify the Ex-Partner-Relation",
() -> httpGet(
() -> httpGet(asGlobalAgent(),
"/api/hs/office/relations?relationType=EX_PARTNER&personUuid=%{Person: %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}")
.expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].anchor.tradeName").contains(
@@ -115,7 +117,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify(
"Verify the Representative-Relation",
() -> httpGet(
() -> httpGet(asGlobalAgent(),
"/api/hs/office/relations?relationType=REPRESENTATIVE&personUuid=%{Person: Erbengemeinschaft %{givenNameOfDeceasedPerson} %{familyNameOfDeceasedPerson}}")
.expecting(OK).expecting(JSON).expectArrayElements(1),
path("[0].anchor.tradeName").contains(
@@ -128,7 +130,7 @@ public class ReplaceDeceasedPartnerWithCommunityOfHeirs extends UseCase<ReplaceD
verify(
"Verify the Debitor-Relation",
() -> httpGet(
() -> httpGet(asGlobalAgent(),
"/api/hs/office/debitors?partnerNumber=%{partnerNumber}")
.expecting(OK).expecting(JSON).expectArrayElements(1),
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 org.springframework.http.HttpStatus;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
public class CreatePerson extends UseCase<CreatePerson> {
public CreatePerson(final ScenarioTest testSuite, final String resultAlias) {
@@ -15,7 +17,7 @@ public class CreatePerson extends UseCase<CreatePerson> {
protected HttpResponse run() {
return withTitle("Create the Person", () ->
httpPost("/api/hs/office/persons", usingJsonBody("""
httpPost(asGlobalAgent(), "/api/hs/office/persons", usingJsonBody("""
{
"personType": ${personType},
"tradeName": ${tradeName}
@@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static net.hostsharing.hsadminng.hs.scenarios.FakeLoginUser.asGlobalAgent;
import static org.springframework.http.HttpStatus.OK;
public class ShouldUpdatePersonData extends UseCase<ShouldUpdatePersonData> {
@@ -18,29 +19,28 @@ public class ShouldUpdatePersonData extends UseCase<ShouldUpdatePersonData> {
obtain(
"personUuid",
() -> httpGet("/api/hs/office/persons?name=" + uriEncoded("%{oldFamilyName}"))
() -> httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{oldFamilyName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
withTitle("Patch the Additional Phone-Number into the Person", () ->
httpPatch("/api/hs/office/persons/%{personUuid}", usingJsonBody("""
return withTitle("Patch the Additional Phone-Number into the Person", () ->
httpPatch(
asGlobalAgent(), "/api/hs/office/persons/%{personUuid}", usingJsonBody("""
{
"familyName": ${newFamilyName}
}
"""))
.expecting(HttpStatus.OK)
);
return null;
}
@Override
protected void verify(final UseCase<ShouldUpdatePersonData>.HttpResponse response) {
verify(
"Verify that the Family Name Got Amended",
() -> httpGet("/api/hs/office/persons/%{personUuid}")
() -> httpGet(asGlobalAgent(), "/api/hs/office/persons/%{personUuid}")
.expecting(OK).expecting(JSON),
path("familyName").contains("%{newFamilyName}")
);
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.scenarios.UseCase;
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.NO_CONTENT;
import static org.springframework.http.HttpStatus.OK;
@@ -19,7 +20,7 @@ public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperations
obtain("Operations-Contact: %{operationsContactPerson}",
() ->
httpGet("/api/hs/office/relations?relationType=OPERATIONS&name=" + uriEncoded(
httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=OPERATIONS&name=" + uriEncoded(
"%{operationsContactPerson}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
@@ -27,7 +28,7 @@ public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperations
);
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)
);
}
@@ -36,7 +37,7 @@ public class RemoveOperationsContactFromPartner extends UseCase<RemoveOperations
protected void verify(final UseCase<RemoveOperationsContactFromPartner>.HttpResponse response) {
verify(
"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)
);
}
@@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import org.springframework.http.HttpStatus;
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.OK;
@@ -19,7 +20,7 @@ public class SubscribeExistingPersonAndContactToMailinglist extends UseCase<Subs
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
@@ -27,20 +28,20 @@ public class SubscribeExistingPersonAndContactToMailinglist extends UseCase<Subs
obtain(
"Person: %{subscriberGivenName} %{subscriberFamilyName}", () ->
httpGet("/api/hs/office/persons?name=%{subscriberFamilyName}")
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=%{subscriberFamilyName}")
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In real scenarios there are most likely multiple results and you have to choose the right one."
);
obtain("Contact: %{subscriberEMailAddress}", () ->
httpGet("/api/hs/office/contacts?emailAddress=%{subscriberEMailAddress}")
httpGet(asGlobalAgent(), "/api/hs/office/contacts?emailAddress=%{subscriberEMailAddress}")
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"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",
"mark": ${mailingList},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
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.OK;
@@ -17,13 +18,13 @@ public class SubscribeNewPersonAndContactToMailinglist extends UseCase<Subscribe
protected HttpResponse run() {
obtain("Person: %{partnerPersonTradeName}", () ->
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
httpGet(asGlobalAgent(), "/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
.expecting(OK).expecting(JSON),
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
);
return httpPost("/api/hs/office/relations", usingJsonBody("""
return httpPost(asGlobalAgent(), "/api/hs/office/relations", usingJsonBody("""
{
"type": "SUBSCRIBER",
"mark": ${mailingList},
@@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.scenarios.UseCase;
import net.hostsharing.hsadminng.hs.scenarios.ScenarioTest;
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.OK;
@@ -17,7 +18,7 @@ public class UnsubscribeFromMailinglist extends UseCase<UnsubscribeFromMailingli
protected HttpResponse run() {
obtain("Subscription: %{subscriberEMailAddress}", () ->
httpGet("/api/hs/office/relations?relationType=SUBSCRIBER" +
httpGet(asGlobalAgent(), "/api/hs/office/relations?relationType=SUBSCRIBER" +
"&mark=" + uriEncoded("%{mailingList}") +
"&contactData=" + uriEncoded("%{subscriberEMailAddress}"))
.expecting(OK).expecting(JSON),
@@ -26,7 +27,7 @@ public class UnsubscribeFromMailinglist extends UseCase<UnsubscribeFromMailingli
);
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)
);
}
@@ -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)
@Retention(RUNTIME)
public @interface Requires {
String value();
String[] value();
}
@@ -31,7 +31,9 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -62,8 +64,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(IgnoreOnFailureExtension.class)
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<>();
protected ScenarioTest scenarioTest = this;
@@ -116,20 +116,17 @@ public abstract class ScenarioTest extends ContextBasedTest {
@SneakyThrows
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(Requires::value)
.orElse(null);
if (testMethodRequires != null) {
.filter(Objects::nonNull)
.flatMap(annotation -> Stream.of(annotation.value()))
.collect(Collectors.toSet());
if (!testMethodRequires.isEmpty()) {
for (Method potentialProducerMethod : getPotentialProducerMethods()) {
final var producesAnnot = potentialProducerMethod.getAnnotation(Produces.class);
final var testMethodProduces = producedAliases(producesAnnot);
// @formatter:off
if ( // that method can produce something required
testMethodProduces.contains(testMethodRequires) &&
// and it does not produce anything we already have (would cause errors)
SetUtils.intersection(testMethodProduces, knowVariables().keySet()).isEmpty()
if ( thatMethodProducesSomethingRequired(testMethodProduces, testMethodRequires) &&
thatMethodDoesNotProduceAnythingWeAlreadyHave(testMethodProduces)
) {
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
invokeProducerMethod(this, potentialProducerMethod);
}
// @formatter:on
}
assertThat(knowVariables().containsKey(testMethodRequires))
assertThat(haveIntersection(knowVariables().keySet(), testMethodRequires))
.as("no @Producer for @Required(\"" + testMethodRequires + "\") found")
.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) {
final var producesAnnot = currentTestMethod.getAnnotation(Produces.class);
if (producesAnnot != null) {
@@ -37,7 +37,6 @@ import static java.net.URLEncoder.encode;
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.KEEP_COMMENTS;
import static net.hostsharing.hsadminng.config.JwtFakeBearer.bearer;
import static net.hostsharing.hsadminng.test.DebuggerDetection.isDebuggerAttached;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
@@ -76,7 +75,7 @@ public abstract class UseCase<T extends UseCase<?>> {
requirements.put(alias, useCaseFactory);
}
public final HttpResponse doRun() {
public final HttpResponse thenExpect(final HttpStatus expectedStatus) {
if (introduction != null) {
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);
}
keepInProduceAlias(response);
resetProperties();
@@ -104,7 +106,14 @@ public abstract class UseCase<T extends UseCase<?>> {
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) {
}
@@ -171,20 +180,22 @@ public abstract class UseCase<T extends UseCase<?>> {
}
@SneakyThrows
public final HttpResponse httpGet(final String uriPathWithPlaceholders) {
public final HttpResponse httpGet(final FakeLoginUser loginUser, final String uriPathWithPlaceholders) {
return httpGet(uriPathWithPlaceholders,
req -> req.header("Authorization", bearer(ScenarioTest.RUN_AS_USER)));
req -> req.header("Authorization", loginUser.bearer()));
}
@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 requestBody = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder()
.POST(BodyPublishers.ofString(requestBody))
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json")
.header("Authorization", bearer(ScenarioTest.RUN_AS_USER))
.header("Authorization", loginUser.bearer())
.timeout(seconds(HTTP_TIMEOUT_SECONDS))
.build();
final var response = client.send(request, BodyHandlers.ofString());
@@ -192,14 +203,17 @@ public abstract class UseCase<T extends UseCase<?>> {
}
@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 requestBody = bodyJsonTemplate.resolvePlaceholders();
final var request = HttpRequest.newBuilder()
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody))
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json")
.header("Authorization", bearer(ScenarioTest.RUN_AS_USER))
.header("Authorization", loginUser.bearer())
.timeout(seconds(HTTP_TIMEOUT_SECONDS))
.build();
final var response = client.send(request, BodyHandlers.ofString());
@@ -207,13 +221,13 @@ public abstract class UseCase<T extends UseCase<?>> {
}
@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 request = HttpRequest.newBuilder()
.DELETE()
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
.header("Content-Type", "application/json")
.header("Authorization", bearer(ScenarioTest.RUN_AS_USER))
.header("Authorization", loginUser.bearer())
.timeout(seconds(HTTP_TIMEOUT_SECONDS))
.build();
final var response = client.send(request, BodyHandlers.ofString());