From 959c1d15b2c60b2c831eb7c50c03bc34c9867d8e Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 16:26:20 +0300 Subject: [PATCH] Add basic integration tests for all BaseController extensions --- .../controller/ApplicationControllerTest.java | 57 ++ .../boot/controller/BaseControllerTest.java | 569 ++++++++++++++++++ .../boot/controller/GroupControllerTest.java | 57 ++ .../boot/controller/IBaseController.java | 23 + .../boot/controller/LayerControllerTest.java | 60 ++ .../boot/controller/UserControllerTest.java | 254 ++++++++ 6 files changed, 1020 insertions(+) create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java new file mode 100644 index 000000000..2e948b2eb --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java @@ -0,0 +1,57 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import de.terrestris.shogun.lib.controller.ApplicationController; +import de.terrestris.shogun.lib.model.Application; +import de.terrestris.shogun.lib.model.Group; +import de.terrestris.shogun.lib.repository.ApplicationRepository; + +import java.util.ArrayList; +import java.util.List; + +public class ApplicationControllerTest extends BaseControllerTest { + + public void setBaseEntity() { + entityClass = Application.class; + } + + public void setBasePath() { + basePath = "/applications"; + } + + public void insertTestData() { + Application entity1 = new Application(); + Application entity2 = new Application(); + Application entity3 = new Application(); + + entity1.setName("Application 1"); + entity2.setName("Application 2"); + entity3.setName("Application 3"); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java new file mode 100644 index 000000000..6043b9086 --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java @@ -0,0 +1,569 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import de.terrestris.shogun.boot.config.ApplicationConfig; +import de.terrestris.shogun.boot.config.JdbcConfiguration; +import de.terrestris.shogun.lib.controller.BaseController; +import de.terrestris.shogun.lib.enumeration.PermissionCollectionType; +import de.terrestris.shogun.lib.model.BaseEntity; +import de.terrestris.shogun.lib.model.User; +import de.terrestris.shogun.lib.repository.BaseCrudRepository; +import de.terrestris.shogun.lib.repository.UserRepository; +import de.terrestris.shogun.lib.repository.security.permission.UserClassPermissionRepository; +import de.terrestris.shogun.lib.repository.security.permission.UserInstancePermissionRepository; +import de.terrestris.shogun.lib.service.security.permission.UserClassPermissionService; +import de.terrestris.shogun.lib.service.security.permission.UserInstancePermissionService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.servlet.server.Encoding; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest( + classes = { + ApplicationConfig.class, + JdbcConfiguration.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT +) +@ActiveProfiles("test") +public abstract class BaseControllerTest implements IBaseController { + + @Autowired + protected R repository; + + @Autowired + protected UserRepository userRepository; + + @Autowired + protected UserInstancePermissionRepository userInstancePermissionRepository; + + @Autowired + protected UserClassPermissionRepository userClassPermissionRepository; + + @Autowired + protected UserInstancePermissionService userInstancePermissionService; + + @Autowired + protected UserClassPermissionService userClassPermissionService; + + @Autowired + private WebApplicationContext context; + + @Autowired + protected ObjectMapper objectMapper; + + protected Class entityClass; + + protected String basePath; + + protected MockMvc mockMvc; + + protected List testData; + + protected User adminUser; + + protected User user; + + public void initMockMvc() { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .alwaysDo(print()) + .apply(springSecurity()) + .build(); + } + + public void initAdminUser() { + User adminUser = new User(); + String keycloakId = "bf5efad6-50f5-448c-b808-60dc0259d70b"; + adminUser.setKeycloakId(keycloakId); + UserRepresentation keycloakRepresentation = new UserRepresentation(); + keycloakRepresentation.setEmail("admin@shogun.de"); + keycloakRepresentation.setEnabled(true); + keycloakRepresentation.setUsername("admin"); + ArrayList realmRoles = new ArrayList<>(); + realmRoles.add("ROLE_ADMIN"); + keycloakRepresentation.setRealmRoles(realmRoles); + adminUser.setKeycloakRepresentation(keycloakRepresentation); + + User persistedAdminUser = userRepository.save(adminUser); + + this.adminUser = persistedAdminUser; + } + + public void initUser() { + User user = new User(); + String keycloakId = "01e680f5-e8a4-460f-8897-12b4cf893739"; + user.setKeycloakId(keycloakId); + UserRepresentation keycloakRepresentation = new UserRepresentation(); + keycloakRepresentation.setEmail("user@shogun.de"); + keycloakRepresentation.setEnabled(true); + keycloakRepresentation.setUsername("user"); + ArrayList realmRoles = new ArrayList<>(); + realmRoles.add("ROLE_USER"); + keycloakRepresentation.setRealmRoles(realmRoles); + user.setKeycloakRepresentation(keycloakRepresentation); + + User persistedUser = userRepository.save(user); + + this.user = persistedUser; + } + + public void cleanupPermissions() { + userInstancePermissionRepository.deleteAll(); + userClassPermissionRepository.deleteAll(); + }; + + public void deinitAdminUser() { + userRepository.delete(this.adminUser); + } + + public void deinitUser() { + userRepository.delete(this.user); + } + + public void cleanupTestData() { + repository.deleteAll(); + } + + public Authentication getMockAuthentication(User mockUser) { + IDToken idToken = new IDToken(); + idToken.setSubject(mockUser.getKeycloakId()); + + KeycloakSecurityContext securityContext = new KeycloakSecurityContext(null, null, null, idToken); + KeycloakPrincipal principal = new KeycloakPrincipal<>(mockUser.getKeycloakRepresentation().getUsername(), securityContext); + + Set roles = mockUser.getKeycloakRepresentation().getRealmRoles().stream().collect(Collectors.toSet()); + SimpleKeycloakAccount account = new SimpleKeycloakAccount(principal, roles, null); + Authentication authentication = new KeycloakAuthenticationToken(account, false); + + return authentication; + } + + @BeforeEach + public void setUp() { + initMockMvc(); + initAdminUser(); + initUser(); + setBaseEntity(); + setBasePath(); + insertTestData(); + } + + @AfterEach + public void teardown() { + cleanupPermissions(); + deinitUser(); + deinitAdminUser(); + cleanupTestData(); + } + + @Test + public void findAll_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + } + + @Test + public void findAll_shouldReturnNoEntitiesForRoleUserWithoutExplicitPermissions() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(0))); + } + + @Test + public void findAll_shouldReturnAllAvailableEntitiesForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(this.testData.get(0), this.user, PermissionCollectionType.READ); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(1))); + } + + @Test + public void findAll_shouldReturnAllAvailableEntitiesForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.adminUser))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(testData.size()))); + } + + @Test + public void findOne_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + } + + @Test + public void findOne_shouldReturnNoEntityForRoleUserWithoutExplicitPermission() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + public void findOne_shouldReturnAnAvailableEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(this.testData.get(0), this.user, PermissionCollectionType.READ); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasEntry("id", testData.get(0).getId().intValue()))); + } + + @Test + public void findOne_shouldReturnAnAvailableEntityForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasEntry("id", testData.get(0).getId().intValue()))); + } + + @Test + public void add_shouldDenyAccessForRoleAnonymous() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void add_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void add_shouldCreateTheEntityForRoleUser() throws Exception { + + userClassPermissionService.setPermission(entityClass, this.user, PermissionCollectionType.CREATE); + + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } + + @Test + public void add_shouldCreateTheEntityForRoleAdmin() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } + + @Test + public void update_shouldDenyAccessForRoleAnonymous() throws Exception { + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + } + + @Test + public void update_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + public void update_shouldUpdateTheEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(testData.get(0), this.user, PermissionCollectionType.UPDATE); + + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + Optional updatedEntity = repository.findById(testData.get(0).getId()); + assertNotEquals(updatedEntity.get().getCreated(), updatedEntity.get().getModified()); + } + + @Test + public void update_shouldUpdateTheEntityForRoleAdmin() throws Exception { + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + Optional updatedEntity = repository.findById(testData.get(0).getId()); + assertNotEquals(updatedEntity.get().getCreated(), updatedEntity.get().getModified()); + } + + @Test + public void delete_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void delete_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void delete_shouldDeleteAnAvailableEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(testData.get(0), this.user, PermissionCollectionType.READ_DELETE); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(2, persistedEntities.size()); + } + + @Test + public void delete_shouldDeleteAnAvailableEntityForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(2, persistedEntities.size()); + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java new file mode 100644 index 000000000..aadb571bb --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java @@ -0,0 +1,57 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import de.terrestris.shogun.lib.controller.GroupController; +import de.terrestris.shogun.lib.model.Group; +import de.terrestris.shogun.lib.repository.GroupRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class GroupControllerTest extends BaseControllerTest { + + public void setBaseEntity() { + entityClass = Group.class; + } + + public void setBasePath() { + basePath = "/groups"; + } + + public void insertTestData() { + Group entity1 = new Group(); + Group entity2 = new Group(); + Group entity3 = new Group(); + + entity1.setKeycloakId(UUID.randomUUID().toString()); + entity2.setKeycloakId(UUID.randomUUID().toString()); + entity3.setKeycloakId(UUID.randomUUID().toString()); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java new file mode 100644 index 000000000..58cd53dcf --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java @@ -0,0 +1,23 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +public interface IBaseController { + void setBaseEntity(); + void setBasePath(); + void insertTestData(); +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java new file mode 100644 index 000000000..ef9f009e5 --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java @@ -0,0 +1,60 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import de.terrestris.shogun.lib.controller.LayerController; +import de.terrestris.shogun.lib.enumeration.LayerType; +import de.terrestris.shogun.lib.model.Layer; +import de.terrestris.shogun.lib.repository.LayerRepository; + +import java.util.ArrayList; +import java.util.List; + +public class LayerControllerTest extends BaseControllerTest { + + public void setBaseEntity() { + entityClass = Layer.class; + } + + public void setBasePath() { + basePath = "/layers"; + } + + public void insertTestData() { + Layer entity1 = new Layer(); + Layer entity2 = new Layer(); + Layer entity3 = new Layer(); + + entity1.setName("Layer 1"); + entity1.setType(LayerType.WMS); + entity2.setName("Layer 2"); + entity2.setType(LayerType.WFS); + entity3.setName("Layer 3"); + entity3.setType(LayerType.TILEWMS); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java new file mode 100644 index 000000000..6b1f46bc6 --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java @@ -0,0 +1,254 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import de.terrestris.shogun.lib.controller.UserController; +import de.terrestris.shogun.lib.enumeration.PermissionCollectionType; +import de.terrestris.shogun.lib.model.User; +import de.terrestris.shogun.lib.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.server.Encoding; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +public class UserControllerTest extends BaseControllerTest { + + public void setBaseEntity () { + entityClass = User.class; + }; + + public void setBasePath() { + basePath = "/users"; + } + + public void insertTestData() { + User entity1 = new User(); + User entity2 = new User(); + User entity3 = new User(); + + entity1.setKeycloakId(UUID.randomUUID().toString()); + entity2.setKeycloakId(UUID.randomUUID().toString()); + entity3.setKeycloakId(UUID.randomUUID().toString()); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + + @Test + @Override + public void findAll_shouldReturnAllAvailableEntitiesForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.adminUser))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(testData.size() + 2))); + } + + @Test + @Override + public void add_shouldDenyAccessForRoleAnonymous() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void add_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void add_shouldCreateTheEntityForRoleUser() throws Exception { + + userClassPermissionService.setPermission(entityClass, this.user, PermissionCollectionType.CREATE); + + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(6, persistedEntities.size()); + } + + @Test + @Override + public void add_shouldCreateTheEntityForRoleAdmin() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(6, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDeleteAnAvailableEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(testData.get(0), this.user, PermissionCollectionType.DELETE); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDeleteAnAvailableEntityForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } +}