HsOfficePartnerControllerAcceptanceTest against real repo+db

This commit is contained in:
Michael Hoennig 2022-09-14 09:24:19 +02:00
parent bc27e6dc89
commit a3d2dd3db1
5 changed files with 223 additions and 97 deletions

View File

@ -3,12 +3,14 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.Mapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartnersApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerUpdateResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
@ -31,45 +33,21 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
@Autowired
private HsOfficePartnerRepository partnerRepo;
@Autowired
private HsOfficePersonRepository personRepo;
@Autowired
private HsOfficeContactRepository contactRepo;
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsOfficePartnerResource>> listPartners(
final String currentUser,
final String assumedRoles,
final String name) {
// TODO.feat: context.define(currentUser, assumedRoles);
context.define(currentUser, assumedRoles);
// TODO.feat: final var entities = partnerRepo.findPartnerByOptionalNameLike(name);
final var entities = List.of(
HsOfficePartnerEntity.builder()
.uuid(UUID.randomUUID())
.person(HsOfficePersonEntity.builder()
.tradeName("Ixx AG")
.build())
.contact(HsOfficeContactEntity.builder()
.label("Ixx AG")
.build())
.build(),
HsOfficePartnerEntity.builder()
.uuid(UUID.randomUUID())
.person(HsOfficePersonEntity.builder()
.tradeName("Ypsilon GmbH")
.build())
.contact(HsOfficeContactEntity.builder()
.label("Ypsilon GmbH")
.build())
.build(),
HsOfficePartnerEntity.builder()
.uuid(UUID.randomUUID())
.person(HsOfficePersonEntity.builder()
.tradeName("Zett OHG")
.build())
.contact(HsOfficeContactEntity.builder()
.label("Zett OHG")
.build())
.build()
);
final var entities = partnerRepo.findPartnerByOptionalNameLike(name);
final var resources = Mapper.mapList(entities, HsOfficePartnerResource.class,
PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER);
@ -83,14 +61,27 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final String assumedRoles,
final HsOfficePartnerResource body) {
// TODO.feat: context.define(currentUser, assumedRoles);
context.define(currentUser, assumedRoles);
if (body.getUuid() == null) {
body.setUuid(UUID.randomUUID());
}
// TODO.feat: final var saved = partnerRepo.save(map(body, HsOfficePartnerEntity.class));
final var saved = map(body, HsOfficePartnerEntity.class, PARTNER_RESOURCE_TO_ENTITY_POSTMAPPER);
final var entityToSave = map(body, HsOfficePartnerEntity.class);
if (entityToSave.getContact().getUuid() != null) {
contactRepo.findByUuid(entityToSave.getContact().getUuid()).ifPresent(entityToSave::setContact);
} else {
entityToSave.getContact().setUuid(UUID.randomUUID());
entityToSave.setContact(contactRepo.save(entityToSave.getContact()));
}
if (entityToSave.getPerson().getUuid() != null) {
personRepo.findByUuid(entityToSave.getPerson().getUuid()).ifPresent(entityToSave::setPerson);
} else {
entityToSave.getPerson().setUuid(UUID.randomUUID());
entityToSave.setPerson(personRepo.save(entityToSave.getPerson()));
}
final var saved = partnerRepo.save(entityToSave);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@ -103,37 +94,29 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<HsOfficePartnerResource> getPartnerByUuid(
final String currentUser,
final String assumedRoles,
final UUID partnerUuid) {
// TODO.feat: context.define(currentUser, assumedRoles);
context.define(currentUser, assumedRoles);
// TODO.feat: final var result = partnerRepo.findByUuid(partnerUuid);
final var result =
partnerUuid.equals(UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6")) ? null :
HsOfficePartnerEntity.builder()
.uuid(UUID.randomUUID())
.person(HsOfficePersonEntity.builder()
.tradeName("Ixx AG")
.build())
.contact(HsOfficeContactEntity.builder()
.label("Ixx AG")
.build())
.build();
if (result == null) {
final var result = partnerRepo.findByUuid(partnerUuid);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(map(result, HsOfficePartnerResource.class, PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER));
return ResponseEntity.ok(map(result.get(), HsOfficePartnerResource.class, PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER));
}
@Override
@Transactional
public ResponseEntity<Void> deletePartnerByUuid(final String currentUser, final String assumedRoles, final UUID userUuid) {
return null;
}
@Override
@Transactional
public ResponseEntity<HsOfficePartnerResource> updatePartner(
final String currentUser,
final String assumedRoles,

View File

@ -45,7 +45,7 @@ end; $$;
Creates the roles and their assignments for a new contact for the AFTER INSERT TRIGGER.
*/
create or replace function createRbacRolesForhsOfficeContact()
create or replace function createRbacRolesForHsOfficeContact()
returns trigger
language plpgsql
strict as $$
@ -88,11 +88,11 @@ end; $$;
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
*/
create trigger createRbacRolesForhsOfficeContact_Trigger
create trigger createRbacRolesForHsOfficeContact_Trigger
after insert
on hs_office_contact
for each row
execute procedure createRbacRolesForhsOfficeContact();
execute procedure createRbacRolesForHsOfficeContact();
--//

View File

@ -5,6 +5,9 @@ import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.test.JpaAttempt;
import org.json.JSONException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -12,16 +15,20 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid;
import static net.hostsharing.test.JsonBuilder.jsonObject;
import static net.hostsharing.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = HsadminNgApplication.class
classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@Transactional
class HsOfficePartnerControllerAcceptanceTest {
@ -34,31 +41,47 @@ class HsOfficePartnerControllerAcceptanceTest {
@Autowired
Context contextMock;
@Autowired
HsOfficePartnerRepository partnerRepository;
HsOfficePartnerRepository partnerRepo;
@Autowired
JpaAttempt jpaAttempt;
Set<UUID> tempPartnerUuids = new HashSet<>();
@Nested
@Accepts({ "Partner:F(Find)" })
class ListPartners {
@Test
void testHostsharingAdmin_withoutAssumedRoles_canViewAllPartners_ifNoCriteriaGiven() {
void globalAdmin_withoutAssumedRoles_canViewAllPartners_ifNoCriteriaGiven() throws JSONException {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("current-user", "alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/office/partners")
.then().assertThat()
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].contact.label", is("Ixx AG"))
.body("[0].person.tradeName", is("Ixx AG"))
.body("[1].contact.label", is("Ypsilon GmbH"))
.body("[1].person.tradeName", is("Ypsilon GmbH"))
.body("[2].contact.label", is("Zett OHG"))
.body("[2].person.tradeName", is("Zett OHG"))
.body("size()", greaterThanOrEqualTo(3));
.body("", lenientlyEquals("""
[
{
"person": { "tradeName": "First Impressions GmbH" },
"contact": { "label": "first contact" }
},
{
"person": { "tradeName": "Ostfriesische Kuhhandel OHG" },
"contact": { "label": "third contact" }
},
{
"person": { "tradeName": "Rockshop e.K." },
"contact": { "label": "second contact" }
}
]
"""));
// @formatter:on
}
}
@ -91,13 +114,13 @@ class HsOfficePartnerControllerAcceptanceTest {
""";
@Test
void hostsharingAdmin_withoutAssumedRole_canAddPartner_withExplicitUuid() {
void globalAdmin_withoutAssumedRole_canAddPartner_withExplicitUuid() {
final var givenUUID = UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6");
final var givenUUID = toCleanup(UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6"));
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("current-user", "alex@hostsharing.net")
.contentType(ContentType.JSON)
.body(jsonObject(NEW_PARTNER_JSON_WITHOUT_UUID)
.with("uuid", givenUUID.toString()).toString())
@ -110,24 +133,25 @@ class HsOfficePartnerControllerAcceptanceTest {
.body("uuid", is("3fa85f64-5717-4562-b3fc-2c963f66afa6"))
.body("registrationNumber", is("123456"))
.body("person.tradeName", is("Test Corp."))
.body("contact.label", is("Test Corp."))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new partner can be viewed by its own admin
// finally, the new partner can be accessed under the given UUID
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isEqualTo(givenUUID);
// TODO.feat: context.define("partner-admin@ttt.example.com");
// assertThat(partnerRepository.findByUuid(newUserUuid))
// .hasValueSatisfying(c -> assertThat(c.getPerson().getTradeName()).isEqualTo("Test Corp."));
context.define("alex@hostsharing.net");
assertThat(partnerRepo.findByUuid(newUserUuid))
.hasValueSatisfying(c -> assertThat(c.getPerson().getTradeName()).isEqualTo("Test Corp."));
}
@Test
void hostsharingAdmin_withoutAssumedRole_canAddPartner_withGeneratedUuid() {
void globalAdmin_withoutAssumedRole_canAddPartner_withGeneratedUuid() {
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("current-user", "alex@hostsharing.net")
.contentType(ContentType.JSON)
.body(NEW_PARTNER_JSON_WITHOUT_UUID)
.port(port)
@ -142,13 +166,10 @@ class HsOfficePartnerControllerAcceptanceTest {
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new partner can be viewed by its own admin
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
// finally, the new partner can be accessed under the generated UUID
final var newUserUuid = toCleanup(UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)));
assertThat(newUserUuid).isNotNull();
// TODO.feat: context.define("partner-admin@ttt.example.com");
// assertThat(partnerRepository.findByUuid(newUserUuid))
// .hasValueSatisfying(c -> assertThat(c.getPerson().getTradeName()).isEqualTo("Test Corp."));
}
}
@ -157,39 +178,82 @@ class HsOfficePartnerControllerAcceptanceTest {
class GetPartner {
@Test
void hostsharingAdmin_withoutAssumedRole_canGetArbitraryPartner() {
// TODO.feat: final var givenPartnerUuid = partnerRepository.findPartnerByOptionalNameLike("Ixx").get(0).getUuid();
final var givenPartnerUuid = UUID.randomUUID();
void globalAdmin_withoutAssumedRole_canGetArbitraryPartner() {
context.define("alex@hostsharing.net");
final var givenPartnerUuid = partnerRepo.findPartnerByOptionalNameLike("First").get(0).getUuid();
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("current-user", "alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/office/partners/" + givenPartnerUuid)
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("person.tradeName", is("Ixx AG"))
.body("contact.label", is("Ixx AG"));
// @formatter:on
.body("", lenientlyEquals("""
{
"person": { "tradeName": "First Impressions GmbH" },
"contact": { "label": "first contact" }
}
""")); // @formatter:on
}
@Test
@Accepts({ "Partner:X(Access Control)" })
void normalUser_canNotGetUnrelatedPartner() {
// TODO.feat: final var givenPartnerUuid = partnerRepository.findPartnerByOptionalNameLike("Ixx").get(0).getUuid();
final UUID givenPartnerUuid = UUID.fromString("3fa85f64-5717-4562-b3fc-2c963f66afa6");
context.define("alex@hostsharing.net");
final var givenPartnerUuid = partnerRepo.findPartnerByOptionalNameLike("First").get(0).getUuid();
RestAssured // @formatter:off
.given()
.header("current-user", "somebody@example.org")
.header("current-user", "drew@hostsharing.org")
.port(port)
.when()
.get("http://localhost/api/hs/office/partners/" + givenPartnerUuid)
.then().log().body().assertThat()
.statusCode(404);
// @formatter:on
.statusCode(404); // @formatter:on
}
@Test
@Accepts({ "Partner:X(Access Control)" })
void contactAdminUser_canGetRelatedPartner() {
context.define("alex@hostsharing.net");
final var givenPartnerUuid = partnerRepo.findPartnerByOptionalNameLike("first contact").get(0).getUuid();
RestAssured // @formatter:off
.given()
.header("current-user", "customer-admin@firstcontact.example.com")
.port(port)
.when()
.get("http://localhost/api/hs/office/partners/" + givenPartnerUuid)
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"person": { "tradeName": "First Impressions GmbH" },
"contact": { "label": "first contact" }
}
""")); // @formatter:on
}
}
private UUID toCleanup(final UUID tempPartnerUuid) {
tempPartnerUuids.add(tempPartnerUuid);
return tempPartnerUuid;
}
@AfterEach
void cleanup() {
tempPartnerUuids.forEach(uuid -> {
jpaAttempt.transacted(() -> {
context.define("alex@hostsharing.net", null);
System.out.println("DELETING temporary partner: " + uuid);
final var count = partnerRepo.deleteByUuid(uuid);
assertThat(count).isGreaterThan(0);
});
});
}
}

View File

@ -9,10 +9,11 @@ import java.util.function.Predicate;
public class IsValidUuidMatcher extends BaseMatcher<CharSequence> {
public static Matcher<CharSequence> isUuidValid() {
return new IsValidUuidMatcher();
}
/**
* Checks if the given String represents a valid UUID.
* @param actual the given String
* @return true if valid UUID, false otherwise
*/
public static boolean isUuidValid(final String actual) {
try {
UUID.fromString(actual);
@ -22,6 +23,20 @@ public class IsValidUuidMatcher extends BaseMatcher<CharSequence> {
return true;
}
/**
* Creates a matcher for RestAssured to validate if String is a valid UUID.
*
* @return the RestAssuredMatcher
*/
public static Matcher<CharSequence> isUuidValid() {
return new IsValidUuidMatcher();
}
/**
* Creates matcher for AssertJ to validate if String is a valid UUID.
*
* @return the Predicate to be used as a matcher in AssertJ
*/
public static Predicate<String> isValidUuid() {
return IsValidUuidMatcher::isUuidValid;
}

View File

@ -0,0 +1,64 @@
package net.hostsharing.test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.json.JSONException;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;
public class JsonMatcher extends BaseMatcher<CharSequence> {
private final String expected;
private JSONCompareMode compareMode;
public JsonMatcher(final String expected, final JSONCompareMode compareMode) {
this.expected = expected;
this.compareMode = compareMode;
}
/**
* Creates a matcher for RestAssured to validate if the actual JSON matches the expected JSON leniently.
*
* @see package org.skyscreamer.jsonassert.JSONCompareMode.LENIENT
*
* @return the RestAssuredMatcher
*/
public static Matcher<CharSequence> lenientlyEquals(final String expected) {
return new JsonMatcher(expected, JSONCompareMode.LENIENT);
}
/**
* Creates a matcher for RestAssured to validate if the actual JSON matches the expected JSON strictly.
*
* @see package org.skyscreamer.jsonassert.JSONCompareMode.STRICT
*
* @return the RestAssuredMatcher
*/
public static Matcher<CharSequence> strictlyEquals(final String expected) {
return new JsonMatcher(expected, JSONCompareMode.STRICT);
}
@Override
public boolean matches(final Object actual) {
if (actual == null || actual.getClass().isAssignableFrom(CharSequence.class)) {
return false;
}
try {
final var actualJson = new ObjectMapper().writeValueAsString(actual);
compareMode = JSONCompareMode.LENIENT;
JSONAssert.assertEquals(expected, actualJson, compareMode);
return true;
} catch (final JSONException | JsonProcessingException e) {
throw new AssertionError(e);
}
}
@Override
public void describeTo(final Description description) {
description.appendText("leniently matches JSON");
}
}