align create methods and improve and align acceptance tests

This commit is contained in:
Michael Hoennig 2022-08-25 14:46:05 +02:00
parent 9a7b683e7d
commit 2531b9071f
11 changed files with 324 additions and 235 deletions

View File

@ -36,10 +36,10 @@ public class CustomerController implements CustomersApi {
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return ResponseEntity.ok(
mapList(
customerRepository.findCustomerByOptionalPrefixLike(prefix),
CustomerResource.class));
final var result = customerRepository.findCustomerByOptionalPrefixLike(prefix);
return ResponseEntity.ok(mapList(result, CustomerResource.class));
}
@Override

View File

@ -9,6 +9,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.UUID;
@ -25,6 +26,9 @@ public class RbacGrantController implements RbacgrantsApi {
@Autowired
private RbacGrantRepository rbacGrantRepository;
@Autowired
private EntityManager em;
@Override
@Transactional(readOnly = true)
public ResponseEntity<RbacGrantResource> getGrantById(
@ -61,7 +65,7 @@ public class RbacGrantController implements RbacgrantsApi {
@Override
@Transactional
public ResponseEntity<Void> grantRoleToUser(
public ResponseEntity<RbacGrantResource> grantRoleToUser(
final String currentUser,
final String assumedRoles,
final RbacGrantResource body) {
@ -72,14 +76,16 @@ public class RbacGrantController implements RbacgrantsApi {
context.assumeRoles(assumedRoles);
}
rbacGrantRepository.save(map(body, RbacGrantEntity.class));
final var granted = rbacGrantRepository.save(map(body, RbacGrantEntity.class));
em.flush();
em.refresh(granted);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/rbac-grants/{roleUuid}")
.buildAndExpand(body.getGrantedRoleUuid())
.toUri();
return ResponseEntity.created(uri).build();
return ResponseEntity.created(uri).body(map(granted, RbacGrantResource.class));
}
@Override
@ -100,5 +106,4 @@ public class RbacGrantController implements RbacgrantsApi {
return ResponseEntity.noContent().build();
}
}

View File

@ -17,7 +17,7 @@ public interface RbacGrantRepository extends Repository<RbacGrantEntity, RbacGra
List<RbacGrantEntity> findAll();
void save(final RbacGrantEntity grant);
RbacGrantEntity save(final RbacGrantEntity grant);
@Modifying
@Query(value = """

View File

@ -32,6 +32,9 @@ post:
"201":
description: OK
content:
'application/json':
schema:
$ref: './api-definition/rbac-grant-schemas.yaml#/components/schemas/RbacGrant'
"401":
$ref: './api-definition/error-responses.yaml#/components/responses/Unauthorized'
"403":

View File

@ -0,0 +1,173 @@
package net.hostsharing.hsadminng.hs.hscustomer;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.*;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = HsadminNgApplication.class
)
@Transactional
class CustomerControllerAcceptanceTest {
@LocalServerPort
private Integer port;
@Autowired
Context context;
@Autowired
Context contextMock;
@Autowired
CustomerRepository customerRepository;
@Nested
class ListCustomers {
@Test
void hostsharingAdmin_withoutAssumedRoles_canViewAllCustomers_ifNoCriteriaGiven() {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/customers")
.then().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].prefix", is("xxx"))
.body("[1].prefix", is("yyy"))
.body("[2].prefix", is("zzz"))
.body("size()", greaterThanOrEqualTo(3));
// @formatter:on
}
@Test
void hostsharingAdmin_withoutAssumedRoles_canViewMatchingCustomers_ifCriteriaGiven() throws Exception {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/customers?prefix=y")
.then().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].prefix", is("yyy"))
.body("size()", is(1));
// @formatter:on
}
@Test
void hostsharingAdmin_withoutAssumedCustomerAdminRole_canOnlyViewOwnCustomer() throws Exception {
RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "customer#yyy.admin")
.port(port)
.when()
.get("http://localhost/api/customers")
.then().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].prefix", is("yyy"))
.body("size()", is(1));
// @formatter:on
}
@Test
void customerAdmin_withoutAssumedRole_canOnlyViewOwnCustomer() throws Exception {
RestAssured // @formatter:off
.given()
.header("current-user", "customer-admin@yyy.example.com")
.port(port)
.when()
.get("http://localhost/api/customers")
.then().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].prefix", is("yyy"))
.body("size()", is(1));
// @formatter:on
}
}
@Nested
class CreateCustomer {
@Test
void hostsharingAdmin_withoutAssumedRole_canCreateCustomer() throws Exception {
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "mike@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"reference": 90010,
"prefix": "vvv",
"adminUserName": "customer-admin@vvv.example.com"
}
""")
.port(port)
.when()
.post("http://localhost/api/customers")
.then().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("prefix", is("vvv"))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new customer can be viewed by its own admin
final var newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
context.setCurrentUser("customer-admin@vvv.example.com");
assertThat(customerRepository.findByUuid(newUserUuid))
.hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("vvv"));
}
@Test
void customerAdmin_withoutAssumedRole_canNotCreateCustomer() throws Exception {
RestAssured // @formatter:off
.given()
.header("current-user", "customer-admin@yyy.example.com")
.contentType(ContentType.JSON)
.body("""
{
"reference": 90010,
"prefix": "uuu",
"adminUserName": "customer-admin@uuu.example.com"
}
""")
.port(port)
.when()
.post("http://localhost/api/customers")
.then().assertThat()
.statusCode(403)
.contentType(ContentType.JSON)
.statusCode(403)
.body("message", containsString("add-customer not permitted for customer-admin@yyy.example.com"));
// @formatter:on
// finally, the new customer was not created
context.setCurrentUser("sven@hostsharing.net");
assertThat(customerRepository.findCustomerByOptionalPrefixLike("uuu")).hasSize(0);
}
}
}

