diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java index 0de11b82..a167bc74 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java @@ -28,26 +28,26 @@ public class CustomerController implements CustomersApi { @Override @Transactional(readOnly = true) public ResponseEntity> listCustomers( - String userName, - String assumedRoles, - String prefix + String userName, + String assumedRoles, + String prefix ) { context.setCurrentUser(userName); 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 @Transactional public ResponseEntity addCustomer( - final String currentUser, - final String assumedRoles, - final CustomerResource customer) { + final String currentUser, + final String assumedRoles, + final CustomerResource customer) { context.setCurrentTask("create new customer: #" + customer.getReference() + " / " + customer.getPrefix()); context.setCurrentUser(currentUser); @@ -61,10 +61,10 @@ public class CustomerController implements CustomersApi { final var saved = customerRepository.save(map(customer, CustomerEntity.class)); final var uri = - MvcUriComponentsBuilder.fromController(getClass()) - .path("/api/rbac-users/{id}") - .buildAndExpand(customer.getUuid()) - .toUri(); + MvcUriComponentsBuilder.fromController(getClass()) + .path("/api/rbac-users/{id}") + .buildAndExpand(customer.getUuid()) + .toUri(); return ResponseEntity.created(uri).body(map(saved, CustomerResource.class)); } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java index 5270ec82..09b5ee83 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java @@ -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 getGrantById( @@ -61,7 +65,7 @@ public class RbacGrantController implements RbacgrantsApi { @Override @Transactional - public ResponseEntity grantRoleToUser( + public ResponseEntity 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(); } - } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantRepository.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantRepository.java index cb226ba0..f385d69b 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantRepository.java @@ -17,7 +17,7 @@ public interface RbacGrantRepository extends Repository findAll(); - void save(final RbacGrantEntity grant); + RbacGrantEntity save(final RbacGrantEntity grant); @Modifying @Query(value = """ diff --git a/src/main/resources/api-definition/rbac-grants.yaml b/src/main/resources/api-definition/rbac-grants.yaml index 52e7b4cc..abd39ead 100644 --- a/src/main/resources/api-definition/rbac-grants.yaml +++ b/src/main/resources/api-definition/rbac-grants.yaml @@ -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": diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java new file mode 100644 index 00000000..dd0cc200 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java @@ -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); + } + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerRestTest.java deleted file mode 100644 index b16e1c51..00000000 --- a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerRestTest.java +++ /dev/null @@ -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"); - } - } - -} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerRepositoryIntegrationTest.java index c972889c..fd7a9889 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerRepositoryIntegrationTest.java @@ -27,7 +27,8 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { @Autowired CustomerRepository customerRepository; - @Autowired EntityManager em; + @Autowired + EntityManager em; @Nested class CreateCustomer { @@ -42,7 +43,7 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { final var result = attempt(em, () -> { final var newCustomer = new CustomerEntity( - UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); + UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); return customerRepository.save(newCustomer); }); @@ -61,14 +62,14 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { // when final var result = attempt(em, () -> { final var newCustomer = new CustomerEntity( - UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); + UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); return customerRepository.save(newCustomer); }); // then result.assertExceptionWithRootCauseMessage( - PersistenceException.class, - "add-customer not permitted for customer#xxx.admin"); + PersistenceException.class, + "add-customer not permitted for customer#xxx.admin"); } @Test @@ -79,14 +80,14 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { // when final var result = attempt(em, () -> { final var newCustomer = new CustomerEntity( - UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); + UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); return customerRepository.save(newCustomer); }); // then result.assertExceptionWithRootCauseMessage( - PersistenceException.class, - "add-customer not permitted for customer-admin@xxx.example.com"); + PersistenceException.class, + "add-customer not permitted for customer-admin@xxx.example.com"); } @@ -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 @@ -151,13 +152,13 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { // when final var result = attempt( - em, - () -> customerRepository.findCustomerByOptionalPrefixLike(null)); + em, + () -> customerRepository.findCustomerByOptionalPrefixLike(null)); // then result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin"); + JpaSystemException.class, + "[403] user customer-admin@xxx.example.com", "has no permission to assume role package#yyy00#admin"); } @Test @@ -165,12 +166,12 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { context("unknown@example.org", null); final var result = attempt( - em, - () -> customerRepository.findCustomerByOptionalPrefixLike(null)); + em, + () -> customerRepository.findCustomerByOptionalPrefixLike(null)); result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "hsadminng.currentUser defined as unknown@example.org, but does not exists"); + JpaSystemException.class, + "hsadminng.currentUser defined as unknown@example.org, but does not exists"); } @Test @@ -179,12 +180,12 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { context("unknown@example.org", "customer#xxx.admin"); final var result = attempt( - em, - () -> customerRepository.findCustomerByOptionalPrefixLike(null)); + em, + () -> customerRepository.findCustomerByOptionalPrefixLike(null)); result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "hsadminng.currentUser defined as unknown@example.org, but does not exists"); + JpaSystemException.class, + "hsadminng.currentUser defined as unknown@example.org, but does not exists"); } } @@ -219,9 +220,14 @@ class CustomerRepositoryIntegrationTest extends ContextBasedTest { void exactlyTheseCustomersAreReturned(final List actualResult, final String... customerPrefixes) { assertThat(actualResult) - .hasSize(customerPrefixes.length) - .extracting(CustomerEntity::getPrefix) - .containsExactlyInAnyOrder(customerPrefixes); + .hasSize(customerPrefixes.length) + .extracting(CustomerEntity::getPrefix) + .containsExactlyInAnyOrder(customerPrefixes); } + void allTheseCustomersAreReturned(final List actualResult, final String... customerPrefixes) { + assertThat(actualResult) + .extracting(CustomerEntity::getPrefix) + .contains(customerPrefixes); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantControllerAcceptanceTest.java index 5b00bd10..a057fc73 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantControllerAcceptanceTest.java @@ -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 } } } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java index ebc02317..dc112db0 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java @@ -15,8 +15,8 @@ import javax.persistence.EntityManager; import static org.hamcrest.Matchers.*; @SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = HsadminNgApplication.class + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = HsadminNgApplication.class ) @Accepts({ "ROL:*:S:Schema" }) class RbacRoleControllerAcceptanceTest { @@ -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 } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java index b300b871..3c634c0a 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java @@ -18,8 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.*; @SpringBootTest( - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = HsadminNgApplication.class + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = HsadminNgApplication.class ) @Transactional class RbacUserControllerAcceptanceTest { @@ -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 } @@ -152,10 +152,10 @@ class RbacUserControllerAcceptanceTest { // finally, the user can view its own record final var newUserUuid = UUID.fromString( - location.substring(location.lastIndexOf('/') + 1)); + location.substring(location.lastIndexOf('/') + 1)); context.setCurrentUser("new-user@example.com"); assertThat(rbacUserRepository.findByUuid(newUserUuid)) - .extracting(RbacUserEntity::getName).isEqualTo("new-user@example.com"); + .extracting(RbacUserEntity::getName).isEqualTo("new-user@example.com"); } } } diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserRepositoryIntegrationTest.java index 680f2501..7a29a29b 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserRepositoryIntegrationTest.java @@ -32,7 +32,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { @Autowired JpaAttempt jpaAttempt; - @Autowired EntityManager em; + @Autowired + EntityManager em; @Nested class CreateUser { @@ -45,7 +46,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // when final var result = rbacUserRepository.create( - new RbacUserEntity(null, givenNewUserName)); + new RbacUserEntity(null, givenNewUserName)); // then the persisted user is returned assertThat(result).isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenNewUserName); @@ -53,7 +54,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // and the new user entity can be fetched by the user itself context(givenNewUserName); assertThat(em.find(RbacUserEntity.class, result.getUuid())) - .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenNewUserName); + .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(givenNewUserName); } @Test @@ -73,11 +74,11 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // then: assertThat(result.wasSuccessful()).isTrue(); assertThat(result.returnedValue()).isNotNull() - .extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid); + .extracting(RbacUserEntity::getUuid).isEqualTo(givenUuid); jpaAttempt.transacted(() -> { context(newUserName); assertThat(em.find(RbacUserEntity.class, givenUuid)) - .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(newUserName); + .isNotNull().extracting(RbacUserEntity::getName).isEqualTo(newUserName); }); } } @@ -86,7 +87,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { class FindByOptionalNameLike { private static final String[] ALL_TEST_DATA_USERS = Array.of( - // @formatter:off + // @formatter:off "mike@hostsharing.net", "sven@hostsharing.net", "customer-admin@xxx.example.com", "pac-admin-xxx00@xxx.example.com", "pac-admin-xxx01@xxx.example.com", "pac-admin-xxx02@xxx.example.com", @@ -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 @@ -131,9 +132,9 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { then: exactlyTheseRbacUsersAreReturned( - result, - "customer-admin@xxx.example.com", - "pac-admin-xxx00@xxx.example.com", "pac-admin-xxx01@xxx.example.com", "pac-admin-xxx02@xxx.example.com" + result, + "customer-admin@xxx.example.com", + "pac-admin-xxx00@xxx.example.com", "pac-admin-xxx01@xxx.example.com", "pac-admin-xxx02@xxx.example.com" ); } @@ -147,9 +148,9 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // then: exactlyTheseRbacUsersAreReturned( - result, - "customer-admin@xxx.example.com", - "pac-admin-xxx00@xxx.example.com", "pac-admin-xxx01@xxx.example.com", "pac-admin-xxx02@xxx.example.com" + result, + "customer-admin@xxx.example.com", + "pac-admin-xxx00@xxx.example.com", "pac-admin-xxx01@xxx.example.com", "pac-admin-xxx02@xxx.example.com" ); } @@ -177,7 +178,7 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { class ListUserPermissions { private static final String[] ALL_USER_PERMISSIONS = Array.of( - // @formatter:off + // @formatter:off "global#hostsharing.admin -> global#hostsharing: add-customer", "customer#xxx.admin -> customer#xxx: add-package", @@ -243,13 +244,13 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // when final var result = attempt(em, () -> - rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net") + rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net") ); // then result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "[400] grantedPermissions(...) does not support assumed roles"); + JpaSystemException.class, + "[400] grantedPermissions(...) does not support assumed roles"); } @Test @@ -262,8 +263,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // then allTheseRbacPermissionsAreReturned( - result, - // @formatter:off + result, + // @formatter:off "customer#xxx.admin -> customer#xxx: add-package", "customer#xxx.admin -> customer#xxx: view", "customer#xxx.tenant -> customer#xxx: view", @@ -285,8 +286,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // @formatter:on ); noneOfTheseRbacPermissionsAreReturned( - result, - // @formatter:off + result, + // @formatter:off "customer#yyy.admin -> customer#yyy: add-package", "customer#yyy.admin -> customer#yyy: view", "customer#yyy.tenant -> customer#yyy: view" @@ -301,13 +302,13 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // when final var result = attempt(em, () -> - rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net") + rbacUserRepository.findPermissionsOfUser("mike@hostsharing.net") ); // then result.assertExceptionWithRootCauseMessage( - JpaSystemException.class, - "[403] permissions of user \"mike@hostsharing.net\" are not accessible to user \"customer-admin@xxx.example.com\""); + JpaSystemException.class, + "[403] permissions of user \"mike@hostsharing.net\" are not accessible to user \"customer-admin@xxx.example.com\""); } @Test @@ -320,8 +321,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // then allTheseRbacPermissionsAreReturned( - result, - // @formatter:off + result, + // @formatter:off "customer#xxx.tenant -> customer#xxx: view", // "customer#xxx.admin -> customer#xxx: view" - Not permissions through the customer admin! "package#xxx00.admin -> package#xxx00: add-unixuser", @@ -332,8 +333,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // @formatter:on ); noneOfTheseRbacPermissionsAreReturned( - result, - // @formatter:off + result, + // @formatter:off "customer#yyy.admin -> customer#yyy: add-package", "customer#yyy.admin -> customer#yyy: view", "customer#yyy.tenant -> customer#yyy: view", @@ -368,8 +369,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // then allTheseRbacPermissionsAreReturned( - result, - // @formatter:off + result, + // @formatter:off "customer#xxx.tenant -> customer#xxx: view", // "customer#xxx.admin -> customer#xxx: view" - Not permissions through the customer admin! "package#xxx00.admin -> package#xxx00: add-unixuser", @@ -378,8 +379,8 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { // @formatter:on ); noneOfTheseRbacPermissionsAreReturned( - result, - // @formatter:off + result, + // @formatter:off // no customer admin permissions "customer#xxx.admin -> customer#xxx: add-package", // no permissions on other customer's objects @@ -398,40 +399,47 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest { void exactlyTheseRbacUsersAreReturned(final List actualResult, final String... expectedUserNames) { assertThat(actualResult) - .filteredOn(u -> !u.getName().startsWith("test-user-")) - .extracting(RbacUserEntity::getName) - .containsExactlyInAnyOrder(expectedUserNames); + .extracting(RbacUserEntity::getName) + .filteredOn(n -> !n.startsWith("test-user")) + .containsExactlyInAnyOrder(expectedUserNames); + } + + void allTheseRbacUsersAreReturned(final List actualResult, final String... expectedUserNames) { + assertThat(actualResult) + .extracting(RbacUserEntity::getName) + .filteredOn(n -> !n.startsWith("test-user")) + .contains(expectedUserNames); } void noRbacPermissionsAreReturned( - final List actualResult) { + final List actualResult) { assertThat(actualResult) - .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) - .containsExactlyInAnyOrder(); + .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) + .containsExactlyInAnyOrder(); } void exactlyTheseRbacPermissionsAreReturned( - final List actualResult, - final String... expectedRoleNames) { + final List actualResult, + final String... expectedRoleNames) { assertThat(actualResult) - .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) - .containsExactlyInAnyOrder(expectedRoleNames); + .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) + .containsExactlyInAnyOrder(expectedRoleNames); } void allTheseRbacPermissionsAreReturned( - final List actualResult, - final String... expectedRoleNames) { + final List actualResult, + final String... expectedRoleNames) { assertThat(actualResult) - .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) - .contains(expectedRoleNames); + .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) + .contains(expectedRoleNames); } void noneOfTheseRbacPermissionsAreReturned( - final List actualResult, - final String... unexpectedRoleNames) { + final List actualResult, + final String... unexpectedRoleNames) { assertThat(actualResult) - .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) - .doesNotContain(unexpectedRoleNames); + .extracting(p -> p.getRoleName() + " -> " + p.getObjectTable() + "#" + p.getObjectIdName() + ": " + p.getOp()) + .doesNotContain(unexpectedRoleNames); } }