diff --git a/build.gradle b/build.gradle index 86c3b5d2..d07a7f05 100644 --- a/build.gradle +++ b/build.gradle @@ -169,9 +169,12 @@ project.tasks.check.dependsOn(jacocoTestCoverageVerification) jacocoTestCoverageVerification { violationRules { rule { - excludes = ['net.hostsharing.hsadminng.generated.**'] + excludes = [ + 'net.hostsharing.hsadminng.generated.**', + 'net.hostsharing.hsadminng.HsadminNgApplication' // main method + ] limit { - minimum = 0.8 // TODO: increase to 0.9 + minimum = 0.94 } } @@ -185,8 +188,6 @@ jacocoTestCoverageVerification { 'net.hostsharing.hsadminng.generated.**', 'net.hostsharing.hsadminng.HsadminNgApplication', 'net.hostsharing.hsadminng.TestController', - - // TODO: improve test code coverage: 'net.hostsharing.hsadminng.Mapper', ] @@ -200,13 +201,13 @@ jacocoTestCoverageVerification { element = 'METHOD' excludes = [ 'net.hostsharing.hsadminng.generated.**', - 'net.hostsharing.hsadminng.HsadminNgApplication.*', + 'net.hostsharing.hsadminng.HsadminNgApplication.main', 'net.hostsharing.hsadminng.TestController.*'] limit { counter = 'BRANCH' value = 'COVEREDRATIO' - minimum = 0.5 // TODO: increase test code coverage + minimum = 0.95 } } } diff --git a/src/main/java/net/hostsharing/hsadminng/Mapper.java b/src/main/java/net/hostsharing/hsadminng/Mapper.java index 93f7048b..ff5266bf 100644 --- a/src/main/java/net/hostsharing/hsadminng/Mapper.java +++ b/src/main/java/net/hostsharing/hsadminng/Mapper.java @@ -7,10 +7,8 @@ import java.util.stream.Collectors; /** * A nicer API for ModelMapper. - * - * MOst */ -public class Mapper { +public abstract class Mapper { private final static ModelMapper modelMapper = new ModelMapper(); diff --git a/src/main/java/net/hostsharing/hsadminng/context/Context.java b/src/main/java/net/hostsharing/hsadminng/context/Context.java index 22a86f72..a0fdf70a 100644 --- a/src/main/java/net/hostsharing/hsadminng/context/Context.java +++ b/src/main/java/net/hostsharing/hsadminng/context/Context.java @@ -1,12 +1,16 @@ package net.hostsharing.hsadminng.context; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; -import static org.springframework.transaction.annotation.Propagation.*; +import static org.springframework.transaction.annotation.Propagation.MANDATORY; @Service public class Context { @@ -14,23 +18,55 @@ public class Context { @PersistenceContext private EntityManager em; + @Autowired(required = false) + private HttpServletRequest request; + + @Transactional(propagation = MANDATORY) + public void register(final String currentUser, final String assumedRoles) { + if (request != null) { + setCurrentTask(request.getMethod() + " " + request.getRequestURI()); + } else { + + final Optional caller = + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .walk(frames -> + frames.skip(1) + .filter(c -> c.getDeclaringClass() + .getPackageName() + .startsWith("net.hostsharing.hsadminng")) + .filter(c -> !c.getDeclaringClass().getName().contains("BySpringCGLIB$$")) + .findFirst()); + final var callerName = caller.map( + c -> c.getDeclaringClass().getSimpleName() + "." + c.getMethodName()) + .orElse("unknown"); + setCurrentTask(callerName); + } + setCurrentUser(currentUser); + if (!StringUtils.isBlank(assumedRoles)) { + assumeRoles(assumedRoles); + } + } + @Transactional(propagation = MANDATORY) public void setCurrentTask(final String task) { - em.createNativeQuery( - String.format( - "set local hsadminng.currentTask = '%s';", - task - ) - ).executeUpdate(); + final var sql = String.format( + "set local hsadminng.currentTask = '%s';", + shortenToMaxLength(task, 95) + ); + em.createNativeQuery(sql).executeUpdate(); + } + + public String getCurrentTask() { + return (String) em.createNativeQuery("select current_setting('hsadminng.currentTask');").getSingleResult(); } @Transactional(propagation = MANDATORY) public void setCurrentUser(final String userName) { em.createNativeQuery( - String.format( - "set local hsadminng.currentUser = '%s';", - userName - ) + String.format( + "set local hsadminng.currentUser = '%s';", + userName + ) ).executeUpdate(); assumeNoSpecialRole(); } @@ -42,17 +78,17 @@ public class Context { @Transactional(propagation = MANDATORY) public void assumeRoles(final String roles) { em.createNativeQuery( - String.format( - "set local hsadminng.assumedRoles = '%s';", - roles - ) + String.format( + "set local hsadminng.assumedRoles = '%s';", + roles + ) ).executeUpdate(); } @Transactional(propagation = MANDATORY) public void assumeNoSpecialRole() { em.createNativeQuery( - "set local hsadminng.assumedRoles = '';" + "set local hsadminng.assumedRoles = '';" ).executeUpdate(); } @@ -60,4 +96,7 @@ public class Context { return (String[]) em.createNativeQuery("select assumedRoles()").getSingleResult(); } + private static String shortenToMaxLength(final String task, final int maxLength) { + return task.substring(0, Math.min(task.length(), maxLength)); + } } 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 52337815..2ee61003 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerController.java @@ -29,14 +29,11 @@ public class CustomerController implements CustomersApi { @Override @Transactional(readOnly = true) public ResponseEntity> listCustomers( - String userName, + String currentUser, String assumedRoles, String prefix ) { - context.setCurrentUser(userName); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); final var result = customerRepository.findCustomerByOptionalPrefixLike(prefix); @@ -50,11 +47,8 @@ public class CustomerController implements CustomersApi { final String assumedRoles, final CustomerResource customer) { - context.setCurrentTask("create new customer: #" + customer.getReference() + " / " + customer.getPrefix()); - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); + if (customer.getUuid() == null) { customer.setUuid(UUID.randomUUID()); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java index 8a3d6566..148de774 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hspackage/PackageController.java @@ -29,14 +29,12 @@ public class PackageController implements PackagesApi { @Override @Transactional(readOnly = true) public ResponseEntity> listPackages( - String userName, + String currentUser, String assumedRoles, String name ) { - context.setCurrentUser(userName); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); + final var result = packageRepository.findAllByOptionalNameLike(name); return ResponseEntity.ok(mapList(result, PackageResource.class)); } @@ -49,10 +47,8 @@ public class PackageController implements PackagesApi { final UUID packageUuid, final PackageUpdateResource body) { - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); + final var current = packageRepository.findByUuid(packageUuid); OptionalFromJson.of(body.getDescription()).ifPresent(current::setDescription); final var saved = packageRepository.save(current); 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 4ff43744..bd0e30de 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java @@ -38,10 +38,7 @@ public class RbacGrantController implements RbacgrantsApi { final UUID grantedRoleUuid, final UUID granteeUserUuid) { - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); final var id = new RbacGrantId(granteeUserUuid, grantedRoleUuid); final var result = rbacGrantRepository.findById(id); @@ -57,10 +54,8 @@ public class RbacGrantController implements RbacgrantsApi { final String currentUser, final String assumedRoles) { - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); + return ResponseEntity.ok(mapList(rbacGrantRepository.findAll(), RbacGrantResource.class)); } @@ -71,11 +66,7 @@ public class RbacGrantController implements RbacgrantsApi { final String assumedRoles, final RbacGrantResource body) { - context.setCurrentTask("granting role to user"); - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); final var granted = rbacGrantRepository.save(map(body, RbacGrantEntity.class)); em.flush(); @@ -97,11 +88,7 @@ public class RbacGrantController implements RbacgrantsApi { final UUID grantedRoleUuid, final UUID granteeUserUuid) { - context.setCurrentTask("revoking role from user"); - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); rbacGrantRepository.deleteByRbacGrantId(new RbacGrantId(granteeUserUuid, grantedRoleUuid)); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntity.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntity.java index fa4aa034..901d6266 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntity.java @@ -22,34 +22,34 @@ public class RbacGrantEntity { @Column(name = "grantedbyroleidname", updatable = false, insertable = false) private String grantedByRoleIdName; - @Column(name = "grantedroleidname", updatable = false, insertable = false) - private String grantedRoleIdName; - - @Column(name = "username", updatable = false, insertable = false) - private String granteeUserName; - - private boolean assumed; - @Column(name = "grantedbyroleuuid", updatable = false, insertable = false) private UUID grantedByRoleUuid; + @Column(name = "grantedroleidname", updatable = false, insertable = false) + private String grantedRoleIdName; + @Id @Column(name = "grantedroleuuid") private UUID grantedRoleUuid; + @Column(name = "username", updatable = false, insertable = false) + private String granteeUserName; + @Id @Column(name = "useruuid") private UUID granteeUserUuid; + private boolean assumed; + @Column(name = "objecttable", updatable = false, insertable = false) private String objectTable; - @Column(name = "objectuuid", updatable = false, insertable = false) - private UUID objectUuid; - @Column(name = "objectidname", updatable = false, insertable = false) private String objectIdName; + @Column(name = "objectuuid", updatable = false, insertable = false) + private UUID objectUuid; + @Column(name = "grantedroletype", updatable = false, insertable = false) @Enumerated(EnumType.STRING) private RbacRoleType grantedRoleType; diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java index d3c4200e..dd86fde0 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleController.java @@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.rbac.rbacrole; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.generated.api.v1.api.RbacrolesApi; import net.hostsharing.hsadminng.generated.api.v1.model.RbacRoleResource; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; @@ -26,13 +25,11 @@ public class RbacRoleController implements RbacrolesApi { @Override @Transactional(readOnly = true) public ResponseEntity> listRoles( - final String currentUser, - final String assumedRoles) { + final String currentUser, + final String assumedRoles) { + + context.register(currentUser, assumedRoles); - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class)); } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java index 5e913320..9a6003f2 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserController.java @@ -33,8 +33,7 @@ public class RbacUserController implements RbacusersApi { public ResponseEntity createUser( final RbacUserResource body ) { - context.setCurrentTask("creating new user: " + body.getName()); - context.setCurrentUser(body.getName()); + context.register(body.getName(), null); if (body.getUuid() == null) { body.setUuid(UUID.randomUUID()); @@ -56,10 +55,7 @@ public class RbacUserController implements RbacusersApi { final String assumedRoles, final UUID userUuid) { - context.setCurrentUser(currentUser); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); final var result = rbacUserRepository.findByUuid(userUuid); if (result == null) { @@ -71,28 +67,24 @@ public class RbacUserController implements RbacusersApi { @Override @Transactional(readOnly = true) public ResponseEntity> listUsers( - final String currentUserName, + final String currentUser, final String assumedRoles, final String userName ) { - context.setCurrentUser(currentUserName); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); + return ResponseEntity.ok(mapList(rbacUserRepository.findByOptionalNameLike(userName), RbacUserResource.class)); } @Override @Transactional(readOnly = true) public ResponseEntity> listUserPermissions( - final String currentUserName, + final String currentUser, final String assumedRoles, final UUID userUuid ) { - context.setCurrentUser(currentUserName); - if (!StringUtils.isBlank(assumedRoles)) { - context.assumeRoles(assumedRoles); - } + context.register(currentUser, assumedRoles); + return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUserByUuid(userUuid), RbacUserPermissionResource.class)); } } diff --git a/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java b/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java index 7e007fea..98324ff9 100644 --- a/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java +++ b/src/test/java/net/hostsharing/hsadminng/context/ContextIntegrationTests.java @@ -17,6 +17,15 @@ class ContextIntegrationTests { @Autowired private Context context; + @Test + void registerWithoutHttpServletRequestUsesCallStack() { + + context.register("current-user", null); + + final var currentTask = context.getCurrentTask(); + assertThat(currentTask).isEqualTo("ContextIntegrationTests.registerWithoutHttpServletRequestUsesCallStack"); + } + @Test @Transactional void setCurrentUser() { diff --git a/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java b/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java new file mode 100644 index 00000000..e12b79f3 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/context/ContextUnitTest.java @@ -0,0 +1,37 @@ +package net.hostsharing.hsadminng.context; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ContextUnitTest { + + @Mock + EntityManager em; + + @Mock + Query nativeQuery; + + @InjectMocks + Context context; + + @Test + void registerWithoutHttpServletRequestUsesCallStack() { + given(em.createNativeQuery(any())).willReturn(nativeQuery); + + context.register("current-user", null); + + verify(em).createNativeQuery( + "set local hsadminng.currentTask = 'ContextUnitTest.registerWithoutHttpServletRequestUsesCallStack';"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java index bc0ec5f1..0a9f0955 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hscustomer/CustomerControllerAcceptanceTest.java @@ -118,9 +118,9 @@ class CustomerControllerAcceptanceTest { .contentType(ContentType.JSON) .body(""" { - "reference": 90010, - "prefix": "vvv", - "adminUserName": "customer-admin@vvv.example.com" + "reference": 90020, + "prefix": "ttt", + "adminUserName": "customer-admin@ttt.example.com" } """) .port(port) @@ -129,16 +129,54 @@ class CustomerControllerAcceptanceTest { .then().assertThat() .statusCode(201) .contentType(ContentType.JSON) - .body("prefix", is("vvv")) + .body("prefix", is("ttt")) .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@ttt.example.com"); + assertThat(customerRepository.findByUuid(newUserUuid)) + .hasValueSatisfying(c -> assertThat(c.getPrefix()).isEqualTo("ttt")); + } + + @Test + void hostsharingAdmin_withoutAssumedRole_canCreateCustomerWithGivenUuid() { + + final var givenUuid = UUID.randomUUID(); + + final var location = RestAssured // @formatter:off + .given() + .header("current-user", "mike@hostsharing.net") + .contentType(ContentType.JSON) + .body(""" + { + "uuid": "%s", + "reference": 90010, + "prefix": "vvv", + "adminUserName": "customer-admin@vvv.example.com" + } + """.formatted(givenUuid)) + .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")); + .hasValueSatisfying(c -> { + assertThat(c.getPrefix()).isEqualTo("vvv"); + assertThat(c.getUuid()).isEqualTo(givenUuid); + }); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java deleted file mode 100644 index 45b5a5b6..00000000 --- a/src/test/java/net/hostsharing/hsadminng/hs/hspackage/PackageControllerRestTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package net.hostsharing.hsadminng.hs.hspackage; - -import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; -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.context.ContextConfiguration; -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.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest(PackageController.class) -@ContextConfiguration(classes = { PackageController.class, JsonObjectMapperConfiguration.class }) -class PackageControllerRestTest { - - @Autowired - MockMvc mockMvc; - @MockBean - Context contextMock; - @MockBean - PackageRepository packageRepositoryMock; - - @Nested - class ListPackages { - - @Test - void withoutNameParameter() throws Exception { - - // given - final var givenPacs = List.of(TestPackage.xxx00, TestPackage.xxx01, TestPackage.xxx02); - when(packageRepositoryMock.findAllByOptionalNameLike(null)).thenReturn(givenPacs); - - // when - mockMvc.perform(MockMvcRequestBuilders - .get("/api/packages") - .header("current-user", "mike@hostsharing.net") - .header("assumed-roles", "customer#xxx.admin") - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(3))) - .andExpect(jsonPath("$[0].name", is("xxx00"))) - .andExpect(jsonPath("$[1].uuid", is(TestPackage.xxx01.getUuid().toString()))) - .andExpect(jsonPath("$[2].customer.prefix", is("xxx"))); - - verify(contextMock).setCurrentUser("mike@hostsharing.net"); - verify(contextMock).assumeRoles("customer#xxx.admin"); - } - - @Test - void withNameParameter() throws Exception { - - // given - final var givenPacs = List.of(TestPackage.xxx01); - when(packageRepositoryMock.findAllByOptionalNameLike("xxx01")).thenReturn(givenPacs); - - // when - mockMvc.perform(MockMvcRequestBuilders - .get("/api/packages?name=xxx01") - .header("current-user", "mike@hostsharing.net") - .header("assumed-roles", "customer#xxx.admin") - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].name", is("xxx01"))); - - verify(contextMock).setCurrentUser("mike@hostsharing.net"); - verify(contextMock).assumeRoles("customer#xxx.admin"); - } - } - - @Nested - class updatePackage { - - @Test - void withDescriptionUpdatesDescription() throws Exception { - - // given - final var givenPac = TestPackage.xxx01; - when(packageRepositoryMock.findByUuid(givenPac.getUuid())).thenReturn(givenPac); - when(packageRepositoryMock.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - - // when - mockMvc.perform(MockMvcRequestBuilders - .patch("/api/packages/" + givenPac.getUuid().toString()) - .header("current-user", "mike@hostsharing.net") - .header("assumed-roles", "customer#xxx.admin") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "description": "some description" - } - """) - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().isOk()) - .andExpect(jsonPath("description", is("some description"))); - - verify(contextMock).setCurrentUser("mike@hostsharing.net"); - verify(contextMock).assumeRoles("customer#xxx.admin"); - verify(packageRepositoryMock).save(argThat(entity -> - entity.getDescription().equals("some description") && - entity.getUuid().equals(givenPac.getUuid()))); - } - - @Test - void withoutDescriptionDoesNothing() throws Exception { - - // given - final var givenPac = TestPackage.xxx01; - when(packageRepositoryMock.findByUuid(givenPac.getUuid())).thenReturn(givenPac); - when(packageRepositoryMock.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - - // when - mockMvc.perform(MockMvcRequestBuilders - .patch("/api/packages/" + givenPac.getUuid().toString()) - .header("current-user", "mike@hostsharing.net") - .header("assumed-roles", "customer#xxx.admin") - .contentType(MediaType.APPLICATION_JSON) - .content("{}") - .accept(MediaType.APPLICATION_JSON)) - - // then - .andExpect(status().isOk()) - .andExpect(jsonPath("description", is(givenPac.getDescription()))); - - verify(contextMock).setCurrentUser("mike@hostsharing.net"); - verify(contextMock).assumeRoles("customer#xxx.admin"); - verify(packageRepositoryMock).save(argThat(entity -> - givenPac.getDescription().equals(entity.getDescription()) && - givenPac.getUuid().equals(entity.getUuid()))); - } - } -} diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntityUnitTest.java new file mode 100644 index 00000000..22729d32 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantEntityUnitTest.java @@ -0,0 +1,63 @@ +package net.hostsharing.hsadminng.rbac.rbacgrant; + +import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleType; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +class RbacGrantEntityUnitTest { + + @Test + void getRbacGrantId() { + // given + final var grantedRoleUuid = UUID.randomUUID(); + final var granteeUserUuid = UUID.randomUUID(); + final var entity = new RbacGrantEntity(); + entity.setGrantedRoleUuid(grantedRoleUuid); + entity.setGranteeUserUuid(granteeUserUuid); + + // when + final var grantId = entity.getRbacGrantId(); + + // then + assertThat(grantId).isEqualTo(new RbacGrantId(granteeUserUuid, grantedRoleUuid)); + } + + @Test + void toDisplayAssumed() { + // given + final var entity = new RbacGrantEntity( // @formatter:off + "GrantER", UUID.randomUUID(), + "GrantED", UUID.randomUUID(), + "GrantEE", UUID.randomUUID(), + true, + "ObjectTable", "ObjectId", UUID.randomUUID(), + RbacRoleType.admin); // @formatter:on + + // when + final var display = entity.toDisplay(); + + // then + assertThat(display).isEqualTo("{ grant assumed role GrantED to user GrantEE by role GrantER }"); + } + + @Test + void toDisplayNotAssumed() { + // given + final var entity = new RbacGrantEntity( // @formatter:off + "GrantER", UUID.randomUUID(), + "GrantED", UUID.randomUUID(), + "GrantEE", UUID.randomUUID(), + false, + "ObjectTable", "ObjectId", UUID.randomUUID(), + RbacRoleType.owner); // @formatter:on + + // when + final var display = entity.toDisplay(); + + // then + assertThat(display).isEqualTo("{ grant role GrantED to user GrantEE by role GrantER }"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java new file mode 100644 index 00000000..b0939994 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerRestTest.java @@ -0,0 +1,71 @@ +package net.hostsharing.hsadminng.rbac.rbacuser; + +import net.hostsharing.hsadminng.context.Context; +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.UUID; + +import static net.hostsharing.test.IsValidUuidMatcher.isValidUuid; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(RbacUserController.class) +class RbacUserControllerRestTest { + + @Autowired + MockMvc mockMvc; + @MockBean + Context contextMock; + @MockBean + RbacUserRepository rbacUserRepository; + + @Test + void createUserUsesGivenUuid() throws Exception { + // given + final var givenUuid = UUID.randomUUID(); + + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/rbac-users") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "uuid": "%s" + } + """.formatted(givenUuid)) + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isCreated()) + .andExpect(jsonPath("uuid", is(givenUuid.toString()))); + + // then + verify(rbacUserRepository).create(argThat(entity -> entity.getUuid().equals(givenUuid))); + } + + @Test + void createUserGeneratesRandomUuidIfNotGiven() throws Exception { + // when + mockMvc.perform(MockMvcRequestBuilders + .post("/api/rbac-users") + .contentType(MediaType.APPLICATION_JSON) + .content("{}") + .accept(MediaType.APPLICATION_JSON)) + + // then + .andExpect(status().isCreated()) + .andExpect(jsonPath("uuid", isValidUuid())); + + // then + verify(rbacUserRepository).create(argThat(entity -> entity.getUuid() != null)); + } +} diff --git a/src/test/java/net/hostsharing/test/IsValidUuidMatcher.java b/src/test/java/net/hostsharing/test/IsValidUuidMatcher.java new file mode 100644 index 00000000..61cd9c5f --- /dev/null +++ b/src/test/java/net/hostsharing/test/IsValidUuidMatcher.java @@ -0,0 +1,37 @@ +package net.hostsharing.test; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.UUID; + +public class IsValidUuidMatcher extends BaseMatcher { + + public static Matcher isValidUuid() { + return new IsValidUuidMatcher(); + } + + public static boolean isValidUuid(final String actual) { + try { + UUID.fromString(actual); + } catch (final IllegalArgumentException exc) { + return false; + } + return true; + } + + @Override + public boolean matches(final Object actual) { + if (actual == null || actual.getClass().isAssignableFrom(CharSequence.class)) { + return false; + } + return isValidUuid(actual.toString()); + } + + @Override + public void describeTo(final Description description) { + description.appendText("valid UUID"); + } + +}