View File

@ -1,110 +0,0 @@
package net.hostsharing.hsadminng.hs.hscustomer;
import net.hostsharing.hsadminng.context.Context;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.util.List;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(CustomerController.class)
class CustomerControllerRestTest {
@Autowired
MockMvc mockMvc;
@MockBean
Context contextMock;
@MockBean
CustomerRepository customerRepositoryMock;
@Nested
class ListCustomers {
@Test
void listCustomersWillReturnAllCustomersFromRepositoryIfNoCriteriaGiven() throws Exception {
// given
when(customerRepositoryMock.findCustomerByOptionalPrefixLike(null)).thenReturn(List.of(
TestCustomer.xxx,
TestCustomer.yyy));
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/customers")
.header("current-user", "mike@hostsharing.net")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].prefix", is(TestCustomer.xxx.getPrefix())))
.andExpect(jsonPath("$[1].reference", is(TestCustomer.yyy.getReference()))
);
// then
verify(contextMock).setCurrentUser("mike@hostsharing.net");
verify(contextMock, never()).assumeRoles(anyString());
}
@Test
void listCustomersWillReturnMatchingCustomersFromRepositoryIfCriteriaGiven() throws Exception {
// given
when(customerRepositoryMock.findCustomerByOptionalPrefixLike("x")).thenReturn(List.of(TestCustomer.xxx));
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/customers")
.header("current-user", "mike@hostsharing.net")
.param("prefix", "x")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].prefix", is(TestCustomer.xxx.getPrefix()))
);
// then
verify(contextMock).setCurrentUser("mike@hostsharing.net");
verify(contextMock, never()).assumeRoles(anyString());
}
@Test
void listCustomersWillReturnAllCustomersForGivenAssumedRoles() throws Exception {
// given
when(customerRepositoryMock.findCustomerByOptionalPrefixLike(null)).thenReturn(List.of(TestCustomer.yyy));
// when
mockMvc.perform(MockMvcRequestBuilders
.get("/api/customers")
.header("current-user", "mike@hostsharing.net")
.header("assumed-roles", "customer-admin@yyy.example.com")
.accept(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].prefix", is(TestCustomer.yyy.getPrefix()))
);
// then
verify(contextMock).setCurrentUser("mike@hostsharing.net");
verify(contextMock).assumeRoles("customer-admin@yyy.example.com");
}
}
}

View File

@ -27,7 +27,8 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest {
@Autowired
CustomerRepository customerRepository;
@Autowired EntityManager em;
@Autowired
EntityManager em;
@Nested
class CreateCustomer {
@ -108,7 +109,7 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest {
final var result = customerRepository.findCustomerByOptionalPrefixLike(null);
// then
exactlyTheseCustomersAreReturned(result, "xxx", "yyy", "zzz");
allTheseCustomersAreReturned(result, "xxx", "yyy", "zzz");
}
@Test
@ -120,7 +121,7 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest {
final var result = customerRepository.findCustomerByOptionalPrefixLike(null);
then:
exactlyTheseCustomersAreReturned(result, "xxx", "yyy", "zzz");
allTheseCustomersAreReturned(result, "xxx", "yyy", "zzz");
}
@Test
@ -224,4 +225,9 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest {
.containsExactlyInAnyOrder(customerPrefixes);
}
void allTheseCustomersAreReturned(final List<CustomerEntity> actualResult, final String... customerPrefixes) {
assertThat(actualResult)
.extracting(CustomerEntity::getPrefix)
.contains(customerPrefixes);
}
}

View File

@ -102,7 +102,9 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
@Accepts({ "GRT:R(Read)" })
void packageAdmin_withAssumedUnixUserAdmin_canNotReadItsOwnGrantById() {
// given
final var givenCurrentUserAsPackageAdmin = new Subject("pac-admin-xxx00@xxx.example.com", "unixuser#xxx00-xxxa.admin");
final var givenCurrentUserAsPackageAdmin = new Subject(
"pac-admin-xxx00@xxx.example.com",
"unixuser#xxx00-xxxa.admin");
final var givenGranteeUser = findRbacUserByName("pac-admin-xxx00@xxx.example.com");
final var givenGrantedRole = findRbacRoleByName("package#xxx00.admin");
@ -131,11 +133,17 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
findRbacRoleByName(givenCurrentUserAsPackageAdmin.assumedRole);
// when
givenCurrentUserAsPackageAdmin
final var response = givenCurrentUserAsPackageAdmin
.grantsRole(givenOwnPackageAdminRole).assumed()
.toUser(givenNewUser);
// then
response.assertThat()
.statusCode(201)
.body("grantedByRoleIdName", is("package#xxx00.admin"))
.body("assumed", is(true))
.body("grantedRoleIdName", is("package#xxx00.admin"))
.body("granteeUserName", is(givenNewUser.getName()));
assertThat(findAllGrantsOf(givenCurrentUserAsPackageAdmin))
.extracting(RbacGrantEntity::toDisplay)
.contains("{ grant assumed role " + givenOwnPackageAdminRole.getRoleName() +
@ -160,9 +168,9 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
// then
result.assertThat()
.statusCode(403)
.body("message", containsString("Access to granted role"))
.body("message", containsString("forbidden for {package#xxx00.admin}"))
.statusCode(403);
.body("message", containsString("forbidden for {package#xxx00.admin}"));
assertThat(findAllGrantsOf(givenCurrentUserAsPackageAdmin))
.extracting(RbacGrantEntity::getGranteeUserName)
.doesNotContain(givenNewUser.getName());
@ -200,7 +208,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
.fromUser(givenArbitraryUser);
// then
assertRevoked(revokeResponse);
revokeResponse.assertThat().statusCode(204);
assertThat(findAllGrantsOf(givenCurrentUserAsPackageAdmin))
.extracting(RbacGrantEntity::getGranteeUserName)
.doesNotContain(givenArbitraryUser.getName());
@ -211,10 +219,6 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
assumeThat(response.extract().response().statusCode()).isEqualTo(201);
}
private void assertRevoked(final ValidatableResponse revokeResponse) {
revokeResponse.assertThat().statusCode(204);
}
class Subject {
final String currentUser;
@ -278,7 +282,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
.port(port)
.when()
.post("http://localhost/api/rbac-grants")
.then(); // @formatter:on
.then().log().all(); // @formatter:on
}
}
@ -316,7 +320,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
.delete("http://localhost/api/rbac-grants/%s/%s".formatted(
grantedRole.getUuid(), granteeUser.getUuid()
))
.then(); // @formatter:on
.then().log().all(); // @formatter:on
}
}
@ -344,7 +348,7 @@ class RbacGrantControllerAcceptanceTest extends ContextBasedTest {
.get("http://localhost/api/rbac-grants/%s/%s".formatted(
grantedRole.getUuid(), granteeUser.getUuid()
))
.then(); // @formatter:on
.then().log().all(); // @formatter:on
}
}
}

View File

@ -38,7 +38,7 @@ class RbacRoleControllerAcceptanceTest {
@Test
@Accepts({ "ROL:L(List)" })
void hostsharingAdmin_withoutAssumedRole_canViewPackageAdminRoles() {
void hostsharingAdmin_withoutAssumedRole_canViewAllRoles() {
// @formatter:off
RestAssured
@ -50,15 +50,15 @@ class RbacRoleControllerAcceptanceTest {
.then().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].roleName", is("customer#xxx.admin"))
.body("[1].roleName", is("customer#xxx.owner"))
.body("[2].roleName", is("customer#xxx.tenant"))
.body("", hasItem(hasEntry("roleName", "customer#xxx.admin")))
.body("", hasItem(hasEntry("roleName", "customer#xxx.owner")))
.body("", hasItem(hasEntry("roleName", "customer#xxx.tenant")))
// ...
.body("", hasItem(hasEntry("roleName", "global#hostsharing.admin")))
.body("", hasItem(hasEntry("roleName", "customer#yyy.admin")))
.body("", hasItem(hasEntry("roleName", "package#yyy00.admin")))
.body("", hasItem(hasEntry("roleName", "unixuser#yyy00-aaaa.owner")))
.body( "size()", is(73)); // increases with new test data
.body( "size()", greaterThanOrEqualTo(73)); // increases with new test data
// @formatter:on
}

View File

@ -52,14 +52,14 @@ class RbacUserControllerAcceptanceTest {
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("[0].name", is("customer-admin@xxx.example.com"))
.body("[1].name", is("customer-admin@yyy.example.com"))
.body("[2].name", is("customer-admin@zzz.example.com"))
.body("[3].name", is("mike@hostsharing.net"))
.body("", hasItem(hasEntry("name", "customer-admin@xxx.example.com")))
.body("", hasItem(hasEntry("name", "customer-admin@yyy.example.com")))
.body("", hasItem(hasEntry("name", "customer-admin@zzz.example.com")))
.body("", hasItem(hasEntry("name", "mike@hostsharing.net")))
// ...
.body("[11].name", is("pac-admin-zzz01@zzz.example.com"))
.body("[12].name", is("pac-admin-zzz02@zzz.example.com"))
.body("[13].name", is("sven@hostsharing.net"))
.body("", hasItem(hasEntry("name", "pac-admin-zzz01@zzz.example.com")))
.body("", hasItem(hasEntry("name", "pac-admin-zzz02@zzz.example.com")))
.body("", hasItem(hasEntry("name", "sven@hostsharing.net")))
.body("size()", greaterThanOrEqualTo(14));
// @formatter:on
}

View File

@ -32,7 +32,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
@Autowired
JpaAttempt jpaAttempt;
@Autowired EntityManager em;
@Autowired
EntityManager em;
@Nested
class CreateUser {
@ -106,7 +107,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
final var result = rbacUserRepository.findByOptionalNameLike(null);
// then
exactlyTheseRbacUsersAreReturned(result, ALL_TEST_DATA_USERS);
allTheseRbacUsersAreReturned(result, ALL_TEST_DATA_USERS);
}
@Test
@ -118,7 +119,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
final var result = rbacUserRepository.findByOptionalNameLike(null);
then:
exactlyTheseRbacUsersAreReturned(result, ALL_TEST_DATA_USERS);
allTheseRbacUsersAreReturned(result, ALL_TEST_DATA_USERS);
}
@Test
@ -398,11 +399,18 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
void exactlyTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
assertThat(actualResult)
.filteredOn(u -> !u.getName().startsWith("test-user-"))
.extracting(RbacUserEntity::getName)
.filteredOn(n -> !n.startsWith("test-user"))
.containsExactlyInAnyOrder(expectedUserNames);
}
void allTheseRbacUsersAreReturned(final List<RbacUserEntity> actualResult, final String... expectedUserNames) {
assertThat(actualResult)
.extracting(RbacUserEntity::getName)
.filteredOn(n -> !n.startsWith("test-user"))
.contains(expectedUserNames);
}
void noRbacPermissionsAreReturned(
final List<RbacUserPermission> actualResult) {
assertThat(actualResult)