From d9149d0356165fbf05b9e1a368e3d9400e778254 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:19:06 +0200 Subject: [PATCH 01/40] updated ClusterControllerTests --- backend/app/build.gradle | 2 + .../ugent/pidgeon/controllers/ApiRoutes.java | 7 -- .../com/ugent/pidgeon/CustomObjectMapper.java | 15 ++- .../controllers/ClusterControllerTest.java | 94 +++++++++++++++++-- 4 files changed, 98 insertions(+), 20 deletions(-) diff --git a/backend/app/build.gradle b/backend/app/build.gradle index 8ab8a618..dfbf15f8 100644 --- a/backend/app/build.gradle +++ b/backend/app/build.gradle @@ -44,6 +44,8 @@ dependencies { implementation "org.springframework.boot:spring-boot-devtools" testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' } // tasks.named('test',Test) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java index 763f9cd2..ab5886cc 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java @@ -3,7 +3,6 @@ public final class ApiRoutes { public static final String USERS_BASE_PATH = "/api/users"; public static final String COURSE_BASE_PATH = "/api/courses"; - public static final String DEADLINE_BASE_PATH = "/api/deadlines"; public static final String PROJECT_BASE_PATH = "/api/projects"; public static final String LOGGEDIN_USER_PATH = "/api/user"; @@ -14,10 +13,4 @@ public final class ApiRoutes { public static final String GROUP_MEMBER_BASE_PATH = GROUP_BASE_PATH + "/{groupid}/members"; public static final String GROUP_FEEDBACK_PATH = PROJECT_BASE_PATH + "/{projectid}/groups/{groupid}/score"; public static final String CLUSTER_BASE_PATH = "/api/clusters"; - - public static final String USER_AUTH_PATH = "/api/auth"; - - public static final String GROUP_SCORE_PATH = GROUP_BASE_PATH + "/{groupid}/score"; - - } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/CustomObjectMapper.java b/backend/app/src/test/java/com/ugent/pidgeon/CustomObjectMapper.java index ee3c199f..d4465ef1 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/CustomObjectMapper.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/CustomObjectMapper.java @@ -1,15 +1,20 @@ package com.ugent.pidgeon; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import java.time.OffsetDateTime; public class CustomObjectMapper { public static ObjectMapper createObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer()); - objectMapper.registerModule(module); - return objectMapper; + return JsonMapper.builder() // or different mapper for other format + .addModule(new ParameterNamesModule()) + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + // and possibly other configuration, modules, then: + .build(); } } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java index 24446585..b67a9e87 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java @@ -1,5 +1,9 @@ package com.ugent.pidgeon.controllers; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; +import com.ugent.pidgeon.model.json.GroupClusterJson; +import com.ugent.pidgeon.model.json.GroupJson; import com.ugent.pidgeon.postgre.models.CourseEntity; import com.ugent.pidgeon.postgre.models.GroupClusterEntity; import com.ugent.pidgeon.postgre.models.GroupEntity; @@ -8,6 +12,8 @@ import com.ugent.pidgeon.postgre.repository.GroupRepository; import com.ugent.pidgeon.postgre.repository.GroupUserRepository; import com.ugent.pidgeon.util.*; +import java.time.OffsetDateTime; +import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,6 +31,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(MockitoExtension.class) @@ -42,6 +50,7 @@ public class ClusterControllerTest extends ControllerTest{ private ClusterUtil clusterUtil; @Mock private EntityToJsonConverter entityToJsonConverter; + @Mock private CourseUtil courseUtil; @Mock @@ -51,7 +60,11 @@ public class ClusterControllerTest extends ControllerTest{ private CourseEntity courseEntity; private GroupClusterEntity groupClusterEntity; + private GroupClusterJson groupClusterJson; private GroupEntity groupEntity; + private GroupJson groupJson; + + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); @BeforeEach public void setup() { @@ -65,17 +78,25 @@ public void setup() { courseEntity = new CourseEntity("name", "description",2024); groupClusterEntity = new GroupClusterEntity(1L, 20, "clustername", 5); + groupClusterJson = new GroupClusterJson(1L, "clustername", 20, 5, OffsetDateTime.now(), Collections.emptyList(), ""); groupEntity = new GroupEntity("groupName", 1L); + groupJson = new GroupJson(10, 1L, "Groupname", ""); } @Test public void testGetClustersForCourse() throws Exception { + + /* If the user is enrolled in the course, the clusters are returned */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.enrolled))); when(groupClusterRepository.findClustersWithoutInvidualByCourseId(anyLong())).thenReturn(List.of(groupClusterEntity)); + when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/clusters")) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(groupClusterJson)))); + /* If a certain check fails, the corresponding status code is returned */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/clusters")) @@ -84,21 +105,27 @@ public void testGetClustersForCourse() throws Exception { @Test public void testCreateClusterForCourse() throws Exception { + /* If the user is an admin of the course and the json is valid, the cluster is created */ String request = "{\"name\": \"test\", \"capacity\": 20, \"groupCount\": 5}"; when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", courseEntity)); when(clusterUtil.checkGroupClusterCreateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(groupClusterRepository.save(any())).thenReturn(groupClusterEntity); + when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/clusters") .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isCreated()); + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupClusterJson))); + /* If the json is invalid, the corresponding status code is returned */ when(clusterUtil.checkGroupClusterCreateJson(any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/clusters") .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isIAmATeapot()); + /* If the user is not an admin of the course, the corresponding status code is returned */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/clusters") .contentType(MediaType.APPLICATION_JSON) @@ -108,10 +135,15 @@ public void testCreateClusterForCourse() throws Exception { @Test public void testGetCluster() throws Exception { + /* If the user has acces to the cluster and it isn't an individual cluster, the cluster is returned */ + when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.CLUSTER_BASE_PATH + "/1")) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupClusterJson))); + /* If any check fails, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.CLUSTER_BASE_PATH + "/1")) .andExpect(status().isIAmATeapot()); @@ -120,21 +152,30 @@ public void testGetCluster() throws Exception { //This function also tests doGroupClusterUpdate @Test public void testUpdateCluster() throws Exception { - String request = "{\"name\": \"clustername\", \"capacity\": 20}"; + String request = "{\"name\": \"newclustername\", \"capacity\": 20}"; + /* If the user is an admin of the cluster, the cluster isn't individual and the json is valid, the cluster is updated */ + GroupClusterEntity copy = new GroupClusterEntity(1L, 20, "newclustername", 5); when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + .thenReturn(new CheckResult<>(HttpStatus.OK, "", copy)); when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + copy.setName("newclustername"); + GroupClusterJson updated = new GroupClusterJson(1L, "newclustername", 20, 5, OffsetDateTime.now(), Collections.emptyList(), ""); + when(entityToJsonConverter.clusterEntityToClusterJson(copy)).thenReturn(updated); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updated))); + /* If the json is invalid, the corresponding status code is returned */ when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isForbidden()); + /* If the user is not an admin of the cluster or the cluster is individual, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) @@ -145,14 +186,42 @@ public void testUpdateCluster() throws Exception { @Test public void testPatchCluster() throws Exception { String request = "{\"name\": null, \"capacity\": null}"; + /* If the user is an admin of the cluster and the json is valid, the cluster is updated */ + /* If fields are null they are not updated */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupClusterJson))); + /* If fields are not null they are updated */ + request = "{\"name\": \"newclustername\", \"capacity\": 22}"; + GroupClusterEntity copy = new GroupClusterEntity(1L, 20, "newclustername", 5); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", copy)); + copy.setName("newclustername"); + copy.setMaxSize(22); + GroupClusterJson updated = new GroupClusterJson(1L, "newclustername", 22, 5, OffsetDateTime.now(), Collections.emptyList(), ""); + when(entityToJsonConverter.clusterEntityToClusterJson(copy)).thenReturn(updated); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updated))); + + /* If the json is invalid, the corresponding status code is returned */ + when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isForbidden()); + /* If the user is not an admin of the cluster or the cluster is individual, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) @@ -162,15 +231,18 @@ public void testPatchCluster() throws Exception { @Test public void testDeleteCluster() throws Exception { + /* If the user can delete the cluster, the cluster is deleted */ when(clusterUtil.canDeleteCluster(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.OK,"", null)); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.CLUSTER_BASE_PATH + "/1")) .andExpect(status().isNoContent()); + /* If the delete fails, the corresponding status code is returned */ when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT,"", null)); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.CLUSTER_BASE_PATH + "/1")) .andExpect(status().isIAmATeapot()); + /* If the user can't delete the cluster, the corresponding status code is returned */ when(clusterUtil.canDeleteCluster(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN,"", null)); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.CLUSTER_BASE_PATH + "/1")) .andExpect(status().isForbidden()); @@ -179,19 +251,25 @@ public void testDeleteCluster() throws Exception { @Test public void testCreateGroupForCluster() throws Exception { String request = "{\"name\": \"test\"}"; + /* If the user is an admin of the cluster and the json is valid, the group is created */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); when(groupRepository.save(any())).thenReturn(groupEntity); + when(entityToJsonConverter.groupEntityToJson(groupEntity)).thenReturn(groupJson); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isCreated()); + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupJson))); + /* if the user is not an admin or the cluster is individual, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isForbidden()); + /* If the json is invalid, the corresponding status code is returned */ request = "{\"name\": \"\"}"; mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") .contentType(MediaType.APPLICATION_JSON) From 14bcc40bc0e58cd90f27de8e7a75c8a890cfd512 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 28 Apr 2024 12:16:24 +0200 Subject: [PATCH 02/40] use interceptor in tests,start upgrade coursetests --- .../pidgeon/controllers/CourseController.java | 9 +- .../controllers/ClusterControllerTest.java | 8 +- .../pidgeon/controllers/ControllerTest.java | 48 ++++- .../controllers/CourseControllerTest.java | 187 ++++++++++++++++-- 4 files changed, 212 insertions(+), 40 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java index b0148065..8ce47950 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java @@ -59,7 +59,6 @@ public class CourseController { public ResponseEntity getUserCourses(Auth auth, @RequestParam(value = "archived", required = false) Boolean archived) { long userID = auth.getUserEntity().getId(); try { - Logger.getGlobal().info("Archived: " + archived); List userCourses = new ArrayList<>(); if (archived == null || !archived) { userCourses.addAll(userRepository.findCourseIdsByUserId(userID)); @@ -67,6 +66,7 @@ public ResponseEntity getUserCourses(Auth auth, @RequestParam(value = "archiv if (archived == null || archived) { userCourses.addAll(userRepository.findArchivedCoursesByUserId(userID)); } + // Retrieve course entities based on user courses List courseJSONObjects = userCourses.stream() .map(courseWithRelation -> { @@ -79,7 +79,10 @@ public ResponseEntity getUserCourses(Auth auth, @RequestParam(value = "archiv ) .filter(Objects::nonNull) .toList(); + for (CourseWithRelationJson courseJson: courseJSONObjects) { + Logger.getGlobal().info("UserCourses: " + courseJson); + } // Return the JSON string in ResponseEntity return ResponseEntity.ok(courseJSONObjects); } catch (Exception e) { @@ -117,7 +120,7 @@ public ResponseEntity createCourse(@RequestBody CourseJson courseJson, Auth a courseEntity.setCreatedAt(currentTimestamp); courseEntity.setJoinKey(UUID.randomUUID().toString()); // Save course - courseRepository.save(courseEntity); + courseEntity = courseRepository.save(courseEntity); // Add user as course creator CourseUserEntity courseUserEntity = new CourseUserEntity(courseEntity.getId(), userId, CourseRelation.creator); @@ -147,7 +150,7 @@ private ResponseEntity doCourseUpdate(CourseEntity courseEntity, CourseJson c if (courseJson.getArchived() != null) { courseEntity.setArchivedAt(courseJson.getArchived() ? OffsetDateTime.now() : null); } - courseRepository.save(courseEntity); + courseEntity = courseRepository.save(courseEntity); return ResponseEntity.ok(entityToJsonConverter.courseEntityToCourseWithInfo(courseEntity, courseUtil.getJoinLink(courseEntity.getJoinKey(), "" + courseEntity.getId()), false)); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java index b67a9e87..3e829319 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java @@ -68,13 +68,7 @@ public class ClusterControllerTest extends ControllerTest{ @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(clusterController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { - request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); - return request; - })) - .build(); + setUpController(clusterController); courseEntity = new CourseEntity("name", "description",2024); groupClusterEntity = new GroupClusterEntity(1L, 20, "clustername", 5); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java index 79af404e..bd54e927 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java @@ -1,30 +1,44 @@ package com.ugent.pidgeon.controllers; +import com.ugent.pidgeon.auth.RolesInterceptor; import com.ugent.pidgeon.model.Auth; import com.ugent.pidgeon.model.User; import com.ugent.pidgeon.postgre.models.UserEntity; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.UserRepository; +import java.util.logging.Logger; +import org.apache.juli.logging.Log; +import org.hibernate.annotations.Check; import org.junit.jupiter.api.BeforeEach; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; import java.util.ArrayList; import java.util.Optional; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class ControllerTest { protected MockMvc mockMvc; + private UserEntity mockUser; @Mock protected UserRepository userRepository; + RolesInterceptor rolesInterceptor; + @BeforeEach public void testSetUp() { MockitoAnnotations.openMocks(this); @@ -33,23 +47,37 @@ public void testSetUp() { Auth authUser = new Auth(user, new ArrayList<>()); SecurityContextHolder.getContext().setAuthentication(authUser); - // Only stubbing necessary methods for the test - UserEntity userEntity = mockUser(); - authUser.setUserEntity(userEntity); - lenient().when(userRepository.findById(anyLong())).thenReturn(Optional.of(userEntity)); + mockUser = new UserEntity(); + mockUser.setId(1L); + mockUser.setRole(UserRole.teacher); + authUser.setUserEntity(mockUser); + lenient().when(userRepository.findById(anyLong())).thenReturn(Optional.of(mockUser)); lenient().when(userRepository.findCourseIdsByUserId(anyLong())).thenReturn(new ArrayList<>()); - // when(userRepository.findUserByAzureId(anyString())).thenReturn(userEntity); + lenient().when(userRepository.findUserByAzureId("test")).thenReturn(Optional.of(mockUser)); + Logger.getGlobal().info("User: " + mockUser); + rolesInterceptor = new RolesInterceptor(userRepository); + // when(userRepository.findUserByAzureId(anyString())).thenReturn(userEntity); + } + protected void setUpController(Object controller) { + mockMvc = MockMvcBuilders.standaloneSetup(controller) + .addInterceptors(rolesInterceptor) + .defaultRequest(MockMvcRequestBuilders.get("/**") + .with(request -> { + request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); + return request; + })) + .build(); } - protected UserEntity mockUser() { - UserEntity userEntity = new UserEntity(); - userEntity.setId(1L); - userEntity.setRole(UserRole.student); - return userEntity; + protected UserEntity getMockUser() { + return mockUser; } + protected void setMockUserRoles(UserRole role) { + mockUser.setRole(role); + } } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java index 9e1e05d5..3e4eefda 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java @@ -1,15 +1,21 @@ package com.ugent.pidgeon.controllers; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; import com.ugent.pidgeon.model.ProjectResponseJson; import com.ugent.pidgeon.model.json.CourseReferenceJson; import com.ugent.pidgeon.model.json.CourseWithInfoJson; +import com.ugent.pidgeon.model.json.CourseWithRelationJson; import com.ugent.pidgeon.model.json.ProjectProgressJson; import com.ugent.pidgeon.model.json.UserReferenceJson; import com.ugent.pidgeon.postgre.models.*; import com.ugent.pidgeon.postgre.models.types.CourseRelation; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.*; +import com.ugent.pidgeon.postgre.repository.UserRepository.CourseIdWithRelation; import com.ugent.pidgeon.util.*; +import java.util.Collections; +import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,8 +37,14 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(MockitoExtension.class) @@ -66,25 +78,79 @@ public class CourseControllerTest extends ControllerTest { @InjectMocks private CourseController courseController; + private CourseEntity archivedCourse; + private CourseEntity activeCourse; + private CourseWithInfoJson activeCourseJson; + + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); + + @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(courseController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { - request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); - return request; - })) - .build(); + setUpController(courseController); + + archivedCourse = new CourseEntity("archivedname", "description",2024); + archivedCourse.setArchivedAt(OffsetDateTime.now()); + archivedCourse.setId(1); + activeCourse = new CourseEntity("name", "description",2024); + archivedCourse.setId(2); + + activeCourseJson = new CourseWithInfoJson( + activeCourse.getId(), + activeCourse.getName(), + activeCourse.getDescription(), + new UserReferenceJson("", "", 0L), + new ArrayList<>(), + "", + "", + "", + OffsetDateTime.now(), + OffsetDateTime.now(), + activeCourse.getCourseYear()); + } @Test public void testGetUserCourses() throws Exception { - CourseEntity course = mock(CourseEntity.class); + + /* Mock active course return */ when(userRepository.findCourseIdsByUserId(anyLong())). thenReturn(List.of(new UserRepository.CourseIdWithRelation[]{new UserRepository.CourseIdWithRelation() { @Override public Long getCourseId() { - return 1L; + return activeCourse.getId(); + } + + @Override + public CourseRelation getRelation() { + return CourseRelation.course_admin; + } + + @Override + public String getName() { + return activeCourse.getName(); + } + }})); + CourseWithRelationJson courseJson = new CourseWithRelationJson( + "", + CourseRelation.course_admin, + activeCourse.getName(), + activeCourse.getId(), + activeCourse.getArchivedAt(), + 2, + activeCourse.getCreatedAt(), + activeCourse.getCourseYear() + ); + when(entityToJsonConverter.courseEntityToCourseWithRelation(activeCourse, CourseRelation.course_admin)). + thenReturn(courseJson); + when(courseRepository.findById(activeCourse.getId())).thenReturn(Optional.of(activeCourse)); + + /* Mock archived course return */ + when(userRepository.findArchivedCoursesByUserId(anyLong())). + thenReturn(List.of(new UserRepository.CourseIdWithRelation[]{new UserRepository.CourseIdWithRelation() { + @Override + public Long getCourseId() { + return archivedCourse.getId(); } @Override @@ -94,16 +160,50 @@ public CourseRelation getRelation() { @Override public String getName() { - return ""; + return archivedCourse.getName(); } }})); - when(courseRepository.findById(anyLong())).thenReturn(Optional.empty()); + CourseWithRelationJson archivedCourseJson = new CourseWithRelationJson( + "", + CourseRelation.course_admin, + archivedCourse.getName(), + archivedCourse.getId(), + archivedCourse.getArchivedAt(), + 2, + archivedCourse.getCreatedAt(), + archivedCourse.getCourseYear() + ); + when(entityToJsonConverter.courseEntityToCourseWithRelation(archivedCourse, CourseRelation.course_admin)). + thenReturn(archivedCourseJson); + when(courseRepository.findById(archivedCourse.getId())).thenReturn(Optional.of(archivedCourse)); + /* If no archived param, return archived and active courses */ mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH)) - .andExpect(status().isOk()); - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(courseJson, archivedCourseJson)))); + + /* If archived param is false, return only active courses */ + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "?archived=false")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(courseJson)))); + + /* If archived param is true, return only archived courses */ + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "?archived=true")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(archivedCourseJson)))); + + /* If no courses are found, return empty list */ + when(userRepository.findCourseIdsByUserId(anyLong())).thenReturn(Collections.emptyList()); + when(userRepository.findArchivedCoursesByUserId(anyLong())).thenReturn(Collections.emptyList()); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + /* If error occurs, return 500 */ when(userRepository.findCourseIdsByUserId(anyLong())).thenThrow(new RuntimeException()); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH)) .andExpect(status().isInternalServerError()); @@ -113,26 +213,54 @@ public String getName() { @Test public void testCreateCourse() throws Exception { String courseJson = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024}"; + /* If everything is correct, return 200 */ when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(courseRepository.save(any())).thenReturn(null); + when(courseRepository.save(any())).thenReturn(activeCourse); when(courseUserRepository.save(any())).thenReturn(null); when(groupClusterRepository.save(any())).thenReturn(null); when(courseUtil.getJoinLink(any(), any())).thenReturn(""); when(entityToJsonConverter.courseEntityToCourseWithInfo(any(), any(), anyBoolean())). - thenReturn(new CourseWithInfoJson(0L, "", "", new UserReferenceJson("", "", 0L), - new ArrayList<>(), "", "", "", OffsetDateTime.now(), OffsetDateTime.now(), 2013)); + thenReturn(activeCourseJson); + + Logger.getGlobal().info("User: " + getMockUser().getRole()); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(activeCourseJson))); + + verify(courseUserRepository, times(1)).save(argThat(courseUser -> + courseUser.getCourseId() == activeCourse.getId() && + courseUser.getUserId() == getMockUser().getId() && + courseUser.getRelation().equals(CourseRelation.creator) + )); + verify(groupClusterRepository, times(1)).save(argThat(groupCluster -> + groupCluster.getCourseId() == activeCourse.getId() && + groupCluster.getMaxSize() == 1 && + groupCluster.getGroupAmount() == 0 + )); + verify(entityToJsonConverter, times(1)).courseEntityToCourseWithInfo(activeCourse, "", false); + verify(courseUtil, times(1)).getJoinLink(activeCourse.getJoinKey(), "" + activeCourse.getId()); + + /* If user is not a teacher, return 403 */ + setMockUserRoles(UserRole.student); + mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) + .contentType(MediaType.APPLICATION_JSON) + .content(courseJson)) + .andExpect(status().isForbidden()); + setMockUserRoles(UserRole.teacher); + /* If course json is invalid, return 400 */ + when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); when(courseUtil.checkCourseJson(any(), any(),any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) .andExpect(status().isIAmATeapot()); + /* If error occurs, return 500 */ when(courseUtil.checkCourseJson(any(), any(), any())).thenThrow(new RuntimeException()); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) .contentType(MediaType.APPLICATION_JSON) @@ -145,15 +273,33 @@ public void testCreateCourse() throws Exception { @Test public void testUpdateCourse() throws Exception { String courseJson = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024}"; + /* If admin and valid json, update course and return 200 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new CourseEntity())); + thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); + CourseEntity updatedEntity = new CourseEntity("test", "description",2024); + CourseWithInfoJson updatedJson = new CourseWithInfoJson( + activeCourse.getId(), + "test", + "description", + new UserReferenceJson("", "", 0L), + new ArrayList<>(), + "", + "", + "", + OffsetDateTime.now(), + OffsetDateTime.now(), + 2023 + ); when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(courseRepository.save(any())).thenReturn(null); + when(courseUtil.getJoinLink(any(), any())).thenReturn(""); + when(courseRepository.save(any())).thenReturn(updatedEntity); + when(entityToJsonConverter.courseEntityToCourseWithInfo(updatedEntity, "", false)).thenReturn(updatedJson); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) .andExpect(status().isOk()); + when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) @@ -166,6 +312,7 @@ public void testUpdateCourse() throws Exception { .content(courseJson)) .andExpect(status().isForbidden()); + /* If error occurs, return 500 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenThrow(new RuntimeException()); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) From ded5f6b541b70d5e93e694715f7f43abef60a69d Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 28 Apr 2024 13:00:17 +0200 Subject: [PATCH 03/40] courseUpdate tests upgraded --- .../controllers/ClusterController.java | 2 +- .../controllers/ClusterControllerTest.java | 18 +- .../controllers/CourseControllerTest.java | 195 +++++++++++++----- 3 files changed, 155 insertions(+), 60 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java index 66146efa..5acd9888 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java @@ -164,7 +164,7 @@ public ResponseEntity doGroupClusterUpdate(GroupClusterEntity clusterEntity, } clusterEntity.setMaxSize(clusterJson.getCapacity()); clusterEntity.setName(clusterJson.getName()); - groupClusterRepository.save(clusterEntity); + clusterEntity = groupClusterRepository.save(clusterEntity); return ResponseEntity.ok(entityToJsonConverter.clusterEntityToClusterJson(clusterEntity)); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java index 3e829319..d853db2d 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java @@ -28,6 +28,8 @@ import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; @@ -179,12 +181,16 @@ public void testUpdateCluster() throws Exception { @Test public void testPatchCluster() throws Exception { - String request = "{\"name\": null, \"capacity\": null}"; + /* If the user is an admin of the cluster and the json is valid, the cluster is updated */ + String originalname = groupClusterEntity.getName(); + Integer originalcapacity = groupClusterEntity.getMaxSize(); /* If fields are null they are not updated */ + String request = "{\"name\": null, \"capacity\": null}"; when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupClusterRepository.save(groupClusterEntity)).thenReturn(groupClusterEntity); when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) @@ -192,14 +198,16 @@ public void testPatchCluster() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(groupClusterJson))); + assertEquals(originalname, groupClusterEntity.getName()); + assertEquals(originalcapacity, groupClusterEntity.getMaxSize()); + /* If fields are not null they are updated */ request = "{\"name\": \"newclustername\", \"capacity\": 22}"; GroupClusterEntity copy = new GroupClusterEntity(1L, 20, "newclustername", 5); when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", copy)); - copy.setName("newclustername"); - copy.setMaxSize(22); + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); GroupClusterJson updated = new GroupClusterJson(1L, "newclustername", 22, 5, OffsetDateTime.now(), Collections.emptyList(), ""); + when(groupClusterRepository.save(groupClusterEntity)).thenReturn(copy); when(entityToJsonConverter.clusterEntityToClusterJson(copy)).thenReturn(updated); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) @@ -207,6 +215,8 @@ public void testPatchCluster() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(updated))); + assertNotEquals(originalname, groupClusterEntity.getName()); + assertNotEquals(originalcapacity, groupClusterEntity.getMaxSize()); /* If the json is invalid, the corresponding status code is returned */ when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java index 3e4eefda..1d560a48 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java @@ -34,6 +34,10 @@ import java.util.List; import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; @@ -272,15 +276,93 @@ public void testCreateCourse() throws Exception { // This function also tests all lines of doCourseUpdate @Test public void testUpdateCourse() throws Exception { - String courseJson = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024}"; + String courseJson = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024}"; + /* If admin and valid json, update course and return 200 */ + when(courseUtil.getCourseIfAdmin(anyLong(), any())). + thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); + CourseEntity updatedEntity = new CourseEntity("test", "description",2024); + CourseWithInfoJson updatedJson = new CourseWithInfoJson( + activeCourse.getId(), + "test", + "description", + new UserReferenceJson("", "", 0L), + new ArrayList<>(), + "", + "", + "", + OffsetDateTime.now(), + OffsetDateTime.now(), + 2023 + ); + when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(courseUtil.getJoinLink(any(), any())).thenReturn(""); + when(courseRepository.save(any())).thenReturn(updatedEntity); + when(entityToJsonConverter.courseEntityToCourseWithInfo(updatedEntity, "", false)).thenReturn(updatedJson); + activeCourse.setArchivedAt(OffsetDateTime.now()); + OffsetDateTime originalArchivedAt = activeCourse.getArchivedAt(); + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJson)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updatedJson))); + assertEquals(originalArchivedAt, activeCourse.getArchivedAt()); + activeCourse.setArchivedAt(null); + + + /* If courseJson has archived field, update archived accordingly */ + String courseJsonWithArchivedTrue = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"true\"}"; + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJsonWithArchivedTrue)) + .andExpect(status().isOk()); + assertNotNull(activeCourse.getArchivedAt()); + + + String courseJsonWithArchivedFalse = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"false\"}"; + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJsonWithArchivedFalse)) + .andExpect(status().isOk()); + assertNull(activeCourse.getArchivedAt()); + + + /* If invalid json, return corresponding statuscode */ + when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJson)) + .andExpect(status().isIAmATeapot()); + + /* If not admin, return 403 */ + when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJson)) + .andExpect(status().isForbidden()); + + /* If error occurs, return 500 */ + when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJson)) + .andExpect(status().isInternalServerError()); + } + + @Test + public void testPatchCourse() throws Exception { + /* If admin and valid json, update course and return 200 */ + String originalName = activeCourse.getName(); + String originalDescription = activeCourse.getDescription(); + Integer originalYear = activeCourse.getCourseYear(); when(courseUtil.getCourseIfAdmin(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); - CourseEntity updatedEntity = new CourseEntity("test", "description",2024); + thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); + CourseEntity updatedEntity = new CourseEntity("test", "description2",2024); CourseWithInfoJson updatedJson = new CourseWithInfoJson( activeCourse.getId(), "test", - "description", + "description2", new UserReferenceJson("", "", 0L), new ArrayList<>(), "", @@ -292,70 +374,73 @@ public void testUpdateCourse() throws Exception { ); when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(courseUtil.getJoinLink(any(), any())).thenReturn(""); - when(courseRepository.save(any())).thenReturn(updatedEntity); + when(courseRepository.save(activeCourse)).thenReturn(updatedEntity); when(entityToJsonConverter.courseEntityToCourseWithInfo(updatedEntity, "", false)).thenReturn(updatedJson); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isOk()); - - - when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isIAmATeapot()); - - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isForbidden()); - - /* If error occurs, return 500 */ - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isInternalServerError()); - } + /* If field is not present, do not update it */ + String patchCourseJson = "{\"name\": \"test\"}"; + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(patchCourseJson)) + .andExpect(status().isOk()); + assertNotEquals(originalName, activeCourse.getName()); + assertEquals(originalDescription, activeCourse.getDescription()); + assertEquals(originalYear, activeCourse.getCourseYear()); + assertNull(activeCourse.getArchivedAt()); + /* If fields are present, update them */ + String requestJson = "{\"name\": \"test2\", \"description\": \"description2\",\"courseYear\" : 2034}"; + originalName = activeCourse.getName(); + activeCourse.setArchivedAt(OffsetDateTime.now()); + OffsetDateTime originalArchivedAt = activeCourse.getArchivedAt(); + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updatedJson))); + assertNotEquals(originalName, activeCourse.getName()); + assertNotEquals(originalDescription, activeCourse.getDescription()); + assertNotEquals(originalYear, activeCourse.getCourseYear()); + assertEquals(originalArchivedAt, activeCourse.getArchivedAt()); + activeCourse.setArchivedAt(null); + + + /* If courseJson has archived field, update archived accordingly */ + String courseJsonWithArchivedTrue = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"true\"}"; + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(courseJsonWithArchivedTrue)) + .andExpect(status().isOk()); + assertNotNull(activeCourse.getArchivedAt()); - @Test - public void testPatchCourse() throws Exception { - String courseJson = "{\"name\": null, \"description\": \"description\"}"; - CourseEntity courseEntity = new CourseEntity("name", "description",2024); - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", courseEntity)); - when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(courseRepository.save(any())).thenReturn(null); + String courseJsonWithArchivedFalse = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"false\"}"; mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isOk()); + .contentType(MediaType.APPLICATION_JSON) + .content(courseJsonWithArchivedFalse)) + .andExpect(status().isOk()); + assertNull(activeCourse.getArchivedAt()); - courseJson = "{\"name\": \"name\", \"description\": null}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isOk()); - courseJson = "{\"name\": null, \"description\": null}"; + /* If invalid json, return corresponding statuscode */ + when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isBadRequest()); + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isIAmATeapot()); + /* If not admin, return 403 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isForbidden()); + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isForbidden()); + /* If error occurs, return 500 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenThrow(new RuntimeException()); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") - .contentType(MediaType.APPLICATION_JSON) - .content(courseJson)) - .andExpect(status().isInternalServerError()); + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isInternalServerError()); } From 3f62f4f084e47b32ed66dfae0800c228e86a079f Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:35:59 +0200 Subject: [PATCH 04/40] courseControllerTest almost finished --- .../pidgeon/controllers/CourseController.java | 41 +- .../controllers/ClusterControllerTest.java | 9 +- .../controllers/CourseControllerTest.java | 715 +++++++++++++----- 3 files changed, 568 insertions(+), 197 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java index 8ce47950..22e9bccb 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java @@ -9,6 +9,7 @@ import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.*; import com.ugent.pidgeon.util.*; +import jakarta.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -284,9 +285,9 @@ public ResponseEntity deleteCourse(@PathVariable long courseId, Auth auth) { } } - + Iterable courseUsers = courseUserRepository.findAllUsersByCourseId(courseId); // Delete all courseusers linked to the course - courseUserRepository.deleteAll(courseUserRepository.findAllUsersByCourseId(courseId)); + courseUserRepository.deleteAll(courseUsers); // Delete the course courseRepository.deleteById(courseId); @@ -393,7 +394,7 @@ public ResponseEntity joinCourse(Auth auth, @PathVariable Long courseId, @Pat */ @GetMapping(ApiRoutes.COURSE_BASE_PATH + "/{courseId}/join/{courseKey}") @Roles({UserRole.student, UserRole.teacher}) - public ResponseEntity getCourseJoinKey(Auth auth, @PathVariable Long courseId, @PathVariable String courseKey) { + public ResponseEntity getCourseJoinInformation(Auth auth, @PathVariable Long courseId, @PathVariable String courseKey) { return getJoinLinkGetResponseEntity(courseId, courseKey, auth.getUserEntity()); } @@ -427,7 +428,7 @@ public ResponseEntity joinCourse(Auth auth, @PathVariable Long courseId) { */ @GetMapping(ApiRoutes.COURSE_BASE_PATH + "/{courseId}/join") @Roles({UserRole.student, UserRole.teacher}) - public ResponseEntity getCourseJoinKey(Auth auth, @PathVariable Long courseId) { + public ResponseEntity getCourseJoinInformation(Auth auth, @PathVariable Long courseId) { return getJoinLinkGetResponseEntity(courseId, null, auth.getUserEntity()); } @@ -448,20 +449,7 @@ public ResponseEntity leaveCourse(@PathVariable long courseId, Auth auth) { try { long userId = auth.getUserEntity().getId(); CheckResult checkResult = courseUtil.canLeaveCourse(courseId, auth.getUserEntity()); - if (!checkResult.getStatus().equals(HttpStatus.OK)) { - return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage()); - } - CourseRelation userRelation = checkResult.getData(); - - // Delete the user from the course - courseUserRepository.deleteById(new CourseUserId(courseId, userId)); - if (userRelation.equals(CourseRelation.enrolled)) { - if (!commonDatabaseActions.removeIndividualClusterGroup(courseId, userId)) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to remove user from individual group, contact admin."); - } - } - - return ResponseEntity.ok().build(); + return doRemoveFromCourse(courseId, userId, checkResult); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } @@ -483,6 +471,14 @@ public ResponseEntity leaveCourse(@PathVariable long courseId, Auth auth) { @Roles({UserRole.teacher, UserRole.admin, UserRole.student}) public ResponseEntity removeCourseMember(Auth auth, @PathVariable Long courseId, @PathVariable Long userId) { CheckResult checkResult = courseUtil.canDeleteUser(courseId, userId, auth.getUserEntity()); + return doRemoveFromCourse(courseId, userId, checkResult); + } + + @NotNull + private ResponseEntity doRemoveFromCourse( + Long courseId, + Long userId, + CheckResult checkResult) { if (!checkResult.getStatus().equals(HttpStatus.OK)) { return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage()); } @@ -565,7 +561,9 @@ public ResponseEntity updateCourseMember(Auth auth, @PathVariable Long course if (user == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found"); } - commonDatabaseActions.createNewIndividualClusterGroup(courseId, user); + if (!commonDatabaseActions.createNewIndividualClusterGroup(courseId, user)) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to add user to individual group, contact admin."); + } } else if (courseUserEntity.getRelation().equals(CourseRelation.enrolled)){ if (!commonDatabaseActions.removeIndividualClusterGroup(courseId, requestwithid.getUserId())) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to remove user from individual group, contact admin."); @@ -700,9 +698,12 @@ public ResponseEntity copyCourse(@PathVariable long courseId, Auth auth) { CourseEntity course = checkResult.getData().getFirst(); CheckResult copyCheckRes = commonDatabaseActions.copyCourse(course, auth.getUserEntity().getId()); + if (copyCheckRes.getStatus() != HttpStatus.OK) { + return ResponseEntity.status(copyCheckRes.getStatus()).body(copyCheckRes.getMessage()); + } CourseEntity newCourse = copyCheckRes.getData(); - return ResponseEntity.ok(entityToJsonConverter.courseEntityToCourseWithInfo(newCourse, courseUtil.getJoinLink(newCourse.getJoinKey(), "" + newCourse.getId()), false)); + return ResponseEntity.status(HttpStatus.CREATED).body(entityToJsonConverter.courseEntityToCourseWithInfo(newCourse, courseUtil.getJoinLink(newCourse.getJoinKey(), "" + newCourse.getId()), false)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java index d853db2d..a7b1baef 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java @@ -148,14 +148,17 @@ public void testGetCluster() throws Exception { //This function also tests doGroupClusterUpdate @Test public void testUpdateCluster() throws Exception { - String request = "{\"name\": \"newclustername\", \"capacity\": 20}"; + String request = "{\"name\": \"newclustername\", \"capacity\": 22}"; + String originalname = groupClusterEntity.getName(); + Integer originalcapacity = groupClusterEntity.getMaxSize(); /* If the user is an admin of the cluster, the cluster isn't individual and the json is valid, the cluster is updated */ GroupClusterEntity copy = new GroupClusterEntity(1L, 20, "newclustername", 5); when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", copy)); + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); copy.setName("newclustername"); GroupClusterJson updated = new GroupClusterJson(1L, "newclustername", 20, 5, OffsetDateTime.now(), Collections.emptyList(), ""); + when(groupClusterRepository.save(groupClusterEntity)).thenReturn(copy); when(entityToJsonConverter.clusterEntityToClusterJson(copy)).thenReturn(updated); mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") .contentType(MediaType.APPLICATION_JSON) @@ -163,6 +166,8 @@ public void testUpdateCluster() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(updated))); + assertNotEquals(originalname, groupClusterEntity.getName()); + assertNotEquals(originalcapacity, groupClusterEntity.getMaxSize()); /* If the json is invalid, the corresponding status code is returned */ when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java index 1d560a48..64bf3e4e 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java @@ -3,11 +3,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.ugent.pidgeon.CustomObjectMapper; import com.ugent.pidgeon.model.ProjectResponseJson; +import com.ugent.pidgeon.model.json.CourseJoinInformationJson; import com.ugent.pidgeon.model.json.CourseReferenceJson; import com.ugent.pidgeon.model.json.CourseWithInfoJson; import com.ugent.pidgeon.model.json.CourseWithRelationJson; import com.ugent.pidgeon.model.json.ProjectProgressJson; import com.ugent.pidgeon.model.json.UserReferenceJson; +import com.ugent.pidgeon.model.json.UserReferenceWithRelation; import com.ugent.pidgeon.postgre.models.*; import com.ugent.pidgeon.postgre.models.types.CourseRelation; import com.ugent.pidgeon.postgre.models.types.UserRole; @@ -22,6 +24,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.context.SecurityContextHolder; @@ -34,16 +37,21 @@ import java.util.List; import java.util.Optional; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyIterable; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -199,6 +207,13 @@ public String getName() { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(List.of(archivedCourseJson)))); + /* If course doesn't get found, it just gets filtered */ + when(courseRepository.findById(activeCourse.getId())).thenReturn(Optional.empty()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(archivedCourseJson)))); + /* If no courses are found, return empty list */ when(userRepository.findCourseIdsByUserId(anyLong())).thenReturn(Collections.emptyList()); when(userRepository.findArchivedCoursesByUserId(anyLong())).thenReturn(Collections.emptyList()); @@ -226,8 +241,6 @@ public void testCreateCourse() throws Exception { when(entityToJsonConverter.courseEntityToCourseWithInfo(any(), any(), anyBoolean())). thenReturn(activeCourseJson); - Logger.getGlobal().info("User: " + getMockUser().getRole()); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) @@ -276,10 +289,8 @@ public void testCreateCourse() throws Exception { // This function also tests all lines of doCourseUpdate @Test public void testUpdateCourse() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId(); String courseJson = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024}"; - /* If admin and valid json, update course and return 200 */ - when(courseUtil.getCourseIfAdmin(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); CourseEntity updatedEntity = new CourseEntity("test", "description",2024); CourseWithInfoJson updatedJson = new CourseWithInfoJson( activeCourse.getId(), @@ -294,13 +305,23 @@ public void testUpdateCourse() throws Exception { OffsetDateTime.now(), 2023 ); - when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(courseUtil.getJoinLink(any(), any())).thenReturn(""); - when(courseRepository.save(any())).thenReturn(updatedEntity); + /* If admin and valid json, update course and return 200 */ + when(courseUtil.getCourseIfAdmin(activeCourse.getId(), getMockUser())). + thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); + when(courseUtil.checkCourseJson( + argThat( + json -> json.getName().equals("test") && + json.getDescription().equals("description") && + json.getYear() == 2024 + ), + eq(getMockUser()), + eq(activeCourse.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(courseUtil.getJoinLink(activeCourse.getJoinKey(), ""+activeCourse.getId())).thenReturn(""); + when(courseRepository.save(activeCourse)).thenReturn(updatedEntity); when(entityToJsonConverter.courseEntityToCourseWithInfo(updatedEntity, "", false)).thenReturn(updatedJson); activeCourse.setArchivedAt(OffsetDateTime.now()); OffsetDateTime originalArchivedAt = activeCourse.getArchivedAt(); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) .andExpect(status().isOk()) @@ -312,7 +333,7 @@ public void testUpdateCourse() throws Exception { /* If courseJson has archived field, update archived accordingly */ String courseJsonWithArchivedTrue = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"true\"}"; - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJsonWithArchivedTrue)) .andExpect(status().isOk()); @@ -320,7 +341,7 @@ public void testUpdateCourse() throws Exception { String courseJsonWithArchivedFalse = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"false\"}"; - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJsonWithArchivedFalse)) .andExpect(status().isOk()); @@ -328,22 +349,32 @@ public void testUpdateCourse() throws Exception { /* If invalid json, return corresponding statuscode */ - when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + reset(courseUtil); + when(courseUtil.getCourseIfAdmin(activeCourse.getId(), getMockUser())). + thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); + when(courseUtil.checkCourseJson( + argThat( + json -> json.getName().equals("test") && + json.getDescription().equals("description") && + json.getYear() == 2024 + ), + eq(getMockUser()), + eq(activeCourse.getId()))).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) .andExpect(status().isIAmATeapot()); /* If not admin, return 403 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) .andExpect(status().isForbidden()); /* If error occurs, return 500 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJson)) .andExpect(status().isInternalServerError()); @@ -356,8 +387,6 @@ public void testPatchCourse() throws Exception { String originalName = activeCourse.getName(); String originalDescription = activeCourse.getDescription(); Integer originalYear = activeCourse.getCourseYear(); - when(courseUtil.getCourseIfAdmin(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); CourseEntity updatedEntity = new CourseEntity("test", "description2",2024); CourseWithInfoJson updatedJson = new CourseWithInfoJson( activeCourse.getId(), @@ -372,6 +401,8 @@ public void testPatchCourse() throws Exception { OffsetDateTime.now(), 2023 ); + when(courseUtil.getCourseIfAdmin(anyLong(), any())). + thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(courseUtil.getJoinLink(any(), any())).thenReturn(""); when(courseRepository.save(activeCourse)).thenReturn(updatedEntity); @@ -385,10 +416,20 @@ public void testPatchCourse() throws Exception { assertNotEquals(originalName, activeCourse.getName()); assertEquals(originalDescription, activeCourse.getDescription()); assertEquals(originalYear, activeCourse.getCourseYear()); + assertNull(activeCourse.getArchivedAt()); + originalName = activeCourse.getName(); + String patchCourseJsonNoName = "{\"description\": \"description88\"}"; + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(patchCourseJsonNoName)) + .andExpect(status().isOk()); + assertEquals(originalName, activeCourse.getName()); + assertNotEquals(originalDescription, activeCourse.getDescription()); + assertEquals(originalYear, activeCourse.getCourseYear()); assertNull(activeCourse.getArchivedAt()); /* If fields are present, update them */ String requestJson = "{\"name\": \"test2\", \"description\": \"description2\",\"courseYear\" : 2034}"; - originalName = activeCourse.getName(); + originalDescription = activeCourse.getDescription(); activeCourse.setArchivedAt(OffsetDateTime.now()); OffsetDateTime originalArchivedAt = activeCourse.getArchivedAt(); mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") @@ -403,7 +444,6 @@ public void testPatchCourse() throws Exception { assertEquals(originalArchivedAt, activeCourse.getArchivedAt()); activeCourse.setArchivedAt(null); - /* If courseJson has archived field, update archived accordingly */ String courseJsonWithArchivedTrue = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"true\"}"; mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") @@ -420,6 +460,13 @@ public void testPatchCourse() throws Exception { .andExpect(status().isOk()); assertNull(activeCourse.getArchivedAt()); + /* If no fields are present, return 400 */ + String emptyJson = "{\"ietswatnietboeit\": \"test\"}"; + mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(emptyJson)) + .andExpect(status().isBadRequest()); + /* If invalid json, return corresponding statuscode */ when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); @@ -447,37 +494,55 @@ public void testPatchCourse() throws Exception { @Test public void testGetCourseByCourseId() throws Exception { + /* If user is admin, return course with joinKey information */ when(courseUtil.getJoinLink(any(), any())).thenReturn(""); when(entityToJsonConverter.courseEntityToCourseWithInfo(any(), any(), anyBoolean())). - thenReturn(new CourseWithInfoJson(0L, "", "", new UserReferenceJson("", "", 0L), - new ArrayList<>(), "", "", "", OffsetDateTime.now(), OffsetDateTime.now(), 2023)); + thenReturn(activeCourseJson); when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(new CourseEntity(), CourseRelation.course_admin))); + thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.course_admin))); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(activeCourseJson))); + verify(entityToJsonConverter, times(1)).courseEntityToCourseWithInfo(activeCourse, "", false); + /* If user is not admin, return course without joinKey information */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). - thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "", new Pair<>(null, null))); + thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.enrolled))); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")).andExpect(status().isOk()); + verify(entityToJsonConverter, times(1)).courseEntityToCourseWithInfo(activeCourse, "", true); + /* If course is not found, or user no acces return corresponding status */ + when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). + thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "", new Pair<>(null, null))); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")) .andExpect(status().isNotFound()); + when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). + thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", new Pair<>(null, null))); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")).andExpect(status().isForbidden()); } @Test public void testDeleteCourse() throws Exception { + /* If user is the creator of the course, delete succeeds and also deletes linked projects & coursses */ ProjectEntity project = new ProjectEntity(1, "name", "description", 1L, 1L, true, 20, OffsetDateTime.now()); GroupClusterEntity groupCluster = new GroupClusterEntity(1L, 20, "cluster", 5); when(courseUtil.getCourseIfUserInCourse(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(new CourseEntity(), CourseRelation.creator))); + thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.creator))); when(courseRepository.findAllProjectsByCourseId(anyLong())).thenReturn(List.of(project)); - when(commonDatabaseActions.deleteProject(anyLong())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(commonDatabaseActions.deleteProject(project.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(groupClusterRepository.findByCourseId(anyLong())).thenReturn(List.of(groupCluster)); - when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) + when(commonDatabaseActions.deleteClusterById(groupCluster.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + CourseUserEntity courseUser = new CourseUserEntity(1L, 1L, CourseRelation.creator); + List courseUsers = List.of(courseUser); + when(courseUserRepository.findAllUsersByCourseId(activeCourse.getId())).thenReturn(courseUsers); + doNothing().when(courseUserRepository).deleteAll(anyIterable()); + mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId())) .andExpect(status().isOk()); + verify(courseUserRepository, times(1)).deleteAll(courseUsers); + /* If something goes wrong while deleting a cluster or project, return corresponding status */ when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.NO_CONTENT, "", null)); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) .andExpect(status().isNoContent()); @@ -486,15 +551,18 @@ public void testDeleteCourse() throws Exception { mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) .andExpect(status().isIAmATeapot()); + /* If user isn't in course or course doesn't exist return corresponding status */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", new Pair<>(null, null))); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) .andExpect(status().isBadRequest()); + /* If user isn't the creator of the course, return 403 */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())). thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(new CourseEntity(), CourseRelation.enrolled))); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) .andExpect(status().isForbidden()); + /* If a unexpected error occurs, return 500 */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenThrow(new RuntimeException()); mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) .andExpect(status().isInternalServerError()); @@ -503,29 +571,52 @@ public void testDeleteCourse() throws Exception { @Test public void testGetProjectsByCourseId() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - List projects = Arrays.asList(new ProjectEntity(), new ProjectEntity()); + Pair creatorPair = new Pair<>(activeCourse, CourseRelation.creator); + Pair enrolledPair = new Pair<>(activeCourse, CourseRelation.enrolled); + ProjectEntity project = new ProjectEntity(1, "name", "description", 1L, 1L, true, 20, OffsetDateTime.now()); + ProjectResponseJson projectJson = new ProjectResponseJson( + new CourseReferenceJson("", "Test Course", 1L, OffsetDateTime.now()), + OffsetDateTime.MIN, + "", + 1L, + "Test Description", + "", + "", + 1, + true, + new ProjectProgressJson(1, 1), + 1L, + 1L + ); + /* If user is in course, return projects */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.creator))); + .thenReturn(new CheckResult<>(HttpStatus.OK, "", creatorPair)); + + when(projectRepository.findByCourseId(anyLong())).thenReturn(List.of(project)); + List projects = List.of(project); when(projectRepository.findByCourseId(anyLong())).thenReturn(projects); - when(entityToJsonConverter.projectEntityToProjectResponseJson(any(ProjectEntity.class), any(CourseEntity.class), any(UserEntity.class))).thenReturn(new ProjectResponseJson( - new CourseReferenceJson("", "Test Course", 1L, OffsetDateTime.now()), - OffsetDateTime.MIN, - "", - 1L, - "Test Description", - "", - "", - 1, - true, - new ProjectProgressJson(1, 1), - 1L, - 1L - )); + when(entityToJsonConverter.projectEntityToProjectResponseJson(project, activeCourse, getMockUser())).thenReturn(projectJson); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(projectJson)))); + + /* If a project isn't visible, and user role is student, it should not be returned */ + project.setVisible(false); + when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", enrolledPair)); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", creatorPair)); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(projectJson)))); + /* If user not in course, or course doesn't exit or any other check fails, return corresponding status */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) .andExpect(status().isIAmATeapot()); @@ -534,185 +625,390 @@ public void testGetProjectsByCourseId() throws Exception { @Test public void testJoinCourse() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())).thenReturn(true); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/join/1908")) - .andExpect(status().isOk()); + String urlWithKey = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908"; + String urlWithoutKey = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join"; + CourseEntity course = activeCourse; + /* If join key is correct, course is not archived and no error occurs, return 200 */ + when(courseUtil.checkJoinLink(activeCourse.getId(), "1908", getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); + when(courseUtil.checkJoinLink(activeCourse.getId(), null, getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); + when(commonDatabaseActions.createNewIndividualClusterGroup(activeCourse.getId(), getMockUser())).thenReturn(true); + when(courseUtil.getJoinLink(course.getJoinKey(), ""+course.getId())).thenReturn(""); + when(entityToJsonConverter.courseEntityToCourseWithInfo(activeCourse, "", false)).thenReturn(activeCourseJson); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithKey)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(activeCourseJson))); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithoutKey)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(activeCourseJson))); + verify(courseUserRepository, times(2)).save(argThat(courseUser -> + courseUser.getCourseId() == activeCourse.getId() && + courseUser.getUserId() == getMockUser().getId() && + courseUser.getRelation().equals(CourseRelation.enrolled) + )); + /* If course is archived, return 403 */ + activeCourse.setArchivedAt(OffsetDateTime.now()); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithKey)) + .andExpect(status().isForbidden()); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithoutKey)) + .andExpect(status().isForbidden()); + activeCourse.setArchivedAt(null); + + /* If an error occures when creating individual cluster group, return 500 */ when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())).thenReturn(false); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/join/1908")) + mockMvc.perform(MockMvcRequestBuilders.post(urlWithKey)) + .andExpect(status().isInternalServerError()); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithoutKey)) .andExpect(status().isInternalServerError()); + /* If join key check fails return corresponding status */ when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/join/1908")) - .andExpect(status().isIAmATeapot()); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithKey)) + .andExpect(status().isIAmATeapot()); + mockMvc.perform(MockMvcRequestBuilders.post(urlWithoutKey)) + .andExpect(status().isIAmATeapot()); } @Test - public void testGetJoinKey() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/join/1908")) - .andExpect(status().isOk()); - - when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", course)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/join/1908")) - .andExpect(status().isIAmATeapot()); - } + public void testGetJoinInformation() throws Exception { + CourseEntity course = activeCourse; + CourseJoinInformationJson courseJoinInformationJson = new CourseJoinInformationJson( + activeCourse.getName(), + activeCourse.getDescription() + ); + /* If join key is correct, course is not archived and no error occurs, return 200 */ + when(courseUtil.checkJoinLink(activeCourse.getId(), "1908", getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); + when(courseUtil.checkJoinLink(activeCourse.getId(), null, getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(courseJoinInformationJson))); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(courseJoinInformationJson))); - @Test - public void testJoinCourseNoKey() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())).thenReturn(true); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/join")) - .andExpect(status().isOk()); + /* If course is archived, reutrn 403 */ + activeCourse.setArchivedAt(OffsetDateTime.now()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908")) + .andExpect(status().isForbidden()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join")) + .andExpect(status().isForbidden()); + activeCourse.setArchivedAt(null); - when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())).thenReturn(false); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/join")) - .andExpect(status().isInternalServerError()); + /* If join key check fails return corresponding status */ + when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908")) + .andExpect(status().isIAmATeapot()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join")) + .andExpect(status().isIAmATeapot()); } - @Test - public void testGetCourseJoinKeyNoKey() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/join")) - .andExpect(status().isOk()); - - } @Test public void testLeaveCourse() throws Exception { - when(courseUtil.canLeaveCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.enrolled)); - when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())).thenReturn(true); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/leave")) + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/leave"; + /* If user can leave course, return 200 */ + /* If role is enrolled, an individualclustergroup should be deleted */ + when(courseUtil.canLeaveCourse(activeCourse.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.enrolled)); + when(commonDatabaseActions.removeIndividualClusterGroup(activeCourse.getId(), getMockUser().getId())).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isOk()); + verify(commonDatabaseActions, times(1)).removeIndividualClusterGroup(activeCourse.getId(), getMockUser().getId()); + /* If the role isn't enrolled, no individualclustergroup should be deleted */ + when(courseUtil.canLeaveCourse(activeCourse.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.course_admin)); + reset(commonDatabaseActions); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isOk()); + verify(commonDatabaseActions, times(0)).removeIndividualClusterGroup(activeCourse.getId(), getMockUser().getId()); + + /* If something goes wrong while deleting individual cluster group, return 500 */ + when(courseUtil.canLeaveCourse(activeCourse.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.enrolled)); when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())).thenReturn(false); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/leave")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isInternalServerError()); + verify(commonDatabaseActions, times(1)).removeIndividualClusterGroup(activeCourse.getId(), getMockUser().getId()); + /* If user can't leave course for some reason, return corresponding error code */ when(courseUtil.canLeaveCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/leave")) - .andExpect(status().isBadRequest()); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isBadRequest()); + /* If an unexpected error occurs, return 500 */ when(courseUtil.canLeaveCourse(anyLong(), any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/leave")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isInternalServerError()); } @Test public void testRemoveCourseMember() throws Exception { - String userIdJson = "{\"userId\": 1}"; - when(courseUtil.canDeleteUser(anyLong(), anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.enrolled)); - when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())).thenReturn(true); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/members/2") - .contentType(MediaType.APPLICATION_JSON) - .content(userIdJson)) + long userId = 2L; + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/members/2"; + + /* If user can remove other people, and course exists, return 200 */ + /* If user is admin, removeIndividualClusterGroup gets called */ + when(courseUtil.canDeleteUser(activeCourse.getId(), userId, getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.course_admin)); + when(commonDatabaseActions.removeIndividualClusterGroup(activeCourse.getId(), userId)).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isOk()); + verify(courseUserRepository, times(1)).deleteById(argThat( + id -> id.getCourseId() == activeCourse.getId() && id.getUserId() == userId + )); + verify(commonDatabaseActions, times(0)).removeIndividualClusterGroup(activeCourse.getId(), userId); + + /* If user enrolled, removeIndividualClusterGroup gets called */ + when(courseUtil.canDeleteUser(activeCourse.getId(), userId, getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", CourseRelation.enrolled)); + when(commonDatabaseActions.removeIndividualClusterGroup(activeCourse.getId(), userId)).thenReturn(true); + reset(courseUserRepository); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isOk()); + verify(courseUserRepository, times(1)).deleteById(argThat( + id -> id.getCourseId() == activeCourse.getId() && id.getUserId() == userId + )); + verify(commonDatabaseActions, times(1)).removeIndividualClusterGroup(activeCourse.getId(), userId); - when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())).thenReturn(false); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/members/2") - .contentType(MediaType.APPLICATION_JSON) - .content(userIdJson)) + /* If something goes wrong when removing individual group, return 500 */ + when(commonDatabaseActions.removeIndividualClusterGroup(activeCourse.getId(), userId)).thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isInternalServerError()); + /* If user can't delete the other use, return corresponding status*/ when(courseUtil.canDeleteUser(anyLong(), anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/members/2") - .contentType(MediaType.APPLICATION_JSON) - .content(userIdJson)) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isIAmATeapot()); } @Test public void testAddCourseMember() throws Exception { - String request = "{\"userId\": 1, \"relation\": \"enrolled\"}"; - when(courseUtil.canUpdateUserInCourse(anyLong(), any(), any(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new CourseUserEntity(1, 1, CourseRelation.enrolled))); - when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())) - .thenReturn(true); - when(userUtil.getUserIfExists(anyLong())).thenReturn(new UserEntity("name", "surname", "email", UserRole.teacher, "id")); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/members") + String requestString = "{\"userId\": 1, \"relation\": \"enrolled\"}"; + String requestStringAdmin = "{\"userId\": 1, \"relation\": \"course_admin\"}"; + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/members"; + CourseUserEntity courseUser = new CourseUserEntity(activeCourse.getId(), 1, CourseRelation.enrolled); + UserEntity user = new UserEntity("name", "surname", "email", UserRole.teacher, "id"); + /* If all checks succeed, return 201 */ + + when(courseUtil.canUpdateUserInCourse( + eq(activeCourse.getId()), + argThat( + request -> request.getUserId() == 1 && request.getRelationAsEnum().equals(CourseRelation.course_admin) + ), + eq(getMockUser()), + eq(HttpMethod.POST))).thenReturn(new CheckResult<>(HttpStatus.OK, "", courseUser)); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestStringAdmin)) + .andExpect(status().isCreated()); + /* If user is not enrolled, there is no attempt to create individual cluster group */ + verify(userUtil, times(0)).getUserIfExists(anyLong()); + verify(courseUserRepository, times(1)).save(argThat( + courseUserEntity -> courseUserEntity.getCourseId() == activeCourse.getId() && + courseUserEntity.getUserId() == 1 && + courseUserEntity.getRelation().equals(CourseRelation.course_admin) + )); + verify(commonDatabaseActions, times(0)).createNewIndividualClusterGroup(anyLong(), any()); + + reset(courseUtil); + when(courseUtil.canUpdateUserInCourse( + eq(activeCourse.getId()), + argThat( + request -> request.getUserId() == 1 && request.getRelationAsEnum().equals(CourseRelation.enrolled) + ), + eq(getMockUser()), + eq(HttpMethod.POST))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", courseUser)); + when(userUtil.getUserIfExists(anyLong())).thenReturn(user); + when(commonDatabaseActions.createNewIndividualClusterGroup(activeCourse.getId(), user)).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .content(requestString)) .andExpect(status().isCreated()); + verify(courseUserRepository, times(1)).save(argThat( + courseUserEntity -> courseUserEntity.getCourseId() == activeCourse.getId() && + courseUserEntity.getUserId() == 1 && + courseUserEntity.getRelation().equals(CourseRelation.enrolled) + )); + /* If something goes wrong when creating individual cluster, return 500 */ when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())) .thenReturn(false); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/members") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .content(requestString)) .andExpect(status().isInternalServerError()); - when(courseUtil.canUpdateUserInCourse(anyLong(), any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/members") + /* If user isn't found, return 404 */ + when(userUtil.getUserIfExists(anyLong())).thenReturn(null); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestString)) + .andExpect(status().isNotFound()); + + /* If user can't be added to the course, return corresponding status */ + reset(courseUtil); + when(courseUtil.canUpdateUserInCourse( + anyLong(), any(), any(), any())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) - .content(request)) + .content(requestString)) .andExpect(status().isIAmATeapot()); } @Test public void testUpdateCourseMember() throws Exception { - String request = "{\"userId\": 1, \"relation\": \"enrolled\"}"; - when(courseUtil.canUpdateUserInCourse(anyLong(), any(), any(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new CourseUserEntity(1, 1, CourseRelation.course_admin))); - when(userUtil.getUserIfExists(anyLong())).thenReturn(new UserEntity("name", "surname", "email", UserRole.teacher, "id")); - when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())).thenReturn(true); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1/members/2") - .contentType(MediaType.APPLICATION_JSON) - .content(request)) - .andExpect(status().isOk()); + long userId = 2L; + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/members/" + userId; + String request = "{\"relation\": \"enrolled\"}"; + String adminRequest = "{\"relation\": \"course_admin\"}"; + UserEntity user = new UserEntity("name", "surname", "email", UserRole.teacher, "id"); + CourseUserEntity enrolledUser = new CourseUserEntity(activeCourse.getId(), userId, CourseRelation.enrolled); + CourseUserEntity adminUser = new CourseUserEntity(activeCourse.getId(), userId, CourseRelation.course_admin); + /* If all checks succeed, 200 gets returned */ + /* If the new role is the same as the old, no changes to individualgroupClusters are done */ + when(courseUtil.canUpdateUserInCourse( + eq(activeCourse.getId()), + argThat( + requestJson -> requestJson.getRelationAsEnum().equals(CourseRelation.enrolled) + ), + eq(getMockUser()), + eq(HttpMethod.PATCH))).thenReturn(new CheckResult<>(HttpStatus.OK, "", enrolledUser)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + verify(commonDatabaseActions, times(0)).removeIndividualClusterGroup(anyLong(), anyLong()); + verify(commonDatabaseActions, times(0)).createNewIndividualClusterGroup(anyLong(), any()); + verify(courseUserRepository, times(0)).save(any()); + + /* If the new role is enrolled, individual clustergroup should be created */ + reset(courseUtil); + when(courseUtil.canUpdateUserInCourse( + eq(activeCourse.getId()), + argThat( + requestJson -> requestJson.getRelationAsEnum().equals(CourseRelation.enrolled) + ), + eq(getMockUser()), + eq(HttpMethod.PATCH))).thenReturn(new CheckResult<>(HttpStatus.OK, "", adminUser)); + when(userUtil.getUserIfExists(userId)).thenReturn(user); + when(commonDatabaseActions.createNewIndividualClusterGroup(activeCourse.getId(), user)).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + verify(commonDatabaseActions, times(1)).createNewIndividualClusterGroup(activeCourse.getId(), user); + assertEquals(CourseRelation.enrolled, adminUser.getRelation()); + verify(courseUserRepository, times(1)).save(adminUser); + adminUser.setRelation(CourseRelation.course_admin); + /* If something goes wrong when creating individual cluster, return 500 */ + reset(commonDatabaseActions); + when(commonDatabaseActions.createNewIndividualClusterGroup(anyLong(), any())) + .thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); - when(courseUtil.canUpdateUserInCourse(anyLong(), any(), any(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new CourseUserEntity(1, 3, CourseRelation.enrolled))); - when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())).thenReturn(false); - request = "{\"relation\": \"course_admin\"}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1/members/2") - .contentType(MediaType.APPLICATION_JSON) - .content(request)) + /* If the user doesn't get found when trying to create individualadmin group should return 404 */ + when(userUtil.getUserIfExists(anyLong())).thenReturn(null); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isNotFound()); + + /* If the new role is course_admin, individual clustergroup should be deleted */ + reset(commonDatabaseActions); + reset(courseUtil); + when(courseUtil.canUpdateUserInCourse( + eq(activeCourse.getId()), + argThat( + requestJson -> requestJson.getRelationAsEnum().equals(CourseRelation.course_admin) + ), + eq(getMockUser()), + eq(HttpMethod.PATCH))).thenReturn(new CheckResult<>(HttpStatus.OK, "", enrolledUser)); + when(commonDatabaseActions.removeIndividualClusterGroup(activeCourse.getId(), userId)).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(adminRequest)) + .andExpect(status().isOk()); + verify(commonDatabaseActions, times(1)).removeIndividualClusterGroup(activeCourse.getId(), userId); + assertEquals(CourseRelation.course_admin, enrolledUser.getRelation()); + verify(courseUserRepository, times(1)).save(enrolledUser); + enrolledUser.setRelation(CourseRelation.enrolled); + + /* If something goes wrong when deleting individual cluster, return 500 */ + reset(commonDatabaseActions); + when(commonDatabaseActions.removeIndividualClusterGroup(anyLong(), anyLong())) + .thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(adminRequest)) .andExpect(status().isInternalServerError()); - when(courseUtil.canUpdateUserInCourse(anyLong(), any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1/members/2") - .contentType(MediaType.APPLICATION_JSON) - .content(request)) - .andExpect(status().isBadRequest()); + /* If user can't be updated, return corresponding status */ + reset(courseUtil); + when(courseUtil.canUpdateUserInCourse(anyLong(), any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + } @Test public void testGetCourseMembers() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.course_admin))); - when(courseUserRepository.findAllMembers(anyLong())).thenReturn( - List.of(new CourseUserEntity(1, 2, CourseRelation.creator)) + CourseUserEntity courseUserEntity = new CourseUserEntity(1L, 1L, CourseRelation.enrolled); + UserEntity user = new UserEntity("name", "surname", "email", UserRole.teacher, "id"); + UserReferenceWithRelation userJson = new UserReferenceWithRelation( + new UserReferenceJson("name", "surname", 1L), + ""+CourseRelation.enrolled ); - when(userUtil.getUserIfExists(anyLong())).thenReturn(new UserEntity("name", "surname", "email", UserRole.teacher, "id")); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/members")) - .andExpect(status().isOk()); + List userList = List.of(courseUserEntity); + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/members"; + /* If user is in course, return members */ + when(courseUtil.getCourseIfUserInCourse(activeCourseJson.courseId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.course_admin))); + when(courseUserRepository.findAllMembers(activeCourseJson.courseId())).thenReturn(userList); + when(userUtil.getUserIfExists(courseUserEntity.getUserId())).thenReturn(user); + when(entityToJsonConverter.userEntityToUserReferenceWithRelation(user, CourseRelation.enrolled)).thenReturn(userJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If user doesn't get found it gets filtered out */ + when(userUtil.getUserIfExists(anyLong())).thenReturn(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().json("[]")); + + + /* If user is not in course, or course not found or ... return corresponding status */ + when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/members")) - .andExpect(status().isForbidden()); } @Test public void testGetCourseKey() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/joinKey")) - .andExpect(status().isOk()); + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/joinKey"; + /* If user is admin and course exists, returns joinKey */ + activeCourse.setJoinKey("1908"); + when(courseUtil.getCourseIfAdmin(activeCourse.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().string("1908")); + activeCourse.setJoinKey(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().string("")); + /* If any check fails, return corresponding status */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/joinKey")) .andExpect(status().isIAmATeapot()); @@ -720,27 +1016,96 @@ public void testGetCourseKey() throws Exception { @Test public void testGetAndCreateCourseKey() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1/joinKey")) - .andExpect(status().isOk()); + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/joinKey"; + /* If user is admin and course exists, update and returns joinKey */ + activeCourse.setJoinKey("1908"); + when(courseUtil.getCourseIfAdmin(activeCourse.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", activeCourse)); + mockMvc.perform(MockMvcRequestBuilders.put(url)) + .andExpect(status().isOk()) + .andExpect(content().string(not(equalTo("")))) + .andExpect(content().string(not(equalTo("1908")))); + assertNotEquals("1908", activeCourse.getJoinKey()); + verify(courseRepository, times(1)).save(activeCourse); + + /* If any check fails, return corresponding status */ + when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url)) + .andExpect(status().isIAmATeapot()); - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.COURSE_BASE_PATH + "/1/joinKey")) - .andExpect(status().isIAmATeapot()); } @Test public void testDeleteCourseKey() throws Exception { - CourseEntity course = new CourseEntity("name", "descripton",2024); - course.setId(1); - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/joinKey")) - .andExpect(status().isOk()); + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/joinKey"; + /* If user is admin and course exists, update and returns joinKey */ + activeCourse.setJoinKey("1908"); + when(courseUtil.getCourseIfAdmin(activeCourse.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", activeCourse)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isOk()) + .andExpect(content().string("")); + assertNull(activeCourse.getJoinKey()); + verify(courseRepository, times(1)).save(activeCourse); + + /* If any check fails, return corresponding status */ + when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isIAmATeapot()); + } + + @Test + public void testCopyCourse() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/copy"; + CourseEntity copiedCourse = new CourseEntity("name", "description", 2024); + CourseWithInfoJson copiedCourseJson = new CourseWithInfoJson( + 2L, + "name", + "description", + new UserReferenceJson("", "", 0L), + new ArrayList<>(), + "", + "", + "", + OffsetDateTime.now(), + OffsetDateTime.now(), + 2024 + ); + /* If user is creator, can copy course */ + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.creator))); + when(commonDatabaseActions.copyCourse(activeCourse, getMockUser().getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", copiedCourse)); + when(courseUtil.getJoinLink(copiedCourse.getJoinKey(), ""+copiedCourse.getId())).thenReturn(""); + when(entityToJsonConverter.courseEntityToCourseWithInfo(copiedCourse, "", false)).thenReturn(copiedCourseJson); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(copiedCourseJson))); + + /* If something goes wrong when copying course, return corresponding status */ + when(commonDatabaseActions.copyCourse(activeCourse, getMockUser().getId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isIAmATeapot()); + + /* If user isn't the creator, return 403 */ + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.course_admin))); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isForbidden()); + + /* If user isn't in course, or course not found return corresponding status code */ + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "", new Pair<>(null, null))); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isNotFound()); + + /* If an unexpected error occurs, return 500 */ + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isInternalServerError()); - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1/joinKey")) - .andExpect(status().isIAmATeapot()); } + } \ No newline at end of file From 5563a5a4d7f564dd233f585ce4601ad634211514 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:18:13 +0200 Subject: [PATCH 05/40] cleanup courseController test --- .../controllers/CourseControllerTest.java | 140 +++++++++++------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java index 64bf3e4e..53ddaab2 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java @@ -17,6 +17,7 @@ import com.ugent.pidgeon.postgre.repository.UserRepository.CourseIdWithRelation; import com.ugent.pidgeon.util.*; import java.util.Collections; +import java.util.Objects; import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -233,12 +234,24 @@ public String getName() { public void testCreateCourse() throws Exception { String courseJson = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024}"; /* If everything is correct, return 200 */ - when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(courseUtil.checkCourseJson(argThat( + json -> json.getName().equals("test") && + json.getDescription().equals("description") && + json.getYear() == 2024 + ), eq(getMockUser()), eq(null))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(courseRepository.save(any())).thenReturn(activeCourse); - when(courseUserRepository.save(any())).thenReturn(null); - when(groupClusterRepository.save(any())).thenReturn(null); - when(courseUtil.getJoinLink(any(), any())).thenReturn(""); - when(entityToJsonConverter.courseEntityToCourseWithInfo(any(), any(), anyBoolean())). + when(courseUserRepository.save(argThat( + courseUser -> courseUser.getCourseId() == activeCourse.getId() && + courseUser.getUserId() == getMockUser().getId() && + courseUser.getRelation().equals(CourseRelation.creator) + ))).thenReturn(null); + when(groupClusterRepository.save(argThat( + groupCluster -> groupCluster.getCourseId() == activeCourse.getId() && + groupCluster.getMaxSize() == 1 && + groupCluster.getGroupAmount() == 0 + ))).thenReturn(null); + when(courseUtil.getJoinLink(activeCourse.getJoinKey(), ""+activeCourse.getId())).thenReturn(""); + when(entityToJsonConverter.courseEntityToCourseWithInfo(activeCourse, "", false)). thenReturn(activeCourseJson); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) @@ -258,8 +271,6 @@ public void testCreateCourse() throws Exception { groupCluster.getMaxSize() == 1 && groupCluster.getGroupAmount() == 0 )); - verify(entityToJsonConverter, times(1)).courseEntityToCourseWithInfo(activeCourse, "", false); - verify(courseUtil, times(1)).getJoinLink(activeCourse.getJoinKey(), "" + activeCourse.getId()); /* If user is not a teacher, return 403 */ setMockUserRoles(UserRole.student); @@ -270,6 +281,7 @@ public void testCreateCourse() throws Exception { setMockUserRoles(UserRole.teacher); /* If course json is invalid, return 400 */ + reset(courseUtil); when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); when(courseUtil.checkCourseJson(any(), any(),any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH) @@ -382,8 +394,7 @@ public void testUpdateCourse() throws Exception { @Test public void testPatchCourse() throws Exception { - - /* If admin and valid json, update course and return 200 */ + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId(); String originalName = activeCourse.getName(); String originalDescription = activeCourse.getDescription(); Integer originalYear = activeCourse.getCourseYear(); @@ -401,43 +412,63 @@ public void testPatchCourse() throws Exception { OffsetDateTime.now(), 2023 ); - when(courseUtil.getCourseIfAdmin(anyLong(), any())). + /* If admin and valid json, update course and return 200 */ + when(courseUtil.getCourseIfAdmin(activeCourse.getId(), getMockUser())). thenReturn(new CheckResult<>(HttpStatus.OK, "", activeCourse)); when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(courseUtil.getJoinLink(any(), any())).thenReturn(""); + when(courseUtil.getJoinLink(activeCourse.getJoinKey(), ""+activeCourse.getId())).thenReturn(""); when(courseRepository.save(activeCourse)).thenReturn(updatedEntity); when(entityToJsonConverter.courseEntityToCourseWithInfo(updatedEntity, "", false)).thenReturn(updatedJson); /* If field is not present, do not update it */ String patchCourseJson = "{\"name\": \"test\"}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(patchCourseJson)) .andExpect(status().isOk()); + String finalOriginalDescription = originalDescription; + verify(courseUtil, times(1)).checkCourseJson(argThat( + json -> json.getName().equals("test") && + json.getDescription().equals(finalOriginalDescription) && + Objects.equals(json.getYear(), originalYear) + ), eq(getMockUser()), eq(activeCourse.getId())); assertNotEquals(originalName, activeCourse.getName()); assertEquals(originalDescription, activeCourse.getDescription()); assertEquals(originalYear, activeCourse.getCourseYear()); assertNull(activeCourse.getArchivedAt()); originalName = activeCourse.getName(); + String patchCourseJsonNoName = "{\"description\": \"description88\"}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(patchCourseJsonNoName)) .andExpect(status().isOk()); + String finalOriginalName = originalName; + verify(courseUtil, times(1)).checkCourseJson(argThat( + json -> json.getName().equals(finalOriginalName) && + json.getDescription().equals("description88") && + Objects.equals(json.getYear(), originalYear) + ), eq(getMockUser()), eq(activeCourse.getId())); assertEquals(originalName, activeCourse.getName()); assertNotEquals(originalDescription, activeCourse.getDescription()); assertEquals(originalYear, activeCourse.getCourseYear()); assertNull(activeCourse.getArchivedAt()); + /* If fields are present, update them */ String requestJson = "{\"name\": \"test2\", \"description\": \"description2\",\"courseYear\" : 2034}"; originalDescription = activeCourse.getDescription(); activeCourse.setArchivedAt(OffsetDateTime.now()); OffsetDateTime originalArchivedAt = activeCourse.getArchivedAt(); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestJson)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(updatedJson))); + verify(courseUtil, times(1)).checkCourseJson(argThat( + json -> json.getName().equals("test2") && + json.getDescription().equals("description2") && + json.getYear() == 2034 + ), eq(getMockUser()), eq(activeCourse.getId())); assertNotEquals(originalName, activeCourse.getName()); assertNotEquals(originalDescription, activeCourse.getDescription()); assertNotEquals(originalYear, activeCourse.getCourseYear()); @@ -446,7 +477,7 @@ public void testPatchCourse() throws Exception { /* If courseJson has archived field, update archived accordingly */ String courseJsonWithArchivedTrue = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"true\"}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJsonWithArchivedTrue)) .andExpect(status().isOk()); @@ -454,7 +485,7 @@ public void testPatchCourse() throws Exception { String courseJsonWithArchivedFalse = "{\"name\": \"test\", \"description\": \"description\",\"courseYear\" : 2024, \"archived\": \"false\"}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(courseJsonWithArchivedFalse)) .andExpect(status().isOk()); @@ -462,7 +493,7 @@ public void testPatchCourse() throws Exception { /* If no fields are present, return 400 */ String emptyJson = "{\"ietswatnietboeit\": \"test\"}"; - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(emptyJson)) .andExpect(status().isBadRequest()); @@ -470,21 +501,21 @@ public void testPatchCourse() throws Exception { /* If invalid json, return corresponding statuscode */ when(courseUtil.checkCourseJson(any(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestJson)) .andExpect(status().isIAmATeapot()); /* If not admin, return 403 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestJson)) .andExpect(status().isForbidden()); /* If error occurs, return 500 */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.COURSE_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestJson)) .andExpect(status().isInternalServerError()); @@ -494,83 +525,88 @@ public void testPatchCourse() throws Exception { @Test public void testGetCourseByCourseId() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId(); /* If user is admin, return course with joinKey information */ - when(courseUtil.getJoinLink(any(), any())).thenReturn(""); + when(courseUtil.getJoinLink(activeCourse.getJoinKey(), ""+activeCourse.getId())).thenReturn(""); when(entityToJsonConverter.courseEntityToCourseWithInfo(any(), any(), anyBoolean())). thenReturn(activeCourseJson); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())). thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.course_admin))); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(activeCourseJson))); verify(entityToJsonConverter, times(1)).courseEntityToCourseWithInfo(activeCourse, "", false); /* If user is not admin, return course without joinKey information */ - when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())). thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.enrolled))); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")).andExpect(status().isOk()); + mockMvc.perform(MockMvcRequestBuilders.get(url)).andExpect(status().isOk()); verify(entityToJsonConverter, times(1)).courseEntityToCourseWithInfo(activeCourse, "", true); /* If course is not found, or user no acces return corresponding status */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "", new Pair<>(null, null))); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isNotFound()); when(courseUtil.getCourseIfUserInCourse(anyLong(), any(UserEntity.class))). thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", new Pair<>(null, null))); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1")).andExpect(status().isForbidden()); + mockMvc.perform(MockMvcRequestBuilders.get(url)).andExpect(status().isForbidden()); } @Test public void testDeleteCourse() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId(); + /* If user is the creator of the course, delete succeeds and also deletes linked projects & coursses */ ProjectEntity project = new ProjectEntity(1, "name", "description", 1L, 1L, true, 20, OffsetDateTime.now()); GroupClusterEntity groupCluster = new GroupClusterEntity(1L, 20, "cluster", 5); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())). + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())). thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(activeCourse, CourseRelation.creator))); - when(courseRepository.findAllProjectsByCourseId(anyLong())).thenReturn(List.of(project)); + when(courseRepository.findAllProjectsByCourseId(activeCourse.getId())).thenReturn(List.of(project)); when(commonDatabaseActions.deleteProject(project.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(groupClusterRepository.findByCourseId(anyLong())).thenReturn(List.of(groupCluster)); + when(groupClusterRepository.findByCourseId(activeCourse.getId())).thenReturn(List.of(groupCluster)); when(commonDatabaseActions.deleteClusterById(groupCluster.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); CourseUserEntity courseUser = new CourseUserEntity(1L, 1L, CourseRelation.creator); List courseUsers = List.of(courseUser); when(courseUserRepository.findAllUsersByCourseId(activeCourse.getId())).thenReturn(courseUsers); doNothing().when(courseUserRepository).deleteAll(anyIterable()); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId())) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isOk()); verify(courseUserRepository, times(1)).deleteAll(courseUsers); /* If something goes wrong while deleting a cluster or project, return corresponding status */ when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.NO_CONTENT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isNoContent()); when(commonDatabaseActions.deleteProject(anyLong())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isIAmATeapot()); /* If user isn't in course or course doesn't exist return corresponding status */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", new Pair<>(null, null))); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isBadRequest()); /* If user isn't the creator of the course, return 403 */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())). thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(new CourseEntity(), CourseRelation.enrolled))); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isForbidden()); /* If a unexpected error occurs, return 500 */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.COURSE_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isInternalServerError()); } @Test public void testGetProjectsByCourseId() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/projects"; + Pair creatorPair = new Pair<>(activeCourse, CourseRelation.creator); Pair enrolledPair = new Pair<>(activeCourse, CourseRelation.enrolled); ProjectEntity project = new ProjectEntity(1, "name", "description", 1L, 1L, true, 20, OffsetDateTime.now()); @@ -589,36 +625,35 @@ public void testGetProjectsByCourseId() throws Exception { 1L ); /* If user is in course, return projects */ - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(),getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", creatorPair)); - when(projectRepository.findByCourseId(anyLong())).thenReturn(List.of(project)); List projects = List.of(project); - when(projectRepository.findByCourseId(anyLong())).thenReturn(projects); + when(projectRepository.findByCourseId(activeCourse.getId())).thenReturn(projects); when(entityToJsonConverter.projectEntityToProjectResponseJson(project, activeCourse, getMockUser())).thenReturn(projectJson); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(List.of(projectJson)))); /* If a project isn't visible, and user role is student, it should not be returned */ project.setVisible(false); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", enrolledPair)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json("[]")); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) + when(courseUtil.getCourseIfUserInCourse(activeCourse.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", creatorPair)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(List.of(projectJson)))); /* If user not in course, or course doesn't exit or any other check fails, return corresponding status */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/projects")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isIAmATeapot()); } @@ -674,6 +709,9 @@ public void testJoinCourse() throws Exception { @Test public void testGetJoinInformation() throws Exception { + String urlWithKey = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908"; + String urlWithoutKey = ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join"; + CourseEntity course = activeCourse; CourseJoinInformationJson courseJoinInformationJson = new CourseJoinInformationJson( activeCourse.getName(), @@ -682,28 +720,28 @@ public void testGetJoinInformation() throws Exception { /* If join key is correct, course is not archived and no error occurs, return 200 */ when(courseUtil.checkJoinLink(activeCourse.getId(), "1908", getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); when(courseUtil.checkJoinLink(activeCourse.getId(), null, getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", course)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908")) + mockMvc.perform(MockMvcRequestBuilders.get(urlWithKey)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(courseJoinInformationJson))); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join")) + mockMvc.perform(MockMvcRequestBuilders.get(urlWithoutKey)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(courseJoinInformationJson))); /* If course is archived, reutrn 403 */ activeCourse.setArchivedAt(OffsetDateTime.now()); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908")) + mockMvc.perform(MockMvcRequestBuilders.get(urlWithKey)) .andExpect(status().isForbidden()); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join")) + mockMvc.perform(MockMvcRequestBuilders.get(urlWithoutKey)) .andExpect(status().isForbidden()); activeCourse.setArchivedAt(null); /* If join key check fails return corresponding status */ when(courseUtil.checkJoinLink(anyLong(), any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join/1908")) + mockMvc.perform(MockMvcRequestBuilders.get(urlWithKey)) .andExpect(status().isIAmATeapot()); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/" + activeCourse.getId() + "/join")) + mockMvc.perform(MockMvcRequestBuilders.get(urlWithoutKey)) .andExpect(status().isIAmATeapot()); } From 4fee23e0d5c43e681f814e9ab989d79cfa88ca5b Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Tue, 30 Apr 2024 10:43:00 +0200 Subject: [PATCH 06/40] clustercontroller test cleanup --- .../pidgeon/controllers/CourseController.java | 4 - .../controllers/ClusterControllerTest.java | 104 ++++++++++++------ .../controllers/CourseControllerTest.java | 8 +- 3 files changed, 74 insertions(+), 42 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java index 22e9bccb..092e5fc4 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/CourseController.java @@ -196,10 +196,6 @@ public ResponseEntity patchCourse(@RequestBody CourseJson courseJson, @PathVa return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage()); } - if (courseJson.getName() == null && courseJson.getDescription() == null && courseJson.getYear() == null) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Name, description or year is required"); - } - CourseEntity courseEntity = checkResult.getData(); if (courseJson.getName() == null) { courseJson.setName(courseEntity.getName()); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java index a7b1baef..c64983ca 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java @@ -32,6 +32,8 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -65,6 +67,7 @@ public class ClusterControllerTest extends ControllerTest{ private GroupClusterJson groupClusterJson; private GroupEntity groupEntity; private GroupJson groupJson; + private final Long courseId = 1L; private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); @@ -81,13 +84,14 @@ public void setup() { @Test public void testGetClustersForCourse() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + courseId + "/clusters"; /* If the user is enrolled in the course, the clusters are returned */ - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) + when(courseUtil.getCourseIfUserInCourse(courseId, getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.enrolled))); - when(groupClusterRepository.findClustersWithoutInvidualByCourseId(anyLong())).thenReturn(List.of(groupClusterEntity)); + when(groupClusterRepository.findClustersWithoutInvidualByCourseId(courseId)).thenReturn(List.of(groupClusterEntity)); when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/clusters")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(List.of(groupClusterJson)))); @@ -95,19 +99,23 @@ public void testGetClustersForCourse() throws Exception { /* If a certain check fails, the corresponding status code is returned */ when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.COURSE_BASE_PATH + "/1/clusters")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isBadRequest()); } @Test public void testCreateClusterForCourse() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + courseId +"/clusters"; + /* If the user is an admin of the course and the json is valid, the cluster is created */ String request = "{\"name\": \"test\", \"capacity\": 20, \"groupCount\": 5}"; - when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", courseEntity)); - when(clusterUtil.checkGroupClusterCreateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(courseUtil.getCourseIfAdmin(courseId, getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", courseEntity)); + when(clusterUtil.checkGroupClusterCreateJson(argThat( + json -> json.name().equals("test") && json.capacity().equals(20) && json.groupCount().equals(5) + ))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(groupClusterRepository.save(any())).thenReturn(groupClusterEntity); when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/clusters") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isCreated()) @@ -115,15 +123,16 @@ public void testCreateClusterForCourse() throws Exception { .andExpect(content().json(objectMapper.writeValueAsString(groupClusterJson))); /* If the json is invalid, the corresponding status code is returned */ + reset(clusterUtil); when(clusterUtil.checkGroupClusterCreateJson(any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/clusters") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isIAmATeapot()); /* If the user is not an admin of the course, the corresponding status code is returned */ when(courseUtil.getCourseIfAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.COURSE_BASE_PATH + "/1/clusters") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); @@ -131,36 +140,41 @@ public void testCreateClusterForCourse() throws Exception { @Test public void testGetCluster() throws Exception { + String url = ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId(); + /* If the user has acces to the cluster and it isn't an individual cluster, the cluster is returned */ when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); - when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.CLUSTER_BASE_PATH + "/1")) + when(clusterUtil.getGroupClusterEntityIfNotIndividual(groupClusterEntity.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(groupClusterJson))); /* If any check fails, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.CLUSTER_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isIAmATeapot()); } //This function also tests doGroupClusterUpdate @Test public void testUpdateCluster() throws Exception { + String url = ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId(); String request = "{\"name\": \"newclustername\", \"capacity\": 22}"; String originalname = groupClusterEntity.getName(); Integer originalcapacity = groupClusterEntity.getMaxSize(); /* If the user is an admin of the cluster, the cluster isn't individual and the json is valid, the cluster is updated */ GroupClusterEntity copy = new GroupClusterEntity(1L, 20, "newclustername", 5); - when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); - when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(clusterUtil.checkGroupClusterUpdateJson( + argThat(json -> json.getName().equals("newclustername") && json.getCapacity().equals(22)) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); copy.setName("newclustername"); GroupClusterJson updated = new GroupClusterJson(1L, "newclustername", 20, 5, OffsetDateTime.now(), Collections.emptyList(), ""); when(groupClusterRepository.save(groupClusterEntity)).thenReturn(copy); when(entityToJsonConverter.clusterEntityToClusterJson(copy)).thenReturn(updated); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()) @@ -170,15 +184,18 @@ public void testUpdateCluster() throws Exception { assertNotEquals(originalcapacity, groupClusterEntity.getMaxSize()); /* If the json is invalid, the corresponding status code is returned */ + reset(clusterUtil); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isForbidden()); /* If the user is not an admin of the cluster or the cluster is individual, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); @@ -186,18 +203,21 @@ public void testUpdateCluster() throws Exception { @Test public void testPatchCluster() throws Exception { + String url = ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId(); /* If the user is an admin of the cluster and the json is valid, the cluster is updated */ String originalname = groupClusterEntity.getName(); Integer originalcapacity = groupClusterEntity.getMaxSize(); /* If fields are null they are not updated */ String request = "{\"name\": null, \"capacity\": null}"; - when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); - when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(clusterUtil.checkGroupClusterUpdateJson( + argThat(json -> json.getName() == groupClusterEntity.getName() && json.getCapacity() == groupClusterEntity.getMaxSize()) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); when(groupClusterRepository.save(groupClusterEntity)).thenReturn(groupClusterEntity); when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)).thenReturn(groupClusterJson); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()) @@ -208,13 +228,17 @@ public void testPatchCluster() throws Exception { /* If fields are not null they are updated */ request = "{\"name\": \"newclustername\", \"capacity\": 22}"; + reset(clusterUtil); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); GroupClusterEntity copy = new GroupClusterEntity(1L, 20, "newclustername", 5); - when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + when(clusterUtil.checkGroupClusterUpdateJson( + argThat(json -> json.getName().equals("newclustername") && json.getCapacity().equals(22)) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); GroupClusterJson updated = new GroupClusterJson(1L, "newclustername", 22, 5, OffsetDateTime.now(), Collections.emptyList(), ""); when(groupClusterRepository.save(groupClusterEntity)).thenReturn(copy); when(entityToJsonConverter.clusterEntityToClusterJson(copy)).thenReturn(updated); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()) @@ -224,15 +248,18 @@ public void testPatchCluster() throws Exception { assertNotEquals(originalcapacity, groupClusterEntity.getMaxSize()); /* If the json is invalid, the corresponding status code is returned */ + reset(clusterUtil); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); when(clusterUtil.checkGroupClusterUpdateJson(any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isForbidden()); /* If the user is not an admin of the cluster or the cluster is individual, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.CLUSTER_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isIAmATeapot()); @@ -240,31 +267,36 @@ public void testPatchCluster() throws Exception { @Test public void testDeleteCluster() throws Exception { + String url = ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId(); + /* If the user can delete the cluster, the cluster is deleted */ - when(clusterUtil.canDeleteCluster(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.OK,"", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.CLUSTER_BASE_PATH + "/1")) + when(clusterUtil.canDeleteCluster(groupClusterEntity.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(commonDatabaseActions.deleteClusterById(groupClusterEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.OK,"", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isNoContent()); /* If the delete fails, the corresponding status code is returned */ when(commonDatabaseActions.deleteClusterById(anyLong())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT,"", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.CLUSTER_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isIAmATeapot()); /* If the user can't delete the cluster, the corresponding status code is returned */ when(clusterUtil.canDeleteCluster(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN,"", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.CLUSTER_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isForbidden()); } @Test public void testCreateGroupForCluster() throws Exception { + String url = ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId() + "/groups"; String request = "{\"name\": \"test\"}"; /* If the user is an admin of the cluster and the json is valid, the group is created */ - when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); - when(groupRepository.save(any())).thenReturn(groupEntity); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + when(groupRepository.save(argThat( + group -> group.getName().equals("test") && group.getClusterId() == groupClusterEntity.getId() + ))).thenReturn(groupEntity); when(entityToJsonConverter.groupEntityToJson(groupEntity)).thenReturn(groupJson); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isCreated()) @@ -273,19 +305,19 @@ public void testCreateGroupForCluster() throws Exception { /* if the user is not an admin or the cluster is individual, the corresponding status code is returned */ when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isForbidden()); /* If the json is invalid, the corresponding status code is returned */ request = "{\"name\": \"\"}"; - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); request = "{\"name\": null}"; - mockMvc.perform(MockMvcRequestBuilders.post(ApiRoutes.CLUSTER_BASE_PATH + "/1/groups") + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java index 53ddaab2..f80af245 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/CourseControllerTest.java @@ -491,12 +491,16 @@ public void testPatchCourse() throws Exception { .andExpect(status().isOk()); assertNull(activeCourse.getArchivedAt()); - /* If no fields are present, return 400 */ + /* If no fields are present, change nothing */ String emptyJson = "{\"ietswatnietboeit\": \"test\"}"; mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(emptyJson)) - .andExpect(status().isBadRequest()); + .andExpect(status().isOk()); + assertEquals("test", activeCourse.getName()); + assertEquals("description", activeCourse.getDescription()); + assertEquals(2024, activeCourse.getCourseYear()); + /* If invalid json, return corresponding statuscode */ From 3ee0fe207539212588590fbc3a7888573c595b5c Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:01:18 +0200 Subject: [PATCH 07/40] update groupcontroller tests --- .../pidgeon/controllers/GroupController.java | 4 +- .../controllers/GroupControllerTest.java | 101 ++++++++++++++---- 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupController.java index 14bd4b7e..bdae9bec 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupController.java @@ -136,7 +136,9 @@ public ResponseEntity deleteGroup(@PathVariable("groupid") Long groupid, Auth return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage()); } - commonDatabaseActions.removeGroup(groupid); + if (!commonDatabaseActions.removeGroup(groupid)) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error deleting group"); + } // Return 204 return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Group deleted"); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupControllerTest.java index 9412904f..798e0d89 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupControllerTest.java @@ -1,5 +1,8 @@ package com.ugent.pidgeon.controllers; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; +import com.ugent.pidgeon.model.json.GroupJson; import com.ugent.pidgeon.postgre.models.GroupEntity; import com.ugent.pidgeon.postgre.repository.GroupClusterRepository; import com.ugent.pidgeon.postgre.repository.GroupRepository; @@ -23,6 +26,7 @@ import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; @@ -39,67 +43,87 @@ public class GroupControllerTest extends ControllerTest { @Mock private CommonDatabaseActions commonDatabaseActions; + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); + @InjectMocks private GroupController groupController; private GroupEntity groupEntity; + private GroupJson groupJson; + private Integer capacity = 40; @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(groupController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); return request; })) - .build(); + setUpController(groupController); groupEntity = new GroupEntity("Group test", 1L); + groupEntity.setId(5L); + groupJson = new GroupJson( + capacity, + groupEntity.getId(), + groupEntity.getName(), + "" + ); } @Test public void testGetGroupById() throws Exception { - when(groupUtil.getGroupIfExists(anyLong())) + String url = ApiRoutes.GROUP_BASE_PATH + "/" + groupEntity.getId(); + /* If group exists and users has acces, return groupJson */ + when(groupUtil.getGroupIfExists(groupEntity.getId())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity)); - when(groupUtil.canGetGroup(anyLong(), any())) + when(groupUtil.canGetGroup(groupEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.GROUP_BASE_PATH + "/1")) - .andExpect(status().isOk()); + when(entityToJsonConverter.groupEntityToJson(groupEntity)).thenReturn(groupJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().string(objectMapper.writeValueAsString(groupJson))); + /* If the user doesn't have acces to group, return forbidden */ when(groupUtil.canGetGroup(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.GROUP_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isBadRequest()); + /* If group doesn't exist, return not found */ when(groupUtil.getGroupIfExists(anyLong())) .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.GROUP_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isIAmATeapot()); } //this function also fully tests doGroupNameUpdate @Test public void testUpdateGroupName() throws Exception { + String url = ApiRoutes.GROUP_BASE_PATH + "/" + groupEntity.getId(); + /* If all checks pass, update and return groupJson */ String request = "{\"name\":\"Test Group\"}\n"; - when(groupUtil.canUpdateGroup(anyLong(), any())) + when(groupUtil.canUpdateGroup(groupEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.GROUP_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()); + assertEquals(groupEntity.getName(), "Test Group"); + /* If user can't update group, return corresponding status */ when(groupUtil.canUpdateGroup(anyLong(), any())) .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.GROUP_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isIAmATeapot()); + /* If name isn't provided, return bad request */ request = "{\"name\":\"\"}\n"; - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.GROUP_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); request = "{\"name\":null}\n"; - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.GROUP_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); @@ -107,26 +131,59 @@ public void testUpdateGroupName() throws Exception { @Test public void testPatchGroupName() throws Exception { + String url = ApiRoutes.GROUP_BASE_PATH + "/" + groupEntity.getId(); + /* If all checks pass, update and return groupJson */ String request = "{\"name\":\"Test Group\"}\n"; - when(groupUtil.canUpdateGroup(anyLong(), any())) + when(groupUtil.canUpdateGroup(groupEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.GROUP_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()); + assertEquals(groupEntity.getName(), "Test Group"); + + /* If user can't update group, return corresponding status */ + when(groupUtil.canUpdateGroup(anyLong(), any())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + + /* If name isn't provided, return bad request */ + request = "{\"name\":\"\"}\n"; + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + + request = "{\"name\":null}\n"; + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); } @Test public void testDeleteGroup() throws Exception { - when(groupUtil.canUpdateGroup(anyLong(), any())) + String url = ApiRoutes.GROUP_BASE_PATH + "/" + groupEntity.getId(); + /* If all checks pass, delete and return groupJson */ + when(groupUtil.canUpdateGroup(groupEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.GROUP_BASE_PATH + "/1")) + when(commonDatabaseActions.removeGroup(groupEntity.getId())).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isNoContent()); + /* If something goes wrong while deleting, return internal server error */ + when(commonDatabaseActions.removeGroup(groupEntity.getId())).thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isInternalServerError()); + + /* If user can't update group, return corresponding status */ when(groupUtil.canUpdateGroup(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.GROUP_BASE_PATH + "/1")) - .andExpect(status().isBadRequest()); + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isIAmATeapot()); } } From fe86dca39e7c7b221e01ec26a914c68d373255f8 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:40:25 +0200 Subject: [PATCH 08/40] start groupfeedbackcontroller tests --- .../GroupFeedbackControllerTest.java | 183 ++++++++++++------ 1 file changed, 127 insertions(+), 56 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java index a4295874..2be8f568 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java @@ -1,8 +1,14 @@ package com.ugent.pidgeon.controllers; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -12,12 +18,15 @@ import com.ugent.pidgeon.util.EntityToJsonConverter; import com.ugent.pidgeon.util.GroupFeedbackUtil; import com.ugent.pidgeon.util.GroupUtil; +import java.util.Objects; +import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; 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 org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.context.SecurityContextHolder; @@ -43,51 +52,141 @@ public class GroupFeedbackControllerTest extends ControllerTest { @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(groupFeedbackController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { - request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); - return request; - })) - .build(); - groupFeedbackEntity = new GroupFeedbackEntity(1L, 1L, 0F, "good job.... NOT!"); + setUpController(groupFeedbackController); + groupFeedbackEntity = new GroupFeedbackEntity(4L, 6L, 0F, "good job.... NOT!"); } @Test public void testUpdateGroupScore() throws Exception { - String request = "{\"score\": null,\"feedback\": null}"; - when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); - when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + String url = ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", ""+groupFeedbackEntity.getGroupId()) + .replace("{projectid}", ""+groupFeedbackEntity.getProjectId()); + String requestAllNull = "{\"score\": null,\"feedback\": null}"; + String requestScoreNull = "{\"score\": null,\"feedback\": \"Heel goed gedaan\"}"; + String requestFeedbackNull = "{\"score\": 4.4,\"feedback\": null}"; + String request = "{\"score\": 4.4,\"feedback\": \"Heel goed gedaan\"}"; + String originalFeedback = groupFeedbackEntity.getFeedback(); + Float orginalScore = groupFeedbackEntity.getScore(); + /* If all checks succeed, group feedback is updated succesfully */ + /* If fields are null, nothing is changed */ + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PATCH)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( + json -> json.getScore() == groupFeedbackEntity.getScore() && json.getFeedback() + .equals(groupFeedbackEntity.getFeedback())), eq(groupFeedbackEntity.getProjectId()))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestAllNull)) + .andExpect(status().isOk()); + assertEquals(originalFeedback, groupFeedbackEntity.getFeedback()); + assertEquals(orginalScore, groupFeedbackEntity.getScore()); + verify(groupFeedbackRepository, times(1)).save(groupFeedbackEntity); + /* If score is null, only feedback is updated */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PATCH)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( + json -> json.getScore() == groupFeedbackEntity.getScore() && json.getFeedback().equals("Heel goed gedaan")), eq(groupFeedbackEntity.getProjectId()))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestScoreNull)) + .andExpect(status().isOk()); + assertEquals("Heel goed gedaan", groupFeedbackEntity.getFeedback()); + assertEquals(orginalScore, groupFeedbackEntity.getScore()); + verify(groupFeedbackRepository, times(2)).save(groupFeedbackEntity); + groupFeedbackEntity.setFeedback(originalFeedback); + /* If feedback is null, only score is updated */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PATCH)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( + json -> json.getScore() == 4.4F && json.getFeedback().equals(groupFeedbackEntity.getFeedback())), eq(groupFeedbackEntity.getProjectId()))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestFeedbackNull)) + .andExpect(status().isOk()); + assertEquals(originalFeedback, groupFeedbackEntity.getFeedback()); + assertEquals(4.4F, groupFeedbackEntity.getScore()); + verify(groupFeedbackRepository, times(3)).save(groupFeedbackEntity); + groupFeedbackEntity.setScore(orginalScore); + /* If all fields are filled, both are updated */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PATCH)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( + json -> json.getScore() == 4.4F && json.getFeedback().equals("Heel goed gedaan")), eq(groupFeedbackEntity.getProjectId()))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()); + assertEquals("Heel goed gedaan", groupFeedbackEntity.getFeedback()); + assertEquals(4.4F, groupFeedbackEntity.getScore()); + verify(groupFeedbackRepository, times(4)).save(groupFeedbackEntity); - when(groupFeedbackRepository.save(any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.patch( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + /* If json check fails, return corresponding status code */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PATCH)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( + new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestAllNull)) + .andExpect(status().isBadRequest()); + + /* If group feedback check fails, return corresponding status code */ + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PATCH)).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(requestAllNull)) + .andExpect(status().isIAmATeapot()); + + } + + @Test + public void testUpdateGroupScorePut() throws Exception { + String url = ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", ""+groupFeedbackEntity.getGroupId()) + .replace("{projectid}", ""+groupFeedbackEntity.getProjectId()); + String request = "{\"score\": 4.4,\"feedback\": \"Heel goed gedaan\"}"; + /* If all checks succeed, group feedback is updated succesfully */ + /* If all fields are filled, both are updated */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PUT)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( + json -> json.getScore() == 4.4F && json.getFeedback().equals("Heel goed gedaan")), eq(groupFeedbackEntity.getProjectId()))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isInternalServerError()); + .andExpect(status().isOk()); + assertEquals("Heel goed gedaan", groupFeedbackEntity.getFeedback()); + assertEquals(4.4F, groupFeedbackEntity.getScore()); + verify(groupFeedbackRepository, times(1)).save(groupFeedbackEntity); + + /* If json check fails, return corresponding status code */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PUT)).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isIAmATeapot()); + .andExpect(status().isBadRequest()); - when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())).thenReturn( - new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + /* If group feedback check fails, return corresponding status code */ + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), + HttpMethod.PUT)).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isForbidden()); + .andExpect(status().isIAmATeapot()); } @Test @@ -110,35 +209,7 @@ public void testDeleteGroupScore() throws Exception { .andExpect(status().isIAmATeapot()); } - @Test - public void testUpdateGroupScorePut() throws Exception { - String request = "{\"score\": 4.4,\"feedback\": \"Heel goed gedaan\"}"; - when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); - when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) - .andExpect(status().isOk()); - - when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) - .andExpect(status().isBadRequest()); - when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())) - .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) - .andExpect(status().isIAmATeapot()); - } @Test public void testAddGroupScore() throws Exception { From f475446dbe87c0d6f38f8f4ec5e8a847d72d155f Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 10:59:04 +0200 Subject: [PATCH 09/40] GroupFeedbackController tests upgrades --- .../controllers/GroupFeedbackController.java | 26 +- .../GroupFeedbackControllerTest.java | 265 ++++++++++++++---- 2 files changed, 224 insertions(+), 67 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupFeedbackController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupFeedbackController.java index aa6b9e5d..49318700 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupFeedbackController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/GroupFeedbackController.java @@ -36,12 +36,12 @@ public class GroupFeedbackController { private GroupUtil groupUtil; @Autowired private EntityToJsonConverter entityToJsonConverter; - @Autowired - private ProjectRepository projectRepository; - @Autowired - private GroupRepository groupRepository; - @Autowired - private CourseUtil courseUtil; + @Autowired + private ProjectRepository projectRepository; + @Autowired + private GroupRepository groupRepository; + @Autowired + private CourseUtil courseUtil; /** * Function to update the score of a group @@ -222,19 +222,19 @@ public ResponseEntity getCourseGrades(@PathVariable("courseId") long courseId List grades = new ArrayList<>(); for (ProjectEntity project : projects) { - Long GroupId = groupRepository.groupIdByProjectAndUser(project.getId(), user.getId()); - if (GroupId == null) { // Student not yet in a group for this project + Long groupId = groupRepository.groupIdByProjectAndUser(project.getId(), user.getId()); + if (groupId == null) { // Student not yet in a group for this project grades.add(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(null, project)); - } - CheckResult checkResult = groupFeedbackUtil.getGroupFeedbackIfExists(GroupId, project.getId()); - if (checkResult.getStatus() != HttpStatus.OK) { - grades.add(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(null, project)); } else { + CheckResult checkResult = groupFeedbackUtil.getGroupFeedbackIfExists(groupId, project.getId()); + if (checkResult.getStatus() != HttpStatus.OK) { + grades.add(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(null, project)); + } else { GroupFeedbackEntity groupFeedbackEntity = checkResult.getData(); grades.add(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(groupFeedbackEntity, project)); + } } } - return ResponseEntity.ok(grades); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java index 2be8f568..df28a51d 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupFeedbackControllerTest.java @@ -10,14 +10,28 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; +import com.ugent.pidgeon.model.json.GroupFeedbackJson; +import com.ugent.pidgeon.model.json.GroupFeedbackJsonWithProject; +import com.ugent.pidgeon.postgre.models.CourseEntity; import com.ugent.pidgeon.postgre.models.GroupFeedbackEntity; +import com.ugent.pidgeon.postgre.models.ProjectEntity; +import com.ugent.pidgeon.postgre.models.types.CourseRelation; import com.ugent.pidgeon.postgre.repository.GroupFeedbackRepository; +import com.ugent.pidgeon.postgre.repository.GroupRepository; +import com.ugent.pidgeon.postgre.repository.ProjectRepository; import com.ugent.pidgeon.util.CheckResult; +import com.ugent.pidgeon.util.CourseUtil; import com.ugent.pidgeon.util.EntityToJsonConverter; import com.ugent.pidgeon.util.GroupFeedbackUtil; import com.ugent.pidgeon.util.GroupUtil; +import com.ugent.pidgeon.util.Pair; +import java.time.OffsetDateTime; +import java.util.List; import java.util.Objects; import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; @@ -39,21 +53,33 @@ public class GroupFeedbackControllerTest extends ControllerTest { @Mock private GroupFeedbackRepository groupFeedbackRepository; @Mock + private ProjectRepository projectRepository; + @Mock private GroupFeedbackUtil groupFeedbackUtil; @Mock + private GroupRepository groupRepository; + @Mock private GroupUtil groupUtil; @Mock + private CourseUtil courseUtil; + @Mock private EntityToJsonConverter entityToJsonConverter; @InjectMocks private GroupFeedbackController groupFeedbackController; private GroupFeedbackEntity groupFeedbackEntity; + private GroupFeedbackJson groupFeedbackJson; + + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); @BeforeEach public void setup() { setUpController(groupFeedbackController); - groupFeedbackEntity = new GroupFeedbackEntity(4L, 6L, 0F, "good job.... NOT!"); + groupFeedbackEntity = new GroupFeedbackEntity(4L, 6L, 1F, "good job.... NOT!"); + groupFeedbackJson = new GroupFeedbackJson(groupFeedbackEntity.getScore(), groupFeedbackEntity.getFeedback(), + groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + } @Test @@ -74,10 +100,13 @@ public void testUpdateGroupScore() throws Exception { json -> json.getScore() == groupFeedbackEntity.getScore() && json.getFeedback() .equals(groupFeedbackEntity.getFeedback())), eq(groupFeedbackEntity.getProjectId()))) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity)).thenReturn(groupFeedbackJson); mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestAllNull)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); assertEquals(originalFeedback, groupFeedbackEntity.getFeedback()); assertEquals(orginalScore, groupFeedbackEntity.getScore()); verify(groupFeedbackRepository, times(1)).save(groupFeedbackEntity); @@ -88,14 +117,18 @@ public void testUpdateGroupScore() throws Exception { when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( json -> json.getScore() == groupFeedbackEntity.getScore() && json.getFeedback().equals("Heel goed gedaan")), eq(groupFeedbackEntity.getProjectId()))) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + groupFeedbackJson.setFeedback("Heel goed gedaan"); mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestScoreNull)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); assertEquals("Heel goed gedaan", groupFeedbackEntity.getFeedback()); assertEquals(orginalScore, groupFeedbackEntity.getScore()); verify(groupFeedbackRepository, times(2)).save(groupFeedbackEntity); groupFeedbackEntity.setFeedback(originalFeedback); + groupFeedbackJson.setFeedback(originalFeedback); /* If feedback is null, only score is updated */ reset(groupFeedbackUtil); when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), @@ -103,14 +136,18 @@ public void testUpdateGroupScore() throws Exception { when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( json -> json.getScore() == 4.4F && json.getFeedback().equals(groupFeedbackEntity.getFeedback())), eq(groupFeedbackEntity.getProjectId()))) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + groupFeedbackJson.setScore(4.4F); mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(requestFeedbackNull)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); assertEquals(originalFeedback, groupFeedbackEntity.getFeedback()); assertEquals(4.4F, groupFeedbackEntity.getScore()); verify(groupFeedbackRepository, times(3)).save(groupFeedbackEntity); groupFeedbackEntity.setScore(orginalScore); + groupFeedbackJson.setScore(orginalScore); /* If all fields are filled, both are updated */ reset(groupFeedbackUtil); when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), @@ -118,14 +155,25 @@ public void testUpdateGroupScore() throws Exception { when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( json -> json.getScore() == 4.4F && json.getFeedback().equals("Heel goed gedaan")), eq(groupFeedbackEntity.getProjectId()))) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + groupFeedbackJson.setFeedback("Heel goed gedaan"); + groupFeedbackJson.setScore(4.4F); mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); assertEquals("Heel goed gedaan", groupFeedbackEntity.getFeedback()); assertEquals(4.4F, groupFeedbackEntity.getScore()); verify(groupFeedbackRepository, times(4)).save(groupFeedbackEntity); + /* If an exception is thrown, return internal server error */ + doThrow(new RuntimeException()).when(groupFeedbackRepository).save(any()); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); + /* If json check fails, return corresponding status code */ reset(groupFeedbackUtil); when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), @@ -160,14 +208,25 @@ public void testUpdateGroupScorePut() throws Exception { when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( json -> json.getScore() == 4.4F && json.getFeedback().equals("Heel goed gedaan")), eq(groupFeedbackEntity.getProjectId()))) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity)).thenReturn(groupFeedbackJson); + groupFeedbackJson.setFeedback("Heel goed gedaan"); + groupFeedbackJson.setScore(4.4F); mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); assertEquals("Heel goed gedaan", groupFeedbackEntity.getFeedback()); assertEquals(4.4F, groupFeedbackEntity.getScore()); verify(groupFeedbackRepository, times(1)).save(groupFeedbackEntity); + /* If an exception is thrown, return internal server error */ + doThrow(new RuntimeException()).when(groupFeedbackRepository).save(any()); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); /* If json check fails, return corresponding status code */ reset(groupFeedbackUtil); @@ -191,21 +250,23 @@ public void testUpdateGroupScorePut() throws Exception { @Test public void testDeleteGroupScore() throws Exception { - when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())) + String url = ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", ""+groupFeedbackEntity.getGroupId()) + .replace("{projectid}", ""+groupFeedbackEntity.getProjectId()); + /* If user can delete group feedback, delete it */ + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), HttpMethod.DELETE)) .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); - mockMvc.perform(MockMvcRequestBuilders.delete( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isOk()); + verify(groupFeedbackRepository, times(1)).delete(groupFeedbackEntity); + /* If an exception is thrown, return internal server error */ doThrow(new RuntimeException()).when(groupFeedbackRepository).delete(any()); - mockMvc.perform(MockMvcRequestBuilders.delete( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) - .andExpect(status().isInternalServerError()); + mockMvc.perform(MockMvcRequestBuilders.delete(url)).andExpect(status().isInternalServerError()); + /* If the groupfeedback can't be deleted by the user, return corresponding status code */ when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())) .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isIAmATeapot()); } @@ -213,36 +274,52 @@ public void testDeleteGroupScore() throws Exception { @Test public void testAddGroupScore() throws Exception { - String request = "{\"score\": 4.4,\"feedback\": \"Heel goed gedaan\"}"; - when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); - when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) - .contentType(MediaType.APPLICATION_JSON) - .content(request)) - .andExpect(status().isCreated()); + String url = ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", ""+groupFeedbackEntity.getGroupId()) + .replace("{projectid}", ""+groupFeedbackEntity.getProjectId()); + String request = "{\"score\": " + groupFeedbackEntity.getScore() + ",\"feedback\": \"" + groupFeedbackEntity.getFeedback() + "\"}"; + /* If all checks succeed, group feedback is added succesfully */ + when(groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser(), HttpMethod.POST)) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(argThat( + json -> json.getScore() == groupFeedbackEntity.getScore() && json.getFeedback().equals(groupFeedbackEntity.getFeedback())), eq(groupFeedbackEntity.getProjectId()))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupFeedbackRepository.save(any())).thenReturn(groupFeedbackEntity); + when(entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity)).thenReturn(groupFeedbackJson); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); + verify(groupFeedbackRepository, times(1)).save(argThat( + groupFeedback -> groupFeedback.getScore() == groupFeedbackEntity.getScore() && + groupFeedback.getFeedback().equals(groupFeedbackEntity.getFeedback()) && + groupFeedback.getGroupId() == groupFeedbackEntity.getGroupId() && + groupFeedback.getProjectId() == groupFeedbackEntity.getProjectId())); + /* If an exception is thrown, return internal server error */ + reset(groupFeedbackRepository); when(groupFeedbackRepository.save(any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isInternalServerError()); + /* If json check fails, return corresponding status code */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", null)); when(groupFeedbackUtil.checkGroupFeedbackUpdateJson(any(), anyLong())).thenReturn( new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isBadRequest()); + /* If user can't add group feedback, return corresponding status code */ when(groupFeedbackUtil.checkGroupFeedbackUpdate(anyLong(), anyLong(), any(), any())).thenReturn( new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1")) + mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isForbidden()); @@ -250,32 +327,112 @@ public void testAddGroupScore() throws Exception { @Test public void testGetGroupScore() throws Exception { - when(groupFeedbackUtil.checkGroupFeedback(anyLong(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - when(groupFeedbackUtil.getGroupFeedbackIfExists(anyLong(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); - mockMvc.perform(MockMvcRequestBuilders.get( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) - .andExpect(status().isOk()); + String url = ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", ""+groupFeedbackEntity.getGroupId()) + .replace("{projectid}", ""+groupFeedbackEntity.getProjectId()); + /* If all checks succeed, group feedback is returned */ + when(groupFeedbackUtil.checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity)).thenReturn(groupFeedbackJson); + when(groupUtil.canGetProjectGroupData(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupFeedbackUtil.getGroupFeedbackIfExists(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity)).thenReturn(groupFeedbackJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJson))); - when(groupFeedbackUtil.getGroupFeedbackIfExists(anyLong(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) + /* If feedback doesn't exist, return not found */ + reset(groupFeedbackUtil); + when(groupFeedbackUtil.checkGroupFeedback(anyLong(), anyLong())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupFeedbackUtil.getGroupFeedbackIfExists(anyLong(), anyLong())) + .thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isNotFound()); + + /* User can't get project group data, return forbidden */ + reset(groupUtil); + when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any())) + .thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isForbidden()); + + /* If check fails, return corresponding status code */ + when(groupFeedbackUtil.checkGroupFeedback(anyLong(), anyLong())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); + } + + @Test + public void testGetCourseGrades() throws Exception { + CourseEntity courseEntity = new CourseEntity("Test course", "TestCourseDescription", 2013); + courseEntity.setId(99L); + ProjectEntity project1 = new ProjectEntity(courseEntity.getId(),"Test project", "TestProjectDescription", 1L, 11L, true, 44, OffsetDateTime.now()); + ProjectEntity project2 = new ProjectEntity(courseEntity.getId(),"Test project", "TestProjectDescription", 2L, 11L, true, 44, OffsetDateTime.now()); + project2.setId(1L); + project1.setId(2L); + long project1GroupId = 4L; + long project2GroupId = 5L; + GroupFeedbackJsonWithProject groupFeedbackJsonWithProject1 = new GroupFeedbackJsonWithProject(project1.getName(), "UrlOfProject1", project1.getId(), groupFeedbackJson, project1.getMaxScore()); + GroupFeedbackJsonWithProject groupFeedbackJsonWithProject2 = new GroupFeedbackJsonWithProject(project2.getName(), "UrlOfProject2", project2.getId(), null, project2.getMaxScore()); + List groupFeedbackJsonWithProjects = List.of(groupFeedbackJsonWithProject1, groupFeedbackJsonWithProject2); + String url = ApiRoutes.COURSE_BASE_PATH + "/" + courseEntity.getId() + "/grades"; + /* If all checks succeed, course grades are returned */ + when(courseUtil.getCourseIfUserInCourse(courseEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.enrolled))); + when(projectRepository.findByCourseId(courseEntity.getId())) + .thenReturn(List.of(project1, project2)); + when(groupRepository.groupIdByProjectAndUser(project1.getId(), getMockUser().getId())) + .thenReturn(project1GroupId); + when(groupRepository.groupIdByProjectAndUser(project2.getId(), getMockUser().getId())) + .thenReturn(project2GroupId); + when(groupFeedbackUtil.getGroupFeedbackIfExists(project1GroupId, project1.getId())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupFeedbackEntity)); + when(groupFeedbackUtil.getGroupFeedbackIfExists(project2GroupId, project2.getId())) + .thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "", null)); + when(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(groupFeedbackEntity, project1)) + .thenReturn(groupFeedbackJsonWithProject1); + when(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(null, project2)) + .thenReturn(groupFeedbackJsonWithProject2); + + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJsonWithProjects))); + + /* If project is not visible, filter it out */ + project2.setVisible(false); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(groupFeedbackJsonWithProject1)))); + project2.setVisible(true); + + /* If user is not yet in group also have null as group feedback */ + when(groupRepository.groupIdByProjectAndUser(project1.getId(), getMockUser().getId())) + .thenReturn(null); + GroupFeedbackJsonWithProject project1NoGroup = new GroupFeedbackJsonWithProject(project1.getName(), "UrlOfProject1", project1.getId(), null, project1.getMaxScore()); + groupFeedbackJsonWithProjects = List.of(project1NoGroup, groupFeedbackJsonWithProject2); + when(entityToJsonConverter.groupFeedbackEntityToJsonWithProject(null, project1)) + .thenReturn(project1NoGroup); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(groupFeedbackJsonWithProjects))); + + /* If user isn't enrolled in the course, return BAD REQUEST */ + when(courseUtil.getCourseIfUserInCourse(courseEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.course_admin))); + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isBadRequest()); - when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any())).thenReturn( - new CheckResult<>(HttpStatus.CONFLICT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) - .andExpect(status().isConflict()); - - when(groupFeedbackUtil.checkGroupFeedback(anyLong(), anyLong())).thenReturn( - new CheckResult<>(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get( - ApiRoutes.GROUP_FEEDBACK_PATH.replace("{groupid}", "1").replace("{projectid}", "1"))) - .andExpect(status().isBandwidthLimitExceeded()); + /* If course check fails, return corresponding status code */ + when(courseUtil.getCourseIfUserInCourse(courseEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); } } From d51ce6efdb355e2010ffdcfa5612ed006f077e6d Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 11:20:04 +0200 Subject: [PATCH 10/40] Update GroupMembersControllerTest.java --- .../GroupMembersControllerTest.java | 180 +++++++++++------- 1 file changed, 108 insertions(+), 72 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupMembersControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupMembersControllerTest.java index 9e90bb1c..70fd1ac9 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupMembersControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/GroupMembersControllerTest.java @@ -2,9 +2,15 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; +import com.ugent.pidgeon.model.json.UserReferenceJson; import com.ugent.pidgeon.postgre.models.UserEntity; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.GroupMemberRepository; @@ -19,6 +25,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -36,123 +43,152 @@ public class GroupMembersControllerTest extends ControllerTest { @InjectMocks private GroupMemberController groupMemberController; + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); + private UserEntity userEntity; + private UserEntity userEntity2; + private UserReferenceJson userReferenceJson; + private UserReferenceJson userReferenceJson2; + private final long groupId = 10L; @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(groupMemberController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { - request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); - return request; - })) - .build(); + setUpController(groupMemberController); userEntity = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); - userEntity.setId(1L); + userEntity.setId(5L); + userEntity2 = new UserEntity("name2", "surname2", "email2", UserRole.student, "azureid2"); + userEntity2.setId(6L); + userReferenceJson = new UserReferenceJson(userEntity.getName(), userEntity.getEmail(), userEntity.getId()); + userReferenceJson2 = new UserReferenceJson(userEntity2.getName(), userEntity2.getEmail(), userEntity2.getId()); } @Test public void testRemoveMemberFromGroup() throws Exception { - when(groupUtil.canRemoveUserFromGroup(anyLong(), anyLong(), any())) + String url = ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", ""+groupId) + "/" + userEntity.getId(); + /* If all checks pass, the user is removed from the group */ + when(groupUtil.canRemoveUserFromGroup(groupId, userEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(groupMemberRepository.removeMemberFromGroup(anyLong(), anyLong())).thenReturn(1); - mockMvc.perform(MockMvcRequestBuilders.delete( - ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1") + "/1")) + when(groupMemberRepository.removeMemberFromGroup(groupId, userEntity.getId())).thenReturn(1); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isNoContent()); + verify(groupMemberRepository, times(1)).removeMemberFromGroup(groupId, userEntity.getId()); - when(groupMemberRepository.removeMemberFromGroup(anyLong(), anyLong())).thenReturn(0); - mockMvc.perform(MockMvcRequestBuilders.delete( - ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1") + "/1")) + /* If something goes wrong return internal server error */ + when(groupMemberRepository.removeMemberFromGroup(groupId, userEntity.getId())).thenReturn(0); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isInternalServerError()); - when(groupUtil.canRemoveUserFromGroup(anyLong(), anyLong(), any())) + /* If use can't be removed from group return corresponding status */ + when(groupUtil.canRemoveUserFromGroup(groupId, userEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete( - ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1") + "/1")) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isBadRequest()); } @Test public void testRemoveMemberFromGroupInferred() throws Exception { - when(groupUtil.canRemoveUserFromGroup(anyLong(), anyLong(), any())) + String url = ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", ""+groupId); + /* If all checks pass, the user is removed from the group */ + when(groupUtil.canRemoveUserFromGroup(groupId, getMockUser().getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(groupMemberRepository.removeMemberFromGroup(anyLong(), anyLong())).thenReturn(1); - mockMvc.perform( - MockMvcRequestBuilders.delete(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) + when(groupMemberRepository.removeMemberFromGroup(groupId, getMockUser().getId())).thenReturn(1); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isNoContent()); + verify(groupMemberRepository, times(1)).removeMemberFromGroup(groupId, getMockUser().getId()); - when(groupMemberRepository.removeMemberFromGroup(anyLong(), anyLong())).thenReturn(0); - mockMvc.perform( - MockMvcRequestBuilders.delete(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) + /* If something goes wrong return internal server error */ + when(groupMemberRepository.removeMemberFromGroup(groupId, getMockUser().getId())).thenReturn(0); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isInternalServerError()); - when(groupUtil.canRemoveUserFromGroup(anyLong(), anyLong(), any())) + /* If use can't be removed from group return corresponding status */ + when(groupUtil.canRemoveUserFromGroup(groupId, getMockUser().getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform( - MockMvcRequestBuilders.delete(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isBadRequest()); } @Test public void testAddMemberToGroup() throws Exception { - when(groupUtil.canAddUserToGroup(anyLong(), anyLong(), any())) + String url = ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", ""+groupId) + "/" + userEntity.getId(); + + /* If all checks succeed, the user is added to the group */ + when(groupUtil.canAddUserToGroup(groupId, userEntity.getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(groupMemberRepository.findAllMembersByGroupId(anyLong())) - .thenReturn(List.of(userEntity)); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1") + "/1")) - .andExpect(status().isOk()); - - when(groupMemberRepository.findAllMembersByGroupId(anyLong())).thenThrow( - new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1") + "/1")) + when(groupMemberRepository.findAllMembersByGroupId(groupId)) + .thenReturn(List.of(userEntity, userEntity2)); + when(entityToJsonConverter.userEntityToUserReference(userEntity)).thenReturn(userReferenceJson); + when(entityToJsonConverter.userEntityToUserReference(userEntity2)).thenReturn(userReferenceJson2); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userReferenceJson, userReferenceJson2)))); + verify(groupMemberRepository, times(1)).addMemberToGroup(groupId, userEntity.getId()); + + /* If something goes wrong return internal server error */ + when(groupMemberRepository.addMemberToGroup(groupId, userEntity.getId())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.post(url)) .andExpect(status().isInternalServerError()); - when(groupUtil.canAddUserToGroup(anyLong(), anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.post( - ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1") + "/1")) - .andExpect(status().isBadRequest()); + /* If user can't be added to group return corresponding status */ + when(groupUtil.canAddUserToGroup(groupId, userEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isIAmATeapot()); + } @Test public void testAddMemberToGroupInferred() throws Exception { - when(groupUtil.canAddUserToGroup(anyLong(), anyLong(), any())) + String url = ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", ""+groupId); + UserReferenceJson mockUserJson = new UserReferenceJson(getMockUser().getName(), getMockUser().getEmail(), getMockUser().getId()); + + /* If all checks succeed, the user is added to the group */ + when(groupUtil.canAddUserToGroup(groupId, getMockUser().getId(), getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(groupMemberRepository.findAllMembersByGroupId(anyLong())) - .thenReturn(List.of(userEntity)); - mockMvc.perform( - MockMvcRequestBuilders.post(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) - .andExpect(status().isOk()); - - when(groupMemberRepository.findAllMembersByGroupId(anyLong())).thenThrow( - new RuntimeException()); - mockMvc.perform( - MockMvcRequestBuilders.post(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) + when(groupMemberRepository.findAllMembersByGroupId(groupId)) + .thenReturn(List.of(getMockUser(), userEntity2)); + when(entityToJsonConverter.userEntityToUserReference(getMockUser())).thenReturn(mockUserJson); + when(entityToJsonConverter.userEntityToUserReference(userEntity2)).thenReturn(userReferenceJson2); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(mockUserJson, userReferenceJson2)))); + verify(groupMemberRepository, times(1)).addMemberToGroup(groupId, getMockUser().getId()); + + /* If something goes wrong return internal server error */ + when(groupMemberRepository.addMemberToGroup(groupId, getMockUser().getId())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.post(url)) .andExpect(status().isInternalServerError()); - when(groupUtil.canAddUserToGroup(anyLong(), anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform( - MockMvcRequestBuilders.post(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) - .andExpect(status().isBadRequest()); + /* If user can't be added to group return corresponding status */ + when(groupUtil.canAddUserToGroup(groupId, getMockUser().getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.post(url)) + .andExpect(status().isIAmATeapot()); } @Test public void testFindAllMembersByGroupId() throws Exception { - when(groupUtil.canGetGroup(anyLong(), any())) + String url = ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", ""+groupId); + List members = List.of(userEntity, userEntity2); + List userReferenceJsons = List.of(userReferenceJson, userReferenceJson2); + when(groupMemberRepository.findAllMembersByGroupId(groupId)).thenReturn(members); + when(entityToJsonConverter.userEntityToUserReference(userEntity)).thenReturn(userReferenceJson); + when(entityToJsonConverter.userEntityToUserReference(userEntity2)).thenReturn(userReferenceJson2); + + /* If user can get group return list of members */ + when(groupUtil.canGetGroup(groupId, getMockUser())) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(groupMemberRepository.findAllMembersByGroupId(anyLong())) - .thenReturn(List.of(userEntity)); - mockMvc.perform( - MockMvcRequestBuilders.get(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) - .andExpect(status().isOk()); - - when(groupUtil.canGetGroup(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform( - MockMvcRequestBuilders.get(ApiRoutes.GROUP_MEMBER_BASE_PATH.replace("{groupid}", "1"))) - .andExpect(status().isBadRequest()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(userReferenceJsons))); + + /* If use can't get group return corresponding status */ + when(groupUtil.canGetGroup(groupId, getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); } } \ No newline at end of file From 0354cd48195e2d070406487f478c9e647882c99a Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 12:48:20 +0200 Subject: [PATCH 11/40] Start projectcontroller test upgrades --- .../controllers/ProjectController.java | 2 +- ...rojectsJson.java => UserProjectsJson.java} | 2 +- .../controllers/ProjectControllerTest.java | 909 ++++++------------ 3 files changed, 270 insertions(+), 643 deletions(-) rename backend/app/src/main/java/com/ugent/pidgeon/model/json/{userProjectsJson.java => UserProjectsJson.java} (73%) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ProjectController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ProjectController.java index 53058ab6..1c1ec265 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ProjectController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ProjectController.java @@ -82,7 +82,7 @@ public ResponseEntity getProjects(Auth auth) { } } - return ResponseEntity.ok().body(new userProjectsJson(enrolledProjects, adminProjects)); + return ResponseEntity.ok().body(new UserProjectsJson(enrolledProjects, adminProjects)); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/userProjectsJson.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/UserProjectsJson.java similarity index 73% rename from backend/app/src/main/java/com/ugent/pidgeon/model/json/userProjectsJson.java rename to backend/app/src/main/java/com/ugent/pidgeon/model/json/UserProjectsJson.java index 85b1c909..6560acb6 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/json/userProjectsJson.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/UserProjectsJson.java @@ -3,6 +3,6 @@ import com.ugent.pidgeon.model.ProjectResponseJson; import java.util.List; -public record userProjectsJson(List enrolledProjects, List adminProjects) { +public record UserProjectsJson(List enrolledProjects, List adminProjects) { } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java index 255f599d..0bd9693f 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java @@ -1,14 +1,14 @@ package com.ugent.pidgeon.controllers; -import com.ugent.pidgeon.model.Auth; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; import com.ugent.pidgeon.model.ProjectResponseJson; import com.ugent.pidgeon.model.json.CourseReferenceJson; -import com.ugent.pidgeon.model.json.ProjectJson; import com.ugent.pidgeon.model.json.ProjectProgressJson; -import com.ugent.pidgeon.model.json.userProjectsJson; +import com.ugent.pidgeon.model.json.ProjectResponseJsonWithStatus; +import com.ugent.pidgeon.model.json.UserProjectsJson; import com.ugent.pidgeon.postgre.models.*; import com.ugent.pidgeon.postgre.models.types.CourseRelation; -import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.*; import com.ugent.pidgeon.util.CheckResult; import com.ugent.pidgeon.util.ClusterUtil; @@ -17,30 +17,27 @@ import com.ugent.pidgeon.util.EntityToJsonConverter; import com.ugent.pidgeon.util.Pair; import com.ugent.pidgeon.util.ProjectUtil; -import java.util.Objects; +import java.util.Collections; +import java.util.Optional; +import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.ArgumentMatchers; -import org.springframework.data.jpa.repository.support.SimpleJpaRepository; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import java.time.OffsetDateTime; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -public class ProjectControllerTest { - - protected MockMvc mockMvc; +public class ProjectControllerTest extends ControllerTest { @InjectMocks private ProjectController projectController; @@ -78,645 +75,275 @@ public class ProjectControllerTest { @Mock private GroupRepository grouprRepository; + private final ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); + private ProjectEntity projectEntity; + private ProjectEntity projectEntity2; + private ProjectResponseJson projectResponseJson; + private ProjectResponseJson projectResponseJson2; + private CourseEntity courseEntity; + private CourseEntity courseEntity2; + private final long groupClusterId = 7L; + @BeforeEach void setUp() { - MockitoAnnotations.openMocks(this); - } - - - @Test - void testGetProjectShouldReturnOneProject() { - // Mock data - Auth auth = mock(Auth.class); - ProjectEntity project = new ProjectEntity(); - project.setName("Project 1"); - project.setId(1L); - project.setVisible(true); - List projects = new ArrayList<>(); - projects.add(project); - UserEntity user = new UserEntity("Test", "De Tester", "test.tester@test.com", UserRole.student, - "azure"); - user.setId(1L); - - // Mock repository behavior - when(projectRepository.findProjectsByUserId(anyLong())).thenReturn(projects); - when(auth.getUserEntity()).thenReturn(user); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", - new Pair<>(new CourseEntity(), CourseRelation.enrolled))); - - // Call controller method - ResponseEntity response = projectController.getProjects(auth); - - // Verify response - assertInstanceOf(userProjectsJson.class, response.getBody()); - userProjectsJson responseBody = (userProjectsJson) response.getBody(); - assertEquals(1, responseBody.enrolledProjects().size()); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - } - - @Test - void testGetProjectShouldReturnMultipleProject() { - // Mock data - Auth auth = mock(Auth.class); - ProjectEntity project1 = new ProjectEntity(); - project1.setName("Project 1"); - project1.setId(1L); - project1.setVisible(true); - ProjectEntity project2 = new ProjectEntity(); - project2.setName("Project 2"); - project2.setId(2L); - project2.setVisible(true); - List projects = new ArrayList<>(); - projects.add(project1); - projects.add(project2); - UserEntity user = new UserEntity("Test", "De Tester", "test.tester@test.com", UserRole.student, - "azure"); - user.setId(1L); - - // Mock repository behavior - when(projectRepository.findProjectsByUserId(anyLong())).thenReturn(projects); - when(auth.getUserEntity()).thenReturn(user); - when(courseUtil.getCourseIfUserInCourse(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", - new Pair<>(new CourseEntity(), CourseRelation.enrolled))); - - // Call controller method - ResponseEntity response = projectController.getProjects(auth); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertInstanceOf(userProjectsJson.class, response.getBody()); - userProjectsJson responseBody = (userProjectsJson) response.getBody(); - assertEquals(2, responseBody.enrolledProjects().size()); - } - + setUpController(projectController); + + courseEntity = new CourseEntity("courseName", "courseUrl", 2020); + courseEntity.setId(24L); + courseEntity2 = new CourseEntity("courseName2", "courseUrl2", 2021); + courseEntity2.setId(25L); + + projectEntity = new ProjectEntity( + courseEntity.getId(), + "projectName", + "projectDescription", + groupClusterId, + 38L, + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(64); + projectResponseJson = new ProjectResponseJson( + new CourseReferenceJson(courseEntity.getName(), "course1URL", courseEntity.getId(), null), + OffsetDateTime.now(), + projectEntity.getName(), + projectEntity.getId(), + projectEntity.getDescription(), + "submissionUrl", + "testUrl", + projectEntity.getMaxScore(), + projectEntity.isVisible(), + new ProjectProgressJson(0, 0), + 1L, + groupClusterId + ); + + projectEntity2 = new ProjectEntity( + courseEntity2.getId(), + "projectName2", + "projectDescription2", + groupClusterId, + 39L, + true, + 32, + OffsetDateTime.now() + ); + projectEntity2.setId(65); + projectResponseJson2 = new ProjectResponseJson( + new CourseReferenceJson(courseEntity2.getName(), "course2URL", courseEntity2.getId(), null), + OffsetDateTime.now(), + projectEntity2.getName(), + projectEntity2.getId(), + projectEntity2.getDescription(), + "submissionUrl", + "testUrl", + projectEntity2.getMaxScore(), + projectEntity2.isVisible(), + new ProjectProgressJson(0, 0), + 1L, + groupClusterId + ); - @Test - void testGetProjectByIdShouldReturnProject() { - // Mock data - // auth object - Auth auth = mock(Auth.class); - // projects - ProjectEntity project1 = new ProjectEntity(); - project1.setName("Project 1"); - project1.setId(1L); - project1.setVisible(true); - ProjectEntity project2 = new ProjectEntity(); - project2.setName("Project 2"); - project2.setId(2L); - project2.setVisible(true); - project2.setCourseId(1L); - ProjectEntity project3 = new ProjectEntity(); - project3.setName("Project 3"); - project3.setId(3L); - project3.setVisible(true); - List projects = new ArrayList<>(); - projects.add(project1); - projects.add(project2); - projects.add(project3); - // users - UserEntity user = new UserEntity("Test", "De Tester", "test.tester@test.com", UserRole.student, - "azure"); - user.setId(1L); - //check results - CourseEntity courseEntity = new CourseEntity(); - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProject", - project2); - CheckResult> courseCheck = new CheckResult<>(HttpStatus.OK, "TestCourse", - new Pair<>(courseEntity, CourseRelation.enrolled)); - - // Mock repository behavior - when(projectUtil.canGetProject(2L, user)).thenReturn(checkResult); - when(courseUtil.getCourseIfUserInCourse(1L, user)).thenReturn(courseCheck); - when(auth.getUserEntity()).thenReturn(user); - when(entityToJsonConverter.projectEntityToProjectResponseJson(project2, courseCheck.getData().getFirst(), - user)).thenReturn(new ProjectResponseJson( - new CourseReferenceJson("TestCourse", ApiRoutes.COURSE_BASE_PATH + "/" + 1L, 1L, OffsetDateTime.now()), - OffsetDateTime.MAX, - "Test", 2L, "TestProject", "testUrl", "testUrl", 0, true, new ProjectProgressJson(0, 0), - 1L, 1L)); - - // Call controller method - ResponseEntity response = projectController.getProjectById(2L, auth); - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - ProjectResponseJson responseBody = (ProjectResponseJson) response.getBody(); - assert responseBody != null; - assertEquals(2L, responseBody.projectId()); - - } - - - @Test - void testGetProjectByIdShouldFailReasonCanNotGetProject() { - // Mock data - // auth object - Auth auth = mock(Auth.class); - // projects - ProjectEntity project1 = new ProjectEntity(); - project1.setName("Project 1"); - project1.setId(1L); - ProjectEntity project2 = new ProjectEntity(); - project2.setName("Project 2"); - project2.setId(2L); - project2.setCourseId(1L); - ProjectEntity project3 = new ProjectEntity(); - project3.setName("Project 3"); - project3.setId(3L); - List projects = new ArrayList<>(); - projects.add(project1); - projects.add(project2); - projects.add(project3); - // users - UserEntity user = new UserEntity("Test", "De Tester", "test.tester@test.com", UserRole.student, - "azure"); - user.setId(1L); - //check results - CourseEntity courseEntity = new CourseEntity(); - CheckResult checkResult = new CheckResult<>(HttpStatus.FORBIDDEN, - "testProjectForbidden", - project2); - CheckResult courseCheck = new CheckResult<>(HttpStatus.OK, "TestCourse", - courseEntity); - - // Mock repository behavior - when(projectUtil.canGetProject(2L, user)).thenReturn(checkResult); - when(courseUtil.getCourseIfExists(1L)).thenReturn(courseCheck); - when(auth.getUserEntity()).thenReturn(user); - when(entityToJsonConverter.projectEntityToProjectResponseJson(project2, courseCheck.getData(), - user)).thenReturn(new ProjectResponseJson( - new CourseReferenceJson("TestCourse", ApiRoutes.COURSE_BASE_PATH + "/" + 1L, 1L, OffsetDateTime.now()), - OffsetDateTime.MAX, - "Test", 2L, "TestProject", "testUrl", "testUrl", 0, true, new ProjectProgressJson(0, 0), - 1L, 1L)); - - // Call controller method - ResponseEntity response = projectController.getProjectById(2L, auth); - // Verify response - assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); - assertEquals("testProjectForbidden", Objects.requireNonNull(response.getBody()).toString()); - - } - - - @Test - void testGetProjectByIdShouldFailReasonCanNotGetCourse() { - // Mock data - // auth object - Auth auth = mock(Auth.class); - // projects - ProjectEntity project1 = new ProjectEntity(); - project1.setName("Project 1"); - project1.setId(1L); - project1.setVisible(true); - ProjectEntity project2 = new ProjectEntity(); - project2.setName("Project 2"); - project2.setId(2L); - project2.setVisible(true); - project2.setCourseId(1L); - ProjectEntity project3 = new ProjectEntity(); - project3.setName("Project 3"); - project3.setId(3L); - project3.setVisible(true); - List projects = new ArrayList<>(); - projects.add(project1); - projects.add(project2); - projects.add(project3); - // users - UserEntity user = new UserEntity("Test", "De Tester", "test.tester@test.com", UserRole.student, - "azure"); - user.setId(1L); - //check results - CourseEntity courseEntity = new CourseEntity(); - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProject", - project2); - CheckResult> courseCheck = new CheckResult<>(HttpStatus.FORBIDDEN, "testCourseForbidden", - new Pair<>(courseEntity, CourseRelation.enrolled)); - - // Mock repository behavior - when(projectUtil.canGetProject(2L, user)).thenReturn(checkResult); - when(courseUtil.getCourseIfUserInCourse(1L, user)).thenReturn(courseCheck); - when(auth.getUserEntity()).thenReturn(user); - when(entityToJsonConverter.projectEntityToProjectResponseJson(project2, courseCheck.getData().getFirst(), - user)).thenReturn(new ProjectResponseJson( - new CourseReferenceJson("TestCourse", ApiRoutes.COURSE_BASE_PATH + "/" + 1L, 1L, OffsetDateTime.now()), - OffsetDateTime.MAX, - "Test", 2L, "TestProject", "testUrl", "testUrl", 0, true, new ProjectProgressJson(0, 0), - 1L, 1L)); - - // Call controller method - ResponseEntity response = projectController.getProjectById(2L, auth); - // Verify response - assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); - assertEquals("testCourseForbidden", Objects.requireNonNull(response.getBody()).toString()); - - } - - @Test - public void testCreateProjectShouldMakeProject() { - // Mock data - long courseId = 1L; - ProjectJson projectJson = - new ProjectJson("Test Project", "Test Description", 1L, 1L, true, 100, OffsetDateTime.MAX); - ProjectEntity projectEntity = - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX); - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(1L); - when(auth.getUserEntity()).thenReturn(user); - - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - - CheckResult checkAcces = new CheckResult<>(HttpStatus.OK, "TestIsAdmin", - courseEntity); - - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProjectJson", null); - - // Mock repository behavior - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - - when(courseUtil.getCourseIfAdmin(courseId, user)).thenReturn(checkAcces); - when(projectUtil.checkProjectJson(projectJson, courseId)).thenReturn(checkResult); - when(courseRepository.findById(courseId)).thenReturn(Optional.of(courseEntity)); - when(courseUserRepository.findById(ArgumentMatchers.any(CourseUserId.class))).thenReturn( - Optional.of(new CourseUserEntity(1, 1, CourseRelation.course_admin))); - when(groupClusterRepository.findById(projectJson.getGroupClusterId())).thenReturn( - Optional.of(new GroupClusterEntity(1L, 20, "Testcluster", 10))); - when(projectRepository.save(ArgumentMatchers.any(ProjectEntity.class))).thenReturn( - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX)); - // Call controller method - ResponseEntity responseEntity = projectController.createProject(courseId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - } - - - @Test - public void testCreateProjectShouldFailReasonCanNotGetCourse() { - // Mock data - long courseId = 1L; - ProjectJson projectJson = - new ProjectJson("Test Project", "Test Description", 1L, 1L, true, 100, OffsetDateTime.MAX); - ProjectEntity projectEntity = - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX); - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(1L); - when(auth.getUserEntity()).thenReturn(user); - - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - - CheckResult checkAcces = new CheckResult<>(HttpStatus.FORBIDDEN, "TestIsAdmin", - courseEntity); - - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProjectJson", null); - - // Mock repository behavior - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - - when(courseUtil.getCourseIfAdmin(courseId, user)).thenReturn(checkAcces); - when(projectUtil.checkProjectJson(projectJson, courseId)).thenReturn(checkResult); - when(courseRepository.findById(courseId)).thenReturn(Optional.of(courseEntity)); - when(courseUserRepository.findById(ArgumentMatchers.any(CourseUserId.class))).thenReturn( - Optional.of(new CourseUserEntity(1, 1, CourseRelation.course_admin))); - when(groupClusterRepository.findById(projectJson.getGroupClusterId())).thenReturn( - Optional.of(new GroupClusterEntity(1L, 20, "Testcluster", 10))); - when(projectRepository.save(ArgumentMatchers.any(ProjectEntity.class))).thenReturn( - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX)); - // Call controller method - ResponseEntity responseEntity = projectController.createProject(courseId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); - } - - @Test - public void testCreateProjectShouldFailReasonCanNotGetProjectJson() { - // Mock data - long courseId = 1L; - ProjectJson projectJson = - new ProjectJson("Test Project", "Test Description", 1L, 1L, true, 100, OffsetDateTime.MAX); - ProjectEntity projectEntity = - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX); - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(1L); - when(auth.getUserEntity()).thenReturn(user); - - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - - CheckResult checkAcces = new CheckResult<>(HttpStatus.OK, "TestIsAdmin", - courseEntity); - - CheckResult checkResult = new CheckResult<>(HttpStatus.FORBIDDEN, "TestProjectJson", - null); - - // Mock repository behavior - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - - when(courseUtil.getCourseIfAdmin(courseId, user)).thenReturn(checkAcces); - when(projectUtil.checkProjectJson(projectJson, courseId)).thenReturn(checkResult); - when(courseRepository.findById(courseId)).thenReturn(Optional.of(courseEntity)); - when(courseUserRepository.findById(ArgumentMatchers.any(CourseUserId.class))).thenReturn( - Optional.of(new CourseUserEntity(1, 1, CourseRelation.course_admin))); - when(groupClusterRepository.findById(projectJson.getGroupClusterId())).thenReturn( - Optional.of(new GroupClusterEntity(1L, 20, "Testcluster", 10))); - when(projectRepository.save(ArgumentMatchers.any(ProjectEntity.class))).thenReturn( - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX)); - // Call controller method - ResponseEntity responseEntity = projectController.createProject(courseId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); - } - - @Test - public void testCreateProjectShouldFailReasonInternalServer1() { - // Mock data - long courseId = 1L; - ProjectJson projectJson = - new ProjectJson("Test Project", "Test Description", 1L, 1L, true, 100, OffsetDateTime.MAX); - ProjectEntity projectEntity = - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX); - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(1L); - when(auth.getUserEntity()).thenReturn(user); - - CheckResult checkResult = new CheckResult<>(HttpStatus.FORBIDDEN, "TestProjectJson", - null); - - // Mock repository behavior - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - - when(projectUtil.checkProjectJson(projectJson, courseId)).thenReturn(checkResult); - when(courseUserRepository.findById(ArgumentMatchers.any(CourseUserId.class))).thenReturn( - Optional.of(new CourseUserEntity(1, 1, CourseRelation.course_admin))); - when(groupClusterRepository.findById(projectJson.getGroupClusterId())).thenReturn( - Optional.of(new GroupClusterEntity(1L, 20, "Testcluster", 10))); - when(projectRepository.save(ArgumentMatchers.any(ProjectEntity.class))).thenReturn( - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX)); - // Call controller method - ResponseEntity responseEntity = projectController.createProject(courseId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, responseEntity.getStatusCode()); - } - - - @Test - public void testCreateProjectShouldFailReasonInternalServer2() { - // Mock data - long courseId = 1L; - ProjectJson projectJson = - new ProjectJson("Test Project", "Test Description", null, 1L, true, 100, - OffsetDateTime.MAX); - ProjectEntity projectEntity = - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX); - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(1L); - when(auth.getUserEntity()).thenReturn(user); - - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - - CheckResult checkAcces = new CheckResult<>(HttpStatus.OK, "TestIsAdmin", - courseEntity); - - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProjectJson", - null); - - // Mock repository behavior - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - when(courseUtil.getCourseIfAdmin(courseId, user)).thenReturn(checkAcces); - when(projectUtil.checkProjectJson(projectJson, courseId)).thenReturn(checkResult); - when(courseRepository.findById(courseId)).thenReturn(Optional.of(courseEntity)); - when(courseUserRepository.findById(ArgumentMatchers.any(CourseUserId.class))).thenReturn( - Optional.of(new CourseUserEntity(1, 1, CourseRelation.course_admin))); - when(groupClusterRepository.findById(projectJson.getGroupClusterId())).thenReturn( - Optional.of(new GroupClusterEntity(1L, 20, "Testcluster", 10))); - when(projectRepository.save(ArgumentMatchers.any(ProjectEntity.class))).thenReturn( - new ProjectEntity(1, "Test Project", "Test Description", 1L, 1L, true, 100, - OffsetDateTime.MAX)); - - // Call controller method - ResponseEntity responseEntity = projectController.createProject(courseId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, responseEntity.getStatusCode()); - assertEquals("Internal error while creating project without group, contact an administrator", - responseEntity.getBody()); - } - - - @Test - void testPutProjectByIdShouldUpdateProject() { - // Mock data - long projectId = 1L; - long userId = 1L; - long courseId = 1L; - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(userId); - ProjectEntity projectEntity = new ProjectEntity(1, "Test Project", "old description", 1L, 1L, - false, 100, OffsetDateTime.MAX); - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - ProjectJson projectJson = new ProjectJson("Test Project", "new description", 1L, 1L, true, 100, - OffsetDateTime.MAX); - - ProjectEntity newProjectEntity = new ProjectEntity(1, "Test Project", "new description", 1L, 1L, - true, 100, OffsetDateTime.MAX); - - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProject", - projectEntity); - CheckResult checkProject = new CheckResult<>(HttpStatus.OK, "TestProjectJson", null); - - // Mock behavior - when(auth.getUserEntity()).thenReturn(user); - when(projectUtil.getProjectIfAdmin(projectId, user)).thenReturn(checkResult); - when(projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId())).thenReturn( - checkProject); - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - when(courseRepository.findById(projectId)).thenReturn(Optional.of(courseEntity)); - when(entityToJsonConverter.projectEntityToProjectResponseJson(any(), any(), any())).thenReturn( - new ProjectResponseJson( - new CourseReferenceJson("TestCourse", ApiRoutes.COURSE_BASE_PATH + "/" + 1L, 1L, OffsetDateTime.now()), - OffsetDateTime.MAX, - "Test", 2L, "TestProject", "testUrl", "testUrl", 0, true, new ProjectProgressJson(0, 0), - 1L, 1L)); - // Call controller method - ResponseEntity responseEntity = projectController.putProjectById(projectId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - - - } - - @Test - void testPatchProjectByIdShouldUpdateProject() { - // Mock data - long projectId = 1L; - long userId = 1L; - long courseId = 1L; - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(userId); - ProjectEntity projectEntity = new ProjectEntity(1, "Test Project", "old description", 1L, 1L, - false, 100, OffsetDateTime.MAX); - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - ProjectJson projectJson = new ProjectJson("Test Project", "new description", null, 1L, true, - 100, - OffsetDateTime.MAX); - - ProjectEntity newProjectEntity = new ProjectEntity(1, "Test Project", "new description", 1L, 1L, - true, 100, OffsetDateTime.MAX); - - CheckResult checkResult = new CheckResult<>(HttpStatus.OK, "TestProject", - projectEntity); - CheckResult checkProject = new CheckResult<>(HttpStatus.OK, "TestProjectJson", null); - - // Mock behavior - when(auth.getUserEntity()).thenReturn(user); - when(projectUtil.getProjectIfAdmin(projectId, user)).thenReturn(checkResult); - when(projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId())).thenReturn( - checkProject); - when(projectRepository.save(projectEntity)).thenReturn(projectEntity); - when(courseRepository.findById(projectId)).thenReturn(Optional.of(courseEntity)); - when(entityToJsonConverter.projectEntityToProjectResponseJson(any(), any(), any())).thenReturn( - new ProjectResponseJson( - new CourseReferenceJson("TestCourse", ApiRoutes.COURSE_BASE_PATH + "/" + 1L, 1L, OffsetDateTime.now()), - OffsetDateTime.MAX, - "Test", 2L, "TestProject", "testUrl", "testUrl", 0, true, new ProjectProgressJson(0, 0), - 1L, 1L)); - // Call controller method - ResponseEntity responseEntity = projectController.patchProjectById(projectId, projectJson, - auth); - - // Verify response - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); } @Test - void testDeleteProjectByIdShouldDeleteProject() { - // Mock data - long projectId = 1L; - long userId = 1L; - long courseId = 1L; - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(userId); - ProjectEntity projectEntity = new ProjectEntity(1, "Test Project", "old description", 1L, 1L, - false, 100, OffsetDateTime.MAX); - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - - CheckResult projectCheck = new CheckResult<>(HttpStatus.OK, "TestProject", - projectEntity); - CheckResult deleteResult = new CheckResult<>(HttpStatus.OK, "TestDelete", null); - // Mock behavior - when(auth.getUserEntity()).thenReturn(user); - when(projectUtil.getProjectIfAdmin(projectId, user)).thenReturn(projectCheck); - when(commonDatabaseActions.deleteProject(projectId)).thenReturn(deleteResult); - - // Call controller method - ResponseEntity responseEntity = projectController.deleteProjectById(projectId, auth); - - // Verify response - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + void testGetProjects() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH; + List projectEntities = List.of(projectEntity, projectEntity2); + ProjectResponseJsonWithStatus projectJsonWithStatus = new ProjectResponseJsonWithStatus( + projectResponseJson2, + "completed" + ); + when(courseUtil.getCourseIfUserInCourse(courseEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.creator)) + ); + when(courseUtil.getCourseIfUserInCourse(courseEntity2.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity2, CourseRelation.enrolled)) + ); + when(projectRepository.findProjectsByUserId(getMockUser().getId())).thenReturn(projectEntities); + when(entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, getMockUser())) + .thenReturn(projectResponseJson); + when(entityToJsonConverter.projectEntityToProjectResponseJsonWithStatus(projectEntity2, courseEntity2, getMockUser())) + .thenReturn(projectJsonWithStatus); + + /* Returns the user's projects */ + UserProjectsJson userProjectsJson = new UserProjectsJson( + List.of(projectJsonWithStatus), + List.of(projectResponseJson) + ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(userProjectsJson))); + + /* If project is visible and role enrolled, don't return it */ + projectEntity2.setVisible(false); + userProjectsJson = new UserProjectsJson( + Collections.emptyList(), + List.of(projectResponseJson) + ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(userProjectsJson))); + + /* If a coursecheck fails, return corresponding status */ + when(courseUtil.getCourseIfUserInCourse(courseEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); } @Test - void testDeleteProjectByIdShouldFailReasonCanNotGetProject() { - // Mock data - long projectId = 1L; - long userId = 1L; - long courseId = 1L; - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(userId); - ProjectEntity projectEntity = new ProjectEntity(1, "Test Project", "old description", 1L, 1L, - false, 100, OffsetDateTime.MAX); - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - - CheckResult projectCheck = new CheckResult<>(HttpStatus.FORBIDDEN, "TestProject", - projectEntity); - CheckResult deleteResult = new CheckResult<>(HttpStatus.OK, "TestDelete", null); - // Mock behavior - when(auth.getUserEntity()).thenReturn(user); - when(projectUtil.getProjectIfAdmin(projectId, user)).thenReturn(projectCheck); - when(commonDatabaseActions.deleteProject(projectId)).thenReturn(deleteResult); - - // Call controller method - ResponseEntity responseEntity = projectController.deleteProjectById(projectId, auth); - - // Verify response - assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); + void testGetProject() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId(); + + /* If user can get project, return project */ + when(projectUtil.canGetProject(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(courseUtil.getCourseIfUserInCourse(projectEntity.getCourseId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.enrolled)) + ); + when(entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, getMockUser())) + .thenReturn(projectResponseJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(projectResponseJson))); + + /* If user is enrolled and project not visible, return forbidden */ + projectEntity.setVisible(false); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isNotFound()); + + /* If user is not enrolled and project not visible, return project */ + when(courseUtil.getCourseIfUserInCourse(projectEntity.getCourseId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, CourseRelation.course_admin)) + ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(projectResponseJson))); + + /* If user can't get project, return corresponding status */ + when(projectUtil.canGetProject(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); } @Test - void testGetGroupsOfProjectShouldReturnGroups() { - // Mock data - long projectId = 1L; - long userId = 1L; - long courseId = 1L; - long groupId = 1L; - Auth auth = mock(Auth.class); - UserEntity user = new UserEntity(); - user.setId(userId); - ProjectEntity projectEntity = new ProjectEntity(1, "Test Project", "old description", 1L, 1L, - false, 100, OffsetDateTime.MAX); - CourseEntity courseEntity = new CourseEntity(); - courseEntity.setId(courseId); - List groupIds = new ArrayList<>(); - groupIds.add(groupId); - List groups = new ArrayList<>(); - GroupEntity groupEntity = new GroupEntity(); - groupEntity.setId(groupId); - groups.add(groupEntity); - - CheckResult projectCheck = new CheckResult<>(HttpStatus.OK, "TestProject", - projectEntity); - CheckResult> groupCheck = new CheckResult<>(HttpStatus.OK, "TestGroups", - groups); - // Mock behavior - when(auth.getUserEntity()).thenReturn(user); - when(projectUtil.canGetProject(projectId, user)).thenReturn(projectCheck); - when(clusterUtil.isIndividualCluster(projectEntity.getGroupClusterId())).thenReturn(false); - when(projectRepository.findGroupIdsByProjectId(projectId)).thenReturn(groupIds); - when(grouprRepository.findById(groupId)).thenReturn(Optional.of(groupEntity)); - // Call controller method - ResponseEntity responseEntity = projectController.getGroupsOfProject(projectId, auth); - - // Verify response - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + public void testCreateProject() throws Exception { + String url = ApiRoutes.COURSE_BASE_PATH + "/" + courseEntity.getId() + "/projects"; + String request = "{\n" + + " \"name\": \"" + projectEntity.getName() + "\",\n" + + " \"description\": \"" + projectEntity.getDescription() + "\",\n" + + " \"groupClusterId\": " + projectEntity.getGroupClusterId() + ",\n" + + " \"visible\": " + projectEntity.isVisible() + ",\n" + + " \"maxScore\": " + projectEntity.getMaxScore() + ",\n" + + " \"deadline\": \"" + projectEntity.getDeadline() + "\"\n" + + "}"; + + /* If all checks succeed, create course */ + when(courseUtil.getCourseIfAdmin(courseEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", courseEntity) + ); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals(projectEntity.getName() ) + && json.getDescription().equals(projectEntity.getDescription()) + && json.getGroupClusterId().equals(projectEntity.getGroupClusterId()) + && json.isVisible().equals(projectEntity.isVisible()) + && json.getMaxScore().equals(projectEntity.getMaxScore()) + && json.getDeadline().toInstant().equals(projectEntity.getDeadline().toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(projectRepository.save(any())).thenReturn(projectEntity); + when(entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, + getMockUser())) + .thenReturn(projectResponseJson); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(projectResponseJson))); + verify(projectRepository).save(argThat( + project -> project.getName().equals(projectEntity.getName()) + && project.getDescription().equals(projectEntity.getDescription()) + && project.getGroupClusterId() == projectEntity.getGroupClusterId() + && project.isVisible().equals(projectEntity.isVisible()) + && project.getMaxScore().equals(projectEntity.getMaxScore()) + && project.getDeadline().toInstant().equals(projectEntity.getDeadline().toInstant()) + )); + + /* If groupClusterId is not provided, use invalid groupClusterId */ + reset(projectUtil); + request = "{\n" + + " \"name\": \"" + projectEntity.getName() + "\",\n" + + " \"description\": \"" + projectEntity.getDescription() + "\",\n" + + " \"visible\": " + projectEntity.isVisible() + ",\n" + + " \"maxScore\": " + projectEntity.getMaxScore() + ",\n" + + " \"deadline\": \"" + projectEntity.getDeadline() + "\"\n" + + "}"; + GroupClusterEntity individualClusterEntity = new GroupClusterEntity(courseEntity.getId(), 2, "Individual", 1); + + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.of(individualClusterEntity)); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals(projectEntity.getName() ) + && json.getDescription().equals(projectEntity.getDescription()) + && json.getGroupClusterId().equals(individualClusterEntity.getId()) + && json.isVisible().equals(projectEntity.isVisible()) + && json.getMaxScore().equals(projectEntity.getMaxScore()) + && json.getDeadline().toInstant().equals(projectEntity.getDeadline().toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + verify(projectRepository).save(argThat( + project -> project.getName().equals(projectEntity.getName()) + && project.getDescription().equals(projectEntity.getDescription()) + && project.getGroupClusterId() == individualClusterEntity.getId() + && project.isVisible().equals(projectEntity.isVisible()) + && project.getMaxScore().equals(projectEntity.getMaxScore()) + && project.getDeadline().toInstant().equals(projectEntity.getDeadline().toInstant()) + )); + + /* If unexpected error occurs, return internal server error */ + doThrow(new RuntimeException()).when(projectRepository).save(any()); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); + + /* If project json is invalid, return corresponding status */ + reset(projectUtil); + when(projectUtil.checkProjectJson(any(), anyLong())).thenReturn( + new CheckResult<>(HttpStatus.BAD_REQUEST, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + + /* If no individual cluster is found, return internal server error */ + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn(Optional.empty()); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); + + /* If user no access to course, return corresponding status code */ + when(courseUtil.getCourseIfAdmin(courseEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.post(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); } From 7bae207396822429d199f7ccf8a1a3561d1c239a Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 15:05:26 +0200 Subject: [PATCH 12/40] projectcontrollertest upgrade finished --- .../controllers/ProjectControllerTest.java | 357 ++++++++++++++++++ 1 file changed, 357 insertions(+) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java index 0bd9693f..e95b0b5b 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ProjectControllerTest.java @@ -4,6 +4,7 @@ import com.ugent.pidgeon.CustomObjectMapper; import com.ugent.pidgeon.model.ProjectResponseJson; import com.ugent.pidgeon.model.json.CourseReferenceJson; +import com.ugent.pidgeon.model.json.GroupJson; import com.ugent.pidgeon.model.json.ProjectProgressJson; import com.ugent.pidgeon.model.json.ProjectResponseJsonWithStatus; import com.ugent.pidgeon.model.json.UserProjectsJson; @@ -33,6 +34,7 @@ import java.util.List; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -229,10 +231,19 @@ void testGetProject() throws Exception { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(projectResponseJson))); + /* If get course with relation check fails, return correpsonding status */ + when(courseUtil.getCourseIfUserInCourse(projectEntity.getCourseId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); + /* If user can't get project, return corresponding status */ when(projectUtil.canGetProject(projectEntity.getId(), getMockUser())).thenReturn( new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); } @Test @@ -346,5 +357,351 @@ public void testCreateProject() throws Exception { .andExpect(status().isIAmATeapot()); } + @Test + void testPutProjectById() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId(); + OffsetDateTime newDeadline = OffsetDateTime.now().plusDays(1); + String request = "{\n" + + " \"name\": \"" + "UpdatedName" + "\",\n" + + " \"description\": \"" + "UpdatedDescription" + "\",\n" + + " \"groupClusterId\": " + groupClusterId * 4 + ",\n" + + " \"visible\": " + false + ",\n" + + " \"maxScore\": " + (projectEntity.getMaxScore() + 33) + ",\n" + + " \"deadline\": \"" + newDeadline + "\"\n" + + "}"; + String orginalName = projectEntity.getName(); + String orginalDescription = projectEntity.getDescription(); + long orginalGroupClusterId = projectEntity.getGroupClusterId(); + boolean orginalVisible = projectEntity.isVisible(); + int orginalMaxScore = projectEntity.getMaxScore(); + OffsetDateTime orginalDeadline = projectEntity.getDeadline(); + ProjectResponseJson updatedJson = new ProjectResponseJson( + new CourseReferenceJson(courseEntity.getName(), "course1URL", courseEntity.getId(), null), + newDeadline, + "UpdatedName", + projectEntity.getId(), + "UpdatedDescription", + "submissionUrl", + "testUrl", + projectEntity.getMaxScore() + 33, + false, + new ProjectProgressJson(0, 0), + 1L, + groupClusterId * 4 + ); + /* If all checks pass, update and return the project */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals("UpdatedName") + && json.getDescription().equals("UpdatedDescription") + && json.getGroupClusterId().equals(groupClusterId * 4) + && json.isVisible().equals(false) + && json.getMaxScore().equals(projectEntity.getMaxScore() + 33) + && json.getDeadline().toInstant().equals(newDeadline.toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(projectRepository.save(any())).thenReturn(projectEntity); + when(courseRepository.findById(courseEntity.getId())).thenReturn(Optional.of(courseEntity)); + when(entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, + getMockUser())) + .thenReturn(updatedJson); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updatedJson))); + assertEquals(projectEntity.getName(), "UpdatedName"); + assertEquals(projectEntity.getDescription(), "UpdatedDescription"); + assertEquals(projectEntity.getGroupClusterId(), groupClusterId * 4); + assertEquals(projectEntity.isVisible(), false); + assertEquals(projectEntity.getMaxScore(), orginalMaxScore + 33); + assertEquals(projectEntity.getDeadline().toInstant(), newDeadline.toInstant()); + verify(projectRepository, times(1)).save(projectEntity); + projectEntity.setName(orginalName); + projectEntity.setDescription(orginalDescription); + projectEntity.setGroupClusterId(orginalGroupClusterId); + projectEntity.setVisible(orginalVisible); + projectEntity.setMaxScore(orginalMaxScore); + projectEntity.setDeadline(orginalDeadline); + + /* If groupClusterId is not provided, use invalid groupClusterId */ + reset(projectUtil); + request = "{\n" + + " \"name\": \"" + "UpdatedName" + "\",\n" + + " \"description\": \"" + "UpdatedDescription" + "\",\n" + + " \"visible\": " + false + ",\n" + + " \"maxScore\": " + (projectEntity.getMaxScore() + 33) + ",\n" + + " \"deadline\": \"" + newDeadline + "\"\n" + + "}"; + GroupClusterEntity individualClusterEntity = new GroupClusterEntity(courseEntity.getId(), 2, "Individual", 1); + + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.of(individualClusterEntity)); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals("UpdatedName") + && json.getDescription().equals("UpdatedDescription") + && json.getGroupClusterId().equals(individualClusterEntity.getId()) + && json.isVisible().equals(false) + && json.getMaxScore().equals(projectEntity.getMaxScore() + 33) + && json.getDeadline().toInstant().equals(newDeadline.toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + assertEquals(projectEntity.getName(), "UpdatedName"); + assertEquals(projectEntity.getDescription(), "UpdatedDescription"); + assertEquals(projectEntity.getGroupClusterId(), individualClusterEntity.getId()); + assertEquals(projectEntity.isVisible(), false); + assertEquals(projectEntity.getMaxScore(), orginalMaxScore + 33); + assertEquals(projectEntity.getDeadline().toInstant(), newDeadline.toInstant()); + verify(projectRepository, times(2)).save(projectEntity); + projectEntity.setGroupClusterId(orginalGroupClusterId); + + /* If project json is invalid, return corresponding status */ + reset(projectUtil); + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(projectUtil.checkProjectJson(any(), anyLong())).thenReturn( + new CheckResult<>(HttpStatus.BAD_REQUEST, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + + /* If individual cluster is not found, return internal server error */ + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn(Optional.empty()); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); + + /* If user has no acces to project, return corresponding status */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + } + + @Test // Same as above but patch instead of put + void testPatchProjectById() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId(); + OffsetDateTime newDeadline = OffsetDateTime.now().plusDays(1); + String request = "{\n" + + " \"name\": \"" + "UpdatedName" + "\",\n" + + " \"description\": \"" + "UpdatedDescription" + "\",\n" + + " \"groupClusterId\": " + groupClusterId * 4 + ",\n" + + " \"visible\": " + false + ",\n" + + " \"maxScore\": " + (projectEntity.getMaxScore() + 33) + ",\n" + + " \"deadline\": \"" + newDeadline + "\"\n" + + "}"; + String orginalName = projectEntity.getName(); + String orginalDescription = projectEntity.getDescription(); + long orginalGroupClusterId = projectEntity.getGroupClusterId(); + boolean orginalVisible = projectEntity.isVisible(); + int orginalMaxScore = projectEntity.getMaxScore(); + OffsetDateTime orginalDeadline = projectEntity.getDeadline(); + ProjectResponseJson updatedJson = new ProjectResponseJson( + new CourseReferenceJson(courseEntity.getName(), "course1URL", courseEntity.getId(), null), + newDeadline, + "UpdatedName", + projectEntity.getId(), + "UpdatedDescription", + "submissionUrl", + "testUrl", + projectEntity.getMaxScore() + 33, + false, + new ProjectProgressJson(0, 0), + 1L, + groupClusterId * 4 + ); + /* If all checks pass, update and return the project */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals("UpdatedName") + && json.getDescription().equals("UpdatedDescription") + && json.getGroupClusterId().equals(groupClusterId * 4) + && json.isVisible().equals(false) + && json.getMaxScore().equals(projectEntity.getMaxScore() + 33) + && json.getDeadline().toInstant().equals(newDeadline.toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(projectRepository.save(any())).thenReturn(projectEntity); + when(courseRepository.findById(courseEntity.getId())).thenReturn(Optional.of(courseEntity)); + when(entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, + getMockUser())) + .thenReturn(updatedJson); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updatedJson))); + assertEquals(projectEntity.getName(), "UpdatedName"); + assertEquals(projectEntity.getDescription(), "UpdatedDescription"); + assertEquals(projectEntity.getGroupClusterId(), groupClusterId * 4); + assertEquals(projectEntity.isVisible(), false); + assertEquals(projectEntity.getMaxScore(), orginalMaxScore + 33); + assertEquals(projectEntity.getDeadline().toInstant(), newDeadline.toInstant()); + verify(projectRepository, times(1)).save(projectEntity); + projectEntity.setName(orginalName); + projectEntity.setDescription(orginalDescription); + projectEntity.setGroupClusterId(orginalGroupClusterId); + projectEntity.setVisible(orginalVisible); + projectEntity.setMaxScore(orginalMaxScore); + projectEntity.setDeadline(orginalDeadline); + + /* If project json is invalid, return corresponding status */ + reset(projectUtil); + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(projectUtil.checkProjectJson(any(), anyLong())).thenReturn( + new CheckResult<>(HttpStatus.BAD_REQUEST, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isBadRequest()); + + /* Only update the fields that are provided */ + reset(projectUtil); + request = "{\n" + + " \"name\": \"" + "UpdatedName" + "\",\n" + + " \"description\": \"" + "UpdatedDescription" + "\"\n" + + "}"; + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals("UpdatedName") + && json.getDescription().equals("UpdatedDescription") + && json.getGroupClusterId().equals(orginalGroupClusterId) + && json.isVisible().equals(orginalVisible) + && json.getMaxScore().equals(orginalMaxScore) + && json.getDeadline().toInstant().equals(orginalDeadline.toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + assertEquals(projectEntity.getName(), "UpdatedName"); + assertEquals(projectEntity.getDescription(), "UpdatedDescription"); + assertEquals(projectEntity.getGroupClusterId(), orginalGroupClusterId); + assertEquals(projectEntity.isVisible(), orginalVisible); + assertEquals(projectEntity.getMaxScore(), orginalMaxScore); + assertEquals(projectEntity.getDeadline().toInstant(), orginalDeadline.toInstant()); + verify(projectRepository, times(2)).save(projectEntity); + projectEntity.setName(orginalName); + projectEntity.setDescription(orginalDescription); + + /* Different fields not present */ + reset(projectUtil); + request = "{\n" + + " \"deadline\": \"" + newDeadline + "\"\n" + + "}"; + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(projectUtil.checkProjectJson(argThat( + json -> json.getName().equals(orginalName) + && json.getDescription().equals(orginalDescription) + && json.getGroupClusterId().equals(orginalGroupClusterId) + && json.isVisible().equals(orginalVisible) + && json.getMaxScore().equals(orginalMaxScore) + && json.getDeadline().toInstant().equals(newDeadline.toInstant()) + ), eq(courseEntity.getId()))).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + assertEquals(projectEntity.getName(), orginalName); + assertEquals(projectEntity.getDescription(), orginalDescription); + assertEquals(projectEntity.getGroupClusterId(), orginalGroupClusterId); + assertEquals(projectEntity.isVisible(), orginalVisible); + assertEquals(projectEntity.getMaxScore(), orginalMaxScore); + assertEquals(projectEntity.getDeadline().toInstant(), newDeadline.toInstant()); + verify(projectRepository, times(3)).save(projectEntity); + projectEntity.setDeadline(orginalDeadline); + + /* If user has no acces to project, return corresponding status */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + } + + @Test + void getGroupsOfProject() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId() + "/groups"; + GroupEntity groupEntity = new GroupEntity("groupName", 1L); + long groupId = 83L; + groupEntity.setId(groupId); + GroupJson groupJson = new GroupJson(44, groupEntity.getId(), groupEntity.getName(), "groupClusterUrl"); + + /* If all checks pass, return groups */ + when(projectUtil.canGetProject(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(clusterUtil.isIndividualCluster(projectEntity.getGroupClusterId())).thenReturn(false); + when(projectRepository.findGroupIdsByProjectId(projectEntity.getId())).thenReturn(List.of(groupId)); + when(grouprRepository.findById(groupId)).thenReturn(Optional.of(groupEntity)); + when(entityToJsonConverter.groupEntityToJson(groupEntity)).thenReturn(groupJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(groupJson)))); + + /* If inidividual cluster return no content */ + when(clusterUtil.isIndividualCluster(projectEntity.getGroupClusterId())).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isNoContent()); + + /* If user has no acces to project, return corresponding status */ + when(projectUtil.canGetProject(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); + } + + @Test + void testDeleteProjectById() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId(); + + /* If all checks pass, delete project */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.OK, "", projectEntity) + ); + when(commonDatabaseActions.deleteProject(projectEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isOk()); + + /* If deleting project fails, return corresponding status */ + when(commonDatabaseActions.deleteProject(projectEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isInternalServerError()); + + /* If user has no acces to project, return corresponding status */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), getMockUser())).thenReturn( + new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null) + ); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isIAmATeapot()); + } } \ No newline at end of file From 8830fd3d611e9171391d8738a6c4903cb733a099 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 20:20:43 +0200 Subject: [PATCH 13/40] UserControllertests --- .../pidgeon/controllers/UserController.java | 6 +- .../controllers/UserControllerTest.java | 378 ++++++++++++++++-- 2 files changed, 348 insertions(+), 36 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java index 7ec5e23f..7351275b 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java @@ -39,10 +39,10 @@ public class UserController { * @return user object */ @GetMapping(ApiRoutes.USERS_BASE_PATH + "/{userid}") - @Roles({UserRole.student}) + @Roles({UserRole.student, UserRole.teacher}) public ResponseEntity getUserById(@PathVariable("userid") Long userid,Auth auth) { UserEntity requester = auth.getUserEntity(); - if (requester.getId() != userid) { + if (requester.getId() != userid && requester.getRole() != UserRole.admin) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You do not have access to this user"); } @@ -82,7 +82,6 @@ public ResponseEntity getUsersByNameOrSurname( return ResponseEntity.status(HttpStatus.OK).body(new ArrayList<>()); } - UserEntity user = null; if (name == null) name = ""; if (surname == null) surname = ""; @@ -164,7 +163,6 @@ public ResponseEntity patchUserById(@PathVariable("userid") Long userid, @Req userUpdateJson.setEmail(user.getEmail()); } - Logger.getGlobal().info(userUpdateJson.getRole()); if (userUpdateJson.getRole() == null) { userUpdateJson.setRole(user.getRole().toString()); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/UserControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/UserControllerTest.java index 9f848b42..a60a6cf2 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/UserControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/UserControllerTest.java @@ -1,14 +1,25 @@ package com.ugent.pidgeon.controllers; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; +import com.ugent.pidgeon.model.json.UserJson; import com.ugent.pidgeon.postgre.models.UserEntity; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.util.CheckResult; import com.ugent.pidgeon.util.UserUtil; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,81 +42,384 @@ public class UserControllerTest extends ControllerTest { private UserController userController; private UserEntity userEntity; + private UserJson userJson; + private UserJson mockUserJson; + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(userController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { - request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); - return request; - })) - .build(); - userEntity = new UserEntity("name", "surname", "email", UserRole.student, "azureId"); + setUpController(userController); + userEntity = new UserEntity("Bob", "Testman", "email", UserRole.student, "azureId"); + userEntity.setId(74L); + mockUserJson = new UserJson(getMockUser()); + userJson = new UserJson(userEntity); } @Test public void testGetUserById() throws Exception { + String url = ApiRoutes.USERS_BASE_PATH + "/" + getMockUser().getId(); + String urlSomeoneElse = ApiRoutes.USERS_BASE_PATH + "/" + userEntity.getId(); + /* Can get ur own user information */ + when(userUtil.getUserIfExists(getMockUser().getId())).thenReturn(getMockUser()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(mockUserJson))); + + /* Can't get someone else's user information */ when(userUtil.getUserIfExists(anyLong())).thenReturn(userEntity); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/1")) - .andExpect(status().isOk()); + mockMvc.perform(MockMvcRequestBuilders.get(urlSomeoneElse)) + .andExpect(status().isForbidden()); + + /* Admin can get someone else's user information */ + getMockUser().setRole(UserRole.admin); + mockMvc.perform(MockMvcRequestBuilders.get(urlSomeoneElse)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(userJson))); + /* If user not found return 404 */ when(userUtil.getUserIfExists(anyLong())).thenReturn(null); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/1")) + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/999")) .andExpect(status().isNotFound()); + } + + private String createGetUsersUrl(String name, String surname, String email) { + String start = ApiRoutes.USERS_BASE_PATH; + boolean first = true; + if (name != null) { + start += "?name=" + name; + first = false; + } + if (surname != null) { + if (first) { + start += "?surname=" + surname; + first = false; + } else { + start += "&surname=" + surname; + } + } + if (email != null) { + if (first) { + start += "?email=" + email; + first = false; + } else { + start += "&email=" + email; + } + } + return start; + } - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/2")) + @Test + public void testGetUsersByNameOrSurname() throws Exception { + setMockUserRoles(UserRole.admin); + /* If email is present in the url, user gets returned based on email */ + String url = createGetUsersUrl(null, null, "email"); + when(userRepository.findByEmail("email")).thenReturn(userEntity); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If email and name are present they need to match case insensitive */ + url = createGetUsersUrl("name", null, "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + url = createGetUsersUrl(userEntity.getName(), null, "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + url = createGetUsersUrl(userEntity.getName().toUpperCase(), null, "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If email and surname are present they need to match case insensitive */ + url = createGetUsersUrl(null, "surname", "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + url = createGetUsersUrl(null, userEntity.getSurname(), "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If all three are present they need to match case insensitive */ + url = createGetUsersUrl("name", "surname", "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + url = createGetUsersUrl(userEntity.getName(), userEntity.getSurname(), "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + url = createGetUsersUrl(userEntity.getName().toUpperCase(), userEntity.getSurname().toUpperCase(), "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + url = createGetUsersUrl(null, userEntity.getSurname().toUpperCase(), "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If no user with email return empty list */ + when(userRepository.findByEmail("email")).thenReturn(null); + url = createGetUsersUrl(null, null, "email"); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + /* If email isn't present in the url, users get returned based on name and surname */ + url = createGetUsersUrl("name", "surname", null); + when(userRepository.findByName("name", "surname")).thenReturn(List.of(userEntity)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If both name and surname are less than 3 characters, return empty list */ + url = createGetUsersUrl("na", "su", null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + /* If one of the two is long enough, return the user */ + url = createGetUsersUrl("name", "su", null); + when(userRepository.findByName("name", "su")).thenReturn(List.of(userEntity)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + /* If only name, return based on name, needs to be longer then 3 characters */ + url = createGetUsersUrl("name", null, null); + when(userRepository.findByName("name", "")).thenReturn(List.of(userEntity)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + url = createGetUsersUrl("na", null, null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + /* If only surname, return based on surname, needs to be longer then 3 characters */ + url = createGetUsersUrl(null, "surname", null); + when(userRepository.findByName("", "surname")).thenReturn(List.of(userEntity)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(userJson)))); + + url = createGetUsersUrl(null, "su", null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + /* Only admin can use this route */ + setMockUserRoles(UserRole.student); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH)) + .andExpect(status().isForbidden()); + + setMockUserRoles(UserRole.teacher); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH)) .andExpect(status().isForbidden()); } @Test - public void testGetUserByAzureId() throws Exception { + public void testGetLoggedInUser() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.LOGGEDIN_USER_PATH)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(mockUserJson))); } @Test public void testUpdateUserById() throws Exception { - String request = "{\"name\":\"John\",\"surname\":\"Doe\",\"email\":\"john@example.com\",\"role\":\"admin\"}"; - when(userUtil.checkForUserUpdateJson(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.OK, "", userEntity)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.USERS_BASE_PATH + "/1") + setMockUserRoles(UserRole.admin); + String url = ApiRoutes.USERS_BASE_PATH + "/" + userEntity.getId(); + String request = "{\"name\":\"John\",\"surname\":\"Doe\",\"email\":\"john@example.com\",\"role\":\"teacher\"}"; + UserEntity updateUserEntity = new UserEntity("John", "Doe", "john@example.com", UserRole.teacher, "azureId"); + updateUserEntity.setId(userEntity.getId()); + UserJson updatedUserJson = new UserJson(updateUserEntity); + + when(userUtil.checkForUserUpdateJson(eq(userEntity.getId()), argThat( + json -> json.getName().equals("John") && + json.getSurname().equals("Doe") && + json.getEmail().equals("john@example.com") && + json.getRoleAsEnum().equals(UserRole.teacher))) + ) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", userEntity)); + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isOk()); + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updatedUserJson))); + verify(userRepository, times(1)).save(userEntity); + assertEquals("John", userEntity.getName()); + assertEquals("Doe", userEntity.getSurname()); + assertEquals("john@example.com", userEntity.getEmail()); + assertEquals(UserRole.teacher, userEntity.getRole()); + + /* If updatecheck fails return corresponding status */ + reset(userUtil); + when(userUtil.checkForUserUpdateJson(anyLong(), any())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + + /* Only admin can update user */ + setMockUserRoles(UserRole.student); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isForbidden()); - when(userUtil.checkForUserUpdateJson(anyLong(), any())). - thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.USERS_BASE_PATH + "/1") + setMockUserRoles(UserRole.teacher); + mockMvc.perform(MockMvcRequestBuilders.put(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isBadRequest()); + .andExpect(status().isForbidden()); } @Test public void testPatchUserById() throws Exception { - String request = "{\"name\": null,\"surname\": null,\"email\": null,\"role\": null}"; - when(userUtil.getUserIfExists(anyLong())).thenReturn(userEntity); - when(userUtil.checkForUserUpdateJson(anyLong(), any())) + setMockUserRoles(UserRole.admin); + String url = ApiRoutes.USERS_BASE_PATH + "/" + userEntity.getId(); + String request = "{\"name\":\"John\",\"surname\":\"Doe\",\"email\":\"john@example.com\",\"role\":\"teacher\"}"; + UserEntity updateUserEntity = new UserEntity("John", "Doe", "john@example.com", UserRole.teacher, "azureId"); + updateUserEntity.setId(userEntity.getId()); + UserJson updatedUserJson = new UserJson(updateUserEntity); + String originalName = userEntity.getName(); + String originalSurname = userEntity.getSurname(); + String originalEmail = userEntity.getEmail(); + UserRole originalRole = userEntity.getRole(); + + /* If all fields are present, update them all */ + when(userUtil.getUserIfExists(userEntity.getId())).thenReturn(userEntity); + when(userUtil.checkForUserUpdateJson(eq(userEntity.getId()), argThat( + json -> json.getName().equals("John") && + json.getSurname().equals("Doe") && + json.getEmail().equals("john@example.com") && + json.getRoleAsEnum().equals(UserRole.teacher))) + ) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", userEntity)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(updatedUserJson))); + verify(userRepository, times(1)).save(userEntity); + assertEquals("John", userEntity.getName()); + assertEquals("Doe", userEntity.getSurname()); + assertEquals("john@example.com", userEntity.getEmail()); + assertEquals(UserRole.teacher, userEntity.getRole()); + userEntity.setName(originalName); + userEntity.setSurname(originalSurname); + userEntity.setEmail(originalEmail); + userEntity.setRole(originalRole); + + + /* If not all fields are present, update only the ones that are */ + request = "{\"name\":\"Tom\"}"; + reset(userUtil); + when(userUtil.getUserIfExists(userEntity.getId())).thenReturn(userEntity); + when(userUtil.checkForUserUpdateJson(eq(userEntity.getId()), argThat( + json -> json.getName().equals("Tom") && + json.getSurname().equals(userEntity.getSurname()) && + json.getEmail().equals(userEntity.getEmail()) && + json.getRoleAsEnum().equals(userEntity.getRole()))) + ) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", userEntity)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + + verify(userRepository, times(2)).save(userEntity); + assertEquals("Tom", userEntity.getName()); + assertEquals(originalSurname, userEntity.getSurname()); + assertEquals(originalEmail, userEntity.getEmail()); + assertEquals(originalRole, userEntity.getRole()); + userEntity.setName(originalName); + + request = "{\"surname\":\"Riddle\"}"; + reset(userUtil); + when(userUtil.getUserIfExists(userEntity.getId())).thenReturn(userEntity); + when(userUtil.checkForUserUpdateJson(eq(userEntity.getId()), argThat( + json -> json.getName().equals(userEntity.getName()) && + json.getSurname().equals("Riddle") && + json.getEmail().equals(userEntity.getEmail()) && + json.getRoleAsEnum().equals(userEntity.getRole()))) + ) .thenReturn(new CheckResult<>(HttpStatus.OK, "", userEntity)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.USERS_BASE_PATH + "/1") + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isOk()); + verify(userRepository, times(3)).save(userEntity); + assertEquals(originalName, userEntity.getName()); + assertEquals("Riddle", userEntity.getSurname()); + assertEquals(originalEmail, userEntity.getEmail()); + assertEquals(originalRole, userEntity.getRole()); + + /* If updatecheck fails return corresponding status */ + reset(userUtil); + when(userUtil.getUserIfExists(userEntity.getId())).thenReturn(userEntity); when(userUtil.checkForUserUpdateJson(anyLong(), any())) - .thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.USERS_BASE_PATH + "/1") + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) - .andExpect(status().isBadRequest()); + .andExpect(status().isIAmATeapot()); - when(userUtil.getUserIfExists(anyLong())).thenReturn(null); - mockMvc.perform(MockMvcRequestBuilders.patch(ApiRoutes.USERS_BASE_PATH + "/1") + /* If user doesn't exist return 404 */ + when(userUtil.getUserIfExists(userEntity.getId())).thenReturn(null); + mockMvc.perform(MockMvcRequestBuilders.patch(url) .contentType(MediaType.APPLICATION_JSON) .content(request)) .andExpect(status().isNotFound()); + + /* Only admin can update user */ + setMockUserRoles(UserRole.student); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isForbidden()); + + setMockUserRoles(UserRole.teacher); + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isForbidden()); } + } From 0c38c98d2546c944b49d757053db5ab5f477e06f Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 20:21:06 +0200 Subject: [PATCH 14/40] Added a transactional to file submission --- .../com/ugent/pidgeon/controllers/SubmissionController.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index a943ce41..e3ea69e8 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -17,6 +17,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -158,6 +159,7 @@ public ResponseEntity getSubmissions(@PathVariable("projectid") long projecti * @ApiPath /api/projects/{projectid}/submit */ @PostMapping(ApiRoutes.PROJECT_BASE_PATH + "/{projectid}/submit") + @Transactional //Route to submit a file, it accepts a multiform with the file and submissionTime @Roles({UserRole.teacher, UserRole.student}) public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @PathVariable("projectid") long projectid, Auth auth) { @@ -225,7 +227,8 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P return ResponseEntity.ok(entityToJsonConverter.getSubmissionJson(submissionEntity)); } catch (Exception e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving file: " + e.getMessage()); + throw new RuntimeException(e); + //return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving file: " + e.getMessage()); } } From d0b38a3385f8fd11b8731e52f8876f28f9d380ca Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 21:45:23 +0200 Subject: [PATCH 15/40] ClusterUtilTests --- .../ugent/pidgeon/util/ClusterUtilTest.java | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java new file mode 100644 index 00000000..3f7d690d --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java @@ -0,0 +1,254 @@ +package com.ugent.pidgeon.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import com.ugent.pidgeon.model.json.GroupClusterCreateJson; +import com.ugent.pidgeon.model.json.GroupClusterUpdateJson; +import com.ugent.pidgeon.postgre.models.GroupClusterEntity; +import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.UserRole; +import com.ugent.pidgeon.postgre.repository.GroupClusterRepository; +import java.util.Optional; +import org.hibernate.annotations.Check; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; + +@ExtendWith(MockitoExtension.class) +public class ClusterUtilTest { + + @Mock + private GroupClusterRepository groupClusterRepository; + @Mock + private CourseUtil courseUtil; + + @Spy + @InjectMocks + private ClusterUtil clusterUtil; + + private GroupClusterEntity clusterEntity; + + private UserEntity mockUser; + + @BeforeEach + public void setUp() { + clusterEntity = new GroupClusterEntity(1L, 20, "clustername", 5); + clusterEntity.setId(4L); + mockUser = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); + } + + @Test + void testIsIndividualCluster() { + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.of(clusterEntity)); + // Test if the cluster is an individual cluster + clusterEntity.setMaxSize(1); + assertTrue(clusterUtil.isIndividualCluster(clusterEntity)); + assertTrue(clusterUtil.isIndividualCluster(clusterEntity.getId())); + + + // Test if the cluster is not an individual cluster + clusterEntity.setMaxSize(2); + assertFalse(clusterUtil.isIndividualCluster(clusterEntity)); + assertFalse(clusterUtil.isIndividualCluster(clusterEntity.getId())); + + // Test if the cluster is null + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.empty()); + assertFalse(clusterUtil.isIndividualCluster(null)); + assertFalse(clusterUtil.isIndividualCluster(clusterEntity.getId())); + } + + @Test + void testCanDeleteCluster() { + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", clusterEntity)) + .when(clusterUtil) + .getGroupClusterEntityIfAdminAndNotIndividual(clusterEntity.getId(), mockUser); + + when(groupClusterRepository.usedInProject(clusterEntity.getId())).thenReturn(false); + + CheckResult result = clusterUtil.canDeleteCluster(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* usedInProject returns true */ + when(groupClusterRepository.usedInProject(clusterEntity.getId())).thenReturn(true); + result = clusterUtil.canDeleteCluster(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* getGroupClusterEntity fails */ + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(clusterEntity.getId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Group cluster does not exist", null)); + + result = clusterUtil.canDeleteCluster(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + } + + @Test + void testGetGroupClusterEntityIfNotIndividual() { + /* All checks succeed */ + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.of(clusterEntity)); + when(courseUtil.getCourseIfUserInCourse(clusterEntity.getCourseId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + doReturn(false).when(clusterUtil).isIndividualCluster(clusterEntity); + CheckResult result = + clusterUtil.getGroupClusterEntityIfNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(clusterEntity, result.getData()); + + /* Group cluster is individual cluster */ + doReturn(true).when(clusterUtil).isIndividualCluster(clusterEntity); + result = clusterUtil.getGroupClusterEntityIfNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Course check fails, return corresponding status */ + when(courseUtil.getCourseIfUserInCourse(clusterEntity.getCourseId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Course does not exist", null)); + result = clusterUtil.getGroupClusterEntityIfNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Group cluster does not exist */ + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.empty()); + result = clusterUtil.getGroupClusterEntityIfNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + } + + @Test + void testGetGroupClusterEntityIfAdminAndNotIndividual() { + /* All checks succeed */ + when(clusterUtil.getGroupClusterEntityIfNotIndividual(clusterEntity.getId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", clusterEntity)); + + when(courseUtil.getCourseIfAdmin(clusterEntity.getCourseId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + CheckResult result = + clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(clusterEntity, result.getData()); + + /* Course check fails, return corresponding status */ + when(courseUtil.getCourseIfAdmin(clusterEntity.getCourseId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + result = clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* getGroupClusterEntityIfNotIndividual fails */ + when(clusterUtil.getGroupClusterEntityIfNotIndividual(clusterEntity.getId(), mockUser)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + result = clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(clusterEntity.getId(), mockUser); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + } + + @Test + void testPartOfCourse() { + /* All checks succeed */ + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.of(clusterEntity)); + + CheckResult result = clusterUtil.partOfCourse(clusterEntity.getId(), clusterEntity.getCourseId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Group cluster not linked to course */ + result = clusterUtil.partOfCourse(clusterEntity.getId(), clusterEntity.getCourseId() + 1); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Group cluster does not exist */ + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.empty()); + result = clusterUtil.partOfCourse(clusterEntity.getId(), clusterEntity.getCourseId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + } + + @Test + void testGetClusterIfExists() { + /* All checks succeed */ + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.of(clusterEntity)); + + CheckResult result = clusterUtil.getClusterIfExists(clusterEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(clusterEntity, result.getData()); + + /* Group cluster does not exist */ + when(groupClusterRepository.findById(clusterEntity.getId())).thenReturn(Optional.empty()); + result = clusterUtil.getClusterIfExists(clusterEntity.getId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + } + + @Test + void testCheckGroupClusterUpdateJson() { + GroupClusterUpdateJson json = new GroupClusterUpdateJson(); + /* All checks succeed */ + json.setCapacity(5); + json.setName("clustername"); + CheckResult result = clusterUtil.checkGroupClusterUpdateJson(json); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Capacity is smaller than 1 */ + json.setCapacity(0); + result = clusterUtil.checkGroupClusterUpdateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Name is empty */ + json.setName(""); + result = clusterUtil.checkGroupClusterUpdateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Capacity is null */ + json.setCapacity(null); + result = clusterUtil.checkGroupClusterUpdateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Name is null */ + json.setCapacity(5); + json.setName(null); + result = clusterUtil.checkGroupClusterUpdateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + } + + @Test + void testCheckGroupClusterCreateJson() { + GroupClusterCreateJson json = new GroupClusterCreateJson("clustername", 5, 5); + /* All checks succeed */ + CheckResult result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* GroupCount is negative */ + json = new GroupClusterCreateJson("clustername", 5, -5); + result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Capacity is smaller than 1 */ + json = new GroupClusterCreateJson("clustername", 0, 5); + result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Name is empty */ + json = new GroupClusterCreateJson("", 5, 5); + result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Capacity is null */ + json = new GroupClusterCreateJson("clustername", null, 5); + result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Name is null */ + json = new GroupClusterCreateJson(null, 5, 5); + result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* GroupCount is null */ + json = new GroupClusterCreateJson("clustername", 5, null); + result = clusterUtil.checkGroupClusterCreateJson(json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + } + +} From 1088603a25eda32a9f75415b8ce11fd5007322cb Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 5 May 2024 23:57:49 +0200 Subject: [PATCH 16/40] courseUtil tests --- .../com/ugent/pidgeon/util/CourseUtil.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/CourseUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/CourseUtil.java index c73c16a1..8e1f2823 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/CourseUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/CourseUtil.java @@ -61,7 +61,8 @@ public CheckResult> getCourseIfUserInCourse(l if (courseUserEntity == null && !user.getRole().equals(UserRole.admin)) { return new CheckResult<>(HttpStatus.FORBIDDEN, "User is not part of the course", null); } - return new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, courseUserEntity.getRelation())); + CourseRelation relation = courseUserEntity != null ? courseUserEntity.getRelation() : CourseRelation.creator; + return new CheckResult<>(HttpStatus.OK, "", new Pair<>(courseEntity, relation)); } @@ -113,7 +114,7 @@ public CheckResult canUpdateUserInCourse(long courseId, Course return new CheckResult<>(HttpStatus.BAD_REQUEST, "User is already part of the course", null); } if (!userUtil.userExists(request.getUserId())) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "User does not exist", null); + return new CheckResult<>(HttpStatus.NOT_FOUND, "User does not exist", null); } } else { if (!courseMember) { @@ -121,17 +122,20 @@ public CheckResult canUpdateUserInCourse(long courseId, Course } } - if (user.getId() == request.getUserId()) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Cannot change your own relation with this course", null); + boolean isAdmin = user.getRole().equals(UserRole.admin); + + if (user.getId() == request.getUserId() && !isAdmin) { + return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot change your own relation with this course", null); } - if (request.getRelationAsEnum().equals(CourseRelation.creator)) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Cannot change the creator of the course", null); + + if (request.getRelationAsEnum().equals(CourseRelation.creator) && !isAdmin) { + return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot change the creator of the course", null); } - boolean isAdmin = user.getRole().equals(UserRole.admin); boolean isCreator = userRelation.equals(CourseRelation.creator); boolean creatingAdmin = request.getRelationAsEnum().equals(CourseRelation.course_admin); - if (creatingAdmin && !isAdmin && !isCreator) { + boolean downgradingAdmin = courseMember && courseUserEntity.getRelation().equals(CourseRelation.course_admin) && !creatingAdmin; + if ((creatingAdmin || downgradingAdmin) && !isAdmin && !isCreator) { return new CheckResult<>(HttpStatus.FORBIDDEN, "Only the course creator can create course admins", null); } @@ -152,10 +156,10 @@ public CheckResult canLeaveCourse(long courseId, UserEntity user CourseEntity course = courseCheck.getData().getFirst(); CourseRelation relation = courseCheck.getData().getSecond(); if (relation.equals(CourseRelation.creator)) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Cannot leave a course you created", null); + return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot leave a course you created", null); } if (course.getArchivedAt() != null) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Cannot leave an archived course", null); + return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot leave an archived course", null); } return new CheckResult<>(HttpStatus.OK, "", relation); } @@ -181,15 +185,20 @@ public CheckResult canDeleteUser(long courseId, long userId, Use CourseUserEntity courseUserEntity = courseUserRepository.findById(new CourseUserId(courseId, userId)).orElse(null); if (courseUserEntity == null) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "User is not part of the course", null); + return new CheckResult<>(HttpStatus.NOT_FOUND, "User is not part of the course", null); } if (user.getId() == userId) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Cannot delete yourself from the course", null); + return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot delete yourself from the course", null); } if (courseUserEntity.getRelation().equals(CourseRelation.creator)) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Cannot delete the creator of the course", null); + return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot delete the creator of the course", null); + } + + boolean isAdmin = user.getRole().equals(UserRole.admin); + if (courseUserEntity.getRelation().equals(CourseRelation.course_admin) && !userRelation.equals(CourseRelation.creator) && !isAdmin){ + return new CheckResult<>(HttpStatus.FORBIDDEN, "Only the creator can delete course admins", null); } return new CheckResult<>(HttpStatus.OK, "", courseUserEntity.getRelation()); @@ -260,7 +269,6 @@ public CheckResult checkCourseJson(CourseJson courseJson, UserEntity user, } } - if (courseJson.getName() == null || courseJson.getDescription() == null || courseJson.getYear() == null) { Logger.getGlobal().info(""+ courseJson.getYear()); return new CheckResult<>(HttpStatus.BAD_REQUEST, "name, description and year are required", null); From 0260207e11ebe4609b0d5dfd961a21b897c0a6bd Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 6 May 2024 10:54:06 +0200 Subject: [PATCH 17/40] Update CourseUtilTest.java --- .../ugent/pidgeon/util/CourseUtilTest.java | 441 +++++++++++++++--- 1 file changed, 387 insertions(+), 54 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/CourseUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/CourseUtilTest.java index b2f5ca09..089851cb 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/CourseUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/CourseUtilTest.java @@ -4,6 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import com.ugent.pidgeon.model.json.CourseJson; @@ -11,17 +14,20 @@ import com.ugent.pidgeon.model.json.UserIdJson; import com.ugent.pidgeon.postgre.models.CourseEntity; import com.ugent.pidgeon.postgre.models.CourseUserEntity; +import com.ugent.pidgeon.postgre.models.CourseUserId; import com.ugent.pidgeon.postgre.models.UserEntity; import com.ugent.pidgeon.postgre.models.types.CourseRelation; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.CourseRepository; import com.ugent.pidgeon.postgre.repository.CourseUserRepository; +import java.time.OffsetDateTime; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -35,6 +41,10 @@ public class CourseUtilTest { @Mock private CourseRepository courseRepository; + @Mock + private UserUtil userUtil; + + @Spy @InjectMocks private CourseUtil courseUtil; @@ -47,9 +57,9 @@ public class CourseUtilTest { @BeforeEach public void setUp() { user = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); - user.setId(1L); + user.setId(44L); course = new CourseEntity("name", "description",2024); - course.setId(1L); + course.setId(9L); course.setJoinKey("key"); cuEnrolled = new CourseUserEntity(1L, 1L, CourseRelation.enrolled); cuAdmin = new CourseUserEntity(1L, 2L, CourseRelation.course_admin); @@ -57,103 +67,426 @@ public void setUp() { } @Test - public void testGetCourseIfAdmin() throws Exception { - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); - when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuAdmin)); - CheckResult result = courseUtil.getCourseIfAdmin(1L, user); - assertEquals(HttpStatus.OK, result.getStatus()); - assertEquals(course, result.getData()); + public void testGetCourseIfAdmin() { + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair(course, CourseRelation.course_admin))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + + CheckResult check = courseUtil.getCourseIfAdmin(course.getId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(course, check.getData()); + /* User is not a course admin */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair(course, CourseRelation.enrolled))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.getCourseIfAdmin(course.getId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is not a course admin, but a platform admin */ + user.setRole(UserRole.admin); + check = courseUtil.getCourseIfAdmin(course.getId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* Get course fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.getCourseIfAdmin(course.getId(), user); + assertEquals(HttpStatus.I_AM_A_TEAPOT, check.getStatus()); + } + + @Test + public void testGetCourseIfUserInCourse() { + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", course)).when(courseUtil).getCourseIfExists(course.getId()); when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuEnrolled)); - result = courseUtil.getCourseIfAdmin(1L, user); - assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("User is not an admin of the course", result.getMessage()); + CheckResult> check = courseUtil.getCourseIfUserInCourse(course.getId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(course, check.getData().getFirst()); + assertEquals(CourseRelation.enrolled, check.getData().getSecond()); + + when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuAdmin)); + check = courseUtil.getCourseIfUserInCourse(course.getId(), user); + assertEquals(CourseRelation.course_admin, check.getData().getSecond()); + + when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuCreator)); + check = courseUtil.getCourseIfUserInCourse(course.getId(), user); + assertEquals(CourseRelation.creator, check.getData().getSecond()); + + /* User isn't in course */ + when(courseUserRepository.findById(any())).thenReturn(Optional.empty()); + check = courseUtil.getCourseIfUserInCourse(course.getId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User isn't in course but is admin */ + user.setRole(UserRole.admin); + check = courseUtil.getCourseIfUserInCourse(course.getId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* Get course fails */ + reset(courseUtil); + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(courseUtil).getCourseIfExists(course.getId()); + check = courseUtil.getCourseIfUserInCourse(course.getId(), user); + assertEquals(HttpStatus.I_AM_A_TEAPOT, check.getStatus()); } @Test - public void testGetCourseIfExists() throws Exception { - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); - CheckResult check = courseUtil.getCourseIfExists(1L); + public void testGetCourseIfExists() { + reset(courseUtil); + /* All checks succeed */ + when(courseRepository.findById(course.getId())).thenReturn(Optional.of(course)); + CheckResult check = courseUtil.getCourseIfExists(course.getId()); assertEquals(HttpStatus.OK, check.getStatus()); assertEquals(course, check.getData()); - when(courseRepository.findById(anyLong())).thenReturn(Optional.empty()); - check = courseUtil.getCourseIfExists(1L); + /* Course does not exist */ + when(courseRepository.findById(course.getId())).thenReturn(Optional.empty()); + check = courseUtil.getCourseIfExists(course.getId()); assertEquals(HttpStatus.NOT_FOUND, check.getStatus()); - assertEquals("Course not found", check.getMessage()); assertNull(check.getData()); } @Test - public void testCanUpdateUserInCourse() throws Exception { + public void testCanUpdateUserInCourse() { CourseMemberRequestJson request = new CourseMemberRequestJson(); - request.setUserId(2L); - request.setRelation(String.valueOf(CourseRelation.enrolled)); - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); + request.setUserId(5L); + request.setRelation("course_admin"); + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair(course, CourseRelation.creator))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuAdmin)); - CheckResult checkResult = courseUtil.canUpdateUserInCourse( - 1L, request, user, HttpMethod.PATCH - ); - assertEquals(HttpStatus.OK, checkResult.getStatus()); + + CheckResult check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(cuAdmin, check.getData()); + + /* User is not creator but trying to add admin */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair(course, CourseRelation.course_admin))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is not creator but trying to downgrade admin */ + request.setRelation("enrolled"); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is general admin and trying to add admin */ + request.setRelation("course_admin"); + user.setRole(UserRole.admin); + when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuEnrolled)); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* User is trying to change the creator */ + request.setRelation("creator"); + user.setRole(UserRole.teacher); + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair(course, CourseRelation.creator))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is trying to change the creator as admin */ + user.setRole(UserRole.admin); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.OK, check.getStatus()); + user.setRole(UserRole.teacher); + request.setRelation("enrolled"); + + /* User is trying to change it's own role */ + request.setUserId(user.getId()); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is trying to change it's own role as admin */ + user.setRole(UserRole.admin); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.OK, check.getStatus()); + user.setRole(UserRole.teacher); + + /* User isn't in course on patch */ + request.setUserId(5L); + when(courseUserRepository.findById(any())).thenReturn(Optional.empty()); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.PATCH); + assertEquals(HttpStatus.BAD_REQUEST, check.getStatus()); + + /* Post everything succeeds */ + when(userUtil.userExists(request.getUserId())).thenReturn(true); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* User doesn't exist */ + when(userUtil.userExists(request.getUserId())).thenReturn(false); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.NOT_FOUND, check.getStatus()); + + /* User is already in course on POST */ + when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuEnrolled)); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.BAD_REQUEST, check.getStatus()); + + /* Invalid relation */ + request.setRelation("invalid"); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.BAD_REQUEST, check.getStatus()); + + /* Relation not present */ + request.setRelation(null); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.BAD_REQUEST, check.getStatus()); + + /* UserId not present */ + request.setRelation("enrolled"); + request.setUserId(null); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.BAD_REQUEST, check.getStatus()); + + /* User is not an admin */ + request.setUserId(5L); + when(courseUserRepository.findById(any())).thenReturn(Optional.empty()); + when(userUtil.userExists(request.getUserId())).thenReturn(true); + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.enrolled))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is not in course but is admin */ + user.setRole(UserRole.admin); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* get course fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canUpdateUserInCourse(course.getId(), request, user, HttpMethod.POST); + assertEquals(HttpStatus.I_AM_A_TEAPOT, check.getStatus()); + } @Test public void testCanLeaveCourse() throws Exception { - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); - when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuAdmin)); - CheckResult checkResult = courseUtil.canLeaveCourse(1L, user); - assertEquals(HttpStatus.OK, checkResult.getStatus()); - assertEquals(CourseRelation.course_admin, checkResult.getData()); + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.enrolled))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + CheckResult check = courseUtil.canLeaveCourse(course.getId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(CourseRelation.enrolled, check.getData()); + + /* Course is archived */ + course.setArchivedAt(OffsetDateTime.now()); + check = courseUtil.canLeaveCourse(course.getId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is course creator */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.creator))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canLeaveCourse(course.getId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* get course fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canLeaveCourse(course.getId(), user); + assertEquals(HttpStatus.I_AM_A_TEAPOT, check.getStatus()); } @Test public void testCanDeleteUser() throws Exception { - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); - when(courseUserRepository.findById(any())).thenReturn(Optional.of(cuAdmin)); - CheckResult checkResult = courseUtil.canDeleteUser( - 1L, 5L, user - ); - assertEquals(HttpStatus.OK, checkResult.getStatus()); - assertEquals(CourseRelation.course_admin, checkResult.getData()); + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.creator))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && + arg.getUserId() == cuEnrolled.getUserId()) + )).thenReturn(Optional.of(cuEnrolled)); + CheckResult check = courseUtil.canDeleteUser(course.getId(), cuEnrolled.getUserId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(CourseRelation.enrolled, check.getData()); + + /* User is course admin */ + reset(courseUserRepository); + when(courseUserRepository.findById( + argThat(arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == cuAdmin.getUserId()) + )).thenReturn(Optional.of(cuAdmin)); + check = courseUtil.canDeleteUser(course.getId(), cuAdmin.getUserId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* User isn't course creator but tries to delete admin */ + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.course_admin))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canDeleteUser(course.getId(), cuAdmin.getUserId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is general admin and tries to delete admin */ + user.setRole(UserRole.admin); + check = courseUtil.canDeleteUser(course.getId(), cuAdmin.getUserId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* User tries to delete creator */ + reset(courseUserRepository); + when(courseUserRepository.findById( + argThat(arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == cuCreator.getUserId()) + )).thenReturn(Optional.of(cuCreator)); + check = courseUtil.canDeleteUser(course.getId(), cuCreator.getUserId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User tries to delete itself */ + reset(courseUserRepository); + when(courseUserRepository.findById( + argThat(arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == user.getId()) + )).thenReturn(Optional.of(new CourseUserEntity(1L, 1L, CourseRelation.enrolled))); + check = courseUtil.canDeleteUser(course.getId(), user.getId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is trying to delete non-existing user */ + reset(courseUserRepository); + when(courseUserRepository.findById( + argThat(arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == cuEnrolled.getUserId()) + )).thenReturn(Optional.empty()); + check = courseUtil.canDeleteUser(course.getId(), cuEnrolled.getUserId(), user); + assertEquals(HttpStatus.NOT_FOUND, check.getStatus()); + + /* User is not an admin */ + user.setRole(UserRole.student); + doReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(course, CourseRelation.enrolled))) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canDeleteUser(course.getId(), cuEnrolled.getUserId(), user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* User is not in course but is admin */ + user.setRole(UserRole.admin); + reset(courseUserRepository); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && + arg.getUserId() == cuEnrolled.getUserId()) + )).thenReturn(Optional.of(cuEnrolled)); + check = courseUtil.canDeleteUser(course.getId(), cuEnrolled.getUserId(), user); + assertEquals(HttpStatus.OK, check.getStatus()); + + /* get course fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(courseUtil).getCourseIfUserInCourse(course.getId(), user); + check = courseUtil.canDeleteUser(course.getId(), cuEnrolled.getUserId(), user); + assertEquals(HttpStatus.I_AM_A_TEAPOT, check.getStatus()); } @Test public void testGetJoinLink() throws Exception { - String link = courseUtil.getJoinLink("key", "1"); - assertEquals("/api/courses/1/join/key", link); - link = courseUtil.getJoinLink(null, "1"); - assertEquals("/api/courses/1/join", link); + /* Link with key */ + String link = courseUtil.getJoinLink("key", "898"); + assertEquals("/api/courses/898/join/key", link); + /* Link without key */ + link = courseUtil.getJoinLink(null, "334"); + assertEquals("/api/courses/334/join", link); } @Test public void testCheckJoinLink() throws Exception { - when(courseRepository.findById(anyLong())).thenReturn(Optional.of(course)); - when(courseUserRepository.findById(any())).thenReturn(Optional.empty()); - CheckResult result = courseUtil.checkJoinLink(1L, "key", user); - assertEquals(HttpStatus.OK, result.getStatus()); - assertEquals(course, result.getData()); + /* All checks succeed */ + when(courseRepository.findById(course.getId())).thenReturn(Optional.of(course)); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == user.getId()) + )).thenReturn(Optional.empty()); - result = courseUtil.checkJoinLink(1L, null, user); - assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("Course requires a join key. Use /api/courses/1/join/{courseKey}", - result.getMessage()); + /* Course without key */ + course.setJoinKey(null); + CheckResult check = courseUtil.checkJoinLink(course.getId(), null, user); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(course, check.getData()); + + /* Course with key */ + course.setJoinKey("key"); + check = courseUtil.checkJoinLink(course.getId(), "key", user); + assertEquals(HttpStatus.OK, check.getStatus()); + assertEquals(course, check.getData()); + + /* Check fails */ + /* User already in course */ + reset(courseUserRepository); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == user.getId()) + )).thenReturn(Optional.of(cuEnrolled)); + check = courseUtil.checkJoinLink(course.getId(), "key", user); + assertEquals(HttpStatus.BAD_REQUEST, check.getStatus()); + + /* Course with key but no key provided */ + check = courseUtil.checkJoinLink(course.getId(), null, user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* Course with key but wrong key provided */ + check = courseUtil.checkJoinLink(course.getId(), "wrong", user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* Course without key but key provided */ + course.setJoinKey(null); + check = courseUtil.checkJoinLink(course.getId(), "key", user); + assertEquals(HttpStatus.FORBIDDEN, check.getStatus()); + + /* Course does not exist */ + when(courseRepository.findById(course.getId())).thenReturn(Optional.empty()); + check = courseUtil.checkJoinLink(course.getId(), "key", user); + assertEquals(HttpStatus.NOT_FOUND, check.getStatus()); } @Test public void testCheckCourseJson() throws Exception { - CourseJson courseJson = new CourseJson("name", "description", null,2023); + CourseJson courseJson = new CourseJson( + "name", "description", null, 2024 + ); + /* Creating a course */ + user.setRole(UserRole.teacher); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == user.getId()) + )).thenReturn(Optional.of(cuCreator)); CheckResult result = courseUtil.checkCourseJson(courseJson, user, null); assertEquals(HttpStatus.OK, result.getStatus()); + /* Updating a course */ + CheckResult result2 = courseUtil.checkCourseJson(courseJson, user, course.getId()); + + /* Name is empty */ + courseJson.setName(""); + result = courseUtil.checkCourseJson(courseJson, user, null); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* name is null */ + courseJson.setName(null); + result = courseUtil.checkCourseJson(courseJson, user, null); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* description is null */ + courseJson.setName("name"); courseJson.setDescription(null); result = courseUtil.checkCourseJson(courseJson, user, null); assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); - assertEquals("name, description and year are required", result.getMessage()); + /* year is null */ courseJson.setDescription("description"); - courseJson.setName(""); + courseJson.setYear(null); result = courseUtil.checkCourseJson(courseJson, user, null); assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); - assertEquals("Name cannot be empty", result.getMessage()); + + /* creator can (un)archive course */ + courseJson.setYear(2024); + courseJson.setArchived(true); + result = courseUtil.checkCourseJson(courseJson, user, course.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* not-creator can't (un)archive course */ + reset(courseUserRepository); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == user.getId()) + )).thenReturn(Optional.of(cuAdmin)); + result = courseUtil.checkCourseJson(courseJson, user, course.getId()); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* User has to be in course to update */ + reset(courseUserRepository); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == (course.getId()) && arg.getUserId() == user.getId()) + )).thenReturn(Optional.empty()); + result = courseUtil.checkCourseJson(courseJson, user, course.getId()); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); } } From 3c01ef5d0bd48d89da5a7ba8aec90479caea1b3e Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 6 May 2024 20:56:59 +0200 Subject: [PATCH 18/40] groupfeedbackutil tests --- .../ugent/pidgeon/util/GroupFeedbackUtil.java | 4 +- .../com/ugent/pidgeon/util/ProjectUtil.java | 2 +- .../pidgeon/util/GroupFeedbackUtilTest.java | 224 ++++++++++++++++++ 3 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/GroupFeedbackUtilTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/GroupFeedbackUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/GroupFeedbackUtil.java index 346bc21c..03e21232 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/GroupFeedbackUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/GroupFeedbackUtil.java @@ -100,11 +100,11 @@ public CheckResult checkGroupFeedbackUpdateJson(UpdateGroupScoreRequest re return new CheckResult<>(projectCheck.getStatus(), projectCheck.getMessage(), null); } Integer maxScore = projectCheck.getData().getMaxScore(); - if (request.getScore() == null || request.getFeedback() == null) { + if ((request.getScore() == null && maxScore != null) || request.getFeedback() == null) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "Score and feedback need to be provided", null); } - if (maxScore != null && request.getScore() < 0) { + if (request.getScore() != null && request.getScore() < 0) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "Score can't be lower than 0", null); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java index 32d4fd0a..adc90f1e 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java @@ -88,7 +88,7 @@ public CheckResult getProjectIfAdmin(long projectId, UserEntity u public CheckResult checkProjectJson(ProjectJson projectJson, long courseId) { if (projectJson.getName() == null || projectJson.getDescription() == null || - projectJson.getMaxScore() == null || + projectJson.getMaxScore() == null || //TODO: maxScore shouldn't be required projectJson.getGroupClusterId() == null || projectJson.getDeadline() == null) { return new CheckResult<>(HttpStatus.BAD_REQUEST, diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupFeedbackUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupFeedbackUtilTest.java new file mode 100644 index 00000000..6eb7581c --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupFeedbackUtilTest.java @@ -0,0 +1,224 @@ +package com.ugent.pidgeon.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +import com.ugent.pidgeon.model.json.UpdateGroupScoreRequest; +import com.ugent.pidgeon.postgre.models.GroupEntity; +import com.ugent.pidgeon.postgre.models.GroupFeedbackEntity; +import com.ugent.pidgeon.postgre.models.ProjectEntity; +import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.UserRole; +import com.ugent.pidgeon.postgre.repository.GroupFeedbackRepository; +import java.time.OffsetDateTime; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; + +@ExtendWith(MockitoExtension.class) +public class GroupFeedbackUtilTest { + @Mock + private ProjectUtil projectUtil; + @Mock + private GroupUtil groupUtil; + @Mock + private GroupFeedbackRepository groupFeedbackRepository; + + @Spy + @InjectMocks + private GroupFeedbackUtil groupFeedbackUtil; + + private GroupFeedbackEntity groupFeedbackEntity; + private UserEntity mockUser; + private ProjectEntity projectEntity; + private GroupEntity groupEntity; + + @BeforeEach + public void setup() { + groupFeedbackEntity = new GroupFeedbackEntity( + 5L, + 10L, + 10.0f, + "Good job!" + ); + mockUser = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); + mockUser.setId(2L); + projectEntity = new ProjectEntity( + 13L, + "projectName", + "projectDescription", + 21L, + 38L, + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(groupFeedbackEntity.getProjectId()); + groupEntity = new GroupEntity("test", projectEntity.getGroupClusterId()); + groupEntity.setId(groupFeedbackEntity.getGroupId()); + } + + @Test + public void testGetGroupFeedbackIfExists() { + /* GroupFeedback found */ + when(groupFeedbackRepository.findById(argThat( + id -> id.getGroupId() == groupFeedbackEntity.getGroupId() && id.getProjectId() == groupFeedbackEntity.getProjectId() + ))).thenReturn(java.util.Optional.of(groupFeedbackEntity)); + CheckResult result = groupFeedbackUtil.getGroupFeedbackIfExists(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(groupFeedbackEntity, result.getData()); + + /* GroupFeedback not found */ + reset(groupFeedbackRepository); + when(groupFeedbackRepository.findById(argThat( + id -> id.getGroupId() == groupFeedbackEntity.getGroupId() && id.getProjectId() == groupFeedbackEntity.getProjectId() + ))).thenReturn(java.util.Optional.empty()); + result = groupFeedbackUtil.getGroupFeedbackIfExists(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + assertNull(result.getData()); + } + + @Test + public void testCheckGroupFeedback() { + /* All schecks succeed */ + when(projectUtil.getProjectIfExists(groupFeedbackEntity.getProjectId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", projectEntity)); + when(groupUtil.getGroupIfExists(groupFeedbackEntity.getGroupId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity)); + + CheckResult result = groupFeedbackUtil.checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Group doesn't belong to project */ + groupEntity.setClusterId(0); + result = groupFeedbackUtil.checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Group get fails */ + when(groupUtil.getGroupIfExists(groupFeedbackEntity.getGroupId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Group not found", null)); + result = groupFeedbackUtil.checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Project get fails */ + when(projectUtil.getProjectIfExists(groupFeedbackEntity.getProjectId())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "Project not found", null)); + result = groupFeedbackUtil.checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + } + + @Test + public void testCheckGroupFeedbackUpdate() { + /* All checks succeed: patch/put */ + doReturn(new CheckResult<>(HttpStatus.OK, "", null)).when(groupFeedbackUtil).checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + when(groupUtil.isAdminOfGroup(groupFeedbackEntity.getGroupId(), mockUser)).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupFeedbackRepository.findById(argThat( + id -> id.getGroupId() == groupFeedbackEntity.getGroupId() && id.getProjectId() == groupFeedbackEntity.getProjectId() + ))).thenReturn(Optional.of(groupFeedbackEntity)); + + CheckResult result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PATCH); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(groupFeedbackEntity, result.getData()); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PUT); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(groupFeedbackEntity, result.getData()); + + /* Group already exists: post */ + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.POST); + assertEquals(HttpStatus.CONFLICT, result.getStatus()); + + /* All checks succeed: post */ + reset(groupFeedbackRepository); + when(groupFeedbackRepository.findById(argThat( + id -> id.getGroupId() == groupFeedbackEntity.getGroupId() && id.getProjectId() == groupFeedbackEntity.getProjectId() + ))).thenReturn(Optional.empty()); + + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.POST); + assertEquals(HttpStatus.OK, result.getStatus()); + assertNull(result.getData()); + + /* Group doesn't exist: patch/put */ + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PATCH); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PUT); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + /* Admin check fails */ + when(groupUtil.isAdminOfGroup(groupFeedbackEntity.getGroupId(), mockUser)).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "Not an admin", null)); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PATCH); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PUT); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.POST); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* groupFeedbackCheckFails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Group feedback not found", null)).when(groupFeedbackUtil).checkGroupFeedback(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PATCH); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.PUT); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + result = groupFeedbackUtil.checkGroupFeedbackUpdate(groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId(), mockUser, HttpMethod.POST); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + } + + @Test + public void testCheckGroupFeedbackUpdateJson() { + /* All checks succeed */ + UpdateGroupScoreRequest updateGroupScoreRequest = new UpdateGroupScoreRequest(); + updateGroupScoreRequest.setScore(Float.valueOf(projectEntity.getMaxScore())); + updateGroupScoreRequest.setFeedback("Good job!"); + when(projectUtil.getProjectIfExists(groupFeedbackEntity.getProjectId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", projectEntity)); + + CheckResult result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Score is too high */ + updateGroupScoreRequest.setScore((float) (projectEntity.getMaxScore() + 1)); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Maxscore is null while score is too high */ + projectEntity.setMaxScore(null); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.OK, result.getStatus()); + projectEntity.setMaxScore(34); + + /* Score is negative */ + updateGroupScoreRequest.setScore(-1.0f); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Score is null */ + updateGroupScoreRequest.setScore(null); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Score is null but so is maxScore */ + projectEntity.setMaxScore(null); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.OK, result.getStatus()); + projectEntity.setMaxScore(34); + + /* Feedback is null */ + updateGroupScoreRequest.setScore(Float.valueOf(projectEntity.getMaxScore())); + updateGroupScoreRequest.setFeedback(null); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Project get fails */ + when(projectUtil.getProjectIfExists(groupFeedbackEntity.getProjectId())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "Project not found", null)); + result = groupFeedbackUtil.checkGroupFeedbackUpdateJson(updateGroupScoreRequest, groupFeedbackEntity.getProjectId()); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + } + + +} From b9fbba60473594d4a0a7e0e3d2b315217352e103 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 6 May 2024 21:53:12 +0200 Subject: [PATCH 19/40] GroupUtil tests --- .../com/ugent/pidgeon/util/GroupUtil.java | 3 - .../com/ugent/pidgeon/util/GroupUtilTest.java | 301 ++++++++++++------ 2 files changed, 208 insertions(+), 96 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/GroupUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/GroupUtil.java index bf76ef38..b3820e05 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/GroupUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/GroupUtil.java @@ -120,9 +120,6 @@ public CheckResult canAddUserToGroup(long groupId, long userId, UserEntity return new CheckResult<>(HttpStatus.NOT_FOUND, "User not found", null); } - - - if (groupClusterRepository.userInGroupForCluster(group.getClusterId(), userId)) { return new CheckResult<>(HttpStatus.FORBIDDEN, "User is already in a group for this cluster", null); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java index 4a668191..d62c8eb0 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; import com.ugent.pidgeon.postgre.models.GroupClusterEntity; @@ -18,6 +19,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; @@ -35,165 +37,278 @@ public class GroupUtilTest { @Mock private UserUtil userUtil; + @Spy @InjectMocks private GroupUtil groupUtil; private GroupEntity group; - private UserEntity user; + private UserEntity mockUser; private GroupClusterEntity groupCluster; private ProjectEntity project; @BeforeEach public void setup() { - group = new GroupEntity("Groupname", 1L); - group.setId(1L); - user = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); - user.setId(1L); - groupCluster = new GroupClusterEntity(1L, 5, "cluster test", 20); - groupCluster.setId(1L); - project = new ProjectEntity(1L, "name", "description", 1L, 1L, true, 20, OffsetDateTime.now()); - project.setId(1L); + group = new GroupEntity("Groupname", 12L); + group.setId(54L); + mockUser = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); + mockUser.setId(10L); + groupCluster = new GroupClusterEntity(9L, 5, "cluster test", 20); + groupCluster.setId(12L); + project = new ProjectEntity(9L, "name", "description", 12L, null, true, 20, OffsetDateTime.now()); + project.setId(88L); } @Test - public void testGetGroupIfExists() throws Exception { - when(groupRepository.findById(1L)).thenReturn(Optional.of(group)); - CheckResult result = groupUtil.getGroupIfExists(1L); + public void testGetGroupIfExists() { + /* Group exists */ + when(groupRepository.findById(group.getId())).thenReturn(Optional.of(group)); + CheckResult result = groupUtil.getGroupIfExists(group.getId()); assertEquals(HttpStatus.OK, result.getStatus()); assertEquals(group, result.getData()); + /* Group does not exist */ + when(groupRepository.findById(2L)).thenReturn(Optional.empty()); result = groupUtil.getGroupIfExists(2L); assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - assertEquals("Group not found", result.getMessage()); } @Test - public void testCanGetGroup() throws Exception { - when(groupRepository.userAccessToGroup(1L, 1L)).thenReturn(true); - CheckResult result = groupUtil.canGetGroup(1L, user); + public void testCanGetGroup() { + /* User has access to group */ + when(groupRepository.userAccessToGroup(mockUser.getId(), group.getId())).thenReturn(true); + CheckResult result = groupUtil.canGetGroup(group.getId(), mockUser); assertEquals(HttpStatus.OK, result.getStatus()); - result = groupUtil.canGetGroup(2L, user); + /* User doesn't have access to group */ + when(groupRepository.userAccessToGroup(mockUser.getId(), group.getId())).thenReturn(false); + result = groupUtil.canGetGroup(group.getId(), mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("User does not have access to this group", result.getMessage()); + + /* User has no acces but is admin */ + mockUser.setRole(UserRole.admin); + result = groupUtil.canGetGroup(group.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); } @Test - public void testIsAdminOfGroup() throws Exception { - when(groupRepository.isAdminOfGroup(1L, user.getId())).thenReturn(true); - CheckResult result = groupUtil.isAdminOfGroup(1L, user); + public void testIsAdminOfGroup() { + /* User is admin of group */ + when(groupRepository.isAdminOfGroup(mockUser.getId(), group.getId())).thenReturn(true); + CheckResult result = groupUtil.isAdminOfGroup(group.getId(), mockUser); assertEquals(HttpStatus.OK, result.getStatus()); - result = groupUtil.isAdminOfGroup(2L, user); + /* User is not admin of group */ + when(groupRepository.isAdminOfGroup(mockUser.getId(), group.getId())).thenReturn(false); + result = groupUtil.isAdminOfGroup(group.getId(), mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("User is not an admin of this group", result.getMessage()); + + /* User is admin */ + mockUser.setRole(UserRole.admin); + result = groupUtil.isAdminOfGroup(group.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); } @Test - public void testCanUpdateGroup() throws Exception { - when(groupRepository.findById(1L)).thenReturn(Optional.of(group)); - when(groupRepository.isAdminOfGroup(1L, user.getId())).thenReturn(true); - when(clusterUtil.isIndividualCluster(1L)).thenReturn(false); - CheckResult result = groupUtil.canUpdateGroup(group.getId(), user); + public void testCanUpdateGroup() { + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", group)).when(groupUtil).getGroupIfExists(group.getId()); + doReturn(new CheckResult<>(HttpStatus.OK, "", null)).when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(false); + CheckResult result = groupUtil.canUpdateGroup(group.getId(), mockUser); assertEquals(HttpStatus.OK, result.getStatus()); - assertEquals(group, result.getData()); - when(clusterUtil.isIndividualCluster(1L)).thenReturn(true); - result = groupUtil.canUpdateGroup(group.getId(), user); + /* Group is individual cluster */ + when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(true); + result = groupUtil.canUpdateGroup(group.getId(), mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("Cannot update individual group", result.getMessage()); - when(groupRepository.isAdminOfGroup(1L, user.getId())).thenReturn(false); - result = groupUtil.canUpdateGroup(group.getId(), user); + /* User is not admin of group */ + doReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "User is not an admin of this group", null)).when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + result = groupUtil.canUpdateGroup(group.getId(), mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("User is not an admin of this group", result.getMessage()); - when(groupRepository.findById(1L)).thenReturn(Optional.empty()); - result = groupUtil.canUpdateGroup(group.getId(), user); + /* Group does not exist */ + doReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "Group not found", null)).when(groupUtil).getGroupIfExists(group.getId()); + result = groupUtil.canUpdateGroup(group.getId(), mockUser); assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - assertEquals("Group not found", result.getMessage()); } @Test - public void TestCanAddUserToGroup() throws Exception { - when(groupRepository.findById(1L)).thenReturn(Optional.of(group)); - when(groupRepository.isAdminOfGroup(user.getId(), 1L)).thenReturn(true); - when(groupClusterRepository.userInGroupForCluster(anyLong(), anyLong())).thenReturn(false); - when(groupRepository.userInGroup(anyLong(), anyLong())).thenReturn(false); - when(clusterUtil.getClusterIfExists(group.getClusterId())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupCluster)); - when(groupRepository.countUsersInGroup(group.getId())).thenReturn(1); - when(clusterUtil.isIndividualCluster(groupCluster.getId())).thenReturn(false); - when(groupRepository.findById(1L)).thenReturn(Optional.of(group)); - UserEntity userToAdd = new UserEntity(); - userToAdd.setId(2L); - userToAdd.setRole(UserRole.student); - when(userUtil.getUserIfExists(2L)).thenReturn(userToAdd); - when(groupRepository.isAdminOfGroup(2L, 1L)).thenReturn(false); - CheckResult result = groupUtil.canAddUserToGroup(group.getId(), 2L, user); + public void TestCanAddUserToGroup() { + long otherUserId = 5L; + UserEntity otherUser = new UserEntity("othername", "othersurname", "otheremail", UserRole.student, "otherazureid"); + /* All checks succeed */ + /* Trying to add yourself to the group */ + when(groupRepository.findById(group.getId())).thenReturn(Optional.of(group)); + when(groupRepository.userAccessToGroup(mockUser.getId(), group.getId())).thenReturn(true); + when(groupClusterRepository.inArchivedCourse(group.getClusterId())).thenReturn(false); + when(userUtil.getUserIfExists(mockUser.getId())).thenReturn(mockUser); + when(groupClusterRepository.userInGroupForCluster(group.getClusterId(), mockUser.getId())).thenReturn(false); + when(groupRepository.userInGroup(group.getId(), mockUser.getId())).thenReturn(false); + when(clusterUtil.getClusterIfExists(group.getClusterId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupCluster)); + when(groupRepository.countUsersInGroup(group.getId())).thenReturn(groupCluster.getMaxSize() - 1); + when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(false); + doReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + + CheckResult result = groupUtil.canAddUserToGroup(group.getId(), mockUser.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Trying to add someone else as admin */ + doReturn(new CheckResult<>(HttpStatus.OK, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + when(userUtil.getUserIfExists(otherUserId)).thenReturn(otherUser); + when(groupClusterRepository.userInGroupForCluster(group.getClusterId(), otherUserId)).thenReturn(false); + when(groupRepository.userInGroup(group.getId(), otherUserId)).thenReturn(false); + doReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), otherUser); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); assertEquals(HttpStatus.OK, result.getStatus()); + + /* User trying to add is admin */ + doReturn(new CheckResult<>(HttpStatus.OK, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), otherUser); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Cluster is individual */ + when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(true); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Group is already full */ + when(groupRepository.countUsersInGroup(group.getId())).thenReturn(groupCluster.getMaxSize()); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* ClusterEntity is not found */ + when(clusterUtil.getClusterIfExists(group.getClusterId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + + /* User is already in that group */ + when(groupRepository.userInGroup(group.getId(), otherUserId)).thenReturn(true); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* User is already in group for cluster */ + when(groupClusterRepository.userInGroupForCluster(group.getClusterId(), otherUserId)).thenReturn(true); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* User to add doesn't exist */ + when(userUtil.getUserIfExists(otherUserId)).thenReturn(null); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + /* User trying to add a different user while not being admin */ + doReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* User trying to join group in archived course */ + when(groupClusterRepository.inArchivedCourse(group.getClusterId())).thenReturn(true); + result = groupUtil.canAddUserToGroup(group.getId(), mockUser.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* User trying to join group they don't have acces too */ + when(groupRepository.userAccessToGroup(mockUser.getId(), group.getId())).thenReturn(false); + result = groupUtil.canAddUserToGroup(group.getId(), mockUser.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Group not found */ + when(groupRepository.findById(group.getId())).thenReturn(Optional.empty()); + result = groupUtil.canAddUserToGroup(group.getId(), mockUser.getId(), mockUser); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); } @Test public void testCanRemoveUserFromGroup() throws Exception { - when(groupRepository.findById(1L)).thenReturn(Optional.of(group)); - when(groupRepository.isAdminOfGroup(1L, user.getId())).thenReturn(true); - when(groupRepository.userInGroup(1L, 2L)).thenReturn(true); - when(clusterUtil.isIndividualCluster(groupCluster.getId())).thenReturn(false); - CheckResult result = groupUtil.canRemoveUserFromGroup(group.getId(), 2L, user); + /* All checks succeed */ + /* Trying to leave group */ + when(groupRepository.findById(group.getId())).thenReturn(Optional.of(group)); + when(groupClusterRepository.inArchivedCourse(group.getClusterId())).thenReturn(false); + when(groupRepository.userInGroup(group.getId(), mockUser.getId())).thenReturn(true); + when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(false); + + CheckResult result = groupUtil.canRemoveUserFromGroup(group.getId(), mockUser.getId(), mockUser); assertEquals(HttpStatus.OK, result.getStatus()); - when(clusterUtil.isIndividualCluster(groupCluster.getId())).thenReturn(true); - result = groupUtil.canRemoveUserFromGroup(group.getId(), 2L, user); + /* Trying to remove someone else */ + long otherUserId = 5L; + doReturn(new CheckResult<>(HttpStatus.OK, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + when(groupRepository.userInGroup(group.getId(), otherUserId)).thenReturn(true); + + result = groupUtil.canRemoveUserFromGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Individual cluster */ + when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(true); + result = groupUtil.canRemoveUserFromGroup(group.getId(), otherUserId, mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("Cannot remove user from individual group", result.getMessage()); - when(groupRepository.userInGroup(1L, 2L)).thenReturn(false); - result = groupUtil.canRemoveUserFromGroup(group.getId(), 2L, user); + /* User is not in group */ + when(groupRepository.userInGroup(group.getId(), otherUserId)).thenReturn(false); + result = groupUtil.canRemoveUserFromGroup(group.getId(), otherUserId, mockUser); assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - assertEquals("User is not in the group", result.getMessage()); - when(groupRepository.isAdminOfGroup(1L, user.getId())).thenReturn(false); - result = groupUtil.canRemoveUserFromGroup(group.getId(), 2L, user); + /* Trying to leave group in archived course */ + when(groupClusterRepository.inArchivedCourse(group.getClusterId())).thenReturn(true); + result = groupUtil.canRemoveUserFromGroup(group.getId(), mockUser.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Trying to add someone else while not admin */ + doReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)). + when(groupUtil).isAdminOfGroup(group.getId(), mockUser); + result = groupUtil.canRemoveUserFromGroup(group.getId(), otherUserId, mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("User is not an admin of this group", result.getMessage()); - when(groupRepository.findById(1L)).thenReturn(Optional.empty()); - result = groupUtil.canRemoveUserFromGroup(group.getId(), 2L, user); + /* Group not found */ + when(groupRepository.findById(group.getId())).thenReturn(Optional.empty()); + result = groupUtil.canRemoveUserFromGroup(group.getId(), mockUser.getId(), mockUser); assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - assertEquals("Group not found", result.getMessage()); } @Test public void testCanGetProjectGroupData() throws Exception { - when(projectUtil.getProjectIfExists(project.getId())) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", project)); - when(groupRepository.findByIdAndClusterId(group.getId(), project.getGroupClusterId())) - .thenReturn(Optional.of(group)); - when(groupRepository.userInGroup(group.getId(), user.getId())).thenReturn(true); - when(projectUtil.isProjectAdmin(project.getId(), user)) - .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), - user); + /* All checks succeed */ + when(projectUtil.getProjectIfExists(project.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", project)); + when(groupRepository.findByIdAndClusterId(group.getId(), project.getGroupClusterId())).thenReturn(Optional.of(group)); + + /* User in the group */ + when(groupRepository.userInGroup(group.getId(), mockUser.getId())).thenReturn(true); + when(projectUtil.isProjectAdmin(project.getId(), mockUser)).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + CheckResult result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* User not in group but project admin */ + when(groupRepository.userInGroup(group.getId(), mockUser.getId())).thenReturn(false); + when(projectUtil.isProjectAdmin(project.getId(), mockUser)).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* User not in group but general admin */ + when(groupRepository.userInGroup(group.getId(), mockUser.getId())).thenReturn(false); + when(projectUtil.isProjectAdmin(project.getId(), mockUser)).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); + mockUser.setRole(UserRole.admin); + result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); assertEquals(HttpStatus.OK, result.getStatus()); - when(groupRepository.userInGroup(group.getId(), user.getId())).thenReturn(false); - when(projectUtil.isProjectAdmin(project.getId(), user)) - .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), user); + /* User not in group and not admin */ + mockUser.setRole(UserRole.student); + result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - assertEquals("User does not have access to the submissions of the group", result.getMessage()); - when(groupRepository.findByIdAndClusterId(group.getId(), project.getGroupClusterId())) - .thenReturn(Optional.empty()); - result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), user); + /* Group not part of the project */ + when(groupRepository.findByIdAndClusterId(group.getId(), project.getGroupClusterId())).thenReturn(Optional.empty()); + result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - assertEquals("Group not part of the project", result.getMessage()); - when(projectUtil.getProjectIfExists(project.getId())) - .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", project)); - result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), user); + /* Project not found */ + when(projectUtil.getProjectIfExists(project.getId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); - assertEquals("", result.getMessage()); } } From 02d31dddd10f9f5af1266436de50c2129596777b Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 6 May 2024 22:37:12 +0200 Subject: [PATCH 20/40] ProjectUtilTests --- .../ugent/pidgeon/model/json/ProjectJson.java | 2 +- .../com/ugent/pidgeon/util/ProjectUtil.java | 10 +- .../com/ugent/pidgeon/util/GroupUtilTest.java | 1 + .../java/com/ugent/pidgeon/util/PairTest.java | 14 + .../ugent/pidgeon/util/ProjectUtilTest.java | 323 +++++++++--------- 5 files changed, 173 insertions(+), 177 deletions(-) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/PairTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectJson.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectJson.java index ffff3f2e..abbf1b22 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectJson.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectJson.java @@ -19,7 +19,7 @@ public class ProjectJson { @JsonSerialize(using = OffsetDateTimeSerializer.class) private OffsetDateTime deadline; - public ProjectJson(String name, String description, Long groupClusterId, Long testId, Boolean visible, Integer maxScore, OffsetDateTime deadline) { + public ProjectJson(String name, String description, Long groupClusterId, Boolean visible, Integer maxScore, OffsetDateTime deadline) { this.name = name; this.description = description; this.groupClusterId = groupClusterId; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java index adc90f1e..b689a36a 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/ProjectUtil.java @@ -88,11 +88,10 @@ public CheckResult getProjectIfAdmin(long projectId, UserEntity u public CheckResult checkProjectJson(ProjectJson projectJson, long courseId) { if (projectJson.getName() == null || projectJson.getDescription() == null || - projectJson.getMaxScore() == null || //TODO: maxScore shouldn't be required projectJson.getGroupClusterId() == null || projectJson.getDeadline() == null) { return new CheckResult<>(HttpStatus.BAD_REQUEST, - "name, description, maxScore and deadline are required fields", null); + "name, description and deadline are required fields", null); } if (projectJson.getName().isBlank()) { @@ -109,8 +108,8 @@ public CheckResult checkProjectJson(ProjectJson projectJson, long courseId return new CheckResult<>(HttpStatus.BAD_REQUEST, "Deadline is in the past", null); } - if (projectJson.getMaxScore() < 0) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Max score cannot be negative", null); + if (projectJson.getMaxScore() != null && projectJson.getMaxScore() <= 0) { + return new CheckResult<>(HttpStatus.BAD_REQUEST, "Max score cannot be negative or zero", null); } return new CheckResult<>(HttpStatus.OK, "", null); @@ -132,8 +131,7 @@ public CheckResult canGetProject(long projectId, UserEntity user) boolean studentof = projectRepository.userPartOfProject(projectId, user.getId()); boolean isAdmin = - (user.getRole() == UserRole.admin) || (projectRepository.adminOfProject(projectId, - user.getId())); + (user.getRole() == UserRole.admin); if (studentof || isAdmin) { return new CheckResult<>(HttpStatus.OK, "", project); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java index d62c8eb0..2d482636 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java @@ -116,6 +116,7 @@ public void testCanUpdateGroup() { when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(false); CheckResult result = groupUtil.canUpdateGroup(group.getId(), mockUser); assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(group, result.getData()); /* Group is individual cluster */ when(clusterUtil.isIndividualCluster(group.getClusterId())).thenReturn(true); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/PairTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/PairTest.java new file mode 100644 index 00000000..c046313f --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/PairTest.java @@ -0,0 +1,14 @@ +package com.ugent.pidgeon.util; + +import org.junit.jupiter.api.Test; + +public class PairTest { + + @Test + public void testPair() { + Pair pair = new Pair<>("test", 1); + assert(pair.getFirst().equals("test")); + assert(pair.getSecond().equals(1)); + } + +} diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/ProjectUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/ProjectUtilTest.java index 7a64a4da..85ad1db1 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/ProjectUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/ProjectUtilTest.java @@ -7,17 +7,22 @@ import com.ugent.pidgeon.model.json.ProjectJson; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import java.time.OffsetDateTime; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) public class ProjectUtilTest { @Mock @@ -26,217 +31,195 @@ public class ProjectUtilTest { @Mock private ClusterUtil clusterUtil; + @Spy @InjectMocks private ProjectUtil projectUtil; + private ProjectEntity projectEntity; + private UserEntity mockUser; + @BeforeEach public void setUp() { - MockitoAnnotations.openMocks(this); + projectEntity = new ProjectEntity( + 99L, + "projectName", + "projectDescription", + 69L, + 38L, + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(64); + + mockUser = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); + mockUser.setId(10L); } @Test public void testUserPartOfProject() { - long projectId = 1L; - long userId = 1L; - when(projectRepository.userPartOfProject(projectId, userId)).thenReturn(true); - boolean result = projectUtil.userPartOfProject(projectId, userId); - assertEquals(true, result); - } + /* User in project */ + when(projectRepository.userPartOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(true); + assertEquals(true, projectUtil.userPartOfProject(projectEntity.getId(), mockUser.getId())); - @Test - public void testGetProjectIfExists() { - long projectId = 1L; - ProjectEntity projectEntity = new ProjectEntity(); - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.of(projectEntity)); - CheckResult result = projectUtil.getProjectIfExists(projectId); - assertEquals(HttpStatus.OK, result.getStatus()); + /* User not in project */ + when(projectRepository.userPartOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(false); + assertEquals(false, projectUtil.userPartOfProject(projectEntity.getId(), mockUser.getId())); } @Test - public void testGetProjectIfExistsNotFound() { - long projectId = 1L; - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.empty()); - CheckResult result = projectUtil.getProjectIfExists(projectId); - assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - } + public void testGetProjectIfExists() { + /* Project found */ + when(projectRepository.findById(projectEntity.getId())).thenReturn(java.util.Optional.of(projectEntity)); + CheckResult checkResult = projectUtil.getProjectIfExists(projectEntity.getId()); + assertEquals(HttpStatus.OK, checkResult.getStatus()); + assertEquals(projectEntity, checkResult.getData()); + /* Project not found */ + when(projectRepository.findById(projectEntity.getId())).thenReturn(java.util.Optional.empty()); + checkResult = projectUtil.getProjectIfExists(projectEntity.getId()); + assertEquals(HttpStatus.NOT_FOUND, checkResult.getStatus()); + } @Test public void testIsProjectAdmin() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.admin); - when(projectRepository.adminOfProject(projectId, user.getId())).thenReturn(true); - CheckResult result = projectUtil.isProjectAdmin(projectId, user); - assertEquals(HttpStatus.OK, result.getStatus()); - } + /* User is admin */ + when(projectRepository.adminOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(true); + CheckResult checkResult = projectUtil.isProjectAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, checkResult.getStatus()); + /* User is not admin */ + when(projectRepository.adminOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(false); + checkResult = projectUtil.isProjectAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, checkResult.getStatus()); - @Test - public void testIsProjectAdminForbidden() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.student); - when(projectRepository.adminOfProject(projectId, user.getId())).thenReturn(false); - CheckResult result = projectUtil.isProjectAdmin(projectId, user); - assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + /* User is general admin */ + mockUser.setRole(UserRole.admin); + checkResult = projectUtil.isProjectAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, checkResult.getStatus()); } @Test public void testGetProjectIfAdmin() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.admin); - ProjectEntity projectEntity = new ProjectEntity(); - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.of(projectEntity)); - when(projectRepository.adminOfProject(projectId, user.getId())).thenReturn(true); - CheckResult result = projectUtil.getProjectIfAdmin(projectId, user); - assertEquals(HttpStatus.OK, result.getStatus()); - } + /* All checks succeed */ + doReturn(new CheckResult<>(HttpStatus.OK, "", projectEntity)).when(projectUtil).getProjectIfExists(projectEntity.getId()); + when(projectRepository.adminOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(true); - @Test - public void testGetProjectIfAdminForbidden() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.student); - ProjectEntity projectEntity = new ProjectEntity(); - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.of(projectEntity)); - when(projectRepository.adminOfProject(projectId, user.getId())).thenReturn(false); - CheckResult result = projectUtil.getProjectIfAdmin(projectId, user); - assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - } + CheckResult checkResult = projectUtil.getProjectIfAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, checkResult.getStatus()); - @Test - public void testCheckProjectJson() { - long courseId = 1L; - ProjectJson projectJson = new ProjectJson(); - projectJson.setName("Test Project"); - projectJson.setDescription("This is a test project."); - projectJson.setMaxScore(100); - projectJson.setGroupClusterId(1L); - projectJson.setDeadline(OffsetDateTime.now().plusDays(1)); - when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), courseId)).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = projectUtil.checkProjectJson(projectJson, courseId); - assertEquals(HttpStatus.OK, result.getStatus()); - } + /* User is not admin */ + when(projectRepository.adminOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(false); + checkResult = projectUtil.getProjectIfAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, checkResult.getStatus()); - @Test - public void testCheckProjectJsonNullName() { - long courseId = 1L; - ProjectJson projectJson = new ProjectJson(); - projectJson.setDescription("This is a test project."); - projectJson.setMaxScore(100); - projectJson.setGroupClusterId(1L); - projectJson.setDeadline(OffsetDateTime.now().plusDays(1)); - when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), courseId)).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = projectUtil.checkProjectJson(projectJson, courseId); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); - } + /* User is not project admin but admin role */ + mockUser.setRole(UserRole.admin); + checkResult = projectUtil.getProjectIfAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, checkResult.getStatus()); - @Test - public void testCheckProjectJsonNullDescription() { - long courseId = 1L; - ProjectJson projectJson = new ProjectJson(); - projectJson.setName("Test Project"); - projectJson.setMaxScore(100); - projectJson.setGroupClusterId(1L); - projectJson.setDeadline(OffsetDateTime.now().plusDays(1)); - when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), courseId)).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = projectUtil.checkProjectJson(projectJson, courseId); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + /* Project not found */ + doReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "Project not found", null)).when(projectUtil).getProjectIfExists(projectEntity.getId()); + checkResult = projectUtil.getProjectIfAdmin(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.NOT_FOUND, checkResult.getStatus()); } @Test - public void testCheckProjectJsonNullMaxScore() { - long courseId = 1L; - ProjectJson projectJson = new ProjectJson(); - projectJson.setName("Test Project"); - projectJson.setDescription("This is a test project."); - projectJson.setGroupClusterId(1L); + public void testCheckProjectJson() { + ProjectJson projectJson = new ProjectJson( + "UpdateProjectName", + "UpdateProjectDescription", + 69L, + true, + 34, + OffsetDateTime.now().plusDays(1) + ); + + /* All checks succeed */ + when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), projectEntity.getCourseId())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + CheckResult checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.OK, checkResult.getStatus()); + + /* projectJson maxScore is negative */ + projectJson.setMaxScore(-1); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); + + /* projectJson maxScore is zero */ + projectJson.setMaxScore(0); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); + + /* projectJson no max score */ + projectJson.setMaxScore(null); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.OK, checkResult.getStatus()); + + /* projectJson deadline is already passed */ + projectJson.setDeadline(OffsetDateTime.now().minusDays(1)); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); + + /* Cluster not part of course */ + when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), projectEntity.getCourseId())) + .thenReturn(new CheckResult<>(HttpStatus.NOT_FOUND, "Cluster not part of course", null)); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.NOT_FOUND, checkResult.getStatus()); + + /* name is blank */ + projectJson.setName(""); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); + + /* deadline is null */ + projectJson.setDeadline(null); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); + + /* groupClusterId is null */ projectJson.setDeadline(OffsetDateTime.now().plusDays(1)); - when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), courseId)).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = projectUtil.checkProjectJson(projectJson, courseId); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); - } + projectJson.setGroupClusterId(null); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); + /* description is null */ + projectJson.setDescription(null); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); - @Test - public void testCheckProjectJsonNullDeadline() { - long courseId = 1L; - ProjectJson projectJson = new ProjectJson(); - projectJson.setName("Test Project"); - projectJson.setDescription("This is a test project."); - projectJson.setMaxScore(100); - projectJson.setGroupClusterId(1L); - when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), courseId)).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = projectUtil.checkProjectJson(projectJson, courseId); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); - } - - @Test - public void testCheckProjectDeadlinePast() { - long courseId = 1L; - ProjectJson projectJson = new ProjectJson(); - projectJson.setName("Test Project"); - projectJson.setDescription("This is a test project."); - projectJson.setMaxScore(100); - projectJson.setGroupClusterId(1L); - projectJson.setDeadline(OffsetDateTime.now().minusDays(1)); - when(clusterUtil.partOfCourse(projectJson.getGroupClusterId(), courseId)).thenReturn( - new CheckResult<>(HttpStatus.OK, "", null)); - CheckResult result = projectUtil.checkProjectJson(projectJson, courseId); - assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + /* name is null */ + projectJson.setName(null); + checkResult = projectUtil.checkProjectJson(projectJson, projectEntity.getCourseId()); + assertEquals(HttpStatus.BAD_REQUEST, checkResult.getStatus()); } @Test public void testCanGetProject() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.admin); - ProjectEntity projectEntity = new ProjectEntity(); - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.of(projectEntity)); - when(projectRepository.userPartOfProject(projectId, user.getId())).thenReturn(true); - when(projectRepository.adminOfProject(projectId, user.getId())).thenReturn(true); - CheckResult result = projectUtil.canGetProject(projectId, user); - assertEquals(HttpStatus.OK, result.getStatus()); - } + /* User is student */ + when(projectRepository.findById(projectEntity.getId())).thenReturn(java.util.Optional.of(projectEntity)); + when(projectRepository.userPartOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(true); - @Test - public void testCanGetProjectForbidden() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.student); - ProjectEntity projectEntity = new ProjectEntity(); - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.of(projectEntity)); - when(projectRepository.userPartOfProject(projectId, user.getId())).thenReturn(false); - when(projectRepository.adminOfProject(projectId, user.getId())).thenReturn(false); - CheckResult result = projectUtil.canGetProject(projectId, user); - assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); - } + CheckResult checkResult = projectUtil.canGetProject(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, checkResult.getStatus()); - @Test - public void testCanGetProjectNotFound() { - long projectId = 1L; - UserEntity user = new UserEntity(); - user.setId(1L); - user.setRole(UserRole.admin); - when(projectRepository.findById(projectId)).thenReturn(java.util.Optional.empty()); - CheckResult result = projectUtil.canGetProject(projectId, user); - assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); - } + /* User is admin */ + when(projectRepository.userPartOfProject(projectEntity.getId(), mockUser.getId())).thenReturn(false); + mockUser.setRole(UserRole.admin); + checkResult = projectUtil.canGetProject(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.OK, checkResult.getStatus()); + /* User is not part of project */ + mockUser.setRole(UserRole.student); + checkResult = projectUtil.canGetProject(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.FORBIDDEN, checkResult.getStatus()); + /* Project not found */ + when(projectRepository.findById(projectEntity.getId())).thenReturn(java.util.Optional.empty()); + checkResult = projectUtil.canGetProject(projectEntity.getId(), mockUser); + assertEquals(HttpStatus.NOT_FOUND, checkResult.getStatus()); + } } \ No newline at end of file From e08bdbdb3502febaa7eb624af5376a8ccbf04e49 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 6 May 2024 22:42:20 +0200 Subject: [PATCH 21/40] StringMatcher tests --- .../com/ugent/pidgeon/util/StringMatcher.java | 1 + .../ugent/pidgeon/util/StringMatcherTest.java | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/StringMatcherTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/StringMatcher.java b/backend/app/src/main/java/com/ugent/pidgeon/util/StringMatcher.java index ff37bb79..0b1a6dd1 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/StringMatcher.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/StringMatcher.java @@ -8,6 +8,7 @@ public class StringMatcher { "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; + private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); /** diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/StringMatcherTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/StringMatcherTest.java new file mode 100644 index 00000000..c3be6e06 --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/StringMatcherTest.java @@ -0,0 +1,41 @@ +package com.ugent.pidgeon.util; + +import org.junit.jupiter.api.Test; + +public class StringMatcherTest { + + @Test + public void testIsValidEmail() { + assert (StringMatcher.isValidEmail("name.surname@UGent.be")); + assert (StringMatcher.isValidEmail("namesurname@UGent.be")); + + } + + @Test + public void testIsValidEmailNoEndPart() { + assert (!StringMatcher.isValidEmail("name.surname@UGent")); + } + + @Test + public void testIsValidEmailNoAt() { + assert (!StringMatcher.isValidEmail("name.surnameUGent.be")); + } + + @Test + public void testIsValidEmailNoStartPart() { + assert (!StringMatcher.isValidEmail("@UGent.be")); + } + + @Test + public void testIsValidEmailNoDot() { + assert (!StringMatcher.isValidEmail("name.surname@UGentbe")); + + } + + + + + + + +} From ab1fb515597392d522e26d3a55582784f8c69c9a Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Tue, 7 May 2024 10:14:18 +0200 Subject: [PATCH 22/40] Update UserUtilTest.java --- .../com/ugent/pidgeon/util/UserUtilTest.java | 80 ++++++++++++++++--- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/UserUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/UserUtilTest.java index 4f61e535..bd9a059f 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/UserUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/UserUtilTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; @@ -16,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -24,6 +26,7 @@ public class UserUtilTest { @Mock private UserRepository userRepository; + @Spy @InjectMocks private UserUtil userUtil; @@ -32,25 +35,29 @@ public class UserUtilTest { @BeforeEach public void setUp() { user = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); - user.setId(1L); + user.setId(87L); } @Test public void testUserExists() { - when(userRepository.existsById(anyLong())).thenReturn(true); - assertTrue(userUtil.userExists(1L)); + /* The user exists */ + when(userRepository.existsById(user.getId())).thenReturn(true); + assertTrue(userUtil.userExists(user.getId())); - when(userRepository.existsById(anyLong())).thenReturn(false); - assertFalse(userUtil.userExists(1L)); + /* The user does not exist */ + when(userRepository.existsById(user.getId())).thenReturn(false); + assertFalse(userUtil.userExists(user.getId())); } @Test public void testGetUserIfExists() { - when(userRepository.findById(anyLong())).thenReturn(Optional.of(user)); - assertEquals(user, userUtil.getUserIfExists(1L)); + /* The user exists */ + when(userRepository.findById(user.getId())).thenReturn(Optional.of(user)); + assertEquals(user, userUtil.getUserIfExists(user.getId())); - when(userRepository.findById(anyLong())).thenReturn(Optional.empty()); - assertNull(userUtil.getUserIfExists(1L)); + /* The user does not exist */ + when(userRepository.findById(user.getId())).thenReturn(Optional.empty()); + assertNull(userUtil.getUserIfExists(user.getId())); } @Test @@ -61,14 +68,61 @@ public void testCheckForUserUpdateJson() { json.setEmail("newEmail@example.com"); json.setRole("student"); - when(userRepository.findById(anyLong())).thenReturn(Optional.of(user)); - CheckResult result = userUtil.checkForUserUpdateJson(1L, json); + /* All checks succeed */ + when(userRepository.findById(user.getId())).thenReturn(Optional.of(user)); + CheckResult result = userUtil.checkForUserUpdateJson(user.getId(), json); assertEquals(HttpStatus.OK, result.getStatus()); assertEquals(user, result.getData()); + /* Not a valid email */ json.setEmail("invalidEmail"); - result = userUtil.checkForUserUpdateJson(1L, json); + result = userUtil.checkForUserUpdateJson(user.getId(), json); assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); - assertEquals("Email is not valid", result.getMessage()); + json.setEmail("newEmail@example.com"); + + /* Surname is blank */ + json.setSurname(""); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Name is blank */ + json.setSurname("newSurname"); + json.setName(""); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Role is not valid */ + json.setName("newName"); + json.setRole("invalidRole"); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Role is null */ + json.setRole(null); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Email is null */ + json.setRole("student"); + json.setEmail(null); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Surname is null */ + json.setEmail("email.email@email.email"); + json.setSurname(null); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Name is null */ + json.setSurname("newSurname"); + json.setName(null); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* User not found */ + when(userRepository.findById(user.getId())).thenReturn(Optional.empty()); + result = userUtil.checkForUserUpdateJson(user.getId(), json); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); } } \ No newline at end of file From 63eeee6b00b5209598ce983f78092b98a68ee9d8 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Tue, 7 May 2024 10:32:51 +0200 Subject: [PATCH 23/40] start globalerrorhandler test Doesn't seem to be executing the code correctly but somehow the test succeeds --- .../global/GlobalErrorHandlerTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java diff --git a/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java new file mode 100644 index 00000000..1659e3a3 --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java @@ -0,0 +1,44 @@ +package com.ugent.pidgeon.global; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.ugent.pidgeon.controllers.ControllerTest; +import com.ugent.pidgeon.controllers.UserController; +import com.ugent.pidgeon.postgre.repository.UserRepository; +import com.ugent.pidgeon.util.UserUtil; +import org.junit.jupiter.api.BeforeEach; +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 org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@ExtendWith(MockitoExtension.class) +public class GlobalErrorHandlerTest extends ControllerTest { + + + + @Mock + private UserUtil userUtil; + + @InjectMocks + private UserController userController; + + + @BeforeEach + public void setUp() { + setUpController(userController); + } + + @Test + public void testHandleHttpMessageNotReadableException() throws Exception { + when(userUtil.getUserIfExists(anyLong())).thenThrow(new HttpMessageNotReadableException("test")); + mockMvc.perform(MockMvcRequestBuilders.get("/api/users/1")) + .andExpect(status().isBadRequest()); + } + +} From 9b58a01a9d0102a11f8a0f42524c46065c4d8ca9 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 8 May 2024 12:04:39 +0200 Subject: [PATCH 24/40] GlobalErroHandlerTest --- .../com/ugent/pidgeon/GlobalErrorHandler.java | 11 +++-- .../pidgeon/controllers/ControllerTest.java | 3 ++ .../global/GlobalErrorHandlerTest.java | 40 ++++++++++++++++--- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/GlobalErrorHandler.java b/backend/app/src/main/java/com/ugent/pidgeon/GlobalErrorHandler.java index 715598cc..47371b60 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/GlobalErrorHandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/GlobalErrorHandler.java @@ -31,6 +31,7 @@ private void logError(Exception ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); } + /* Gets thrown when a invalid json is sent */ @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity handleHttpMessageNotReadableException(HttpServletRequest request, Exception ex) { logError(ex); @@ -40,15 +41,17 @@ public ResponseEntity handleHttpMessageNotReadableException(Htt "Unable to process the request due to invalid or missing data. Please ensure the request body is properly formatted and all required fields are provided.", path)); } - @ExceptionHandler(NoResourceFoundException.class) - public ResponseEntity handleHttpMessageNotFoundException(HttpServletRequest request, Exception ex) { + /* Gets thrown when endpoint doesn't exist */ + @ExceptionHandler(NoHandlerFoundException.class) + public ResponseEntity handleNoHandlerFoundException(HttpServletRequest request, Exception ex) { logError(ex); String path = request.getRequestURI(); HttpStatus status = HttpStatus.NOT_FOUND; return ResponseEntity.status(status).body(new ApiErrorReponse(OffsetDateTime.now(), status.value(), status.getReasonPhrase(), - "Endpoint doesn't exist", path)); + "Resource/endpoint doesn't exist", path)); } + /* Gets thrown when the method is not allowed */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity handleMethodNotSupportedException(HttpServletRequest request, Exception ex) { logError(ex); @@ -58,6 +61,7 @@ public ResponseEntity handleMethodNotSupportedException(HttpSer "Method not supported", path)); } + /* Gets thrown when u path variable is of the wrong type */ @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity handleMethodArgumentTypeMismatchException(HttpServletRequest request, Exception ex) { logError(ex); @@ -67,6 +71,7 @@ public ResponseEntity handleMethodArgumentTypeMismatchException "Invalid url argument type", path)); } + /* Gets thrown when an unexpected error occurs */ @ExceptionHandler(Exception.class) public ResponseEntity handleException(HttpServletRequest request, Exception ex) { logError(ex); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java index bd54e927..9ffa1e8f 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java @@ -1,5 +1,6 @@ package com.ugent.pidgeon.controllers; +import com.ugent.pidgeon.GlobalErrorHandler; import com.ugent.pidgeon.auth.RolesInterceptor; import com.ugent.pidgeon.model.Auth; import com.ugent.pidgeon.model.User; @@ -13,6 +14,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; @@ -64,6 +66,7 @@ public void testSetUp() { protected void setUpController(Object controller) { mockMvc = MockMvcBuilders.standaloneSetup(controller) .addInterceptors(rolesInterceptor) + .setControllerAdvice(new GlobalErrorHandler()) .defaultRequest(MockMvcRequestBuilders.get("/**") .with(request -> { request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java index 1659e3a3..7659c309 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/global/GlobalErrorHandlerTest.java @@ -4,8 +4,10 @@ import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.ugent.pidgeon.controllers.ApiRoutes; import com.ugent.pidgeon.controllers.ControllerTest; import com.ugent.pidgeon.controllers.UserController; +import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.UserRepository; import com.ugent.pidgeon.util.UserUtil; import org.junit.jupiter.api.BeforeEach; @@ -14,13 +16,12 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -@ExtendWith(MockitoExtension.class) -public class GlobalErrorHandlerTest extends ControllerTest { +@ExtendWith(MockitoExtension.class) +public class GlobalErrorHandlerTest extends ControllerTest { @Mock private UserUtil userUtil; @@ -36,9 +37,36 @@ public void setUp() { @Test public void testHandleHttpMessageNotReadableException() throws Exception { - when(userUtil.getUserIfExists(anyLong())).thenThrow(new HttpMessageNotReadableException("test")); - mockMvc.perform(MockMvcRequestBuilders.get("/api/users/1")) - .andExpect(status().isBadRequest()); + setMockUserRoles(UserRole.admin); + mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.USERS_BASE_PATH + "/1") + .contentType("application/json") + .content("") + ).andExpect(status().isBadRequest()); + } + + @Test + public void testHandleNoHandlerFound() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/api/doesntexist", 1L) + ).andExpect(status().isNotFound()); + } + + @Test + public void testHandleMethodNotSupportedException() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.multipart(ApiRoutes.USERS_BASE_PATH + "/1") + ).andExpect(status().isMethodNotAllowed()); + } + + @Test + public void testHandleMethodArgumentTypeMismatchException() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/string") + ).andExpect(status().isBadRequest()); + } + + @Test + public void testUnexpectedException() throws Exception { + when(userUtil.getUserIfExists(anyLong())).thenThrow(new RuntimeException("Unexpected exception")); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/1") + ).andExpect(status().isInternalServerError()); } } From 77923bba48bda2de002b816dfaed879d214f85c2 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 8 May 2024 12:29:38 +0200 Subject: [PATCH 25/40] RolesInterceptorTest --- .../ugent/pidgeon/auth/RolesInterceptor.java | 2 +- .../pidgeon/controllers/ControllerTest.java | 9 +- .../pidgeon/global/RolesInterceptorTest.java | 88 +++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/global/RolesInterceptorTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java index f87246fe..17952d89 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java @@ -64,7 +64,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons userEntity = new UserEntity(auth.getUser().firstName,auth.getUser().lastName, auth.getEmail(), UserRole.student, auth.getOid()); OffsetDateTime now = OffsetDateTime.now(); userEntity.setCreatedAt(now); - userRepository.save(userEntity); + userEntity = userRepository.save(userEntity); System.out.println("User created with id: " + userEntity.getId()); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java index 9ffa1e8f..e863f9e4 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java @@ -50,9 +50,14 @@ public void testSetUp() { SecurityContextHolder.getContext().setAuthentication(authUser); // Only stubbing necessary methods for the test - mockUser = new UserEntity(); + mockUser = new UserEntity( + user.firstName, + user.lastName, + user.email, + UserRole.teacher, + user.oid + ); mockUser.setId(1L); - mockUser.setRole(UserRole.teacher); authUser.setUserEntity(mockUser); lenient().when(userRepository.findById(anyLong())).thenReturn(Optional.of(mockUser)); lenient().when(userRepository.findCourseIdsByUserId(anyLong())).thenReturn(new ArrayList<>()); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/global/RolesInterceptorTest.java b/backend/app/src/test/java/com/ugent/pidgeon/global/RolesInterceptorTest.java new file mode 100644 index 00000000..bff3e4e1 --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/global/RolesInterceptorTest.java @@ -0,0 +1,88 @@ +package com.ugent.pidgeon.global; + + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.ugent.pidgeon.controllers.ApiRoutes; +import com.ugent.pidgeon.controllers.ControllerTest; +import com.ugent.pidgeon.controllers.UserController; +import com.ugent.pidgeon.postgre.models.types.UserRole; +import com.ugent.pidgeon.util.UserUtil; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Optional; +import java.util.logging.Logger; +import org.junit.jupiter.api.BeforeEach; +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 org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@ExtendWith(MockitoExtension.class) +public class RolesInterceptorTest extends ControllerTest { + + @Mock + private UserUtil userUtil; + + @InjectMocks + private UserController userController; + + + @BeforeEach + public void setUp() { + setUpController(userController); + } + + @Test + void testEverthingWorks() throws Exception { + when(userUtil.getUserIfExists(getMockUser().getId())).thenReturn(getMockUser()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/1") + ).andExpect(status().isOk()); + } + + @Test + void testNotRequiredRole() throws Exception { + setMockUserRoles(UserRole.student); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH) + ).andExpect(status().isForbidden()); + } + + @Test + void adminSucceedsAllRoleCheck() throws Exception { + setMockUserRoles(UserRole.admin); + when(userUtil.getUserIfExists(getMockUser().getId())).thenReturn(getMockUser()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/1") + ).andExpect(status().isOk()); + } + + @Test + void testUserDoesntExistYet() throws Exception { + reset(userRepository); + when(userUtil.getUserIfExists(getMockUser().getId())).thenReturn(getMockUser()); + when(userRepository.findUserByAzureId(getMockUser().getAzureId())).thenReturn(Optional.empty()); + when(userRepository.save(argThat( + user -> { + Duration duration = Duration.between(user.getCreatedAt(), OffsetDateTime.now()); + return user.getRole() == UserRole.student && + user.getAzureId().equals(getMockUser().getAzureId()) && + user.getName().equals(getMockUser().getName()) && + user.getSurname().equals(getMockUser().getSurname()) && + user.getEmail().equals(getMockUser().getEmail()) && + duration.getSeconds() < 5; + } + ))).thenReturn(getMockUser()); + mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.USERS_BASE_PATH + "/" + getMockUser().getId()) + ).andExpect(status().isOk()); + + } + + +} From ebcd892adac4f73cec26f287faf26f53e9154ae2 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 13:10:05 +0200 Subject: [PATCH 26/40] small fixes to test and controller --- backend/app/build.gradle | 12 ++++ .../controllers/SubmissionController.java | 63 ++++++++++-------- .../docker/DockerSubmissionTestTest.java | 15 ++++- .../DockerSubmissionTestTest/d__test.zip | Bin 162 -> 162 bytes 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/backend/app/build.gradle b/backend/app/build.gradle index 355cff6e..e4a26449 100644 --- a/backend/app/build.gradle +++ b/backend/app/build.gradle @@ -71,3 +71,15 @@ task unitTests (type: Test){ } +task allTest (type: Test) { + + include '**' + useJUnitPlatform() + maxHeapSize = '1G' + + + testLogging { + events "passed" + } +} + diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index 1028825c..8127ae82 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -78,41 +78,48 @@ private SubmissionTemplateModel.SubmissionResult runStructureTest(ZipFile file, } private DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path outputPath) throws IOException { + // Get the test file from the server + String testScript = testEntity.getDockerTestScript(); + String testTemplate = testEntity.getDockerTestTemplate(); + String image = testEntity.getDockerImage(); + + // The first script must always be null, otherwise there is nothing to run on the container + if (testScript == null) { + return null; + } - // Get the test file from the server - String testScript = testEntity.getDockerTestScript(); - String testTemplate = testEntity.getDockerTestTemplate(); - String image = testEntity.getDockerImage(); + // Init container and add input files + DockerSubmissionTestModel model = new DockerSubmissionTestModel(image); + try { - // The first script must always be null, otherwise there is nothing to run on the container - if (testScript == null) { - return null; - } + model.addZipInputFiles(file); + DockerOutput output; - // Init container and add input files - DockerSubmissionTestModel model = new DockerSubmissionTestModel(image); - model.addZipInputFiles(file); - DockerOutput output; - - if (testTemplate == null) { - // This docker test is configured in the simple mode (store test console logs) - output = model.runSubmission(testScript); - } else { - // This docker test is configured in the template mode (store json with feedback) - output = model.runSubmissionWithTemplate(testScript, testTemplate); - } - // Get list of artifact files generated on submission - List artifacts = model.getArtifacts(); + if (testTemplate == null) { + // This docker test is configured in the simple mode (store test console logs) + output = model.runSubmission(testScript); + } else { + // This docker test is configured in the template mode (store json with feedback) + output = model.runSubmissionWithTemplate(testScript, testTemplate); + } + // Get list of artifact files generated on submission + List artifacts = model.getArtifacts(); + + // Copy all files as zip into the output directory + if (artifacts != null && !artifacts.isEmpty()) { + Filehandler.copyFilesAsZip(artifacts, outputPath); + } + + // Cleanup garbage files and container + model.cleanUp(); - // Copy all files as zip into the output directory - if (artifacts != null && !artifacts.isEmpty()) { - Filehandler.copyFilesAsZip(artifacts, outputPath); + return output; + } catch (Exception e) { + model.cleanUp(); + throw new IOException("Error while running docker tests: " + e.getMessage()); } - // Cleanup garbage files and container - model.cleanUp(); - return output; } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/docker/DockerSubmissionTestTest.java b/backend/app/src/test/java/com/ugent/pidgeon/docker/DockerSubmissionTestTest.java index 54a91551..e0507156 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/docker/DockerSubmissionTestTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/docker/DockerSubmissionTestTest.java @@ -16,10 +16,21 @@ import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; public class DockerSubmissionTestTest { + @AfterEach + void cleanUp() { + File file = new File(System.getProperty("user.dir") + "/tmp/test"); + try { + FileUtils.deleteDirectory(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + File initTestFile(String text, String fileName) { String localFileLocation = System.getProperty("user.dir") + "/tmp/test/" + fileName; File file = new File(localFileLocation); @@ -119,6 +130,8 @@ void templateTest() throws InterruptedException { // Extract subtests List results = result.getSubtestResults(); + stm.cleanUp(); + // Testing for the template parser capabilities assertEquals(results.size(), 2); @@ -136,7 +149,6 @@ void templateTest() throws InterruptedException { assertEquals(results.get(1).getOutput(), "HelloWorld2!\n"); assertTrue(result.isAllowed()); - stm.cleanUp(); } @Test @@ -178,6 +190,7 @@ void zipFileInputTest() throws IOException { DockerTestOutput output = stm.runSubmission("cat /shared/input/helloworld.txt"); // run and check if zipfile was properly received assertEquals( "Hello Happy World!", output.logs.get(0)); + stm.cleanUp(); } @Test diff --git a/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip b/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip index 7f6b66459902f5d45867717018fc02ac0d55bd86..a2b51b546796abf071aebaadb0bf24d5340daca8 100644 GIT binary patch delta 26 gcmZ3)xQLM_z?+#xgn@&DgCQz$B2NGlkc@Ky07cyexc~qF delta 26 gcmZ3)xQLM_z?+#xgn@&DgCQVdB2NGlkc@Ky07Sn9lK=n! From 0e2bee58ebea214f9db7ffe33221795be8392335 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 14:08:52 +0200 Subject: [PATCH 27/40] test getSubmission(s) --- .../controllers/SubmissionController.java | 3 +- .../controllers/SubmissionControllerTest.java | 137 ++++++++++++------ 2 files changed, 94 insertions(+), 46 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index 8127ae82..44fb93c2 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -120,7 +120,6 @@ private DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path out } - } /** @@ -179,7 +178,7 @@ public ResponseEntity getSubmissions(@PathVariable("projectid") long projecti if (groupFeedbackEntity == null) { groupFeedbackJson = null; } else { - groupFeedbackJson = new GroupFeedbackJson(groupFeedbackEntity.getScore(), groupFeedbackEntity.getFeedback(), groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); + groupFeedbackJson = entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity); } SubmissionEntity submission = submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectid, groupId).orElse(null); if (submission == null) { diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java index 01112557..c3c12e0a 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java @@ -1,5 +1,7 @@ package com.ugent.pidgeon.controllers; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; import com.ugent.pidgeon.model.json.DockerTestFeedbackJson; import com.ugent.pidgeon.model.json.GroupFeedbackJson; import com.ugent.pidgeon.model.json.GroupJson; @@ -32,7 +34,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(MockitoExtension.class) @@ -63,6 +67,7 @@ public class SubmissionControllerTest extends ControllerTest { @InjectMocks private SubmissionController submissionController; + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); private SubmissionEntity submission; private List groupIds; @@ -73,73 +78,117 @@ public class SubmissionControllerTest extends ControllerTest { private GroupFeedbackEntity groupFeedbackEntity; private MockMultipartFile mockMultipartFile; private FileEntity fileEntity; + private LastGroupSubmissionJson lastGroupSubmissionJson; @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(submissionController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { - request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); - return request; - })) - .build(); - submission = new SubmissionEntity(1L, 1L, 1L, OffsetDateTime.MIN, true, true); - groupIds = List.of(1L); - submissionJson = new SubmissionJson(1L, "projecturl", "groupurl", 1L, - 1L, "fileurl", true, OffsetDateTime.MIN, "structureFeedback", - new DockerTestFeedbackJson(DockerTestType.NONE, "", true), DockerTestState.running.toString(), "artifacturl"); - groupJson = new GroupJson(1, 1L, "groupname", "groupclusterurl"); - groupFeedbackJson = new GroupFeedbackJson(0F, "feedback", 1L, 1L); - groupEntity = new GroupEntity("groupname", 1L); - groupFeedbackEntity = new GroupFeedbackEntity(1L, 1L, 0F, "feedback"); + setUpController(submissionController); + + submission = new SubmissionEntity(22, 45, 99L, OffsetDateTime.MIN, true, true); + submission.setId(56L); + groupIds = List.of(45L); + submissionJson = new SubmissionJson( + submission.getId(), + "projecturl", + "groupurl", + submission.getProjectId(), + submission.getGroupId(), + "fileurl", + true, + OffsetDateTime.MIN, + "structureFeedback", + new DockerTestFeedbackJson(DockerTestType.NONE, "", true), + DockerTestState.running.toString(), + "artifacturl" + ); + groupEntity = new GroupEntity("groupname", 99L); + groupEntity.setId(submission.getGroupId()); + groupJson = new GroupJson(3, groupEntity.getId(), "groupname", "groupclusterurl"); + + groupFeedbackEntity = new GroupFeedbackEntity(groupEntity.getId(), submission.getProjectId(), 3F, "feedback"); + groupFeedbackJson = new GroupFeedbackJson(groupFeedbackEntity.getScore(), groupFeedbackEntity.getFeedback(), groupFeedbackEntity.getGroupId(), + groupFeedbackEntity.getProjectId()); + byte[] fileContent = "Your file content".getBytes(); mockMultipartFile = new MockMultipartFile("file", "filename.txt", MediaType.TEXT_PLAIN_VALUE, fileContent); fileEntity = new FileEntity("name", "dir/name", 1L); + fileEntity.setId(submission.getFileId()); + + lastGroupSubmissionJson = new LastGroupSubmissionJson( + submissionJson, + groupJson, + groupFeedbackJson + ); } @Test public void testGetSubmission() throws Exception { - when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1")) - .andExpect(status().isOk()); + String url = ApiRoutes.SUBMISSION_BASE_PATH + "/" + submission.getId(); + /* all checks succeed */ + when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); + when(entityToJsonConverter.getSubmissionJson(submission)).thenReturn(submissionJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); - when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1")) + /* User can't get submission */ + when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isIAmATeapot()); } @Test public void testGetSubmissions() throws Exception { - List lastGroupSubmissionJsons = List.of(new LastGroupSubmissionJson(submissionJson, groupJson, groupFeedbackJson)); - when(projectUtil.isProjectAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - when(projectRepository.findGroupIdsByProjectId(anyLong())).thenReturn(groupIds); - when(groupRepository.findById(anyLong())).thenReturn(Optional.of(groupEntity)); - when(entityToJsonConverter.groupEntityToJson(any())).thenReturn(groupJson); - when(groupFeedbackRepository.getGroupFeedback(anyLong(), anyLong())).thenReturn(groupFeedbackEntity); - when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(anyLong(), anyLong())).thenReturn(Optional.of(submission)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions")) - .andExpect(status().isOk()); + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + submission.getProjectId() + "/submissions"; + /* all checks succeed */ + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(projectRepository.findGroupIdsByProjectId(submission.getProjectId())).thenReturn(groupIds); + when(groupRepository.findById(groupIds.get(0))).thenReturn(Optional.of(groupEntity)); + when(entityToJsonConverter.groupEntityToJson(groupEntity)).thenReturn(groupJson); + when(groupFeedbackRepository.getGroupFeedback(groupEntity.getId(), submission.getProjectId())).thenReturn(groupFeedbackEntity); + when(entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity)).thenReturn(groupFeedbackJson); + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(submission.getProjectId(), groupEntity.getId())).thenReturn(Optional.of(submission)); + when(entityToJsonConverter.getSubmissionJson(submission)).thenReturn(submissionJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(lastGroupSubmissionJson)))); - when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(anyLong(), anyLong())).thenReturn(Optional.empty()); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions")) - .andExpect(status().isOk()); + /* no submission */ + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(submission.getProjectId(), groupEntity.getId())).thenReturn(Optional.empty()); + lastGroupSubmissionJson.setSubmission(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(lastGroupSubmissionJson)))); - when(groupFeedbackRepository.getGroupFeedback(anyLong(), anyLong())).thenReturn(null); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions")) - .andExpect(status().isOk()); + /* no feedback */ + when(groupFeedbackRepository.getGroupFeedback(groupEntity.getId(), submission.getProjectId())).thenReturn(null); + lastGroupSubmissionJson.setFeedback(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(lastGroupSubmissionJson)))); - when(groupRepository.findById(anyLong())).thenReturn(Optional.empty()); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions")) + /* Unexpected error */ + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isInternalServerError()); - when(projectUtil.isProjectAdmin(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.BAD_REQUEST, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions")) - .andExpect(status().isBadRequest()); + /* group not found */ + reset(projectUtil); + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(groupRepository.findById(groupIds.get(0))).thenReturn(Optional.empty()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isInternalServerError()); + + /* User can't get project */ + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); - when(projectUtil.isProjectAdmin(anyLong(), any())).thenThrow(new RuntimeException()); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions")) - .andExpect(status().isInternalServerError()); } @Test From 732901b8ea811a10faf9a88d7ab418e4ae459fef Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 17:24:31 +0200 Subject: [PATCH 28/40] submitfile fully tested --- backend/app/artifactPath | 0 backend/app/build.gradle | 2 +- .../controllers/SubmissionController.java | 77 +----- .../com/ugent/pidgeon/util/TestRunner.java | 74 ++++++ .../pidgeon/controllers/ControllerTest.java | 2 + .../controllers/SubmissionControllerTest.java | 225 +++++++++++++++++- 6 files changed, 301 insertions(+), 79 deletions(-) create mode 100644 backend/app/artifactPath create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java diff --git a/backend/app/artifactPath b/backend/app/artifactPath new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/build.gradle b/backend/app/build.gradle index e4a26449..04fabadf 100644 --- a/backend/app/build.gradle +++ b/backend/app/build.gradle @@ -21,7 +21,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'org.postgresql:postgresql' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-config' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' @@ -44,6 +43,7 @@ dependencies { implementation "org.springframework.boot:spring-boot-devtools" testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.mockito:mockito-junit-jupiter:4.0.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index 44fb93c2..eefe1d7f 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -60,67 +60,12 @@ public class SubmissionController { private EntityToJsonConverter entityToJsonConverter; @Autowired private CommonDatabaseActions commonDatabaseActions; - @Autowired - private TestUtil testUtil; - - - private SubmissionTemplateModel.SubmissionResult runStructureTest(ZipFile file, TestEntity testEntity) throws IOException { - // There is no structure test for this project - if(testEntity.getStructureTemplate() == null){ - return null; - } - String structureTemplateString = testEntity.getStructureTemplate(); - - // Parse the file - SubmissionTemplateModel model = new SubmissionTemplateModel(); - model.parseSubmissionTemplate(structureTemplateString); - return model.checkSubmission(file); - } - - private DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path outputPath) throws IOException { - // Get the test file from the server - String testScript = testEntity.getDockerTestScript(); - String testTemplate = testEntity.getDockerTestTemplate(); - String image = testEntity.getDockerImage(); - - // The first script must always be null, otherwise there is nothing to run on the container - if (testScript == null) { - return null; - } - - // Init container and add input files - DockerSubmissionTestModel model = new DockerSubmissionTestModel(image); - try { - - model.addZipInputFiles(file); - DockerOutput output; - - if (testTemplate == null) { - // This docker test is configured in the simple mode (store test console logs) - output = model.runSubmission(testScript); - } else { - // This docker test is configured in the template mode (store json with feedback) - output = model.runSubmissionWithTemplate(testScript, testTemplate); - } - // Get list of artifact files generated on submission - List artifacts = model.getArtifacts(); - - // Copy all files as zip into the output directory - if (artifacts != null && !artifacts.isEmpty()) { - Filehandler.copyFilesAsZip(artifacts, outputPath); - } - - // Cleanup garbage files and container - model.cleanUp(); - - return output; - } catch (Exception e) { - model.cleanUp(); - throw new IOException("Error while running docker tests: " + e.getMessage()); - } + @Autowired + private TestUtil testUtil; + @Autowired + private TestRunner testRunner; - } /** * Function to get a submission by its ID @@ -223,7 +168,8 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P try { //Save the file entry in the database to get the id FileEntity fileEntity = new FileEntity("", "", userId); - long fileid = fileRepository.save(fileEntity).getId(); + fileEntity = fileRepository.save(fileEntity); + long fileid = fileEntity.getId(); OffsetDateTime now = OffsetDateTime.now(); SubmissionEntity submissionEntity = new SubmissionEntity( @@ -259,7 +205,7 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P } else { // Check file structure - structureTestResult = runStructureTest(new ZipFile(savedFile), testEntity); + structureTestResult = testRunner.runStructureTest(new ZipFile(savedFile), testEntity); if (structureTestResult == null) { submission.setStructureFeedback( "No specific structure requested for this project."); @@ -285,11 +231,12 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P submission.setDockerTestState(DockerTestState.running); // run docker tests in background File finalSavedFile = savedFile; + Path artifactPath = Filehandler.getSubmissionAritfactPath(projectid, groupId, submission.getId()); + CompletableFuture.runAsync(() -> { try { // Check if docker tests succeed - DockerOutput dockerOutput = runDockerTest(new ZipFile(finalSavedFile), testEntity, - Filehandler.getSubmissionAritfactPath(projectid, groupId, submission.getId())); + DockerOutput dockerOutput = testRunner.runDockerTest(new ZipFile(finalSavedFile), testEntity, artifactPath); if (dockerOutput == null) { throw new RuntimeException("Error while running docker tests."); } @@ -315,8 +262,8 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P } } - return ResponseEntity.ok(entityToJsonConverter.getSubmissionJson(submissionEntity)); - } catch (IOException ex) { + return ResponseEntity.ok(entityToJsonConverter.getSubmissionJson(submission)); + } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Failed to save submissions on file server."); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java b/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java new file mode 100644 index 00000000..c3a555a2 --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java @@ -0,0 +1,74 @@ +package com.ugent.pidgeon.util; + +import com.ugent.pidgeon.model.submissionTesting.DockerOutput; +import com.ugent.pidgeon.model.submissionTesting.DockerSubmissionTestModel; +import com.ugent.pidgeon.model.submissionTesting.SubmissionTemplateModel; +import com.ugent.pidgeon.postgre.models.TestEntity; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.zip.ZipFile; +import org.springframework.stereotype.Component; + +@Component +public class TestRunner { + + public SubmissionTemplateModel.SubmissionResult runStructureTest( + ZipFile file, TestEntity testEntity) throws IOException { + // There is no structure test for this project + if(testEntity.getStructureTemplate() == null){ + return null; + } + String structureTemplateString = testEntity.getStructureTemplate(); + + // Parse the file + SubmissionTemplateModel model = new SubmissionTemplateModel(); + model.parseSubmissionTemplate(structureTemplateString); + return model.checkSubmission(file); + } + + public DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path outputPath) throws IOException { + // Get the test file from the server + String testScript = testEntity.getDockerTestScript(); + String testTemplate = testEntity.getDockerTestTemplate(); + String image = testEntity.getDockerImage(); + + // The first script must always be null, otherwise there is nothing to run on the container + if (testScript == null) { + return null; + } + + // Init container and add input files + DockerSubmissionTestModel model = new DockerSubmissionTestModel(image); + try { + + model.addZipInputFiles(file); + DockerOutput output; + + if (testTemplate == null) { + // This docker test is configured in the simple mode (store test console logs) + output = model.runSubmission(testScript); + } else { + // This docker test is configured in the template mode (store json with feedback) + output = model.runSubmissionWithTemplate(testScript, testTemplate); + } + // Get list of artifact files generated on submission + List artifacts = model.getArtifacts(); + + // Copy all files as zip into the output directory + if (artifacts != null && !artifacts.isEmpty()) { + Filehandler.copyFilesAsZip(artifacts, outputPath); + } + + // Cleanup garbage files and container + model.cleanUp(); + + return output; + } catch (Exception e) { + model.cleanUp(); + throw new IOException("Error while running docker tests: " + e.getMessage()); + } + } + +} diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java index e863f9e4..b506e8b6 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ControllerTest.java @@ -11,8 +11,10 @@ import org.apache.juli.logging.Log; import org.hibernate.annotations.Check; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java index c3c12e0a..02797b82 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java @@ -7,26 +7,41 @@ import com.ugent.pidgeon.model.json.GroupJson; import com.ugent.pidgeon.model.json.LastGroupSubmissionJson; import com.ugent.pidgeon.model.json.SubmissionJson; +import com.ugent.pidgeon.model.submissionTesting.DockerOutput; +import com.ugent.pidgeon.model.submissionTesting.DockerTestOutput; +import com.ugent.pidgeon.model.submissionTesting.SubmissionTemplateModel.SubmissionResult; import com.ugent.pidgeon.postgre.models.FileEntity; import com.ugent.pidgeon.postgre.models.GroupEntity; import com.ugent.pidgeon.postgre.models.GroupFeedbackEntity; import com.ugent.pidgeon.postgre.models.SubmissionEntity; +import com.ugent.pidgeon.postgre.models.TestEntity; import com.ugent.pidgeon.postgre.models.types.DockerTestState; import com.ugent.pidgeon.postgre.models.types.DockerTestType; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.ugent.pidgeon.postgre.repository.*; import com.ugent.pidgeon.util.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.time.OffsetDateTime; import java.util.List; @@ -34,13 +49,21 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @ExtendWith(MockitoExtension.class) public class SubmissionControllerTest extends ControllerTest { + @Mock private GroupRepository groupRepository; @Mock @@ -53,6 +76,8 @@ public class SubmissionControllerTest extends ControllerTest { private TestRepository testRepository; @Mock private GroupFeedbackRepository groupFeedbackRepository; + @Mock + private TestRunner testRunner; @Mock private SubmissionUtil submissionUtil; @@ -79,7 +104,31 @@ public class SubmissionControllerTest extends ControllerTest { private MockMultipartFile mockMultipartFile; private FileEntity fileEntity; private LastGroupSubmissionJson lastGroupSubmissionJson; + private TestEntity testEntity; + + + public static File createTestFile() throws IOException { + // Create a temporary directory + File tempDir = Files.createTempDirectory("test-dir").toFile(); + + // Create a temporary file within the directory + File tempFile = File.createTempFile("test-file", ".zip", tempDir); + // Create some content to write into the zip file + String content = "Hello, this is a test file!"; + byte[] bytes = content.getBytes(); + + // Write the content into a file inside the zip file + try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempFile))) { + ZipEntry entry = new ZipEntry("test.txt"); + zipOut.putNextEntry(entry); + zipOut.write(bytes); + zipOut.closeEntry(); + } + + // Return the File object representing the zip file + return tempFile; + } @BeforeEach public void setup() { @@ -98,20 +147,23 @@ public void setup() { true, OffsetDateTime.MIN, "structureFeedback", - new DockerTestFeedbackJson(DockerTestType.NONE, "", true), - DockerTestState.running.toString(), + new DockerTestFeedbackJson(DockerTestType.NONE, "", true), + null, "artifacturl" ); groupEntity = new GroupEntity("groupname", 99L); groupEntity.setId(submission.getGroupId()); groupJson = new GroupJson(3, groupEntity.getId(), "groupname", "groupclusterurl"); - groupFeedbackEntity = new GroupFeedbackEntity(groupEntity.getId(), submission.getProjectId(), 3F, "feedback"); - groupFeedbackJson = new GroupFeedbackJson(groupFeedbackEntity.getScore(), groupFeedbackEntity.getFeedback(), groupFeedbackEntity.getGroupId(), + groupFeedbackEntity = new GroupFeedbackEntity(groupEntity.getId(), + submission.getProjectId(), 3F, "feedback"); + groupFeedbackJson = new GroupFeedbackJson(groupFeedbackEntity.getScore(), + groupFeedbackEntity.getFeedback(), groupFeedbackEntity.getGroupId(), groupFeedbackEntity.getProjectId()); byte[] fileContent = "Your file content".getBytes(); - mockMultipartFile = new MockMultipartFile("file", "filename.txt", MediaType.TEXT_PLAIN_VALUE, fileContent); + mockMultipartFile = new MockMultipartFile("file", "filename.txt", + MediaType.TEXT_PLAIN_VALUE, fileContent); fileEntity = new FileEntity("name", "dir/name", 1L); fileEntity.setId(submission.getFileId()); @@ -120,6 +172,14 @@ public void setup() { groupJson, groupFeedbackJson ); + + testEntity = new TestEntity( + "dockerImage", + "dockerTestScript", + "dockerTestTemplate", + "structureTemplate" + ); + } @Test @@ -193,13 +253,152 @@ public void testGetSubmissions() throws Exception { @Test public void testSubmitFile() throws Exception { - //TODO: dit ook een correcte test laten uitvoeren met dummyfiles - when(submissionUtil.checkOnSubmit(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", 1L)); - when(fileRepository.save(any())).thenReturn(fileEntity); - when(submissionRepository.save(any())).thenReturn(submission); - mockMvc.perform(MockMvcRequestBuilders.multipart(ApiRoutes.PROJECT_BASE_PATH + "/1/submit") - .file(mockMultipartFile)) + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + submission.getProjectId() + "/submit"; + /* all checks succeed */ + when(submissionUtil.checkOnSubmit(submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity.getId())); + when(fileRepository.save(argThat( + file -> file.getUploadedBy() == getMockUser().getId() + ))).thenReturn(fileEntity); + when(submissionRepository.save(argThat( + sub -> { + Duration duration = Duration.between(sub.getSubmissionTime(), OffsetDateTime.now()); + return sub.getProjectId() == submission.getProjectId() && + sub.getGroupId() == groupEntity.getId() && + sub.getFileId() == fileEntity.getId() && + duration.getSeconds() < 2; + } + ))).thenReturn(submission); + Path path = Path.of(fileEntity.getPath()); + Path artifactPath = Path.of("artifactPath"); + File file = createTestFile(); + try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { + mockedFileHandler.when(() -> Filehandler.getSubmissionPath(submission.getProjectId(), groupEntity.getId(), submission.getId())).thenReturn(path); + mockedFileHandler.when(() -> Filehandler.saveSubmission(path, mockMultipartFile)).thenReturn(file); + mockedFileHandler.when(() -> Filehandler.getSubmissionAritfactPath(anyLong(), anyLong(), anyLong())).thenReturn(artifactPath); + + when(testRunner.runStructureTest(any(), eq(testEntity))).thenReturn(null); + when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath))).thenReturn(null); + + when(entityToJsonConverter.getSubmissionJson(submission)).thenReturn(submissionJson); + + when(testRepository.findByProjectId(submission.getProjectId())).thenReturn(Optional.of(testEntity)); + when(entityToJsonConverter.getSubmissionJson(submission)).thenReturn(submissionJson); + + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + + /* assertEquals(DockerTestState.running, submission.getDockerTestState()); */ // This executes too quickly so we can't test this + + Thread.sleep(1000); + + // File repository needs to save again after setting path + verify(fileRepository, times(1)).save(argThat( + f -> f.getId() == fileEntity.getId() && f.getPath().equals(fileEntity.getPath()) + )); + + // Submissions should be update 3 times, once for the initial save, once for structuretest, once for docker test. + // The first one is being checked by the when(...) + verify(submissionRepository, times(2)).save(argThat( + s -> s.getId() == submission.getId() + )); + + assertEquals(DockerTestState.aborted, submission.getDockerTestState()); + + /* structuretestResult isn't null */ + submission.setStructureAccepted(false); + submission.setStructureFeedback(""); + SubmissionResult submissionResult = new SubmissionResult(true, "structureFeedback-test"); + when(testRunner.runStructureTest(any(), eq(testEntity))).thenReturn(submissionResult); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + + assertTrue(submission.getStructureAccepted()); + assertEquals("structureFeedback-test", submission.getStructureFeedback()); + + /* Correctly updates the dockertype */ + testEntity.setDockerTestTemplate("dockerTestTemplate"); + testEntity.setDockerTestScript("dockerTestScript"); + submission.setDockerType(DockerTestType.NONE); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + assertEquals(DockerTestType.TEMPLATE, submission.getDockerTestType()); + + testEntity.setDockerTestTemplate(null); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + assertEquals(DockerTestType.SIMPLE, submission.getDockerTestType()); + + testEntity.setDockerTestScript(null); + testEntity.setDockerTestTemplate(null); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + assertEquals(DockerTestType.NONE, submission.getDockerTestType()); + + /* A valid docker result is returned */ + testEntity.setDockerImage("dockerImage"); + testEntity.setDockerTestScript("dockerTestScript"); + DockerOutput dockerOutput = new DockerTestOutput( List.of("dockerFeedback-test"), true); + when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath))).thenReturn(dockerOutput); + submission.setDockerAccepted(false); + submission.setDockerFeedback("dockerFeedback-test"); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + + Thread.sleep(1000); + + assertTrue(submission.getDockerAccepted()); + assertEquals("dockerFeedback-test", submission.getDockerFeedback()); + assertEquals(DockerTestState.finished, submission.getDockerTestState()); + + /* No testEntity */ + when(testRepository.findByProjectId(submission.getProjectId())).thenReturn(Optional.empty()); + reset(testRunner); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); + verify(testRunner, times(0)).runStructureTest(any(), eq(testEntity)); + verify(testRunner, times(0)).runDockerTest(any(), eq(testEntity), eq(artifactPath)); + + /* Unexpected error */ + reset(fileRepository); + when(fileRepository.save(any())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) .andExpect(status().isInternalServerError()); + + /* CheckOnSUbmit fails */ + when(submissionUtil.checkOnSubmit(submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(mockMultipartFile)) + .andExpect(status().isIAmATeapot()); + + + + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } // @Test From 548b3b1f994e30f88070e7cb09529760a5a56b2f Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 18:13:46 +0200 Subject: [PATCH 29/40] SubmissionController fully tested --- .../controllers/SubmissionController.java | 2 +- .../controllers/SubmissionControllerTest.java | 157 +++++++++++++----- .../DockerSubmissionTestTest/d__test.zip | Bin 162 -> 162 bytes 3 files changed, 115 insertions(+), 44 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index eefe1d7f..b0518729 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -332,7 +332,7 @@ public ResponseEntity getSubmissionArtifacts(@PathVariable("submissionid") lo } // Set headers for the response HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + zipFile.getFilename()); + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=artifacts.zip" ); headers.add(HttpHeaders.CONTENT_TYPE, "application/zip"); return ResponseEntity.ok() diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java index 02797b82..602a6b06 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java @@ -38,6 +38,9 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; @@ -58,6 +61,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -401,60 +405,127 @@ public void testSubmitFile() throws Exception { } -// @Test -// public void testGetSubmissionFile() throws Exception { -// //TODO: dit ook een correcte test laten uitvoeren met dummyfiles -// when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); -// when(fileRepository.findById(anyLong())).thenReturn(Optional.of(fileEntity)); -// mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1/file")) -// .andExpect(status().isInternalServerError()); -// -// when(fileRepository.findById(anyLong())).thenReturn(Optional.empty()); -// mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1/file")) -// .andExpect(status().isNotFound()); -// -// when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "", null)); -// mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1/file")) -// .andExpect(status().isForbidden()); -// } - -// @Test -// public void testGetStructureFeedback() throws Exception { -// when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); -// mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1/structurefeedback")) -// .andExpect(status().isOk()); -// -// when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); -// mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1/structurefeedback")) -// .andExpect(status().isIAmATeapot()); -// } - -// @Test -// public void testGetDockerFeedback() throws Exception { -// when(submissionUtil.canGetSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); -// mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.SUBMISSION_BASE_PATH + "/1/dockerfeedback")) -// .andExpect(status().isOk()); -// } + @Test + public void testGetSubmissionFile() throws Exception { + try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { + String url = ApiRoutes.SUBMISSION_BASE_PATH + "/" + submission.getId() + "/file"; + Path path = Path.of(fileEntity.getPath()); + File file = createTestFile(); + Resource mockedResource = new FileSystemResource(file); + + /* all checks succeed */ + when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); + when(fileRepository.findById(submission.getFileId())).thenReturn(Optional.of(fileEntity)); + mockedFileHandler.when(() -> Filehandler.getFileAsResource(path)).thenReturn(mockedResource); + + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string( + HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileEntity.getName())) + .andExpect(content().bytes(mockedResource.getInputStream().readAllBytes())); + + /* Resource not found */ + mockedFileHandler.when(() -> Filehandler.getFileAsResource(path)).thenReturn(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isNotFound()); + + /* file not found */ + when(fileRepository.findById(submission.getFileId())).thenReturn(Optional.empty()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isNotFound()); + + /* Unexpected error */ + when(fileRepository.findById(submission.getFileId())).thenReturn(Optional.of(fileEntity)); + mockedFileHandler.reset(); + mockedFileHandler.when(() -> Filehandler.getFileAsResource(path)).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isInternalServerError()); + + /* User can't get submission */ + when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); + } + } + + @Test + public void testGetSubmissionArtifacts() throws Exception { + try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { + String url = ApiRoutes.SUBMISSION_BASE_PATH + "/" + submission.getId() + "/artifacts"; + Path path = Path.of("artifactPath"); + File file = createTestFile(); + Resource mockedResource = new FileSystemResource(file); + + /* all checks succeed */ + when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); + mockedFileHandler.when(() -> Filehandler.getSubmissionAritfactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())).thenReturn(path); + mockedFileHandler.when(() -> Filehandler.getFileAsResource(path)).thenReturn(mockedResource); + + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string( + HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=artifacts.zip")) + .andExpect(content().bytes(mockedResource.getInputStream().readAllBytes())); + + + /* Resource not found */ + mockedFileHandler.when(() -> Filehandler.getFileAsResource(path)).thenReturn(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isNotFound()); + + /* Unexpected error */ + mockedFileHandler.reset(); + mockedFileHandler.when(() -> Filehandler.getSubmissionAritfactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isInternalServerError()); + + /* User can't get submission */ + when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); + } + } @Test public void testDeleteSubmissionById() throws Exception { - when(submissionUtil.canDeleteSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.SUBMISSION_BASE_PATH + "/1")) + String url = ApiRoutes.SUBMISSION_BASE_PATH + "/" + submission.getId(); + /* all checks succeed */ + when(submissionUtil.canDeleteSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isOk()); - when(submissionUtil.canDeleteSubmission(anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.delete(ApiRoutes.SUBMISSION_BASE_PATH + "/1")) + verify(commonDatabaseActions, times(1)).deleteSubmissionById(submission.getId()); + + /* User can't delete submission */ + when(submissionUtil.canDeleteSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) .andExpect(status().isIAmATeapot()); } @Test public void testGetSubmissionsForGroup() throws Exception { - when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions/1")) - .andExpect(status().isOk()); + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + submission.getProjectId() + "/submissions/" + groupEntity.getId(); + /* all checks succeed */ + when(groupUtil.canGetProjectGroupData(groupEntity.getId(), submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + when(submissionRepository.findByProjectIdAndGroupId(submission.getProjectId(), groupEntity.getId())).thenReturn(List.of(submission)); + when(entityToJsonConverter.getSubmissionJson(submission)).thenReturn(submissionJson); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(List.of(submissionJson)))); - when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); - mockMvc.perform(MockMvcRequestBuilders.get(ApiRoutes.PROJECT_BASE_PATH + "/1/submissions/1")) + /* No submissions */ + when(submissionRepository.findByProjectIdAndGroupId(submission.getProjectId(), groupEntity.getId())).thenReturn(List.of()); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json("[]")); + + /* User can't get group */ + when(groupUtil.canGetProjectGroupData(groupEntity.getId(), submission.getProjectId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isIAmATeapot()); } } \ No newline at end of file diff --git a/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip b/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip index a2b51b546796abf071aebaadb0bf24d5340daca8..64c7396e29e5ceba874c38b87d47d33d039aad2b 100644 GIT binary patch delta 26 gcmZ3)xQLM_z?+#xgn@&DgJJH(i97*JKr+q+08Fh0p8x;= delta 26 gcmZ3)xQLM_z?+#xgn@&DgCQz$B2NGlkc@Ky07cyexc~qF From cf4d2b744927b288e85c78581bb002b7e309656b Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 19:32:15 +0200 Subject: [PATCH 30/40] Update altertest to make it easier to test --- .../pidgeon/controllers/TestController.java | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java index 06f6ddc6..305f1b02 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java @@ -118,12 +118,6 @@ private ResponseEntity alterTests( structureTemplate = null; } - /* LOg arguments even if null */ - System.out.println("dockerImage: " + dockerImage); - System.out.println("dockerScript: " + dockerScript); - System.out.println("dockerTemplate: " + dockerTemplate); - System.out.println("structureTemplate: " + structureTemplate); - CheckResult> updateCheckResult = testUtil.checkForTestUpdate(projectId, user, dockerImage, dockerScript, dockerTemplate, httpMethod); @@ -140,35 +134,35 @@ private ResponseEntity alterTests( } // Docker test - if(!(dockerImage == null && dockerScript == null && dockerTemplate == null)) { + if(dockerImage != null) { // update/install image if possible, do so in a seperate thread to reduce wait time. String finalDockerImage = dockerImage; CompletableFuture.runAsync(() -> { - if (finalDockerImage != null) { DockerSubmissionTestModel.installImage(finalDockerImage); - } }); + } - //Update fields - if (dockerImage != null || !httpMethod.equals(HttpMethod.PATCH)) { - testEntity.setDockerImage(dockerImage); - if (!testRepository.imageIsUsed(dockerImage)) { - // Do it on a different thread - String finalDockerImage1 = dockerImage; - CompletableFuture.runAsync(() -> { - DockerSubmissionTestModel.removeDockerImage( - finalDockerImage1); - }); - } + String oldDockerImage = testEntity.getDockerImage(); + + //Update fields + if (dockerImage != null || !httpMethod.equals(HttpMethod.PATCH)) { + testEntity.setDockerImage(dockerImage); + if (!testRepository.imageIsUsed(dockerImage)) { + // Do it on a different thread + String finalDockerImage1 = dockerImage; + CompletableFuture.runAsync(() -> { + DockerSubmissionTestModel.removeDockerImage( + finalDockerImage1); + }); } + } - if (dockerScript != null || !httpMethod.equals(HttpMethod.PATCH)) { - testEntity.setDockerTestScript(dockerScript); - } - if (dockerTemplate != null || !httpMethod.equals(HttpMethod.PATCH)) { - testEntity.setDockerTestTemplate(dockerTemplate); - } + if (dockerScript != null || !httpMethod.equals(HttpMethod.PATCH)) { + testEntity.setDockerTestScript(dockerScript); + } + if (dockerTemplate != null || !httpMethod.equals(HttpMethod.PATCH)) { + testEntity.setDockerTestTemplate(dockerTemplate); } if (structureTemplate != null || !httpMethod.equals(HttpMethod.PATCH)) { @@ -179,6 +173,18 @@ private ResponseEntity alterTests( projectEntity.setTestId(testEntity.getId()); projectRepository.save(projectEntity); // make sure to update test id in project + // Uninstall dockerimage if necessary + if (oldDockerImage != null && !httpMethod.equals(HttpMethod.PATCH)) { + if (!testRepository.imageIsUsed(oldDockerImage)) { + // Do it on a different thread + String finalDockerImage1 = oldDockerImage; + CompletableFuture.runAsync(() -> { + DockerSubmissionTestModel.removeDockerImage( + finalDockerImage1); + }); + } + } + return ResponseEntity.ok(entityToJsonConverter.testEntityToTestJson(testEntity, projectId)); } From ec27a13e21d99d8ecb3e8e84da8cdc2573e24c74 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 20:07:46 +0200 Subject: [PATCH 31/40] AlterTest through PUT/PATCH/POST tested --- .../pidgeon/controllers/TestController.java | 2 +- .../controllers/TestControllerTest.java | 484 +++++++++++++++++- 2 files changed, 476 insertions(+), 10 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java index 305f1b02..e17a9849 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java @@ -174,7 +174,7 @@ private ResponseEntity alterTests( projectRepository.save(projectEntity); // make sure to update test id in project // Uninstall dockerimage if necessary - if (oldDockerImage != null && !httpMethod.equals(HttpMethod.PATCH)) { + if (oldDockerImage != null) { if (!testRepository.imageIsUsed(oldDockerImage)) { // Do it on a different thread String finalDockerImage1 = oldDockerImage; diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java index ce87d826..b5c038a5 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java @@ -1,16 +1,29 @@ package com.ugent.pidgeon.controllers; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ugent.pidgeon.CustomObjectMapper; +import com.ugent.pidgeon.model.json.TestJson; import com.ugent.pidgeon.postgre.models.GroupEntity; +import com.ugent.pidgeon.postgre.models.ProjectEntity; +import com.ugent.pidgeon.postgre.models.TestEntity; import com.ugent.pidgeon.postgre.repository.FileRepository; import com.ugent.pidgeon.postgre.repository.ProjectRepository; import com.ugent.pidgeon.postgre.repository.TestRepository; +import com.ugent.pidgeon.util.CheckResult; +import com.ugent.pidgeon.util.EntityToJsonConverter; import com.ugent.pidgeon.util.Filehandler; +import com.ugent.pidgeon.util.Pair; +import com.ugent.pidgeon.util.ProjectUtil; +import com.ugent.pidgeon.util.TestUtil; +import java.time.OffsetDateTime; import org.junit.jupiter.api.BeforeEach; 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 org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -23,30 +36,483 @@ import java.nio.file.Paths; import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @ExtendWith(MockitoExtension.class) public class TestControllerTest extends ControllerTest{ @Mock - private ProjectRepository projectRepository; + private TestUtil testUtil; @Mock - private FileRepository fileRepository; + private TestRepository testRepository; + @Mock + private ProjectRepository projectRepository; @Mock - private TestRepository testRepository; + private EntityToJsonConverter entityToJsonConverter; + + @InjectMocks - private ControllerTest testController; + private TestController testController; + + + private ObjectMapper objectMapper = CustomObjectMapper.createObjectMapper(); + + private ProjectEntity project; + private TestEntity test; + private TestJson testJson; @BeforeEach public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(testController) - .defaultRequest(MockMvcRequestBuilders.get("/**") - .with(request -> { request.setUserPrincipal(SecurityContextHolder.getContext().getAuthentication()); return request; })) - .build(); + setUpController(testController); + + project = new ProjectEntity( + 67, + "projectName", + "projectDescription", + 5, + 38L, + true, + 34, + OffsetDateTime.now() + ); + project.setId(64); + + test = new TestEntity( + "dockerImageBasic", + "dockerTestScriptBasic", + "dockerTestTemplateBasic", + "structureTemplateBasic" + ); + test.setId(990); + testJson = new TestJson( + "projectUrl", + test.getDockerImage(), + test.getDockerTestScript(), + test.getDockerTestTemplate(), + test.getStructureTemplate() + ); + + when(testRepository.imageIsUsed(any())).thenReturn(true); + } + + @Test + public void testUpdateTest() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; + String dockerImage = "dockerImage"; + String dockerTestScript = "dockerTestScript"; + String dockerTestTemplate = "dockerTestTemplate"; + String structureTemplate = "structureTemplate"; + + TestJson testJson = new TestJson( + "projectUrl", + dockerImage, + dockerTestScript, + dockerTestTemplate, + structureTemplate + ); + /* All checks succeed */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(dockerImage), + eq(dockerTestScript), + eq(dockerTestTemplate), + eq(HttpMethod.POST) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(null, project))); + + when(testRepository.save(argThat( + testEntity -> testEntity.getDockerImage().equals(dockerImage) && + testEntity.getDockerTestScript().equals(dockerTestScript) && + testEntity.getDockerTestTemplate().equals(dockerTestTemplate) && + testEntity.getStructureTemplate().equals(structureTemplate) + ))).thenReturn(test); + + when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); + + mockMvc.perform(MockMvcRequestBuilders.post(url) + .param("dockerimage", dockerImage) + .param("dockerscript", dockerTestScript) + .param("dockertemplate", dockerTestTemplate) + .param("structuretest", structureTemplate) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + verify(projectRepository, times(1)).save(project); + assertEquals(test.getId(), project.getTestId()); + + /* fields are blank */ + String dockerImageBlank = ""; + String dockerTestScriptBlank = ""; + String dockerTemplateBlank = ""; + String structureTemplateBlank = ""; + + testJson = new TestJson( + "projectUrl", + null, + null, + null, + null + ); + reset(testUtil); + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(null), + eq(null), + eq(HttpMethod.POST) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(null, project))); + + reset(testRepository); + when(testRepository.save(argThat( + testEntity -> testEntity.getDockerImage() == null && + testEntity.getDockerTestScript() == null && + testEntity.getDockerTestTemplate() == null && + testEntity.getStructureTemplate() == null + ))).thenReturn(test); + + when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); + + mockMvc.perform(MockMvcRequestBuilders.post(url) + .param("dockerimage", dockerImageBlank) + .param("dockerscript", dockerTestScriptBlank) + .param("dockertemplate", dockerTemplateBlank) + .param("structuretest", structureTemplateBlank) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + /* Fields are null */ + String dockerImageNull = null; + String dockerTestScriptNull = null; + String dockerTemplateNull = null; + String structureTemplateNull = null; + + mockMvc.perform(MockMvcRequestBuilders.post(url) + .param("dockerimage", dockerImageNull) + .param("dockerscript", dockerTestScriptNull) + .param("dockertemplate", dockerTemplateNull) + .param("structuretest", structureTemplateNull) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + /* Check fails */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(dockerImage), + eq(dockerTestScript), + eq(dockerTestTemplate), + eq(HttpMethod.POST) + )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + + mockMvc.perform(MockMvcRequestBuilders.post(url) + .param("dockerimage", dockerImage) + .param("dockerscript", dockerTestScript) + .param("dockertemplate", dockerTestTemplate) + .param("structuretest", structureTemplate) + ).andExpect(status().isIAmATeapot()); + } + + @Test + public void testPutTest() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; + + String originalDockerImage = test.getDockerImage(); + String originalDockerTestScript = test.getDockerTestScript(); + String originalDockerTestTemplate = test.getDockerTestTemplate(); + String originalStructureTemplate = test.getStructureTemplate(); + + String dockerImage = "dockerImage"; + String dockerTestScript = "dockerTestScript"; + String dockerTestTemplate = "dockerTestTemplate"; + String structureTemplate = "structureTemplate"; + + test.setDockerImage(null); + test.setDockerTestScript(null); + test.setDockerTestTemplate(null); + + TestJson testJson = new TestJson( + "projectUrl", + dockerImage, + dockerTestScript, + dockerTestTemplate, + structureTemplate + ); + /* All checks succeed */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(dockerImage), + eq(dockerTestScript), + eq(dockerTestTemplate), + eq(HttpMethod.PUT) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + + when(testRepository.save(test)).thenReturn(test); + + when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); + + mockMvc.perform(MockMvcRequestBuilders.put(url) + .param("dockerimage", dockerImage) + .param("dockerscript", dockerTestScript) + .param("dockertemplate", dockerTestTemplate) + .param("structuretest", structureTemplate) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + verify(projectRepository, times(1)).save(project); + assertEquals(test.getId(), project.getTestId()); + assertEquals(dockerImage, test.getDockerImage()); + assertEquals(dockerTestScript, test.getDockerTestScript()); + assertEquals(dockerTestTemplate, test.getDockerTestTemplate()); + assertEquals(structureTemplate, test.getStructureTemplate()); + + test.setDockerImage(originalDockerImage); + test.setDockerTestScript(originalDockerTestScript); + test.setDockerTestTemplate(originalDockerTestTemplate); + + /* fields are blank */ + String dockerImageBlank = ""; + String dockerTestScriptBlank = ""; + String dockerTemplateBlank = ""; + String structureTemplateBlank = ""; + + testJson = new TestJson( + "projectUrl", + null, + null, + null, + null + ); + reset(testUtil); + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(null), + eq(null), + eq(HttpMethod.PUT) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + + reset(testRepository); + when(testRepository.save(argThat( + testEntity -> testEntity.getDockerImage() == null && + testEntity.getDockerTestScript() == null && + testEntity.getDockerTestTemplate() == null && + testEntity.getStructureTemplate() == null + ))).thenReturn(test); + + when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); + + mockMvc.perform(MockMvcRequestBuilders.put(url) + .param("dockerimage", dockerImageBlank) + .param("dockerscript", dockerTestScriptBlank) + .param("dockertemplate", dockerTemplateBlank) + .param("structuretest", structureTemplateBlank) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + assertNull(test.getDockerImage()); + assertNull(test.getDockerTestScript()); + assertNull(test.getDockerTestTemplate()); + assertNull(test.getStructureTemplate()); + + test.setDockerImage(originalDockerImage); + test.setDockerTestScript(originalDockerTestScript); + test.setDockerTestTemplate(originalDockerTestTemplate); + test.setStructureTemplate(originalStructureTemplate); + + /* Fields are null */ + String dockerImageNull = null; + String dockerTestScriptNull = null; + String dockerTemplateNull = null; + String structureTemplateNull = null; + + when(testRepository.imageIsUsed(any())).thenReturn(true); + + mockMvc.perform(MockMvcRequestBuilders.put(url) + .param("dockerimage", dockerImageNull) + .param("dockerscript", dockerTestScriptNull) + .param("dockertemplate", dockerTemplateNull) + .param("structuretest", structureTemplateNull) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + assertNull(test.getDockerImage()); + assertNull(test.getDockerTestScript()); + assertNull(test.getDockerTestTemplate()); + assertNull(test.getStructureTemplate()); + + test.setDockerImage(originalDockerImage); + test.setDockerTestScript(originalDockerTestScript); + test.setDockerTestTemplate(originalDockerTestTemplate); + test.setStructureTemplate(originalStructureTemplate); + + /* Check fails */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(dockerImage), + eq(dockerTestScript), + eq(dockerTestTemplate), + eq(HttpMethod.PUT) + )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + + mockMvc.perform(MockMvcRequestBuilders.put(url) + .param("dockerimage", dockerImage) + .param("dockerscript", dockerTestScript) + .param("dockertemplate", dockerTestTemplate) + .param("structuretest", structureTemplate) + ).andExpect(status().isIAmATeapot()); + + } + + @Test + public void testGetPatch() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; + + String dockerImage = "dockerImage"; + String dockerTestScript = "dockerTestScript"; + String dockerTestTemplate = "dockerTestTemplate"; + String structureTemplate = "structureTemplate"; + + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(dockerImage), + eq(null), + eq(null), + eq(HttpMethod.PATCH) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + + when(testRepository.save(test)).thenReturn(test); + when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); + + /* Start with test all null, fill them in one by one */ + test.setDockerImage(null); + test.setDockerTestScript(null); + test.setDockerTestTemplate(null); + test.setStructureTemplate(null); + + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .param("dockerimage", dockerImage) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + assertEquals(dockerImage, test.getDockerImage()); + assertNull(test.getDockerTestScript()); + assertNull(test.getDockerTestTemplate()); + assertNull(test.getStructureTemplate()); + + verify(projectRepository, times(1)).save(project); + assertEquals(test.getId(), project.getTestId()); + + reset(testUtil); + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(dockerTestScript), + eq(null), + eq(HttpMethod.PATCH) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + + + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .param("dockerscript", dockerTestScript) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + assertEquals(dockerImage, test.getDockerImage()); + assertEquals(dockerTestScript, test.getDockerTestScript()); + assertNull(test.getDockerTestTemplate()); + assertNull(test.getStructureTemplate()); + + verify(projectRepository, times(2)).save(project); + assertEquals(test.getId(), project.getTestId()); + + reset(testUtil); + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(null), + eq(dockerTestTemplate), + eq(HttpMethod.PATCH) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .param("dockertemplate", dockerTestTemplate) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + assertEquals(dockerImage, test.getDockerImage()); + assertEquals(dockerTestScript, test.getDockerTestScript()); + assertEquals(dockerTestTemplate, test.getDockerTestTemplate()); + assertNull(test.getStructureTemplate()); + + verify(projectRepository, times(3)).save(project); + assertEquals(test.getId(), project.getTestId()); + + reset(testUtil); + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(null), + eq(null), + eq(HttpMethod.PATCH) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .param("structuretest", structureTemplate) + ).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + assertEquals(dockerImage, test.getDockerImage()); + assertEquals(dockerTestScript, test.getDockerTestScript()); + assertEquals(dockerTestTemplate, test.getDockerTestTemplate()); + assertEquals(structureTemplate, test.getStructureTemplate()); + + verify(projectRepository, times(4)).save(project); + assertEquals(test.getId(), project.getTestId()); + + /* Check fails */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(dockerImage), + eq(null), + eq(null), + eq(HttpMethod.PATCH) + )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + + mockMvc.perform(MockMvcRequestBuilders.patch(url) + .param("dockerimage", dockerImage) + ).andExpect(status().isIAmATeapot()); + } - //TODO: tests schrijven eens de backend stabiel is. } From ef31dfe5c236086033a6ded9ea68b1b3ddfd9fe5 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 20:18:10 +0200 Subject: [PATCH 32/40] TestController fully tested --- .../controllers/TestControllerTest.java | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java index b5c038a5..824cc19f 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java @@ -10,6 +10,7 @@ import com.ugent.pidgeon.postgre.repository.ProjectRepository; import com.ugent.pidgeon.postgre.repository.TestRepository; import com.ugent.pidgeon.util.CheckResult; +import com.ugent.pidgeon.util.CommonDatabaseActions; import com.ugent.pidgeon.util.EntityToJsonConverter; import com.ugent.pidgeon.util.Filehandler; import com.ugent.pidgeon.util.Pair; @@ -62,6 +63,8 @@ public class TestControllerTest extends ControllerTest{ @Mock private EntityToJsonConverter entityToJsonConverter; + @Mock + private CommonDatabaseActions commonDatabaseActions; @InjectMocks @@ -105,11 +108,11 @@ public void setup() { test.getStructureTemplate() ); - when(testRepository.imageIsUsed(any())).thenReturn(true); } @Test public void testUpdateTest() throws Exception { + when(testRepository.imageIsUsed(any())).thenReturn(true); String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; String dockerImage = "dockerImage"; String dockerTestScript = "dockerTestScript"; @@ -231,6 +234,7 @@ public void testUpdateTest() throws Exception { @Test public void testPutTest() throws Exception { + when(testRepository.imageIsUsed(any())).thenReturn(true); String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; String originalDockerImage = test.getDockerImage(); @@ -388,6 +392,7 @@ public void testPutTest() throws Exception { @Test public void testGetPatch() throws Exception { + when(testRepository.imageIsUsed(any())).thenReturn(true); String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; String dockerImage = "dockerImage"; @@ -513,6 +518,77 @@ public void testGetPatch() throws Exception { mockMvc.perform(MockMvcRequestBuilders.patch(url) .param("dockerimage", dockerImage) ).andExpect(status().isIAmATeapot()); + } + + @Test + public void testGetTest() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; + + /* All checks succeed */ + when(testUtil.getTestWithAdminStatus(project.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(test, true))); + + when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); + + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + /* Check succeed but user isn't admin */ + when(testUtil.getTestWithAdminStatus(project.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(test, false))); + + testJson.setDockerImage(null); + testJson.setDockerScript(null); + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().json(objectMapper.writeValueAsString(testJson))); + + /* Check fails */ + when(testUtil.getTestWithAdminStatus(project.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + + mockMvc.perform(MockMvcRequestBuilders.get(url)); + } + + @Test + public void testDeleteTest() throws Exception { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/tests"; + + /* All checks succeed */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(null), + eq(null), + eq(HttpMethod.DELETE) + )).thenReturn(new CheckResult<>(HttpStatus.OK, "", new Pair<>(test, project))); + + when(commonDatabaseActions.deleteTestById(project, test)).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isOk()); + + /* Deleting fails */ + when(commonDatabaseActions.deleteTestById(project, test)).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isIAmATeapot()); + + /* Check fails */ + when(testUtil.checkForTestUpdate( + eq(project.getId()), + eq(getMockUser()), + eq(null), + eq(null), + eq(null), + eq(HttpMethod.DELETE) + )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + mockMvc.perform(MockMvcRequestBuilders.delete(url)) + .andExpect(status().isIAmATeapot()); } } From c49f868e5ad664a7a16397c426736264a1ae473a Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 10 May 2024 22:50:34 +0200 Subject: [PATCH 33/40] Commondatabaseactions tests --- .../pidgeon/util/CommonDatabaseActions.java | 69 +- .../util/CommonDataBaseActionsTest.java | 614 ++++++++++++++++++ 2 files changed, 658 insertions(+), 25 deletions(-) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/CommonDataBaseActionsTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/CommonDatabaseActions.java b/backend/app/src/main/java/com/ugent/pidgeon/util/CommonDatabaseActions.java index a4e64a58..8a91fe92 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/CommonDatabaseActions.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/CommonDatabaseActions.java @@ -40,8 +40,7 @@ public class CommonDatabaseActions { private TestRepository testRepository; @Autowired private FileUtil fileUtil; - @Autowired - private FileRepository fileRepository; + @Autowired private CourseRepository courseRepository; @Autowired @@ -55,17 +54,20 @@ public class CommonDatabaseActions { */ public boolean removeGroup(long groupId) { try { - // Delete the group - groupRepository.deleteGroupUsersByGroupId(groupId); - groupRepository.deleteSubmissionsByGroupId(groupId); - groupRepository.deleteGroupFeedbacksByGroupId(groupId); - groupRepository.deleteById(groupId); - - // update groupcount in cluster - groupClusterRepository.findById(groupId).ifPresent(cluster -> { - cluster.setGroupAmount(cluster.getGroupAmount() - 1); - groupClusterRepository.save(cluster); - }); + GroupEntity group = groupRepository.findById(groupId).orElse(null); + if (group != null) { + // Delete the group + groupRepository.deleteGroupUsersByGroupId(groupId); + groupRepository.deleteSubmissionsByGroupId(groupId); + groupRepository.deleteGroupFeedbacksByGroupId(groupId); + groupRepository.deleteById(groupId); + + // update groupcount in cluster + groupClusterRepository.findById(group.getClusterId()).ifPresent(cluster -> { + cluster.setGroupAmount(cluster.getGroupAmount() - 1); + groupClusterRepository.save(cluster); + }); + } return true; } catch (Exception e) { return false; @@ -109,7 +111,16 @@ public boolean removeIndividualClusterGroup(long courseId, long userId) { } // Find the group of the user Optional groupEntityOptional = groupRepository.groupByClusterAndUser(groupClusterEntity.getId(), userId); - return groupEntityOptional.filter(groupEntity -> removeGroup(groupEntity.getId())).isPresent(); + if (!groupEntityOptional.isPresent()) { + return false; + } + GroupEntity groupEntity = groupEntityOptional.get(); + // Delete the group + removeGroup(groupEntity.getId()); + + groupClusterEntity.setGroupAmount(groupClusterEntity.getGroupAmount() - 1); + groupClusterRepository.save(groupClusterEntity); + return true; } /** @@ -139,7 +150,9 @@ public CheckResult deleteProject(long projectId) { return new CheckResult<>(HttpStatus.NOT_FOUND, "Test not found", null); } CheckResult delRes = deleteTestById(projectEntity, testEntity); - return delRes; + if (!delRes.getStatus().equals(HttpStatus.OK)) { + return delRes; + } } projectRepository.delete(projectEntity); @@ -177,13 +190,18 @@ public CheckResult deleteSubmissionById(long submissionId) { * @return CheckResult with the status of the deletion */ public CheckResult deleteTestById(ProjectEntity projectEntity, TestEntity testEntity) { - projectEntity.setTestId(null); - projectRepository.save(projectEntity); - testRepository.deleteById(testEntity.getId()); - if(!testRepository.imageIsUsed(testEntity.getDockerImage())){ - DockerSubmissionTestModel.removeDockerImage(testEntity.getDockerImage()); + try { + projectEntity.setTestId(null); + projectRepository.save(projectEntity); + testRepository.deleteById(testEntity.getId()); + if(!testRepository.imageIsUsed(testEntity.getDockerImage())){ + DockerSubmissionTestModel.removeDockerImage(testEntity.getDockerImage()); + } + return new CheckResult<>(HttpStatus.OK, "", null); + } catch (Exception e) { + return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while deleting test", null); } - return new CheckResult<>(HttpStatus.OK, "", null); + } /** @@ -194,9 +212,10 @@ public CheckResult deleteTestById(ProjectEntity projectEntity, TestEntity public CheckResult deleteClusterById(long clusterId) { try { for (GroupEntity group : groupRepository.findAllByClusterId(clusterId)) { - // Delete all groupUsers - groupUserRepository.deleteAllByGroupId(group.getId()); - groupRepository.deleteById(group.getId()); + boolean res = removeGroup(group.getId()); + if (!res) { + return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while deleting cluster", null); + } } groupClusterRepository.deleteById(clusterId); return new CheckResult<>(HttpStatus.OK, "", null); @@ -271,7 +290,7 @@ public CheckResult copyGroupCluster(GroupClusterEntity group courseId, groupCluster.getMaxSize(), groupCluster.getName(), - groupCluster.getGroupAmount() + copyGroups ? groupCluster.getGroupAmount() : 0 ); newGroupCluster.setCreatedAt(OffsetDateTime.now()); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/CommonDataBaseActionsTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/CommonDataBaseActionsTest.java new file mode 100644 index 00000000..8fedae77 --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/CommonDataBaseActionsTest.java @@ -0,0 +1,614 @@ +package com.ugent.pidgeon.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.ugent.pidgeon.postgre.models.CourseEntity; +import com.ugent.pidgeon.postgre.models.GroupClusterEntity; +import com.ugent.pidgeon.postgre.models.GroupEntity; +import com.ugent.pidgeon.postgre.models.GroupFeedbackEntity; +import com.ugent.pidgeon.postgre.models.ProjectEntity; +import com.ugent.pidgeon.postgre.models.SubmissionEntity; +import com.ugent.pidgeon.postgre.models.TestEntity; +import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.CourseRelation; +import com.ugent.pidgeon.postgre.repository.CourseRepository; +import com.ugent.pidgeon.postgre.repository.CourseUserRepository; +import com.ugent.pidgeon.postgre.repository.GroupClusterRepository; +import com.ugent.pidgeon.postgre.repository.GroupFeedbackRepository; +import com.ugent.pidgeon.postgre.repository.GroupRepository; +import com.ugent.pidgeon.postgre.repository.GroupUserRepository; +import com.ugent.pidgeon.postgre.repository.ProjectRepository; +import com.ugent.pidgeon.postgre.repository.SubmissionRepository; +import com.ugent.pidgeon.postgre.repository.TestRepository; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; + +@ExtendWith(MockitoExtension.class) +public class CommonDataBaseActionsTest { + + @Mock + private GroupRepository groupRepository; + @Mock + private GroupClusterRepository groupClusterRepository; + @Mock + private GroupUserRepository groupUserRepository; + @Mock + private ProjectRepository projectRepository; + @Mock + private GroupFeedbackRepository groupFeedbackRepository; + @Mock + private SubmissionRepository submissionRepository; + @Mock + private TestRepository testRepository; + @Mock + private CourseUserRepository courseUserRepository; + @Mock + private CourseRepository courseRepository; + + @Mock + private FileUtil fileUtil; + + @Spy + @InjectMocks + private CommonDatabaseActions commonDatabaseActions; + + private GroupClusterEntity groupClusterEntity; + private GroupEntity groupEntity; + private UserEntity userEntity; + private CourseEntity courseEntity; + private ProjectEntity projectEntity; + private GroupFeedbackEntity groupFeedbackEntity; + private SubmissionEntity submissionEntity; + private TestEntity testEntity; + + + @BeforeEach + public void setUp() { + courseEntity = new CourseEntity("name", "description",2024); + courseEntity.setId(9L); + + groupClusterEntity = new GroupClusterEntity( + courseEntity.getId(), + 20, + "clusterName", + 5 + ); + groupClusterEntity.setGroupAmount(5); + groupClusterEntity.setId(9L); + + groupEntity = new GroupEntity( + "groupName", + groupClusterEntity.getId() + ); + groupEntity.setId(4L); + groupEntity.setClusterId(groupClusterEntity.getId()); + + userEntity = new UserEntity(); + userEntity.setId(44L); + + + + testEntity = new TestEntity( + "dockerImageBasic", + "dockerTestScriptBasic", + "dockerTestTemplateBasic", + "structureTemplateBasic" + ); + testEntity.setId(38L); + + projectEntity = new ProjectEntity( + courseEntity.getId(), + "projectName", + "projectDescription", + groupClusterEntity.getId(), + testEntity.getId(), + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(64); + + groupFeedbackEntity = new GroupFeedbackEntity( + groupEntity.getId(), + projectEntity.getId(), + 5.0f, + "feedback" + ); + + submissionEntity = new SubmissionEntity( + 22, + 45, + 99L, + OffsetDateTime.MIN, + true, + true + ); + + + + } + + @Test + public void removeGroup() { + long groupId = groupEntity.getId(); + int originalGroupCount = groupClusterEntity.getGroupAmount(); + + when(groupRepository.findById(groupId)).thenReturn(Optional.of(groupEntity)); + when(groupClusterRepository.findById(groupEntity.getClusterId())).thenReturn(Optional.of(groupClusterEntity)); + + assertTrue(commonDatabaseActions.removeGroup(groupId)); + verify(groupRepository, times(1)).deleteGroupUsersByGroupId(groupId); + verify(groupRepository, times(1)).deleteSubmissionsByGroupId(groupId); + verify(groupRepository, times(1)).deleteGroupFeedbacksByGroupId(groupId); + verify(groupRepository, times(1)).deleteById(groupId); + verify(groupClusterRepository, times(1)).save(groupClusterEntity); + + assertEquals(originalGroupCount - 1, groupClusterEntity.getGroupAmount()); + + /* Group not found */ + when(groupRepository.findById(groupId)).thenReturn(Optional.empty()); + assertTrue(commonDatabaseActions.removeGroup(groupId)); + verify(groupRepository, times(1)).deleteGroupUsersByGroupId(groupId); + verify(groupRepository, times(1)).deleteSubmissionsByGroupId(groupId); + verify(groupRepository, times(1)).deleteGroupFeedbacksByGroupId(groupId); + verify(groupRepository, times(1)).deleteById(groupId); + verify(groupClusterRepository, times(1)).save(groupClusterEntity); + + assertEquals(originalGroupCount - 1, groupClusterEntity.getGroupAmount()); + + /* Unexpected error */ + when(groupRepository.findById(groupId)).thenThrow(new RuntimeException()); + assertFalse(commonDatabaseActions.removeGroup(groupId)); + } + + @Test + public void testCreateNewIndividualClusterGroup () { + int originalGroupCount = groupClusterEntity.getGroupAmount(); + + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.of(groupClusterEntity)); + when(groupRepository.save(argThat( + group -> + group.getClusterId() == groupClusterEntity.getId() && + group.getName().equals(userEntity.getName() + " " + userEntity.getSurname()) + ))).thenReturn(groupEntity); + assertTrue( + commonDatabaseActions.createNewIndividualClusterGroup(courseEntity.getId(), userEntity)); + + verify(groupClusterRepository, times(1)).save(groupClusterEntity); + verify(groupUserRepository, times(1)).save(argThat( + groupUser -> + groupUser.getGroupId() == groupEntity.getId() && + groupUser.getUserId() == userEntity.getId() + )); + assertEquals(originalGroupCount + 1, groupClusterEntity.getGroupAmount()); + + /* Group cluster not found */ + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.empty()); + assertFalse(commonDatabaseActions.createNewIndividualClusterGroup(courseEntity.getId(), userEntity)); + } + + @Test + public void testRemoveIndividualClusterGroup() { + long groupId = groupEntity.getId(); + int originalGroupCount = groupClusterEntity.getGroupAmount(); + + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.of(groupClusterEntity)); + when(groupRepository.groupByClusterAndUser(groupClusterEntity.getId(), userEntity.getId())) + .thenReturn(Optional.of(groupEntity)); + + assertTrue(commonDatabaseActions.removeIndividualClusterGroup(courseEntity.getId(), + userEntity.getId())); + + verify(commonDatabaseActions, times(1)).removeGroup(groupId); + verify(groupClusterRepository, times(1)).save(groupClusterEntity); + assertEquals(originalGroupCount - 1, groupClusterEntity.getGroupAmount()); + + /* Group not found */ + when(groupRepository.groupByClusterAndUser(groupClusterEntity.getId(), userEntity.getId())) + .thenReturn(Optional.empty()); + assertFalse(commonDatabaseActions.removeIndividualClusterGroup(courseEntity.getId(), + userEntity.getId())); + + /* Group cluster not found */ + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.empty()); + assertFalse(commonDatabaseActions.removeIndividualClusterGroup(courseEntity.getId(), + userEntity.getId())); + } + + @Test + public void testDeleteProject() { + List groupFeedbackEntities = List.of(groupFeedbackEntity); + List submissionEntities = List.of(submissionEntity); + when(projectRepository.findById(projectEntity.getId())).thenReturn(Optional.of(projectEntity)); + when(groupFeedbackRepository.findByProjectId(projectEntity.getId())).thenReturn(groupFeedbackEntities); + when(submissionRepository.findByProjectId(projectEntity.getId())).thenReturn(submissionEntities); + doReturn(new CheckResult<>(HttpStatus.OK, "", null)).when(commonDatabaseActions).deleteSubmissionById(submissionEntity.getId()); + when(testRepository.findById(projectEntity.getTestId())).thenReturn(Optional.of(testEntity)); + doReturn(new CheckResult<>(HttpStatus.OK, "", null)).when(commonDatabaseActions).deleteTestById(projectEntity, testEntity); + + CheckResult result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(projectRepository, times(1)).delete(projectEntity); + verify(groupFeedbackRepository, times(1)).deleteAll(groupFeedbackEntities); + + /* No test */ + reset(testRepository); + projectEntity.setTestId(null); + result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(testRepository, times(0)).delete(testEntity); + + + /* Test not found */ + projectEntity.setTestId(testEntity.getId()); + when(testRepository.findById(projectEntity.getTestId())).thenReturn(Optional.empty()); + result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + /* Test deletion failed */ + when(testRepository.findById(projectEntity.getTestId())).thenReturn(Optional.of(testEntity)); + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)).when(commonDatabaseActions).deleteTestById(projectEntity, testEntity); + result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Submission deletion failed */ + reset(commonDatabaseActions); + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)).when(commonDatabaseActions).deleteSubmissionById(submissionEntity.getId()); + result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Project not found */ + when(projectRepository.findById(projectEntity.getId())).thenReturn(Optional.empty()); + result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + /* Unexpected error */ + when(projectRepository.findById(projectEntity.getId())).thenThrow(new RuntimeException()); + result = commonDatabaseActions.deleteProject(projectEntity.getId()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + } + + @Test + public void testDeleteSubmissionById() { + when(submissionRepository.findById(submissionEntity.getId())).thenReturn(Optional.of(submissionEntity)); + when(fileUtil.deleteFileById(submissionEntity.getFileId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + CheckResult result = commonDatabaseActions.deleteSubmissionById(submissionEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(submissionRepository, times(1)).delete(submissionEntity); + + /* File deletion failed */ + when(fileUtil.deleteFileById(submissionEntity.getFileId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + result = commonDatabaseActions.deleteSubmissionById(submissionEntity.getId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Submission not found */ + when(submissionRepository.findById(submissionEntity.getId())).thenReturn(Optional.empty()); + result = commonDatabaseActions.deleteSubmissionById(submissionEntity.getId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + /* Unexpected error */ + when(submissionRepository.findById(submissionEntity.getId())).thenThrow(new RuntimeException()); + result = commonDatabaseActions.deleteSubmissionById(submissionEntity.getId()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + } + + @Test + public void testDeleteTestById() { + when(testRepository.imageIsUsed(testEntity.getDockerImage())).thenReturn(false); + + CheckResult result = commonDatabaseActions.deleteTestById(projectEntity, testEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(testRepository, times(1)).deleteById(testEntity.getId()); + verify(projectRepository, times(1)).save(projectEntity); + assertNull(projectEntity.getTestId()); + + /* Image is used */ + when(testRepository.imageIsUsed(testEntity.getDockerImage())).thenReturn(true); + result = commonDatabaseActions.deleteTestById(projectEntity, testEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Unexpected error */ + when(testRepository.imageIsUsed(testEntity.getDockerImage())).thenThrow(new RuntimeException()); + result = commonDatabaseActions.deleteTestById(projectEntity, testEntity); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + } + + @Test + public void testDeleteClusterById() { + List groupEntities = List.of(groupEntity); + when(groupRepository.findAllByClusterId(groupClusterEntity.getId())).thenReturn(groupEntities); + doReturn(true).when(commonDatabaseActions).removeGroup(groupEntity.getId()); + + CheckResult result = commonDatabaseActions.deleteClusterById(groupClusterEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(groupClusterRepository, times(1)).deleteById(groupClusterEntity.getId()); + + /* Group deletion failed */ + reset(commonDatabaseActions); + doReturn(false).when(commonDatabaseActions).removeGroup(groupEntity.getId()); + result = commonDatabaseActions.deleteClusterById(groupClusterEntity.getId()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + + /* Unexpected error */ + when(groupRepository.findAllByClusterId(groupClusterEntity.getId())).thenThrow(new RuntimeException()); + result = commonDatabaseActions.deleteClusterById(groupClusterEntity.getId()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + } + + @Test + public void testCopyCourse() { + String originalCourseKey = "courseKey"; + courseEntity.setJoinKey(originalCourseKey); + Long newCourseId = 39L; + CourseEntity newCourse = new CourseEntity(courseEntity.getName(), courseEntity.getDescription(), courseEntity.getCourseYear()); + newCourse.setJoinKey("randomnewkey"); + newCourse.setId(newCourseId); + + GroupClusterEntity individualCluster = new GroupClusterEntity( + courseEntity.getId(), 20, "clustername", 5 + ); + when(courseRepository.save(argThat( + course -> + course.getName().equals(courseEntity.getName()) && + course.getDescription().equals(courseEntity.getDescription()) && + course.getCourseYear() == courseEntity.getCourseYear() && + !course.getJoinKey().equals(originalCourseKey) + ))).thenReturn(newCourse); + + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.of(individualCluster)); + + doReturn(new CheckResult<>(HttpStatus.OK, "", individualCluster)) + .when(commonDatabaseActions).copyGroupCluster(individualCluster, newCourseId, false); + + List groupClusterEntities = List.of(groupClusterEntity); + long newGroupClusterId = 42L; + GroupClusterEntity groupClusterCopy = new GroupClusterEntity( + 67L, groupClusterEntity.getGroupAmount(), groupClusterEntity.getName(), groupClusterEntity.getMaxSize() + ); + groupClusterCopy.setId(newGroupClusterId); + when(groupClusterRepository.findClustersWithoutInvidualByCourseId(courseEntity.getId())) + .thenReturn(groupClusterEntities); + doReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterCopy)) + .when(commonDatabaseActions).copyGroupCluster(groupClusterEntity, newCourseId, true); + + List projectEntities = List.of(projectEntity); + projectEntity.setGroupClusterId(groupClusterEntity.getId()); + when(projectRepository.findByCourseId(courseEntity.getId())).thenReturn(projectEntities); + doReturn(new CheckResult<>(HttpStatus.OK, "", null)) + .when(commonDatabaseActions).copyProject(projectEntity, newCourseId, newGroupClusterId); + + CheckResult result = commonDatabaseActions.copyCourse(courseEntity, userEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(courseUserRepository, times(1)).save(argThat( + courseUser -> + courseUser.getCourseId() == result.getData().getId() && + courseUser.getUserId() == userEntity.getId() && + courseUser.getRelation().equals(CourseRelation.creator) + )); + + assertNotEquals(originalCourseKey, result.getData().getJoinKey()); + assertEquals(newCourseId, result.getData().getId()); + + CheckResult failedResult; + /* Copyproject fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(commonDatabaseActions).copyProject(projectEntity, newCourseId, newGroupClusterId); + failedResult = commonDatabaseActions.copyCourse(courseEntity, userEntity.getId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, failedResult.getStatus()); + + /* CopyGroupCluster fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(commonDatabaseActions).copyGroupCluster(groupClusterEntity, newCourseId, true); + failedResult = commonDatabaseActions.copyCourse(courseEntity, userEntity.getId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, failedResult.getStatus()); + + /* CopyIndividualCluster fails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(commonDatabaseActions).copyGroupCluster(individualCluster, newCourseId, false); + failedResult = commonDatabaseActions.copyCourse(courseEntity, userEntity.getId()); + assertEquals(HttpStatus.I_AM_A_TEAPOT, failedResult.getStatus()); + + /* Individual cluster isn't found */ + when(groupClusterRepository.findIndividualClusterByCourseId(courseEntity.getId())).thenReturn( + Optional.empty()); + failedResult = commonDatabaseActions.copyCourse(courseEntity, userEntity.getId()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, failedResult.getStatus()); + + } + + @Test + public void testCopyGroupCluster() { + long newCourseId = 39L; + GroupClusterEntity newGroupCluster = new GroupClusterEntity( + newCourseId, groupClusterEntity.getGroupAmount(), groupClusterEntity.getName(), groupClusterEntity.getMaxSize() + ); + newGroupCluster.setId(42L); + + when(groupClusterRepository.save(argThat( + groupCluster -> + groupCluster.getCourseId() == newCourseId && + groupCluster.getGroupAmount() == groupClusterEntity.getGroupAmount() && + groupCluster.getName().equals(groupClusterEntity.getName()) && + groupCluster.getMaxSize() == groupClusterEntity.getMaxSize() + ))).thenReturn(newGroupCluster); + + List groupEntities = List.of(groupEntity); + when(groupRepository.findAllByClusterId(groupClusterEntity.getId())).thenReturn(groupEntities); + when(groupRepository.save(argThat( + group -> + group.getClusterId() == newGroupCluster.getId() && + group.getName().equals(groupEntity.getName()) + ))).thenReturn(groupEntity); + + CheckResult result = commonDatabaseActions.copyGroupCluster(groupClusterEntity, newCourseId, true); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(newGroupCluster, result.getData()); + + /* Don't copy groups */ + reset(groupClusterRepository); + when(groupClusterRepository.save(argThat( + groupCluster -> + groupCluster.getCourseId() == newCourseId && + groupCluster.getGroupAmount() == 0 && + groupCluster.getName().equals(groupClusterEntity.getName()) && + groupCluster.getMaxSize() == groupClusterEntity.getMaxSize() + ))).thenReturn(newGroupCluster); + result = commonDatabaseActions.copyGroupCluster(groupClusterEntity, newCourseId, false); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(newGroupCluster, result.getData()); + + verify(groupRepository, times(1)).save(any()); + } + + @Test + public void testCopyProject() { + long newCourseId = 39L; + long newGroupClusterId = 42L; + long newProjectId = 99L; + long newTestId = 88L; + testEntity.setId(newTestId); + ProjectEntity newProject = new ProjectEntity( + newCourseId, + projectEntity.getName(), + projectEntity.getDescription(), + newGroupClusterId, + projectEntity.getTestId(), + projectEntity.isVisible(), + projectEntity.getMaxScore(), + projectEntity.getDeadline() + ); + newProject.setId(newProjectId); + + when(projectRepository.save(any())).thenReturn(newProject); + + when(testRepository.findById(projectEntity.getTestId())).thenReturn(Optional.of(testEntity)); + doReturn(new CheckResult<>(HttpStatus.OK, "", testEntity)) + .when(commonDatabaseActions).copyTest(testEntity); + + CheckResult result = commonDatabaseActions.copyProject(projectEntity, newCourseId, newGroupClusterId); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(newProject, result.getData()); + assertEquals(newProjectId, result.getData().getId()); + + + + verify(projectRepository, times(1)).save(argThat( + project -> project.getCourseId() == newCourseId && + project.getName().equals(projectEntity.getName()) && + project.getDescription().equals(projectEntity.getDescription()) && + project.getGroupClusterId() == newGroupClusterId && + Objects.equals(project.getTestId(), null) && + project.isVisible() == projectEntity.isVisible() && + Objects.equals(project.getMaxScore(), projectEntity.getMaxScore()) && + project.getDeadline().equals(projectEntity.getDeadline()) + )); + + verify(projectRepository, times(1)).save(argThat( + project -> + project.getCourseId() == newCourseId && + project.getName().equals(projectEntity.getName()) && + project.getDescription().equals(projectEntity.getDescription()) && + project.getGroupClusterId() == newGroupClusterId && + Objects.equals(project.getTestId(), newTestId) && + project.isVisible() == projectEntity.isVisible() && + Objects.equals(project.getMaxScore(), projectEntity.getMaxScore()) && + project.getDeadline().equals(projectEntity.getDeadline()) + )); + + /* CopyTestFails */ + doReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)) + .when(commonDatabaseActions).copyTest(testEntity); + result = commonDatabaseActions.copyProject(projectEntity, newCourseId, newGroupClusterId); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Test not found */ + when(testRepository.findById(projectEntity.getTestId())).thenReturn(Optional.empty()); + result = commonDatabaseActions.copyProject(projectEntity, newCourseId, newGroupClusterId); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + + /* project has no test */ + reset(projectRepository); + reset(testRepository); + reset(commonDatabaseActions); + when(projectRepository.save(any())).thenReturn(newProject); + projectEntity.setTestId(null); + result = commonDatabaseActions.copyProject(projectEntity, newCourseId, newGroupClusterId); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(projectRepository, times(1)).save(argThat( + project -> project.getCourseId() == newCourseId && + project.getName().equals(projectEntity.getName()) && + project.getDescription().equals(projectEntity.getDescription()) && + project.getGroupClusterId() == newGroupClusterId && + Objects.equals(project.getTestId(), null) && + project.isVisible() == projectEntity.isVisible() && + Objects.equals(project.getMaxScore(), projectEntity.getMaxScore()) && + project.getDeadline().equals(projectEntity.getDeadline()) + )); + verify(testRepository, times(0)).findById(projectEntity.getTestId()); + verify(commonDatabaseActions, times(0)).copyTest(testEntity); + } + + @Test + public void testCopyTest() { + long newTestId = 9088L; + TestEntity newTest = new TestEntity( + testEntity.getDockerImage(), + testEntity.getDockerTestScript(), + testEntity.getDockerTestTemplate(), + testEntity.getStructureTemplate() + ); + newTest.setId(newTestId); + + when(testRepository.save(argThat( + test -> + test.getDockerImage().equals(testEntity.getDockerImage()) && + test.getDockerTestScript().equals(testEntity.getDockerTestScript()) && + test.getDockerTestTemplate().equals(testEntity.getDockerTestTemplate()) && + test.getStructureTemplate().equals(testEntity.getStructureTemplate()) + ))).thenReturn(newTest); + + CheckResult result = commonDatabaseActions.copyTest(testEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(newTest, result.getData()); + assertEquals(newTestId, result.getData().getId()); + } +} From c3c80f51e3d6d197cea642a614c990bf5fa6c529 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sat, 11 May 2024 11:37:44 +0200 Subject: [PATCH 34/40] EntityToJsonConverter test --- .../pidgeon/model/json/GroupClusterJson.java | 3 +- .../pidgeon/model/json/ProjectStatus.java | 18 + .../repository/SubmissionRepository.java | 1 + .../pidgeon/util/EntityToJsonConverter.java | 22 +- .../util/EntityToJsonConverterTest.java | 504 ++++++++++++++++-- 5 files changed, 491 insertions(+), 57 deletions(-) create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectStatus.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/GroupClusterJson.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/GroupClusterJson.java index b78b0d66..30714044 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/json/GroupClusterJson.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/GroupClusterJson.java @@ -13,7 +13,6 @@ public record GroupClusterJson( String courseUrl ) { - public GroupClusterJson { - } + } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectStatus.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectStatus.java new file mode 100644 index 00000000..44a2c70b --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/ProjectStatus.java @@ -0,0 +1,18 @@ +package com.ugent.pidgeon.model.json; + +public enum ProjectStatus { + not_started, + correct, + incorrect, + no_group; + + @Override + public String toString() { + if (this == ProjectStatus.not_started) { + return "not started"; + } else if (this == ProjectStatus.no_group) { + return "no group"; + } + return super.toString(); + } +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/SubmissionRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/SubmissionRepository.java index 0f93ae96..a2df7c9a 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/SubmissionRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/SubmissionRepository.java @@ -27,6 +27,7 @@ SELECT MAX(s2.submissionTime) FROM SubmissionEntity s2 WHERE s2.groupId = :groupId AND s2.projectId = :projectId + AND s2.dockerTestState != :#{T(com.ugent.pidgeon.postgre.models.types.DockerTestState).running.toString()} ) ORDER BY s.id DESC LIMIT 1 """) Optional findLatestsSubmissionIdsByProjectAndGroupId(long projectId, long groupId); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/EntityToJsonConverter.java b/backend/app/src/main/java/com/ugent/pidgeon/util/EntityToJsonConverter.java index 02b98182..716ae1bf 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/EntityToJsonConverter.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/EntityToJsonConverter.java @@ -43,8 +43,11 @@ public class EntityToJsonConverter { public GroupJson groupEntityToJson(GroupEntity groupEntity) { GroupClusterEntity cluster = groupClusterRepository.findById(groupEntity.getClusterId()).orElse(null); + if (cluster == null) { + throw new RuntimeException("Cluster not found"); + } GroupJson group = new GroupJson(cluster.getMaxSize(), groupEntity.getId(), groupEntity.getName(), ApiRoutes.CLUSTER_BASE_PATH + "/" + groupEntity.getClusterId()); - if (cluster != null && cluster.getGroupAmount() > 1){ + if (cluster.getMaxSize() > 1){ group.setGroupClusterUrl(ApiRoutes.CLUSTER_BASE_PATH + "/" + cluster.getId()); } else { group.setGroupClusterUrl(null); @@ -144,23 +147,23 @@ public ProjectResponseJsonWithStatus projectEntityToProjectResponseJsonWithStatu if (groupId == null) { return new ProjectResponseJsonWithStatus( projectEntityToProjectResponseJson(project, course, user), - "no group" + ProjectStatus.no_group.toString() ); } SubmissionEntity sub = submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(project.getId(), groupId).orElse(null); - String status; + ProjectStatus status; if (sub == null) { - status = "not started"; - } else if (sub.getStructureAccepted() && sub.getStructureAccepted()) { - status = "correct"; + status = ProjectStatus.not_started; + } else if (sub.getStructureAccepted() && sub.getDockerAccepted()) { + status = ProjectStatus.correct; } else { - status = "incorrect"; + status = ProjectStatus.incorrect; } return new ProjectResponseJsonWithStatus( projectEntityToProjectResponseJson(project, course, user), - status + status.toString() ); } @@ -182,7 +185,7 @@ public ProjectResponseJson projectEntityToProjectResponseJson(ProjectEntity proj String submissionUrl = ApiRoutes.PROJECT_BASE_PATH + "/" + project.getId() + "/submissions"; CourseUserEntity courseUserEntity = courseUserRepository.findById(new CourseUserId(course.getId(), user.getId())).orElse(null); if (courseUserEntity == null) { - return null; + throw new RuntimeException("User not found in course"); } // GroupId is null if the user is a course_admin/creator @@ -229,7 +232,6 @@ public CourseReferenceJson courseEntityToCourseReference(CourseEntity course) { public SubmissionJson getSubmissionJson(SubmissionEntity submission) { DockerTestFeedbackJson feedback; - TestEntity test = testRepository.findByProjectId(submission.getProjectId()).orElse(null); if (submission.getDockerTestState().equals(DockerTestState.running)) { feedback = null; } else if (submission.getDockerTestType().equals(DockerTestType.NONE)) { diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java index 1dd71d81..e0d5942d 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java @@ -1,30 +1,55 @@ package com.ugent.pidgeon.util; +import com.ugent.pidgeon.controllers.ApiRoutes; import com.ugent.pidgeon.model.ProjectResponseJson; +import com.ugent.pidgeon.model.json.CourseJson; +import com.ugent.pidgeon.model.json.CourseReferenceJson; import com.ugent.pidgeon.model.json.CourseWithInfoJson; +import com.ugent.pidgeon.model.json.CourseWithRelationJson; import com.ugent.pidgeon.model.json.GroupClusterJson; +import com.ugent.pidgeon.model.json.GroupFeedbackJson; +import com.ugent.pidgeon.model.json.GroupFeedbackJsonWithProject; import com.ugent.pidgeon.model.json.GroupJson; +import com.ugent.pidgeon.model.json.ProjectProgressJson; +import com.ugent.pidgeon.model.json.ProjectResponseJsonWithStatus; +import com.ugent.pidgeon.model.json.ProjectStatus; import com.ugent.pidgeon.model.json.SubmissionJson; import com.ugent.pidgeon.model.json.TestJson; import com.ugent.pidgeon.model.json.UserReferenceJson; +import com.ugent.pidgeon.model.json.UserReferenceWithRelation; import com.ugent.pidgeon.postgre.models.*; import com.ugent.pidgeon.postgre.models.types.CourseRelation; +import com.ugent.pidgeon.postgre.models.types.DockerTestState; +import com.ugent.pidgeon.postgre.models.types.DockerTestType; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.*; +import com.ugent.pidgeon.postgre.repository.GroupRepository.UserReference; import java.time.OffsetDateTime; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Collections; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -51,63 +76,210 @@ public class EntityToJsonConverterTest { @Mock private SubmissionRepository submissionRepository; + @Spy @InjectMocks private EntityToJsonConverter entityToJsonConverter; - private GroupEntity groupEntity; private GroupClusterEntity groupClusterEntity; + private GroupEntity groupEntity; private UserEntity userEntity; + private UserEntity otherUser; private CourseEntity courseEntity; private ProjectEntity projectEntity; + private GroupFeedbackEntity groupFeedbackEntity; private SubmissionEntity submissionEntity; private TestEntity testEntity; + + private GroupJson groupJson; + private UserReferenceJson userReferenceJson; + private UserReferenceJson otherUserReferenceJson; + private GroupFeedbackJson groupFeedbackJson; + private ProjectResponseJson projectResponseJson; + private CourseReferenceJson courseJson; + @BeforeEach public void setUp() { - groupEntity = new GroupEntity("test group", 1L); - groupEntity.setId(1L); + courseEntity = new CourseEntity("name", "description",2024); + courseEntity.setJoinKey("joinKey"); + courseEntity.setId(9L); + + groupClusterEntity = new GroupClusterEntity( + courseEntity.getId(), + 20, + "clusterName", + 5 + ); + groupClusterEntity.setGroupAmount(5); + groupClusterEntity.setId(9L); + + groupEntity = new GroupEntity( + "groupName", + groupClusterEntity.getId() + ); + groupEntity.setId(4L); + groupEntity.setClusterId(groupClusterEntity.getId()); + + groupJson = new GroupJson( + 20, + 4L, + "groupName", + ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId() + ); + + userEntity = new UserEntity( + "name", + "surname", + "email", + UserRole.student, + "azureId" + ); + userEntity.setId(44L); + userReferenceJson = new UserReferenceJson( + userEntity.getName() + " " + userEntity.getSurname(), + userEntity.getEmail(), + userEntity.getId() + ); + + otherUser = new UserEntity( + "otherName", + "otherSurname", + "otherEmail", + UserRole.student, + "otherAzureId" + ); + otherUserReferenceJson = new UserReferenceJson( + otherUser.getName() + " " + otherUser.getSurname(), + otherUser.getEmail(), + otherUser.getId() + ); + - groupClusterEntity = new GroupClusterEntity(1L, 5, "Test Cluster", 1); - groupClusterEntity.setId(1L); - userEntity = new UserEntity("name", "surname", "email", UserRole.student, "azureid"); - userEntity.setId(1L); + testEntity = new TestEntity( + "dockerImageBasic", + "dockerTestScriptBasic", + "dockerTestTemplateBasic", + "structureTemplateBasic" + ); + testEntity.setId(38L); - courseEntity = new CourseEntity(); - courseEntity.setId(1L); - courseEntity.setName("Test Course"); - courseEntity.setCourseYear(2024); + projectEntity = new ProjectEntity( + courseEntity.getId(), + "projectName", + "projectDescription", + groupClusterEntity.getId(), + testEntity.getId(), + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(64); - projectEntity = new ProjectEntity(); - projectEntity.setId(1L); - projectEntity.setVisible(true); - projectEntity.setName("Test Project"); + courseJson = new CourseReferenceJson(courseEntity.getName(), "courseUrl", courseEntity.getId(), null); - submissionEntity = new SubmissionEntity(1L, 1L, 1L, OffsetDateTime.now(), true, true); - submissionEntity.setId(1L); - testEntity = new TestEntity(); - testEntity.setId(1L); - testEntity.setDockerImage("Test Docker Image"); + projectResponseJson = new ProjectResponseJson( + courseJson, + projectEntity.getDeadline(), + projectEntity.getDescription(), + projectEntity.getId(), + projectEntity.getName(), + "SubmissionURL", + "TestURL", + projectEntity.getMaxScore(), + projectEntity.isVisible(), + new ProjectProgressJson(44, 60), + groupEntity.getId(), + groupClusterEntity.getId() + ); + + groupFeedbackEntity = new GroupFeedbackEntity( + groupEntity.getId(), + projectEntity.getId(), + 5.0f, + "feedback" + ); + + groupFeedbackJson = new GroupFeedbackJson( + groupFeedbackEntity.getScore(), + groupFeedbackEntity.getFeedback(), + groupFeedbackEntity.getGroupId(), + groupFeedbackEntity.getProjectId() + ); + + submissionEntity = new SubmissionEntity( + 22, + 45, + 99L, + OffsetDateTime.MIN, + true, + true + ); } @Test public void testGroupEntityToJson() { - when(groupClusterRepository.findById(anyLong())).thenReturn(Optional.of(groupClusterEntity)); + when(groupClusterRepository.findById(groupEntity.getClusterId())).thenReturn(Optional.of(groupClusterEntity)); when(groupRepository.findGroupUsersReferencesByGroupId(anyLong())).thenReturn( - Collections.emptyList()); + List.of(new UserReference[]{ + new UserReference() { + @Override + public Long getUserId() { + return userEntity.getId(); + } + + @Override + public String getName() { + return userEntity.getName() + " " + userEntity.getSurname(); + } + + @Override + public String getEmail() { + return userEntity.getEmail(); + } + } + + }) + ); GroupJson result = entityToJsonConverter.groupEntityToJson(groupEntity); + assertEquals(groupClusterEntity.getMaxSize(), result.getCapacity()); + assertEquals(groupEntity.getId(), result.getGroupId()); assertEquals(groupEntity.getName(), result.getName()); + assertEquals(ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId(), result.getGroupClusterUrl()); + assertEquals(1, result.getMembers().size()); + UserReferenceJson userReferenceJson = result.getMembers().get(0); + assertEquals(userEntity.getId(), userReferenceJson.getUserId()); + assertEquals(userEntity.getName() + " " + userEntity.getSurname(), userReferenceJson.getName()); + assertEquals(userEntity.getEmail(), userReferenceJson.getEmail()); + + /* Cluster is individual */ + groupClusterEntity.setMaxSize(1); + result = entityToJsonConverter.groupEntityToJson(groupEntity); + assertEquals(1, result.getCapacity()); + assertNull(result.getGroupClusterUrl()); + + /* Issue when groupClusterEntity is null */ + when(groupClusterRepository.findById(groupEntity.getClusterId())).thenReturn(Optional.empty()); + assertThrows(RuntimeException.class, () -> entityToJsonConverter.groupEntityToJson(groupEntity)); + } @Test public void testClusterEntityToClusterJson() { - when(groupRepository.findAllByClusterId(anyLong())).thenReturn( - Collections.singletonList(groupEntity)); - when(groupClusterRepository.findById(anyLong())).thenReturn(Optional.of(groupClusterEntity)); + when(groupRepository.findAllByClusterId(groupClusterEntity.getId())).thenReturn(List.of(groupEntity)); + doReturn(groupJson).when(entityToJsonConverter).groupEntityToJson(groupEntity); + GroupClusterJson result = entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity); + assertEquals(groupClusterEntity.getId(), result.clusterId()); assertEquals(groupClusterEntity.getName(), result.name()); + assertEquals(groupClusterEntity.getMaxSize(), result.capacity()); + assertEquals(groupClusterEntity.getGroupAmount(), result.groupCount()); + assertEquals(groupClusterEntity.getCreatedAt(), result.createdAt()); + assertEquals(1, result.groups().size()); + assertEquals(groupJson, result.groups().get(0)); + assertEquals(ApiRoutes.COURSE_BASE_PATH + "/" + courseEntity.getId(), result.courseUrl()); } @Test @@ -115,43 +287,285 @@ public void testUserEntityToUserReference() { UserReferenceJson result = entityToJsonConverter.userEntityToUserReference(userEntity); assertEquals(userEntity.getId(), result.getUserId()); assertEquals(userEntity.getName() + " " + userEntity.getSurname(), result.getName()); + assertEquals(userEntity.getEmail(), result.getEmail()); + } + + @Test + public void testUserEntityToUserReferenceWithRelation() { + doReturn(userReferenceJson).when(entityToJsonConverter).userEntityToUserReference(userEntity); + UserReferenceWithRelation result = entityToJsonConverter.userEntityToUserReferenceWithRelation(userEntity, CourseRelation.creator); + assertEquals(userReferenceJson, result.getUser()); + assertEquals(CourseRelation.creator.toString(), result.getRelation()); + + result = entityToJsonConverter.userEntityToUserReferenceWithRelation(userEntity, CourseRelation.course_admin); + assertEquals(CourseRelation.course_admin.toString(), result.getRelation()); + + result = entityToJsonConverter.userEntityToUserReferenceWithRelation(userEntity, CourseRelation.enrolled); + assertEquals(CourseRelation.enrolled.toString(), result.getRelation()); } @Test public void testCourseEntityToCourseWithInfo() { - when(courseRepository.findTeacherByCourseId(anyLong())).thenReturn(userEntity); - when(courseRepository.findAssistantsByCourseId(anyLong())).thenReturn(Collections.emptyList()); - CourseWithInfoJson result = entityToJsonConverter.courseEntityToCourseWithInfo(courseEntity, - "joinLink", false); + String joinLink = "JOIN LINK"; + courseEntity.setArchivedAt(OffsetDateTime.now()); + courseEntity.setCreatedAt(OffsetDateTime.MIN); + + when(courseRepository.findTeacherByCourseId(courseEntity.getId())).thenReturn(userEntity); + when(courseRepository.findAssistantsByCourseId(courseEntity.getId())).thenReturn(List.of(otherUser)); + + doReturn(userReferenceJson).when(entityToJsonConverter).userEntityToUserReference(userEntity); + doReturn(otherUserReferenceJson).when(entityToJsonConverter).userEntityToUserReference(otherUser); + + CourseWithInfoJson result = entityToJsonConverter.courseEntityToCourseWithInfo(courseEntity, joinLink, false); assertEquals(courseEntity.getId(), result.courseId()); assertEquals(courseEntity.getName(), result.name()); + assertEquals(courseEntity.getDescription(), result.description()); + assertEquals(userReferenceJson, result.teacher()); + assertEquals(List.of(otherUserReferenceJson), result.assistants()); + assertEquals(joinLink, result.joinUrl()); + assertEquals(courseEntity.getJoinKey(), result.joinKey()); + assertEquals(courseEntity.getArchivedAt().toInstant(), result.archivedAt().toInstant()); + assertEquals(courseEntity.getCreatedAt().toInstant(), result.createdAt().toInstant()); + assertEquals(courseEntity.getCourseYear(), result.year()); + + /* Hide key */ + result = entityToJsonConverter.courseEntityToCourseWithInfo(courseEntity, joinLink, true); + assertNull(result.joinKey()); + assertNull(result.joinUrl()); + + } + + @Test + public void testCourseEntityToCourseWithRelation() { + + int userCount = 5; + courseEntity.setArchivedAt(OffsetDateTime.now()); + courseEntity.setCreatedAt(OffsetDateTime.MIN); + + when(courseUserRepository.countUsersInCourse(courseEntity.getId())).thenReturn(userCount); + CourseWithRelationJson result = entityToJsonConverter.courseEntityToCourseWithRelation(courseEntity, CourseRelation.creator); + assertEquals(ApiRoutes.COURSE_BASE_PATH + "/" + courseEntity.getId(), result.url()); + assertEquals(CourseRelation.creator, result.relation()); + assertEquals(courseEntity.getName(), result.name()); + assertEquals(courseEntity.getId(), result.courseId()); + assertEquals(courseEntity.getArchivedAt().toInstant(), result.archivedAt().toInstant()); + assertEquals(userCount, result.memberCount()); + assertEquals(courseEntity.getCreatedAt().toInstant(), result.createdAt().toInstant()); + assertEquals(courseEntity.getCourseYear(), result.year()); + + } + + @Test + public void testGroupFeedbackEntityToJson() { + GroupFeedbackJson result = entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity); + assertEquals(groupFeedbackEntity.getScore(), result.getScore()); + assertEquals(groupFeedbackEntity.getFeedback(), result.getFeedback()); + assertEquals(groupFeedbackEntity.getGroupId(), result.getGroupId()); + assertEquals(groupFeedbackEntity.getProjectId(), result.getProjectId()); + } + + @Test + public void testGroupFeedbackEntityToJsonWithProjec() { + doReturn(groupFeedbackJson).when(entityToJsonConverter).groupFeedbackEntityToJson(groupFeedbackEntity); + GroupFeedbackJsonWithProject result = entityToJsonConverter.groupFeedbackEntityToJsonWithProject(groupFeedbackEntity, projectEntity); + assertEquals(projectEntity.getName(), result.getProjectName()); + assertEquals(ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId(), result.getProjectUrl()); + assertEquals(projectEntity.getId(), result.getProjectId()); + assertEquals(groupFeedbackJson, result.getGroupFeedback()); + assertEquals(projectEntity.getMaxScore().intValue(), result.getMaxScore()); + + /* No feedback */ + result = entityToJsonConverter.groupFeedbackEntityToJsonWithProject(null, projectEntity); + assertNull(result.getGroupFeedback()); + } + + @Test + public void testProjectEntityToProjectResponseJsonWithStatus() { + submissionEntity.setDockerAccepted(true); + submissionEntity.setStructureAccepted(true); + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(groupEntity.getId()); + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectEntity.getId(), groupEntity.getId())).thenReturn(Optional.of(submissionEntity)); + + doReturn(projectResponseJson).when(entityToJsonConverter).projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + ProjectResponseJsonWithStatus result = entityToJsonConverter.projectEntityToProjectResponseJsonWithStatus(projectEntity, courseEntity, userEntity); + assertEquals(projectResponseJson, result.project()); + assertEquals(ProjectStatus.correct.toString(), result.status()); + + /* Check different statuses */ + + submissionEntity.setDockerAccepted(false); + result = entityToJsonConverter.projectEntityToProjectResponseJsonWithStatus(projectEntity, courseEntity, userEntity); + assertEquals(ProjectStatus.incorrect.toString(), result.status()); + + submissionEntity.setDockerAccepted(true); + submissionEntity.setStructureAccepted(false); + result = entityToJsonConverter.projectEntityToProjectResponseJsonWithStatus(projectEntity, courseEntity, userEntity); + assertEquals(ProjectStatus.incorrect.toString(), result.status()); + + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectEntity.getId(), groupEntity.getId())).thenReturn(Optional.empty()); + result = entityToJsonConverter.projectEntityToProjectResponseJsonWithStatus(projectEntity, courseEntity, userEntity); + assertEquals(ProjectStatus.not_started.toString(), result.status()); + + /* User not in group yet */ + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(null); + result = entityToJsonConverter.projectEntityToProjectResponseJsonWithStatus(projectEntity, courseEntity, userEntity); + assertEquals(ProjectStatus.no_group.toString(), result.status()); } @Test public void testProjectEntityToProjectResponseJson() { - when(projectRepository.findGroupIdsByProjectId(anyLong())).thenReturn( - Collections.singletonList(1L)); - when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(anyLong(), anyLong())) - .thenReturn(Optional.of(submissionEntity)); - when(courseUserRepository.findById(any())).thenReturn( - Optional.of(new CourseUserEntity(1L, 1L, CourseRelation.enrolled))); - when(groupRepository.groupIdByProjectAndUser(anyLong(), anyLong())).thenReturn(1L); - ProjectResponseJson result = entityToJsonConverter.projectEntityToProjectResponseJson( - projectEntity, courseEntity, userEntity); + GroupEntity secondGroup = new GroupEntity("secondGroup", groupClusterEntity.getId()); + SubmissionEntity secondSubmission = new SubmissionEntity(22, 232, 90L, OffsetDateTime.MIN, true, true); + CourseUserEntity courseUser = new CourseUserEntity(projectEntity.getCourseId(), userEntity.getId(), CourseRelation.creator); + + when(projectRepository.findGroupIdsByProjectId(projectEntity.getId())).thenReturn(List.of(groupEntity.getId(), secondGroup.getId())); + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectEntity.getId(), groupEntity.getId())).thenReturn(Optional.of(submissionEntity)); + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectEntity.getId(), secondGroup.getId())).thenReturn(Optional.of(secondSubmission)); + when(courseUserRepository.findById(argThat( + id -> id.getCourseId() == projectEntity.getCourseId() && id.getUserId() == userEntity.getId() + ))).thenReturn(Optional.of(courseUser)); + + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(null); + when(clusterUtil.isIndividualCluster(projectEntity.getGroupClusterId())).thenReturn(false); + + doReturn(courseJson).when(entityToJsonConverter).courseEntityToCourseReference(courseEntity); + + ProjectResponseJson result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertEquals(courseJson, result.course()); + assertEquals(projectEntity.getDeadline(), result.deadline()); + assertEquals(projectEntity.getDescription(), result.description()); assertEquals(projectEntity.getId(), result.projectId()); assertEquals(projectEntity.getName(), result.name()); + assertEquals(ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId() + "/submissions", result.submissionUrl()); + assertEquals(ApiRoutes.TEST_BASE_PATH + "/" + projectEntity.getTestId(), result.testUrl()); + assertEquals(projectEntity.getMaxScore(), result.maxScore()); + assertEquals(projectEntity.isVisible(), result.visible()); + assertEquals(2, result.progress().completed()); + assertEquals(2, result.progress().total()); + assertNull(result.groupId()); // User is a creator/course_admin -> no group + assertEquals(groupClusterEntity.getId(), result.clusterId()); + + /* TestId is null */ + projectEntity.setTestId(null); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertNull(result.testUrl()); + + /* Individual cluster */ + when(clusterUtil.isIndividualCluster(projectEntity.getGroupClusterId())).thenReturn(true); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertNull(result.clusterId()); + + /* User is enrolled and in group */ + courseUser.setRelation(CourseRelation.enrolled); + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(groupEntity.getId()); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertEquals(ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId() + "/submissions/" + groupEntity.getId(), result.submissionUrl()); + assertEquals(groupEntity.getId(), result.groupId()); + + /* User is enrolled but not in group */ + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(null); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertNull(result.submissionUrl()); + + /* One submission is not correct */ + secondSubmission.setDockerAccepted(false); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertEquals(1, result.progress().completed()); + assertEquals(2, result.progress().total()); + + secondSubmission.setDockerAccepted(true); + secondSubmission.setStructureAccepted(false); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertEquals(1, result.progress().completed()); + assertEquals(2, result.progress().total()); + + /* One group didn't make a submission yet */ + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectEntity.getId(), secondGroup.getId())).thenReturn(Optional.empty()); + result = entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity); + assertEquals(1, result.progress().completed()); + assertEquals(2, result.progress().total()); + + /* Error while getting courseUser */ + reset(courseUserRepository); + when(courseUserRepository.findById(argThat( + id -> id.getCourseId() == projectEntity.getCourseId() && id.getUserId() == userEntity.getId() + ))).thenReturn(Optional.empty()); + assertThrows(RuntimeException.class, () -> entityToJsonConverter.projectEntityToProjectResponseJson(projectEntity, courseEntity, userEntity)); } -// @Test -// public void testGetSubmissionJson() { -// SubmissionJson result = entityToJsonConverter.getSubmissionJson(submissionEntity); -// assertEquals(submissionEntity.getId(), result.getSubmissionId()); -// assertEquals(submissionEntity.getProjectId(), result.getProjectId()); -// } + @Test + public void testCourseEntityToCourseReference() { + CourseReferenceJson result = entityToJsonConverter.courseEntityToCourseReference(courseEntity); + assertEquals(courseEntity.getName(), result.getName()); + assertEquals(ApiRoutes.COURSE_BASE_PATH + "/" + courseEntity.getId(), result.getUrl()); + assertEquals(courseEntity.getId(), result.getCourseId()); + assertNull(result.getArchivedAt()); + } + + @Test + public void testGetSubmissionJson() { + submissionEntity.setDockerTestState(DockerTestState.running); + submissionEntity.setSubmissionTime(OffsetDateTime.now()); + submissionEntity.setStructureAccepted(true); + submissionEntity.setStructureFeedback("feedback"); + SubmissionJson result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertEquals(submissionEntity.getId(), result.getSubmissionId()); + assertEquals(ApiRoutes.PROJECT_BASE_PATH + "/" + submissionEntity.getProjectId(), result.getProjectUrl()); + assertEquals(ApiRoutes.GROUP_BASE_PATH + "/" + submissionEntity.getGroupId(), result.getGroupUrl()); + assertEquals(submissionEntity.getProjectId(), result.getProjectId()); + assertEquals(submissionEntity.getGroupId(), result.getGroupId()); + assertEquals(ApiRoutes.SUBMISSION_BASE_PATH + "/" + submissionEntity.getId() + "/file", result.getFileUrl()); + assertTrue(result.getStructureAccepted()); + assertEquals(submissionEntity.getSubmissionTime(), result.getSubmissionTime()); + assertEquals(submissionEntity.getStructureFeedback(), result.getStructureFeedback()); + assertNull(result.getDockerFeedback()); + assertEquals(DockerTestState.running.toString(), result.getDockerStatus()); + assertEquals(ApiRoutes.SUBMISSION_BASE_PATH + "/" + submissionEntity.getId() + "/artifacts", result.getArtifactUrl()); + + /* Docker finished running */ + submissionEntity.setDockerTestState(DockerTestState.finished); + /* No docker test */ + submissionEntity.setDockerType(DockerTestType.NONE); + result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertEquals(DockerTestState.finished.toString(), result.getDockerStatus()); + assertEquals(DockerTestType.NONE, result.getDockerFeedback().type()); + + /* Simple docker test */ + submissionEntity.setDockerFeedback("dockerFeedback - simple"); + submissionEntity.setDockerAccepted(true); + submissionEntity.setDockerType(DockerTestType.SIMPLE); + result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertEquals(DockerTestType.SIMPLE, result.getDockerFeedback().type()); + assertEquals(submissionEntity.getDockerFeedback(), result.getDockerFeedback().feedback()); + assertTrue(result.getDockerFeedback().allowed()); + + /* Template docker test */ + submissionEntity.setDockerFeedback("dockerFeedback - template"); + submissionEntity.setDockerAccepted(false); + submissionEntity.setDockerType(DockerTestType.TEMPLATE); + result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertEquals(DockerTestType.TEMPLATE, result.getDockerFeedback().type()); + assertEquals(submissionEntity.getDockerFeedback(), result.getDockerFeedback().feedback()); + assertFalse(result.getDockerFeedback().allowed()); + + /* Docker aborted */ + submissionEntity.setDockerTestState(DockerTestState.aborted); + result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertEquals(DockerTestState.aborted.toString(), result.getDockerStatus()); + assertEquals(DockerTestType.TEMPLATE, result.getDockerFeedback().type()); + assertEquals(submissionEntity.getDockerFeedback(), result.getDockerFeedback().feedback()); + assertFalse(result.getDockerFeedback().allowed()); + } @Test public void testTestEntityToTestJson() { - TestJson result = entityToJsonConverter.testEntityToTestJson(testEntity, 1L); + TestJson result = entityToJsonConverter.testEntityToTestJson(testEntity, projectEntity.getId()); + assertEquals(ApiRoutes.PROJECT_BASE_PATH + "/" + projectEntity.getId(), result.getProjectUrl()); assertEquals(testEntity.getDockerImage(), result.getDockerImage()); + assertEquals(testEntity.getDockerTestScript(), result.getDockerScript()); + assertEquals(testEntity.getDockerTestTemplate(), result.getDockerTemplate()); + assertEquals(testEntity.getStructureTemplate(), result.getStructureTest()); } + + } \ No newline at end of file From bb0cc3df215b56cdf792d8773ecea03c7ecfe50b Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sat, 11 May 2024 18:03:11 +0200 Subject: [PATCH 35/40] Fileutil + submissionUtil tests --- .../controllers/SubmissionController.java | 1 + .../java/com/ugent/pidgeon/util/FileUtil.java | 16 -- .../ugent/pidgeon/util/SubmissionUtil.java | 18 ++- .../util/EntityToJsonConverterTest.java | 1 - .../ugent/pidgeon/util/FileHandlerTest.java | 60 ++++++++ .../com/ugent/pidgeon/util/FileUtilTest.java | 56 +++++-- .../pidgeon/util/SubmissionUtilTest.java | 142 ++++++++++++++---- 7 files changed, 225 insertions(+), 69 deletions(-) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index b0518729..ee7af795 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -180,6 +180,7 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P false, false ); + submissionEntity.setDockerTestState(DockerTestState.finished); //Save the submission in the database SubmissionEntity submission = submissionRepository.save(submissionEntity); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java index 9ae43466..06b93dce 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java @@ -16,22 +16,6 @@ public class FileUtil { @Autowired private FileRepository fileRepository; - /** - * Save the file entity to the database - * @param filePath path of the file - * @param projectId id of the project - * @param userId id of the user - * @return the saved file entity - * @throws IOException if an error occurs while saving the file - */ - public FileEntity saveFileEntity(Path filePath, long projectId, long userId) throws IOException { - // Save the file entity to the database - Logger.getGlobal().info("file path: " + filePath.toString()); - Logger.getGlobal().info("file name: " + filePath.getFileName().toString()); - FileEntity fileEntity = new FileEntity(filePath.getFileName().toString(), filePath.toString(), userId); - return fileRepository.save(fileEntity); - } - /** * Delete a file by id from the database and server * @param fileId id of the file diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/SubmissionUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/SubmissionUtil.java index 943b73e0..d3135dac 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/SubmissionUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/SubmissionUtil.java @@ -41,10 +41,11 @@ public CheckResult canGetSubmission(long submissionId, UserEnt if (submission == null) { return new CheckResult<>(HttpStatus.NOT_FOUND, "Submission not found", null); } - if (groupUtil.canGetProjectGroupData(submission.getGroupId(), submission.getProjectId(), user).getStatus().equals(HttpStatus.OK)) { + CheckResult groupCheck = groupUtil.canGetProjectGroupData(submission.getGroupId(), submission.getProjectId(), user); + if (groupCheck.getStatus().equals(HttpStatus.OK)) { return new CheckResult<>(HttpStatus.OK, "", submission); } else { - return new CheckResult<>(HttpStatus.FORBIDDEN, "User does not have access to this submission", null); + return new CheckResult<>(groupCheck.getStatus(), groupCheck.getMessage(), null); } } @@ -59,10 +60,11 @@ public CheckResult canDeleteSubmission(long submissionId, User if (submission == null) { return new CheckResult<>(HttpStatus.NOT_FOUND, "Submission not found", null); } - if (projectUtil.isProjectAdmin(submission.getProjectId(), user).getStatus().equals(HttpStatus.OK)) { + CheckResult projectCheck = projectUtil.isProjectAdmin(submission.getProjectId(), user); + if (projectCheck.getStatus().equals(HttpStatus.OK)) { return new CheckResult<>(HttpStatus.OK, "", submission); } else { - return new CheckResult<>(HttpStatus.FORBIDDEN, "User does not have access to delete this submission", null); + return new CheckResult<>(projectCheck.getStatus(), projectCheck.getMessage(), null); } } @@ -81,10 +83,12 @@ public CheckResult checkOnSubmit(long projectId, UserEntity user) { if (groupId == null) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "User is not part of a group for this project", null); } - GroupEntity group = groupUtil.getGroupIfExists(groupId).getData(); - if (group == null) { - return new CheckResult<>(HttpStatus.NOT_FOUND, "Group not found", null); + + CheckResult groupCheck = groupUtil.getGroupIfExists(groupId); + if (groupCheck.getStatus() != HttpStatus.OK) { + return new CheckResult<>(groupCheck.getStatus(), groupCheck.getMessage(), null); } + GroupEntity group = groupCheck.getData(); if (groupClusterRepository.inArchivedCourse(group.getClusterId())) { return new CheckResult<>(HttpStatus.FORBIDDEN, "Cannot submit for a project in an archived course", null); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java index e0d5942d..fa1a2daa 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/EntityToJsonConverterTest.java @@ -118,7 +118,6 @@ public void setUp() { groupClusterEntity.getId() ); groupEntity.setId(4L); - groupEntity.setClusterId(groupClusterEntity.getId()); groupJson = new GroupJson( 20, diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java new file mode 100644 index 00000000..85651d7b --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java @@ -0,0 +1,60 @@ +package com.ugent.pidgeon.util; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.mock.web.MockMultipartFile; + +public class FileHandlerTest { + + + @TempDir + static Path tempDir; + + private MockMultipartFile file; + private final String basicZipFileName = "Testfile.zip"; + private byte[] fileContent; + private final Path testFilePath = Path.of("src/test/test-cases/FilehandlerTestFiles"); + + @AfterEach + public void cleanup() throws Exception { + // Cleanup the files + try (Stream paths = Files.walk(tempDir)) { + paths.map(Path::toFile) + .forEach(File::delete); + } + } + + + @BeforeEach + public void setUp() throws IOException { + fileContent = Files.readAllBytes(testFilePath.resolve(basicZipFileName)); + file = new MockMultipartFile( + basicZipFileName, fileContent + ); + } + + @Test + public void testSaveSubmission() throws Exception { + File savedFile = Filehandler.saveSubmission(tempDir, file); + + assertTrue(savedFile.exists()); + assertEquals(Filehandler.SUBMISSION_FILENAME, savedFile.getName()); + assertEquals(fileContent.length, savedFile.length()); + byte[] savedFileContent = Files.readAllBytes(savedFile.toPath()); + assertEquals(fileContent.length, savedFileContent.length); + + } + +} diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/FileUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/FileUtilTest.java index 7480658a..426ac8d8 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/FileUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/FileUtilTest.java @@ -2,12 +2,17 @@ import com.ugent.pidgeon.postgre.models.FileEntity; import com.ugent.pidgeon.postgre.repository.FileRepository; +import java.util.Optional; +import java.util.logging.FileHandler; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; import java.io.IOException; @@ -16,8 +21,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) public class FileUtilTest { @Mock @@ -26,28 +35,43 @@ public class FileUtilTest { @InjectMocks private FileUtil fileUtil; + private FileEntity fileEntity; + @BeforeEach public void setUp() { - MockitoAnnotations.openMocks(this); + fileEntity = new FileEntity("testName", "testPath", 5L); + fileEntity.setId(2L); } - @Test - public void testSaveFileEntity() throws IOException { - Path filePath = Paths.get("testPath"); - long projectId = 1L; - long userId = 1L; - FileEntity fileEntity = new FileEntity(filePath.getFileName().toString(), filePath.toString(), userId); - when(fileRepository.save(any(FileEntity.class))).thenReturn(fileEntity); - FileEntity result = fileUtil.saveFileEntity(filePath, projectId, userId); - assertEquals(fileEntity, result); - } @Test public void testDeleteFileById() { - long fileId = 1L; - FileEntity fileEntity = new FileEntity("testName", "testPath", 1L); - when(fileRepository.findById(fileId)).thenReturn(java.util.Optional.of(fileEntity)); - CheckResult result = fileUtil.deleteFileById(fileId); - assertEquals(HttpStatus.OK, result.getStatus()); + when(fileRepository.findById(fileEntity.getId())).thenReturn(Optional.of(fileEntity)); + try (MockedStatic mockedFileHandler = Mockito.mockStatic(Filehandler.class)) { + mockedFileHandler.when(() -> Filehandler.deleteLocation(argThat( + path -> path.toString().equals(fileEntity.getPath())) + )).thenAnswer(invocation -> { + // Do nothing + return null; + }); + CheckResult result = fileUtil.deleteFileById(fileEntity.getId()); + assertEquals(HttpStatus.OK, result.getStatus()); + verify(fileRepository, times(1)).delete(fileEntity); + + // Error when file is being deleted + mockedFileHandler.when(() -> Filehandler.deleteLocation(argThat( + path -> path.toString().equals(fileEntity.getPath())) + )).thenThrow(new IOException()); + result = fileUtil.deleteFileById(fileEntity.getId()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatus()); + + // File not found + when(fileRepository.findById(fileEntity.getId())).thenReturn(Optional.empty()); + result = fileUtil.deleteFileById(fileEntity.getId()); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + } catch (Exception e) { + e.printStackTrace(); + } } } \ No newline at end of file diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/SubmissionUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/SubmissionUtilTest.java index 162c793c..0875693c 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/SubmissionUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/SubmissionUtilTest.java @@ -4,6 +4,7 @@ import com.ugent.pidgeon.postgre.models.ProjectEntity; import com.ugent.pidgeon.postgre.models.SubmissionEntity; import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.GroupClusterRepository; import com.ugent.pidgeon.postgre.repository.GroupRepository; import com.ugent.pidgeon.postgre.repository.SubmissionRepository; @@ -48,53 +49,136 @@ public class SubmissionUtilTest { private SubmissionEntity submissionEntity; private ProjectEntity projectEntity; private UserEntity userEntity; + private GroupEntity groupEntity; @BeforeEach public void setUp() { - submissionEntity = new SubmissionEntity(); - submissionEntity.setId(1L); - projectEntity = new ProjectEntity(); - projectEntity.setId(1L); - userEntity = new UserEntity(); - userEntity.setId(1L); + submissionEntity = new SubmissionEntity( + 22, + 45, + 99L, + OffsetDateTime.MIN, + true, + true + ); + submissionEntity.setId(78L); + projectEntity = new ProjectEntity( + 99L, + "projectName", + "projectDescription", + 2L, + 100L, + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(64); + userEntity = new UserEntity( + "name", + "surname", + "email", + UserRole.student, + "azureId" + ); + userEntity.setId(44L); + + groupEntity = new GroupEntity( + "groupName", + 52L + ); + groupEntity.setId(4L); + } @Test public void testCanGetSubmission() { - when(submissionRepository.findById(anyLong())).thenReturn(Optional.of(submissionEntity)); - when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any(UserEntity.class))) + /* All checks succeed */ + when(submissionRepository.findById(submissionEntity.getId())).thenReturn(Optional.of(submissionEntity)); + when(groupUtil.canGetProjectGroupData(submissionEntity.getGroupId(), submissionEntity.getProjectId(), userEntity)) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - assertEquals(submissionEntity, submissionUtil.canGetSubmission(1L, userEntity).getData()); - when(groupUtil.canGetProjectGroupData(anyLong(), anyLong(), any(UserEntity.class))) - .thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "User does not have access to this submission", null)); - assertNull(submissionUtil.canGetSubmission(1L, userEntity).getData()); + CheckResult result = submissionUtil.canGetSubmission(submissionEntity.getId(), userEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(submissionEntity, result.getData()); + + /* User does not have access to the submission */ + when(groupUtil.canGetProjectGroupData(submissionEntity.getGroupId(), submissionEntity.getProjectId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "User does not have access to get this submission", null)); + result = submissionUtil.canGetSubmission(submissionEntity.getId(), userEntity); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Submission not found */ + when(submissionRepository.findById(submissionEntity.getId())).thenReturn(Optional.empty()); + result = submissionUtil.canGetSubmission(submissionEntity.getId(), userEntity); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); } @Test public void testCanDeleteSubmission() { - when(submissionRepository.findById(anyLong())).thenReturn(Optional.of(submissionEntity)); - when(projectUtil.isProjectAdmin(anyLong(), any(UserEntity.class))) + /* All checks succeed */ + when(submissionRepository.findById(submissionEntity.getId())).thenReturn(Optional.of(submissionEntity)); + when(projectUtil.isProjectAdmin(submissionEntity.getProjectId(), userEntity)) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - assertEquals(submissionEntity, submissionUtil.canDeleteSubmission(1L, userEntity).getData()); - when(projectUtil.isProjectAdmin(anyLong(), any(UserEntity.class))) - .thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "User does not have access to delete this submission", null)); - assertNull(submissionUtil.canDeleteSubmission(1L, userEntity).getData()); - } + CheckResult result = submissionUtil.canDeleteSubmission(submissionEntity.getId(), userEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(submissionEntity, result.getData()); + /* User does not have access to delete the submission */ + when(projectUtil.isProjectAdmin(submissionEntity.getProjectId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "User does not have access to delete this submission", null)); + result = submissionUtil.canDeleteSubmission(submissionEntity.getId(), userEntity); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* Submission not found */ + when(submissionRepository.findById(submissionEntity.getId())).thenReturn(Optional.empty()); + result = submissionUtil.canDeleteSubmission(submissionEntity.getId(), userEntity); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + } + @Test public void testCheckOnSubmit() { - ProjectEntity projectEntity = new ProjectEntity(); - projectEntity.setId(1L); + /* All checks succeed */ projectEntity.setDeadline(OffsetDateTime.now().plusDays(1)); - CheckResult projectCheck = new CheckResult<>(HttpStatus.OK, "", projectEntity); - when(projectUtil.getProjectIfExists(anyLong())).thenReturn(projectCheck); - when(groupRepository.groupIdByProjectAndUser(anyLong(), anyLong())).thenReturn(1L); - when(projectUtil.userPartOfProject(anyLong(), anyLong())).thenReturn(true); - when(projectUtil.getProjectIfExists(anyLong())).thenReturn(new CheckResult<>(HttpStatus.OK, "", projectEntity)); - when(groupUtil.getGroupIfExists(anyLong())).thenReturn(new CheckResult<>(HttpStatus.OK, "", new GroupEntity())); - when(groupClusterRepository.inArchivedCourse(anyLong())).thenReturn(false); - assertEquals(1L, submissionUtil.checkOnSubmit(1L, userEntity).getData()); + when(projectUtil.userPartOfProject(projectEntity.getId(), userEntity.getId())).thenReturn(true); + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(groupEntity.getId()); + when(groupUtil.getGroupIfExists(groupEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", groupEntity)); + when(groupClusterRepository.inArchivedCourse(groupEntity.getClusterId())).thenReturn(false); + + when(projectUtil.getProjectIfExists(projectEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.OK, "", projectEntity)); + CheckResult result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Deadline passed */ + projectEntity.setDeadline(OffsetDateTime.now().minusDays(1)); + result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Project not found */ + when(projectUtil.getProjectIfExists(projectEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Project not found", null)); + result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* GroupCluster in archived course */ + when(groupClusterRepository.inArchivedCourse(groupEntity.getClusterId())).thenReturn(true); + result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* Group not found */ + when(groupUtil.getGroupIfExists(groupEntity.getId())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Group not found", null)); + result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* User not part of group */ + when(groupRepository.groupIdByProjectAndUser(projectEntity.getId(), userEntity.getId())).thenReturn(null); + result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* User not part of project */ + when(projectUtil.userPartOfProject(projectEntity.getId(), userEntity.getId())).thenReturn(false); + result = submissionUtil.checkOnSubmit(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); } + + } \ No newline at end of file From 078deb6c70f7421ee4b43ff4ab8ce652c4a685e6 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sat, 11 May 2024 20:22:51 +0200 Subject: [PATCH 36/40] Filehandler tests --- backend/app/build.gradle | 2 +- .../controllers/SubmissionController.java | 7 +- .../java/com/ugent/pidgeon/util/FileUtil.java | 6 +- .../com/ugent/pidgeon/util/Filehandler.java | 110 +----- .../controllers/SubmissionControllerTest.java | 7 +- .../ugent/pidgeon/util/FileHandlerTest.java | 335 +++++++++++++++++- .../DockerSubmissionTestTest/d__test.zip | Bin 162 -> 162 bytes .../FilehandlerTestFiles/Testfile.zip | Bin 0 -> 133 bytes 8 files changed, 349 insertions(+), 118 deletions(-) create mode 100644 backend/app/src/test/test-cases/FilehandlerTestFiles/Testfile.zip diff --git a/backend/app/build.gradle b/backend/app/build.gradle index 04fabadf..8c4995ce 100644 --- a/backend/app/build.gradle +++ b/backend/app/build.gradle @@ -54,6 +54,7 @@ dependencies { // testLogging { // events "passed" + // } // } @@ -61,7 +62,6 @@ task unitTests (type: Test){ exclude '**/docker' - useJUnitPlatform() maxHeapSize = '1G' diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index ee7af795..162735d6 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -7,7 +7,6 @@ import com.ugent.pidgeon.model.json.LastGroupSubmissionJson; import com.ugent.pidgeon.model.json.SubmissionJson; import com.ugent.pidgeon.model.submissionTesting.DockerOutput; -import com.ugent.pidgeon.model.submissionTesting.DockerSubmissionTestModel; import com.ugent.pidgeon.model.submissionTesting.SubmissionTemplateModel; import com.ugent.pidgeon.postgre.models.*; import com.ugent.pidgeon.postgre.models.types.DockerTestState; @@ -21,13 +20,11 @@ import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.time.OffsetDateTime; import java.util.List; @@ -232,7 +229,7 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P submission.setDockerTestState(DockerTestState.running); // run docker tests in background File finalSavedFile = savedFile; - Path artifactPath = Filehandler.getSubmissionAritfactPath(projectid, groupId, submission.getId()); + Path artifactPath = Filehandler.getSubmissionArtifactPath(projectid, groupId, submission.getId()); CompletableFuture.runAsync(() -> { try { @@ -327,7 +324,7 @@ public ResponseEntity getSubmissionArtifacts(@PathVariable("submissionid") lo // Get the file from the server try { - Resource zipFile = Filehandler.getFileAsResource(Filehandler.getSubmissionAritfactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())); + Resource zipFile = Filehandler.getFileAsResource(Filehandler.getSubmissionArtifactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())); if (zipFile == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("No artifacts found for this submission."); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java index 06b93dce..87f482fc 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/FileUtil.java @@ -2,6 +2,7 @@ import com.ugent.pidgeon.postgre.models.FileEntity; import com.ugent.pidgeon.postgre.repository.FileRepository; +import java.io.File; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; @@ -27,8 +28,9 @@ public CheckResult deleteFileById(long fileId) { return new CheckResult<>(HttpStatus.NOT_FOUND, "File not found", null); } try { - Filehandler.deleteLocation(Path.of(fileEntity.getPath())); - } catch (IOException e) { + Path path = Path.of(fileEntity.getPath()); + Filehandler.deleteLocation(new File(path.toString())); + } catch (Exception e) { return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error deleting file", null); } fileRepository.delete(fileEntity); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index 3475b43d..071d1d4f 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -1,24 +1,18 @@ package com.ugent.pidgeon.util; -import com.ugent.pidgeon.postgre.models.FileEntity; -import com.ugent.pidgeon.postgre.repository.FileRepository; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.tika.Tika; import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.InputStreamResource; import org.springframework.web.multipart.MultipartFile; import org.springframework.core.io.Resource; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.Objects; import java.util.logging.Logger; -import java.util.zip.ZipFile; public class Filehandler { @@ -34,7 +28,7 @@ public class Filehandler { */ public static File saveSubmission(Path directory, MultipartFile file) throws IOException { // Check if the file is empty - if (file.isEmpty()) { + if (file == null || file.isEmpty()) { throw new IOException("File is empty"); } @@ -62,30 +56,21 @@ public static File saveSubmission(Path directory, MultipartFile file) throws IOE Files.copy(stream, filePath, StandardCopyOption.REPLACE_EXISTING); } - return tempFile; + return filePath.toFile(); } catch (IOException e) { throw new IOException(e.getMessage()); } } - /** - * Delete a submission from the server - * @param directory directory of the submission to delete - * @throws IOException if an error occurs while deleting the submission - */ - public static void deleteSubmission(Path directory) throws IOException { - deleteLocation(directory); - } /** - * Delete a directory and all its contents - * @param directory directory to delete + * Delete a directory and all its contents, eg: deleteLocation(new File(path.toString()) + * @param uploadDirectory File representing directory to delete * @throws IOException if an error occurs while deleting the directory */ - public static void deleteLocation(Path directory) throws IOException { + public static void deleteLocation(File uploadDirectory) throws IOException { try { - File uploadDirectory = new File(directory.toString()); if (uploadDirectory.exists()) { if(!uploadDirectory.delete()) { throw new IOException("Error while deleting directory"); @@ -97,16 +82,18 @@ public static void deleteLocation(Path directory) throws IOException { } } + + /** * Delete empty parent directories of a directory * @param directory directory to delete */ - private static void deleteEmptyParentDirectories(File directory) { + private static void deleteEmptyParentDirectories(File directory) throws IOException { if (directory != null && directory.isDirectory()) { File[] files = directory.listFiles(); if (files != null && files.length == 0) { if (!directory.delete()) { - System.err.println("Error while deleting empty directory: " + directory.getAbsolutePath()); + throw new IOException("Error while deleting empty directory: " + directory.getAbsolutePath()); } else { deleteEmptyParentDirectories(directory.getParentFile()); } @@ -126,19 +113,10 @@ static public Path getSubmissionPath(long projectid, long groupid, long submissi return Path.of(BASEPATH,"projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); } - static public Path getSubmissionAritfactPath(long projectid, long groupid, long submissionid) { + static public Path getSubmissionArtifactPath(long projectid, long groupid, long submissionid) { return getSubmissionPath(projectid, groupid, submissionid).resolve("artifacts.zip"); } - /** - * Get the path were a test is stored - * @param projectid id of the project - * @return the path of the test - */ - static public Path getTestPath(long projectid) { - return Path.of(BASEPATH,"projects", String.valueOf(projectid), "tests"); - } - /** * Get a file as a resource * @param path path of the file @@ -172,74 +150,6 @@ public static boolean isZipFile(File file) throws IOException { } - /** - * Save a file to the server - * @param file file to save - * @param projectId id of the project - * @return the path of the saved file - * @throws IOException if an error occurs while saving the file - */ - public static Path saveTest(MultipartFile file, long projectId) throws IOException { - // Check if the file is empty - if (file.isEmpty()) { - throw new IOException("File is empty"); - } - - // Create directory if it doesn't exist - Path projectDirectory = getTestPath(projectId); - if (!Files.exists(projectDirectory)) { - Files.createDirectories(projectDirectory); - } - - // Save the file to the server - Path filePath = projectDirectory.resolve(Objects.requireNonNull(file.getOriginalFilename())); - Files.write(filePath, file.getBytes()); - - return filePath; - } - - /** - * Copy a file to the server project directory. - * @param sourceFilePath the path of the file to copy - * @param projectId the ID of the project - * @return the path of the copied file - * @throws IOException if an error occurs while copying the file - */ - public static Path copyTest(Path sourceFilePath, long projectId) throws IOException { - // Check if the source file exists - if (!Files.exists(sourceFilePath)) { - throw new IOException("Source file does not exist"); - } - - // Create project directory if it doesn't exist - Path projectDirectory = getTestPath(projectId); - if (!Files.exists(projectDirectory)) { - Files.createDirectories(projectDirectory); - } - - // Resolve destination file path - Path destinationFilePath = projectDirectory.resolve(sourceFilePath.getFileName()); - - // Copy the file to the project directory - Files.copy(sourceFilePath, destinationFilePath); - - return destinationFilePath; - } - - /** - * Get the structure test file contents as string - * @param path path of the structure test file - * @return the structure test file contents as string - * @throws IOException if an error occurs while reading the file - */ - public static String getStructureTestString(Path path) throws IOException { - try { - return Files.readString(path); - } catch (IOException e) { - throw new IOException("Error while reading testfile: " + e.getMessage()); - } - } - /** * A function for copying internally made lists of files, to a required path. * @param files list of files to copy diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java index 602a6b06..472418c9 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java @@ -28,7 +28,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; -import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.junit.jupiter.api.BeforeEach; @@ -278,7 +277,7 @@ public void testSubmitFile() throws Exception { try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { mockedFileHandler.when(() -> Filehandler.getSubmissionPath(submission.getProjectId(), groupEntity.getId(), submission.getId())).thenReturn(path); mockedFileHandler.when(() -> Filehandler.saveSubmission(path, mockMultipartFile)).thenReturn(file); - mockedFileHandler.when(() -> Filehandler.getSubmissionAritfactPath(anyLong(), anyLong(), anyLong())).thenReturn(artifactPath); + mockedFileHandler.when(() -> Filehandler.getSubmissionArtifactPath(anyLong(), anyLong(), anyLong())).thenReturn(artifactPath); when(testRunner.runStructureTest(any(), eq(testEntity))).thenReturn(null); when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath))).thenReturn(null); @@ -459,7 +458,7 @@ public void testGetSubmissionArtifacts() throws Exception { /* all checks succeed */ when(submissionUtil.canGetSubmission(submission.getId(), getMockUser())).thenReturn(new CheckResult<>(HttpStatus.OK, "", submission)); - mockedFileHandler.when(() -> Filehandler.getSubmissionAritfactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())).thenReturn(path); + mockedFileHandler.when(() -> Filehandler.getSubmissionArtifactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())).thenReturn(path); mockedFileHandler.when(() -> Filehandler.getFileAsResource(path)).thenReturn(mockedResource); mockMvc.perform(MockMvcRequestBuilders.get(url)) @@ -477,7 +476,7 @@ public void testGetSubmissionArtifacts() throws Exception { /* Unexpected error */ mockedFileHandler.reset(); - mockedFileHandler.when(() -> Filehandler.getSubmissionAritfactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())).thenThrow(new RuntimeException()); + mockedFileHandler.when(() -> Filehandler.getSubmissionArtifactPath(submission.getProjectId(), submission.getGroupId(), submission.getId())).thenThrow(new RuntimeException()); mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(status().isInternalServerError()); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java index 85651d7b..89efd857 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/FileHandlerTest.java @@ -2,24 +2,39 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; -import org.apache.commons.io.FileUtils; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; import org.springframework.mock.web.MockMultipartFile; +@ExtendWith(MockitoExtension.class) public class FileHandlerTest { - @TempDir static Path tempDir; private MockMultipartFile file; @@ -30,15 +45,19 @@ public class FileHandlerTest { @AfterEach public void cleanup() throws Exception { // Cleanup the files - try (Stream paths = Files.walk(tempDir)) { - paths.map(Path::toFile) - .forEach(File::delete); + if (Files.exists(tempDir)) { + try (Stream paths = Files.walk(tempDir)) { + paths.map(Path::toFile) + .forEach(File::delete); + } } + } @BeforeEach public void setUp() throws IOException { + tempDir = Files.createTempDirectory("test"); fileContent = Files.readAllBytes(testFilePath.resolve(basicZipFileName)); file = new MockMultipartFile( basicZipFileName, fileContent @@ -54,7 +73,311 @@ public void testSaveSubmission() throws Exception { assertEquals(fileContent.length, savedFile.length()); byte[] savedFileContent = Files.readAllBytes(savedFile.toPath()); assertEquals(fileContent.length, savedFileContent.length); + } + + @Test + public void testSaveSubmission_dirDoesntExist() throws Exception { + File savedFile = Filehandler.saveSubmission(tempDir.resolve("nonexistent"), file); + + assertTrue(savedFile.exists()); + assertEquals(Filehandler.SUBMISSION_FILENAME, savedFile.getName()); + assertEquals(fileContent.length, savedFile.length()); + byte[] savedFileContent = Files.readAllBytes(savedFile.toPath()); + assertEquals(fileContent.length, savedFileContent.length); + } + + @Test + public void testSaveSubmission_errorWhileCreatingDir() throws Exception { + assertThrows(IOException.class, () -> Filehandler.saveSubmission(Path.of(""), file)); + } + + @Test + public void testSaveSubmission_notAZipFile() { + MockMultipartFile notAZipFile = new MockMultipartFile( + "notAZipFile.txt", "This is not a zip file".getBytes() + ); + assertThrows(IOException.class, () -> Filehandler.saveSubmission(tempDir, notAZipFile)); + } + + @Test + public void testSaveSubmission_fileEmpty() { + MockMultipartFile emptyFile = new MockMultipartFile( + "emptyFile.txt", new byte[0] + ); + assertThrows(IOException.class, () -> Filehandler.saveSubmission(tempDir, emptyFile)); + } + + @Test + public void testSaveSubmission_fileNull() { + assertThrows(IOException.class, () -> Filehandler.saveSubmission(tempDir, null)); + } + + @Test + public void testDeleteLocation() throws Exception { + Path testDir = Files.createTempDirectory("test"); + Path tempFile = Files.createTempFile(testDir, "test", ".txt"); + Filehandler.deleteLocation(new File(tempFile.toString())); + assertFalse(Files.exists(testDir)); + } + + @Test + public void testDeleteLocation_parentDirNotEmpty() throws Exception { + Path testDir = Files.createTempDirectory("test"); + Path tempFile = Files.createTempFile(testDir, "test", ".txt"); + Files.createTempFile(testDir, "test2", ".txt"); + Filehandler.deleteLocation(new File(tempFile.toString())); + assertTrue(Files.exists(testDir)); + } + + @Test + public void testDeleteLocation_locationDoesntExist() throws Exception { + Filehandler.deleteLocation(new File("nonexistent")); + } + + @Test + public void testDeleteLocation_errorWhileDeleting() { + // Create a mock File object + File mockDir = mock(File.class); + + when(mockDir.exists()).thenReturn(true); + when(mockDir.delete()).thenReturn(false); + + assertThrows(IOException.class, () -> Filehandler.deleteLocation(mockDir)); + + verify(mockDir).exists(); + verify(mockDir).delete(); + } + + @Test + public void testDeleteLocation_errorWhileDeletingParentDir() { + File mockDir = mock(File.class); + File mockParentDir = mock(File.class); + File[] mockedFiles = new File[0]; + + when(mockDir.exists()).thenReturn(true); + when(mockDir.delete()).thenReturn(true); + when(mockDir.getParentFile()).thenReturn(mockParentDir); + when(mockParentDir.isDirectory()).thenReturn(true); + when(mockParentDir.listFiles()).thenReturn(mockedFiles); + when(mockParentDir.delete()).thenReturn(false); + + assertThrows(IOException.class, () -> Filehandler.deleteLocation(mockDir)); + + verify(mockDir).exists(); + verify(mockDir).delete(); + verify(mockDir).getParentFile(); + verify(mockParentDir).listFiles(); + verify(mockParentDir).delete(); + } + + @Test + public void testDeleteLocation_filesAreNotEmpty() throws IOException { + File mockDir = mock(File.class); + File mockParentDir = mock(File.class); + File[] mockedFiles = new File[1]; + mockedFiles[0] = mock(File.class); + + when(mockDir.exists()).thenReturn(true); + when(mockDir.delete()).thenReturn(true); + when(mockDir.getParentFile()).thenReturn(mockParentDir); + when(mockParentDir.isDirectory()).thenReturn(true); + when(mockParentDir.listFiles()).thenReturn(mockedFiles); + Filehandler.deleteLocation(mockDir); + + verify(mockDir).exists(); + verify(mockDir).delete(); + verify(mockDir).getParentFile(); + verify(mockParentDir).listFiles(); + } + + @Test + public void testDeleteLocation_filesAreNull() throws IOException { + File mockDir = mock(File.class); + File mockParentDir = mock(File.class); + + when(mockDir.exists()).thenReturn(true); + when(mockDir.delete()).thenReturn(true); + when(mockDir.getParentFile()).thenReturn(mockParentDir); + when(mockParentDir.isDirectory()).thenReturn(true); + when(mockParentDir.listFiles()).thenReturn(null); + + Filehandler.deleteLocation(mockDir); + + verify(mockDir).exists(); + verify(mockDir).delete(); + verify(mockDir).getParentFile(); + verify(mockParentDir).listFiles(); + } + + @Test + public void testDeleteLocation_parentDirIsNotADir() throws IOException { + File mockDir = mock(File.class); + File mockParentDir = mock(File.class); + + when(mockDir.exists()).thenReturn(true); + when(mockDir.delete()).thenReturn(true); + when(mockDir.getParentFile()).thenReturn(mockParentDir); + when(mockParentDir.isDirectory()).thenReturn(false); + + Filehandler.deleteLocation(mockDir); + + verify(mockDir).exists(); + verify(mockDir).delete(); + verify(mockDir).getParentFile(); + verify(mockParentDir).isDirectory(); + } + + @Test + public void testDeleteLocation_parentDirIsNull() throws IOException { + File mockDir = mock(File.class); + + when(mockDir.exists()).thenReturn(true); + when(mockDir.delete()).thenReturn(true); + when(mockDir.getParentFile()).thenReturn(null); + + Filehandler.deleteLocation(mockDir); + + verify(mockDir).exists(); + verify(mockDir).delete(); + verify(mockDir).getParentFile(); + } + + @Test + public void testGetSubmissionPath() { + Path submissionPath = Filehandler.getSubmissionPath(1, 2, 3); + assertEquals(Path.of(Filehandler.BASEPATH, "projects", "1", "2", "3"), submissionPath); + } + + @Test + public void testGetSubmissionArtifactPath() { + Path submissionArtifactPath = Filehandler.getSubmissionArtifactPath(1, 2, 3); + assertEquals(Path.of(Filehandler.BASEPATH, "projects", "1", "2", "3", "artifacts.zip"), submissionArtifactPath); + } + + @Test + public void testGetFileAsResource_FileExists() { + try { + File tempFile = Files.createTempFile("testFile", ".txt").toFile(); + + Resource resource = Filehandler.getFileAsResource(tempFile.toPath()); + + assertNotNull(resource); + assertInstanceOf(FileSystemResource.class, resource); + assertEquals(tempFile.getAbsolutePath(), ((FileSystemResource) resource).getFile().getAbsolutePath()); + + assertTrue(tempFile.delete()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetFileAsResource_FileDoesNotExist() { + Resource resource = Filehandler.getFileAsResource(Path.of("nonexistent")); + + assertNull(resource); + } + + @Test + public void testCopyFilesAsZip() throws IOException { + List files = new ArrayList<>(); + File tempFile1 = Files.createTempFile("tempFile1", ".txt").toFile(); + File tempFile2 = Files.createTempFile("tempFile2", ".txt").toFile(); + + try { + files.add(tempFile1); + files.add(tempFile2); + + File zipFile = tempDir.resolve("files.zip").toFile(); + Filehandler.copyFilesAsZip(files, zipFile.toPath()); + + assertTrue(zipFile.exists()); + + try (ZipFile zip = new ZipFile(zipFile)) { + for (File file : files) { + String entryName = file.getName(); + ZipEntry zipEntry = zip.getEntry(entryName); + assertNotNull(zipEntry, "File " + entryName + " not found in the zip file."); + } + } + + + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testCopyFilesAsZip_zipFileAlreadyExist() throws IOException { + List files = new ArrayList<>(); + File tempFile1 = Files.createTempFile("tempFile1", ".txt").toFile(); + File tempFile2 = Files.createTempFile("tempFile2", ".txt").toFile(); + File zipFile = Files.createTempFile(tempDir, "files", ".zip").toFile(); + + try { + files.add(tempFile1); + files.add(tempFile2); + + assertTrue(zipFile.exists()); + + Filehandler.copyFilesAsZip(files, zipFile.toPath()); + + assertTrue(zipFile.exists()); + + try (ZipFile zip = new ZipFile(zipFile)) { + for (File file : files) { + String entryName = file.getName(); + ZipEntry zipEntry = zip.getEntry(entryName); + assertNotNull(zipEntry, "File " + entryName + " not found in the zip file."); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static File createTempFileWithContent(String prefix, String suffix, int fileSizeInBytes) throws IOException { + Path tempFilePath = Files.createTempFile(prefix, suffix); + + try (FileOutputStream outputStream = new FileOutputStream(tempFilePath.toFile())) { + // Write data to the file until it reaches the desired size + for (int i = 0; i < fileSizeInBytes; i++) { + outputStream.write('A'); // Write a byte to the file (in this case, the letter 'A') + } + } + + return tempFilePath.toFile(); + } + + @Test + public void testCopyFilesAsZip_zipFileAlreadyExistNonWriteable() throws IOException { + List files = new ArrayList<>(); + File tempFile1 = createTempFileWithContent("tempFile1", ".txt", 4095); + File tempFile2 = Files.createTempFile("tempFile2", ".txt").toFile(); + File zipFile = Files.createTempFile(tempDir, "files", ".zip").toFile(); + zipFile.setWritable(false); + + try { + files.add(tempFile1); + files.add(tempFile2); + + assertTrue(zipFile.exists()); + + Filehandler.copyFilesAsZip(files, zipFile.toPath()); + + assertTrue(zipFile.exists()); + + try (ZipFile zip = new ZipFile(zipFile)) { + for (File file : files) { + String entryName = file.getName(); + ZipEntry zipEntry = zip.getEntry(entryName); + assertNotNull(zipEntry, "File " + entryName + " not found in the zip file."); + } + } + } catch (IOException e) { + e.printStackTrace(); + } } } diff --git a/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip b/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip index 64c7396e29e5ceba874c38b87d47d33d039aad2b..4e389ca3afc1b890a7685fc0d8db9af2ea9be82b 100644 GIT binary patch delta 28 hcmZ3)xQLNAz?+#xgn@&DgTW?w^+aBOW)Kzc3;5&2He_! eIvE%l6hIUZAR7|k&B_K6V+2AgAZ-fbFaQ9CpBVK3 literal 0 HcmV?d00001 From 1cbbe1a6aad599d2bccd685029bcd3ff2ac44ab0 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sat, 11 May 2024 21:17:00 +0200 Subject: [PATCH 37/40] TestUtiltests --- .../java/com/ugent/pidgeon/util/TestUtil.java | 21 +- .../com/ugent/pidgeon/util/TestUtilTest.java | 361 ++++++++++++++++-- 2 files changed, 341 insertions(+), 41 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/TestUtil.java b/backend/app/src/main/java/com/ugent/pidgeon/util/TestUtil.java index d663dfd7..b3b57abb 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/TestUtil.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/TestUtil.java @@ -6,6 +6,7 @@ import com.ugent.pidgeon.postgre.models.ProjectEntity; import com.ugent.pidgeon.postgre.models.TestEntity; import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.TestRepository; import java.util.logging.Level; import java.util.logging.Logger; @@ -51,15 +52,6 @@ public CheckResult> checkForTestUpdate( String dockerTemplate, HttpMethod httpMethod ) { - /* Log arguments */ - Logger logger = Logger.getGlobal(); - logger.log(Level.INFO, "=========="); - logger.log(Level.INFO, "projectId: " + projectId); - logger.log(Level.INFO, "user: " + user); - logger.log(Level.INFO, "dockerImage: " + dockerImage); - logger.log(Level.INFO, "dockerScript: " + dockerScript); - logger.log(Level.INFO, "dockerTemplate: " + dockerTemplate); - logger.log(Level.INFO, "httpMethod: " + httpMethod); CheckResult projectCheck = projectUtil.getProjectIfAdmin(projectId, user); if (!projectCheck.getStatus().equals(HttpStatus.OK)) { @@ -81,14 +73,17 @@ public CheckResult> checkForTestUpdate( } if(!httpMethod.equals(HttpMethod.PATCH) && dockerImage != null && dockerScript == null) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "A test script is required in a docker test.", null); + return new CheckResult<>(HttpStatus.BAD_REQUEST, "A test script is required if u add a dockerimage.", null); + } + if (!httpMethod.equals(HttpMethod.PATCH) && dockerScript != null && dockerImage == null) { + return new CheckResult<>(HttpStatus.BAD_REQUEST, "A docker image is required if u add a script", null); } - if(!httpMethod.equals(HttpMethod.PATCH) && dockerScript != null && dockerImage == null && DockerSubmissionTestModel.imageExists(dockerImage)) { + if(dockerImage != null && !DockerSubmissionTestModel.imageExists(dockerImage)) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "A valid docker image is required in a docker test.", null); } - if (!httpMethod.equals(HttpMethod.PATCH) && dockerTemplate != null && dockerScript == null) { + if (!httpMethod.equals(HttpMethod.PATCH) && dockerTemplate != null && dockerImage == null) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "A test script and image are required in a docker template test.", null); } @@ -145,6 +140,8 @@ public CheckResult> getTestWithAdminStatus(long projec admin = true; } else if (!isProjectAdmin.getStatus().equals(HttpStatus.FORBIDDEN)){ return new CheckResult<>(isProjectAdmin.getStatus(), isProjectAdmin.getMessage(), null); + } else if (user.getRole().equals(UserRole.admin)) { + admin = true; } return new CheckResult<>(HttpStatus.OK, "", new Pair<>(testEntity, admin)); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/TestUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/TestUtilTest.java index 2ab022a2..3f132ec0 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/TestUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/TestUtilTest.java @@ -1,14 +1,19 @@ package com.ugent.pidgeon.util; +import com.ugent.pidgeon.model.submissionTesting.DockerSubmissionTestModel; import com.ugent.pidgeon.postgre.models.ProjectEntity; import com.ugent.pidgeon.postgre.models.TestEntity; import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.TestRepository; +import java.time.OffsetDateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -19,7 +24,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -31,6 +38,7 @@ public class TestUtilTest { @Mock private ProjectUtil projectUtil; + @Spy @InjectMocks private TestUtil testUtil; @@ -40,56 +48,351 @@ public class TestUtilTest { @BeforeEach public void setUp() { - testEntity = new TestEntity(); - testEntity.setId(1L); - projectEntity = new ProjectEntity(); - projectEntity.setId(1L); - userEntity = new UserEntity(); - userEntity.setId(1L); + projectEntity = new ProjectEntity( + 99L, + "projectName", + "projectDescription", + 2L, + 100L, + true, + 34, + OffsetDateTime.now() + ); + projectEntity.setId(64); + userEntity = new UserEntity( + "name", + "surname", + "email", + UserRole.student, + "azureId" + ); + userEntity.setId(44L); + testEntity = new TestEntity( + "dockerImageBasic", + "dockerTestScriptBasic", + "dockerTestTemplateBasic", + "structureTemplateBasic" + ); + testEntity.setId(38L); } @Test public void testGetTestIfExists() { - when(testRepository.findByProjectId(anyLong())).thenReturn(Optional.of(testEntity)); - assertEquals(testEntity, testUtil.getTestIfExists(1L)); + /* TestEntity exists */ + when(testRepository.findByProjectId(projectEntity.getId())).thenReturn(Optional.of(testEntity)); + assertEquals(testEntity, testUtil.getTestIfExists(projectEntity.getId())); - when(testRepository.findByProjectId(anyLong())).thenReturn(Optional.empty()); - assertNull(testUtil.getTestIfExists(1L)); + /* TestEntity does not exist */ + when(testRepository.findByProjectId(projectEntity.getId())).thenReturn(Optional.empty()); + assertNull(testUtil.getTestIfExists(projectEntity.getId())); } @Test public void testCheckForTestUpdate() { - // Mock the projectUtil.getProjectIfAdmin method to return a CheckResult with HttpStatus.OK - when(projectUtil.getProjectIfAdmin(anyLong(), any(UserEntity.class))) + String dockerImage = "dockerImage"; + String dockerScript = "dockerScript"; + String dockerTemplate = "@dockerTemplate\nExpectedOutput"; + HttpMethod httpMethod = HttpMethod.POST; + + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), userEntity)) .thenReturn(new CheckResult<>(HttpStatus.OK, "", projectEntity)); - // Mock the testRepository.findByProjectId method to return an Optional of testEntity - when(testRepository.findByProjectId(anyLong())).thenReturn(Optional.of(testEntity)); + doReturn(testEntity).when(testUtil).getTestIfExists(projectEntity.getId()); - // Call the checkForTestUpdate method - CheckResult> result = testUtil.checkForTestUpdate(1L, - userEntity, "dockerImage", "", null, HttpMethod.POST); + try (MockedStatic mockedTestModel = mockStatic(DockerSubmissionTestModel.class)) { + mockedTestModel.when(() -> DockerSubmissionTestModel.imageExists(dockerImage)).thenReturn(true); + mockedTestModel.when(() -> DockerSubmissionTestModel.isValidTemplate(any())).thenReturn(true); + projectEntity.setTestId(null); + CheckResult> result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + assertEquals(testEntity, result.getData().getFirst()); + assertEquals(projectEntity, result.getData().getSecond()); - // Assert the result - assertEquals(HttpStatus.OK, result.getStatus()); - assertEquals(testEntity, result.getData().getFirst()); - assertEquals(projectEntity, result.getData().getSecond()); + /* TestEntity not found and method is post */ + doReturn(null).when(testUtil).getTestIfExists(projectEntity.getId()); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + HttpMethod.POST + ); + assertEquals(HttpStatus.OK, result.getStatus()); + doReturn(testEntity).when(testUtil).getTestIfExists(projectEntity.getId()); + + + /* Not a valid template */ + when(DockerSubmissionTestModel.isValidTemplate(any())).thenReturn(false); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + when(DockerSubmissionTestModel.isValidTemplate(any())).thenReturn(true); + + + /* Method is patch and no template provided */ + projectEntity.setTestId(testEntity.getId()); + httpMethod = HttpMethod.PATCH; + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + null, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Method is patch and script is null while test has a dockerImage */ + testEntity.setDockerTestScript(null); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + null, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + testEntity.setDockerTestScript(dockerScript); + + /* Method is patch and script is null but test already has a script */ + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + null, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Method is patch and image is null while test has a dockerScript */ + testEntity.setDockerImage(null); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + null, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + testEntity.setDockerImage(dockerImage); + + /* Method is patch and image is null but test already has an image */ + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + null, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Patch method with everything present in request, nothing in test */ + testEntity.setDockerImage(null); + testEntity.setDockerTestScript(null); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + testEntity.setDockerImage(dockerImage); + testEntity.setDockerTestScript(dockerScript); + + /* Method not patch and template provided without script */ + httpMethod = HttpMethod.PUT; + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + null, + null, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Method not patch and no args provided */ + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + null, + null, + null, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* Invalid dockerImage */ + when(DockerSubmissionTestModel.imageExists(dockerImage)).thenReturn(false); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + when(DockerSubmissionTestModel.imageExists(dockerImage)).thenReturn(true); + + /* dockerImage without script */ + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + null, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* dockerScript without image */ + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + null, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* Method is post and test already exists */ + projectEntity.setTestId(99L); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + HttpMethod.POST + ); + assertEquals(HttpStatus.CONFLICT, result.getStatus()); + + /* Method is delete and test is found */ + httpMethod = HttpMethod.DELETE; + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + null, + null, + null, + httpMethod + ); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* TestEntity not found and method is not post */ + doReturn(null).when(testUtil).getTestIfExists(projectEntity.getId()); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + HttpMethod.PATCH + ); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + + /* Project check fails */ + when(projectUtil.getProjectIfAdmin(projectEntity.getId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Project not found", null)); + result = testUtil.checkForTestUpdate( + projectEntity.getId(), + userEntity, + dockerImage, + dockerScript, + dockerTemplate, + httpMethod + ); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + } } @Test public void testGetTestIfAdmin() { - // Mock the testRepository.findByProjectId method to return an Optional of testEntity - when(testRepository.findByProjectId(anyLong())).thenReturn(Optional.of(testEntity)); + /* TestEntity exists */ + doReturn(testEntity).when(testUtil).getTestIfExists(projectEntity.getId()); + when(projectUtil.isProjectAdmin(projectEntity.getId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + CheckResult result = testUtil.getTestIfAdmin(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + + /* User not admin */ + when(projectUtil.isProjectAdmin(projectEntity.getId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "User is not an admin", null)); + result = testUtil.getTestIfAdmin(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* TestEntity not found */ + doReturn(null).when(testUtil).getTestIfExists(projectEntity.getId()); + result = testUtil.getTestIfAdmin(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); + + } - // Mock the projectUtil.isProjectAdmin method to return a CheckResult with HttpStatus.OK - when(projectUtil.isProjectAdmin(anyLong(), any(UserEntity.class))) + @Test + public void testGetTestWithAdminStatus() { + doReturn(testEntity).when(testUtil).getTestIfExists(projectEntity.getId()); + when(projectUtil.userPartOfProject(projectEntity.getId(), userEntity.getId())).thenReturn(true); + when(projectUtil.isProjectAdmin(projectEntity.getId(), userEntity)) .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); - // Call the getTestIfAdmin method - CheckResult result = testUtil.getTestIfAdmin(1L, userEntity); + CheckResult> result = testUtil.getTestWithAdminStatus(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + assertTrue(result.getData().getSecond()); - // Assert the result + /* User not admin */ + when(projectUtil.isProjectAdmin(projectEntity.getId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.FORBIDDEN, "User is not an admin", null)); + result = testUtil.getTestWithAdminStatus(projectEntity.getId(), userEntity); assertEquals(HttpStatus.OK, result.getStatus()); - assertEquals(testEntity, result.getData()); + assertFalse(result.getData().getSecond()); + + /* User not admin but general admin */ + userEntity.setRole(UserRole.admin); + result = testUtil.getTestWithAdminStatus(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + assertTrue(result.getData().getSecond()); + + /* Project admin check returns unexpected status */ + when(projectUtil.isProjectAdmin(projectEntity.getId(), userEntity)) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "Unexpected error", null)); + result = testUtil.getTestWithAdminStatus(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); + + /* User not part of project */ + when(projectUtil.userPartOfProject(projectEntity.getId(), userEntity.getId())).thenReturn(false); + result = testUtil.getTestWithAdminStatus(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); + + /* TestEntity not found */ + doReturn(null).when(testUtil).getTestIfExists(projectEntity.getId()); + result = testUtil.getTestWithAdminStatus(projectEntity.getId(), userEntity); + assertEquals(HttpStatus.NOT_FOUND, result.getStatus()); } + + } \ No newline at end of file From c21384e0ac8b0d144cb57606677bb860158f3ad5 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sat, 11 May 2024 22:20:52 +0200 Subject: [PATCH 38/40] TestRunnerTest --- .../controllers/SubmissionController.java | 7 +- .../pidgeon/controllers/TestController.java | 24 +-- .../com/ugent/pidgeon/util/TestRunner.java | 9 +- .../controllers/SubmissionControllerTest.java | 12 +- .../controllers/TestControllerTest.java | 74 ++++----- .../ugent/pidgeon/util/TestRunnerTest.java | 155 ++++++++++++++++++ 6 files changed, 218 insertions(+), 63 deletions(-) create mode 100644 backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java index 162735d6..39bdca89 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/SubmissionController.java @@ -7,6 +7,7 @@ import com.ugent.pidgeon.model.json.LastGroupSubmissionJson; import com.ugent.pidgeon.model.json.SubmissionJson; import com.ugent.pidgeon.model.submissionTesting.DockerOutput; +import com.ugent.pidgeon.model.submissionTesting.DockerSubmissionTestModel; import com.ugent.pidgeon.model.submissionTesting.SubmissionTemplateModel; import com.ugent.pidgeon.postgre.models.*; import com.ugent.pidgeon.postgre.models.types.DockerTestState; @@ -203,7 +204,8 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P } else { // Check file structure - structureTestResult = testRunner.runStructureTest(new ZipFile(savedFile), testEntity); + SubmissionTemplateModel model = new SubmissionTemplateModel(); + structureTestResult = testRunner.runStructureTest(new ZipFile(savedFile), testEntity, model); if (structureTestResult == null) { submission.setStructureFeedback( "No specific structure requested for this project."); @@ -234,7 +236,8 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @P CompletableFuture.runAsync(() -> { try { // Check if docker tests succeed - DockerOutput dockerOutput = testRunner.runDockerTest(new ZipFile(finalSavedFile), testEntity, artifactPath); + DockerSubmissionTestModel dockerModel = new DockerSubmissionTestModel(testEntity.getDockerImage()); + DockerOutput dockerOutput = testRunner.runDockerTest(new ZipFile(finalSavedFile), testEntity, artifactPath, dockerModel); if (dockerOutput == null) { throw new RuntimeException("Error while running docker tests."); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java index e17a9849..86933d03 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/TestController.java @@ -58,10 +58,10 @@ public class TestController { @PostMapping(ApiRoutes.PROJECT_BASE_PATH + "/{projectid}/tests") @Roles({UserRole.teacher, UserRole.student}) public ResponseEntity updateTests( - @RequestParam(name = "dockerimage", required = false) String dockerImage, - @RequestParam(name = "dockerscript", required = false) String dockerTest, - @RequestParam(name = "dockertemplate", required = false) String dockerTemplate, - @RequestParam(name = "structuretest", required = false) String structureTest, + @RequestParam(name = "dockerImage", required = false) String dockerImage, + @RequestParam(name = "dockerScript", required = false) String dockerTest, + @RequestParam(name = "dockerTemplate", required = false) String dockerTemplate, + @RequestParam(name = "structureTest", required = false) String structureTest, @PathVariable("projectid") long projectId, Auth auth) { return alterTests(projectId, auth.getUserEntity(), dockerImage, dockerTest, dockerTemplate, structureTest, HttpMethod.POST); @@ -70,10 +70,10 @@ public ResponseEntity updateTests( @PatchMapping(ApiRoutes.PROJECT_BASE_PATH + "/{projectid}/tests") @Roles({UserRole.teacher, UserRole.student}) public ResponseEntity patchTests( - @RequestParam(name = "dockerimage", required = false) String dockerImage, - @RequestParam(name = "dockerscript", required = false) String dockerTest, - @RequestParam(name = "dockertemplate", required = false) String dockerTemplate, - @RequestParam(name = "structuretest", required = false) String structureTest, + @RequestParam(name = "dockerImage", required = false) String dockerImage, + @RequestParam(name = "dockerScript", required = false) String dockerTest, + @RequestParam(name = "dockerTemplate", required = false) String dockerTemplate, + @RequestParam(name = "structureTest", required = false) String structureTest, @PathVariable("projectid") long projectId, Auth auth) { return alterTests(projectId, auth.getUserEntity(), dockerImage, dockerTest, dockerTemplate, structureTest, HttpMethod.PATCH); @@ -82,10 +82,10 @@ public ResponseEntity patchTests( @PutMapping(ApiRoutes.PROJECT_BASE_PATH + "/{projectid}/tests") @Roles({UserRole.teacher, UserRole.student}) public ResponseEntity putTests( - @RequestParam(name = "dockerimage", required = false) String dockerImage, - @RequestParam(name = "dockerscript", required = false) String dockerTest, - @RequestParam(name = "dockertemplate", required = false) String dockerTemplate, - @RequestParam(name = "structuretest", required = false) String structureTest, + @RequestParam(name = "dockerImage", required = false) String dockerImage, + @RequestParam(name = "dockerScript", required = false) String dockerTest, + @RequestParam(name = "dockerTemplate", required = false) String dockerTemplate, + @RequestParam(name = "structureTest", required = false) String structureTest, @PathVariable("projectid") long projectId, Auth auth) { return alterTests(projectId, auth.getUserEntity(), dockerImage, dockerTest, dockerTemplate, structureTest, HttpMethod.PUT); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java b/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java index c3a555a2..0b3cdc8d 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/TestRunner.java @@ -15,24 +15,22 @@ public class TestRunner { public SubmissionTemplateModel.SubmissionResult runStructureTest( - ZipFile file, TestEntity testEntity) throws IOException { + ZipFile file, TestEntity testEntity, SubmissionTemplateModel model) throws IOException { // There is no structure test for this project - if(testEntity.getStructureTemplate() == null){ + if(testEntity == null || testEntity.getStructureTemplate() == null){ return null; } String structureTemplateString = testEntity.getStructureTemplate(); // Parse the file - SubmissionTemplateModel model = new SubmissionTemplateModel(); model.parseSubmissionTemplate(structureTemplateString); return model.checkSubmission(file); } - public DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path outputPath) throws IOException { + public DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path outputPath, DockerSubmissionTestModel model) throws IOException { // Get the test file from the server String testScript = testEntity.getDockerTestScript(); String testTemplate = testEntity.getDockerTestTemplate(); - String image = testEntity.getDockerImage(); // The first script must always be null, otherwise there is nothing to run on the container if (testScript == null) { @@ -40,7 +38,6 @@ public DockerOutput runDockerTest(ZipFile file, TestEntity testEntity, Path outp } // Init container and add input files - DockerSubmissionTestModel model = new DockerSubmissionTestModel(image); try { model.addZipInputFiles(file); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java index 472418c9..21b496ae 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/SubmissionControllerTest.java @@ -279,8 +279,8 @@ public void testSubmitFile() throws Exception { mockedFileHandler.when(() -> Filehandler.saveSubmission(path, mockMultipartFile)).thenReturn(file); mockedFileHandler.when(() -> Filehandler.getSubmissionArtifactPath(anyLong(), anyLong(), anyLong())).thenReturn(artifactPath); - when(testRunner.runStructureTest(any(), eq(testEntity))).thenReturn(null); - when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath))).thenReturn(null); + when(testRunner.runStructureTest(any(), eq(testEntity), any())).thenReturn(null); + when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath), any())).thenReturn(null); when(entityToJsonConverter.getSubmissionJson(submission)).thenReturn(submissionJson); @@ -314,7 +314,7 @@ public void testSubmitFile() throws Exception { submission.setStructureAccepted(false); submission.setStructureFeedback(""); SubmissionResult submissionResult = new SubmissionResult(true, "structureFeedback-test"); - when(testRunner.runStructureTest(any(), eq(testEntity))).thenReturn(submissionResult); + when(testRunner.runStructureTest(any(), eq(testEntity), any())).thenReturn(submissionResult); mockMvc.perform(MockMvcRequestBuilders.multipart(url) .file(mockMultipartFile)) .andExpect(status().isOk()) @@ -356,7 +356,7 @@ public void testSubmitFile() throws Exception { testEntity.setDockerImage("dockerImage"); testEntity.setDockerTestScript("dockerTestScript"); DockerOutput dockerOutput = new DockerTestOutput( List.of("dockerFeedback-test"), true); - when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath))).thenReturn(dockerOutput); + when(testRunner.runDockerTest(any(), eq(testEntity), eq(artifactPath), any())).thenReturn(dockerOutput); submission.setDockerAccepted(false); submission.setDockerFeedback("dockerFeedback-test"); mockMvc.perform(MockMvcRequestBuilders.multipart(url) @@ -379,8 +379,8 @@ public void testSubmitFile() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(submissionJson))); - verify(testRunner, times(0)).runStructureTest(any(), eq(testEntity)); - verify(testRunner, times(0)).runDockerTest(any(), eq(testEntity), eq(artifactPath)); + verify(testRunner, times(0)).runStructureTest(any(), eq(testEntity), any()); + verify(testRunner, times(0)).runDockerTest(any(), eq(testEntity), eq(artifactPath), any()); /* Unexpected error */ reset(fileRepository); diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java index 824cc19f..fa9da451 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java @@ -146,10 +146,10 @@ public void testUpdateTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerimage", dockerImage) - .param("dockerscript", dockerTestScript) - .param("dockertemplate", dockerTestTemplate) - .param("structuretest", structureTemplate) + .param("dockerImage", dockerImage) + .param("dockerScript", dockerTestScript) + .param("dockerTemplate", dockerTestTemplate) + .param("structureTest", structureTemplate) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -191,10 +191,10 @@ public void testUpdateTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerimage", dockerImageBlank) - .param("dockerscript", dockerTestScriptBlank) - .param("dockertemplate", dockerTemplateBlank) - .param("structuretest", structureTemplateBlank) + .param("dockerImage", dockerImageBlank) + .param("dockerScript", dockerTestScriptBlank) + .param("dockerTemplate", dockerTemplateBlank) + .param("structureTest", structureTemplateBlank) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -206,10 +206,10 @@ public void testUpdateTest() throws Exception { String structureTemplateNull = null; mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerimage", dockerImageNull) - .param("dockerscript", dockerTestScriptNull) - .param("dockertemplate", dockerTemplateNull) - .param("structuretest", structureTemplateNull) + .param("dockerImage", dockerImageNull) + .param("dockerScript", dockerTestScriptNull) + .param("dockerTemplate", dockerTemplateNull) + .param("structureTest", structureTemplateNull) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -225,10 +225,10 @@ public void testUpdateTest() throws Exception { )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerimage", dockerImage) - .param("dockerscript", dockerTestScript) - .param("dockertemplate", dockerTestTemplate) - .param("structuretest", structureTemplate) + .param("dockerImage", dockerImage) + .param("dockerScript", dockerTestScript) + .param("dockerTemplate", dockerTestTemplate) + .param("structureTest", structureTemplate) ).andExpect(status().isIAmATeapot()); } @@ -273,10 +273,10 @@ public void testPutTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerimage", dockerImage) - .param("dockerscript", dockerTestScript) - .param("dockertemplate", dockerTestTemplate) - .param("structuretest", structureTemplate) + .param("dockerImage", dockerImage) + .param("dockerScript", dockerTestScript) + .param("dockerTemplate", dockerTestTemplate) + .param("structureTest", structureTemplate) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -326,10 +326,10 @@ public void testPutTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerimage", dockerImageBlank) - .param("dockerscript", dockerTestScriptBlank) - .param("dockertemplate", dockerTemplateBlank) - .param("structuretest", structureTemplateBlank) + .param("dockerImage", dockerImageBlank) + .param("dockerScript", dockerTestScriptBlank) + .param("dockerTemplate", dockerTemplateBlank) + .param("structureTest", structureTemplateBlank) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -353,10 +353,10 @@ public void testPutTest() throws Exception { when(testRepository.imageIsUsed(any())).thenReturn(true); mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerimage", dockerImageNull) - .param("dockerscript", dockerTestScriptNull) - .param("dockertemplate", dockerTemplateNull) - .param("structuretest", structureTemplateNull) + .param("dockerImage", dockerImageNull) + .param("dockerScript", dockerTestScriptNull) + .param("dockerTemplate", dockerTemplateNull) + .param("structureTest", structureTemplateNull) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -382,10 +382,10 @@ public void testPutTest() throws Exception { )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerimage", dockerImage) - .param("dockerscript", dockerTestScript) - .param("dockertemplate", dockerTestTemplate) - .param("structuretest", structureTemplate) + .param("dockerImage", dockerImage) + .param("dockerScript", dockerTestScript) + .param("dockerTemplate", dockerTestTemplate) + .param("structureTest", structureTemplate) ).andExpect(status().isIAmATeapot()); } @@ -419,7 +419,7 @@ public void testGetPatch() throws Exception { test.setStructureTemplate(null); mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerimage", dockerImage) + .param("dockerImage", dockerImage) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -444,7 +444,7 @@ public void testGetPatch() throws Exception { mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerscript", dockerTestScript) + .param("dockerScript", dockerTestScript) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -468,7 +468,7 @@ public void testGetPatch() throws Exception { )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockertemplate", dockerTestTemplate) + .param("dockerTemplate", dockerTestTemplate) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -492,7 +492,7 @@ public void testGetPatch() throws Exception { )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("structuretest", structureTemplate) + .param("structureTest", structureTemplate) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -516,7 +516,7 @@ public void testGetPatch() throws Exception { )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerimage", dockerImage) + .param("dockerImage", dockerImage) ).andExpect(status().isIAmATeapot()); } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java new file mode 100644 index 00000000..828a0f7d --- /dev/null +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/TestRunnerTest.java @@ -0,0 +1,155 @@ +package com.ugent.pidgeon.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.ugent.pidgeon.model.submissionTesting.DockerOutput; +import com.ugent.pidgeon.model.submissionTesting.DockerSubmissionTestModel; +import com.ugent.pidgeon.model.submissionTesting.DockerTemplateTestOutput; +import com.ugent.pidgeon.model.submissionTesting.DockerTestOutput; +import com.ugent.pidgeon.model.submissionTesting.SubmissionTemplateModel; +import com.ugent.pidgeon.model.submissionTesting.SubmissionTemplateModel.SubmissionResult; +import com.ugent.pidgeon.postgre.models.TestEntity; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.ZipFile; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class TestRunnerTest { + + @Mock + private SubmissionTemplateModel structureModel; + @Mock + private DockerSubmissionTestModel dockerModel; + @Mock + private ZipFile file; + @Mock + private File artifactFile; + + + + + private List artifacts; + + private TestEntity testEntity; + private SubmissionResult submissionResult; + private DockerTestOutput dockerTestOutput; + private DockerTemplateTestOutput dockerTemplateTestOutput; + + @BeforeEach + public void setUp() { + testEntity = new TestEntity( + "dockerImageBasic", + "dockerTestScriptBasic", + "dockerTestTemplateBasic", + "structureTemplateBasic" + ); + testEntity.setId(38L); + + submissionResult = new SubmissionResult( + true, "submissionResultBasic" + ); + + dockerTestOutput = new DockerTestOutput( + List.of("logs"), true + ); + + dockerTemplateTestOutput = new DockerTemplateTestOutput( + Collections.emptyList(), true + ); + + artifacts = List.of(artifactFile); + } + + @Test + public void testRunStructureTest() throws IOException { + /* The test exists */ + when(structureModel.checkSubmission(file)).thenReturn(submissionResult); + SubmissionResult result = new TestRunner().runStructureTest(file, testEntity, structureModel); + assertEquals(submissionResult, result); + verify(structureModel).parseSubmissionTemplate(testEntity.getStructureTemplate()); + + /* Structure template is null */ + testEntity.setStructureTemplate(null); + result = new TestRunner().runStructureTest(file, testEntity, structureModel); + assertNull(result); + + /* Test entity is null */ + result = new TestRunner().runStructureTest(file, null, structureModel); + assertNull(result); + } + + @Test + public void testRunDockerTest() throws IOException { + try (MockedStatic filehandler = org.mockito.Mockito.mockStatic(Filehandler.class)) { + Path outputPath = Path.of("outputPath"); + AtomicInteger filehandlerCalled = new AtomicInteger(); + filehandlerCalled.set(0); + filehandler.when(() -> Filehandler.copyFilesAsZip(artifacts, outputPath)).thenAnswer( + invocation -> { + filehandlerCalled.getAndIncrement(); + return null; + }); + when(dockerModel.runSubmissionWithTemplate(testEntity.getDockerTestScript(), testEntity.getDockerTestTemplate())) + .thenReturn(dockerTemplateTestOutput); + when(dockerModel.getArtifacts()).thenReturn(artifacts); + + DockerOutput result = new TestRunner().runDockerTest(file, testEntity, outputPath, dockerModel); + assertEquals(dockerTemplateTestOutput, result); + + verify(dockerModel, times(1)).addZipInputFiles(file); + verify(dockerModel, times(1)).cleanUp(); + assertEquals(1, filehandlerCalled.get()); + + /* artifacts are empty */ + when(dockerModel.getArtifacts()).thenReturn(Collections.emptyList()); + result = new TestRunner().runDockerTest(file, testEntity, outputPath, dockerModel); + assertEquals(dockerTemplateTestOutput, result); + verify(dockerModel, times(2)).addZipInputFiles(file); + verify(dockerModel, times(2)).cleanUp(); + assertEquals(1, filehandlerCalled.get()); + + /* aritifacts are null */ + when(dockerModel.getArtifacts()).thenReturn(null); + result = new TestRunner().runDockerTest(file, testEntity, outputPath, dockerModel); + assertEquals(dockerTemplateTestOutput, result); + verify(dockerModel, times(3)).addZipInputFiles(file); + verify(dockerModel, times(3)).cleanUp(); + assertEquals(1, filehandlerCalled.get()); + + /* No template */ + testEntity.setDockerTestTemplate(null); + when(dockerModel.runSubmission(testEntity.getDockerTestScript())).thenReturn(dockerTestOutput); + result = new TestRunner().runDockerTest(file, testEntity, outputPath, dockerModel); + assertEquals(dockerTestOutput, result); + verify(dockerModel, times(4)).addZipInputFiles(file); + verify(dockerModel, times(4)).cleanUp(); + + /* Error gets thrown */ + when(dockerModel.runSubmission(testEntity.getDockerTestScript())).thenThrow(new RuntimeException("Error")); + assertThrows(Exception.class, () -> new TestRunner().runDockerTest(file, testEntity, outputPath, dockerModel)); + verify(dockerModel, times(5)).cleanUp(); + + /* No script */ + testEntity.setDockerTestScript(null); + result = new TestRunner().runDockerTest(file, testEntity, outputPath, dockerModel); + assertNull(result); + } + + + } +} From 5be50cee64b3521f4901d99844ad261efad84ec1 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sat, 11 May 2024 23:03:32 +0200 Subject: [PATCH 39/40] update testControllerTests --- .../controllers/TestControllerTest.java | 163 +++++++++++++----- 1 file changed, 122 insertions(+), 41 deletions(-) diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java index fa9da451..a360aaff 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/TestControllerTest.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.ugent.pidgeon.CustomObjectMapper; import com.ugent.pidgeon.model.json.TestJson; +import com.ugent.pidgeon.model.json.TestUpdateJson; import com.ugent.pidgeon.postgre.models.GroupEntity; import com.ugent.pidgeon.postgre.models.ProjectEntity; import com.ugent.pidgeon.postgre.models.TestEntity; @@ -119,6 +120,13 @@ public void testUpdateTest() throws Exception { String dockerTestTemplate = "dockerTestTemplate"; String structureTemplate = "structureTemplate"; + TestUpdateJson testUpdateJson = new TestUpdateJson( + dockerImage, + dockerTestScript, + dockerTestTemplate, + structureTemplate + ); + TestJson testJson = new TestJson( "projectUrl", dockerImage, @@ -146,11 +154,9 @@ public void testUpdateTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerImage", dockerImage) - .param("dockerScript", dockerTestScript) - .param("dockerTemplate", dockerTestTemplate) - .param("structureTest", structureTemplate) - ).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) + ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -170,6 +176,12 @@ public void testUpdateTest() throws Exception { null, null ); + testUpdateJson = new TestUpdateJson( + dockerImageBlank, + dockerTestScriptBlank, + dockerTemplateBlank, + structureTemplateBlank + ); reset(testUtil); when(testUtil.checkForTestUpdate( eq(project.getId()), @@ -191,11 +203,9 @@ public void testUpdateTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerImage", dockerImageBlank) - .param("dockerScript", dockerTestScriptBlank) - .param("dockerTemplate", dockerTemplateBlank) - .param("structureTest", structureTemplateBlank) - ).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) + ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -205,12 +215,19 @@ public void testUpdateTest() throws Exception { String dockerTemplateNull = null; String structureTemplateNull = null; + testUpdateJson = new TestUpdateJson( + dockerImageNull, + dockerTestScriptNull, + dockerTemplateNull, + structureTemplateNull + ); + + when(testRepository.imageIsUsed(any())).thenReturn(true); + mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerImage", dockerImageNull) - .param("dockerScript", dockerTestScriptNull) - .param("dockerTemplate", dockerTemplateNull) - .param("structureTest", structureTemplateNull) - ).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) + ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -224,11 +241,16 @@ public void testUpdateTest() throws Exception { eq(HttpMethod.POST) )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + testUpdateJson = new TestUpdateJson( + dockerImage, + dockerTestScript, + dockerTestTemplate, + structureTemplate + ); + mockMvc.perform(MockMvcRequestBuilders.post(url) - .param("dockerImage", dockerImage) - .param("dockerScript", dockerTestScript) - .param("dockerTemplate", dockerTestTemplate) - .param("structureTest", structureTemplate) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isIAmATeapot()); } @@ -247,6 +269,13 @@ public void testPutTest() throws Exception { String dockerTestTemplate = "dockerTestTemplate"; String structureTemplate = "structureTemplate"; + TestUpdateJson testUpdateJson = new TestUpdateJson( + dockerImage, + dockerTestScript, + dockerTestTemplate, + structureTemplate + ); + test.setDockerImage(null); test.setDockerTestScript(null); test.setDockerTestTemplate(null); @@ -273,11 +302,9 @@ public void testPutTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerImage", dockerImage) - .param("dockerScript", dockerTestScript) - .param("dockerTemplate", dockerTestTemplate) - .param("structureTest", structureTemplate) - ).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) + ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -298,6 +325,13 @@ public void testPutTest() throws Exception { String dockerTemplateBlank = ""; String structureTemplateBlank = ""; + testUpdateJson = new TestUpdateJson( + dockerImageBlank, + dockerTestScriptBlank, + dockerTemplateBlank, + structureTemplateBlank + ); + testJson = new TestJson( "projectUrl", null, @@ -326,10 +360,8 @@ public void testPutTest() throws Exception { when(entityToJsonConverter.testEntityToTestJson(test, project.getId())).thenReturn(testJson); mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerImage", dockerImageBlank) - .param("dockerScript", dockerTestScriptBlank) - .param("dockerTemplate", dockerTemplateBlank) - .param("structureTest", structureTemplateBlank) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -352,11 +384,16 @@ public void testPutTest() throws Exception { when(testRepository.imageIsUsed(any())).thenReturn(true); + testUpdateJson = new TestUpdateJson( + dockerImageNull, + dockerTestScriptNull, + dockerTemplateNull, + structureTemplateNull + ); + mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerImage", dockerImageNull) - .param("dockerScript", dockerTestScriptNull) - .param("dockerTemplate", dockerTemplateNull) - .param("structureTest", structureTemplateNull) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -381,11 +418,16 @@ public void testPutTest() throws Exception { eq(HttpMethod.PUT) )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + testUpdateJson = new TestUpdateJson( + dockerImage, + dockerTestScript, + dockerTestTemplate, + structureTemplate + ); + mockMvc.perform(MockMvcRequestBuilders.put(url) - .param("dockerImage", dockerImage) - .param("dockerScript", dockerTestScript) - .param("dockerTemplate", dockerTestTemplate) - .param("structureTest", structureTemplate) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isIAmATeapot()); } @@ -418,8 +460,16 @@ public void testGetPatch() throws Exception { test.setDockerTestTemplate(null); test.setStructureTemplate(null); + TestUpdateJson testUpdateJson = new TestUpdateJson( + dockerImage, + null, + null, + null + ); + mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerImage", dockerImage) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -442,9 +492,16 @@ public void testGetPatch() throws Exception { eq(HttpMethod.PATCH) )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + testUpdateJson = new TestUpdateJson( + null, + dockerTestScript, + null, + null + ); mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerScript", dockerTestScript) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -467,8 +524,16 @@ public void testGetPatch() throws Exception { eq(HttpMethod.PATCH) )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + testUpdateJson = new TestUpdateJson( + null, + null, + dockerTestTemplate, + null + ); + mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerTemplate", dockerTestTemplate) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -491,8 +556,16 @@ public void testGetPatch() throws Exception { eq(HttpMethod.PATCH) )).thenReturn(new CheckResult<>(HttpStatus.OK, "",new Pair<>(test, project))); + testUpdateJson = new TestUpdateJson( + null, + null, + null, + structureTemplate + ); + mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("structureTest", structureTemplate) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(content().json(objectMapper.writeValueAsString(testJson))); @@ -515,8 +588,16 @@ public void testGetPatch() throws Exception { eq(HttpMethod.PATCH) )).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "I'm a teapot", null)); + testUpdateJson = new TestUpdateJson( + dockerImage, + null, + null, + null + ); + mockMvc.perform(MockMvcRequestBuilders.patch(url) - .param("dockerImage", dockerImage) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(testUpdateJson)) ).andExpect(status().isIAmATeapot()); } From a2b3de11a60bd9c24464285899aa52eee1b1b9f3 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Sun, 12 May 2024 15:21:13 +0200 Subject: [PATCH 40/40] updated test for clusterfillroute --- .../controllers/ClusterController.java | 4 +- .../pidgeon/model/json/ClusterFillJson.java | 4 + .../controllers/ClusterControllerTest.java | 174 +++++++++++------- .../ugent/pidgeon/util/ClusterUtilTest.java | 62 +++++++ .../com/ugent/pidgeon/util/GroupUtilTest.java | 12 +- 5 files changed, 187 insertions(+), 69 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java index e8c05670..6508be3c 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ClusterController.java @@ -183,7 +183,7 @@ public ResponseEntity doGroupClusterUpdate(GroupClusterEntity clusterEntity, * * @param clusterid identifier of a cluster * @param auth authentication object of the requesting user - * @param clusterFillJson ClusterFillJson object containing a map of all groups and their + * @param clusterFillMap Map object containing a map of all groups and their * members of that cluster * @return ResponseEntity * @HttpMethod PUT @@ -226,7 +226,7 @@ public ResponseEntity fillCluster(@PathVariable("clusterid") Long clusterid, groupCluster.setGroupAmount(clusterFillJson.getClusterGroupMembers().size()); groupClusterRepository.save(groupCluster); - return ResponseEntity.status(HttpStatus.OK).body("Filled group cluster successfully"); + return ResponseEntity.status(HttpStatus.OK).body(entityToJsonConverter.clusterEntityToClusterJson(groupCluster)); } catch (Exception e) { Logger.getGlobal().severe(e.getMessage()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Something went wrong"); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/ClusterFillJson.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/ClusterFillJson.java index 2b57264c..2990f5f9 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/json/ClusterFillJson.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/ClusterFillJson.java @@ -18,4 +18,8 @@ public Map getClusterGroupMembers() { return clusterGroupMembers; } + public void addClusterGroupMembers(String clusterId, Long[] groupIds) { + clusterGroupMembers.put(clusterId, groupIds); + } + } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java index e5b57f6d..2bd77265 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/controllers/ClusterControllerTest.java @@ -9,11 +9,15 @@ import com.ugent.pidgeon.postgre.models.GroupEntity; import com.ugent.pidgeon.postgre.models.types.CourseRelation; import com.ugent.pidgeon.postgre.repository.GroupClusterRepository; +import com.ugent.pidgeon.postgre.repository.GroupMemberRepository; import com.ugent.pidgeon.postgre.repository.GroupRepository; import com.ugent.pidgeon.util.*; import java.time.OffsetDateTime; import java.util.Collections; +import java.util.logging.Logger; import org.junit.jupiter.api.BeforeEach; +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; @@ -30,7 +34,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -43,6 +50,8 @@ public class ClusterControllerTest extends ControllerTest{ GroupClusterRepository groupClusterRepository; @Mock GroupRepository groupRepository; + @Mock + GroupMemberRepository groupMemberRepository; @Mock @@ -54,8 +63,7 @@ public class ClusterControllerTest extends ControllerTest{ private CourseUtil courseUtil; @Mock private CommonDatabaseActions commonDatabaseActions; - @Mock - private GroupMemberController groupMemberController; + @InjectMocks private ClusterController clusterController; @@ -73,10 +81,20 @@ public void setup() { setUpController(clusterController); courseEntity = new CourseEntity("name", "description",2024); - groupClusterEntity = new GroupClusterEntity(1L, 20, "clustername", 5); - groupClusterJson = new GroupClusterJson(1L, "clustername", 20, 5, OffsetDateTime.now(), Collections.emptyList(), ""); + courseEntity.setId(32L); + groupClusterEntity = new GroupClusterEntity(courseEntity.getId(), 20, "clustername", 5); + groupClusterEntity.setId(29L); + groupClusterJson = new GroupClusterJson( + groupClusterEntity.getId(), + groupClusterEntity.getName(), + groupClusterEntity.getMaxSize(), + groupClusterEntity.getGroupAmount(), + OffsetDateTime.now(), + Collections.emptyList(), + ""); groupEntity = new GroupEntity("groupName", 1L); - groupJson = new GroupJson(10, 1L, "Groupname", ""); + groupEntity.setId(78L); + groupJson = new GroupJson(groupClusterEntity.getMaxSize(), groupEntity.getId(), groupEntity.getName(), ""); } @Test @@ -198,66 +216,92 @@ public void testUpdateCluster() throws Exception { .andExpect(status().isBadRequest()); } -// TEST IS OUTDATED, SHOULD WORK WITH MINIMAL CHANGES -// @Test -// public void testFillCluster() throws Exception { -// String request = "{\"clusterGroupMembers\":{\"1\":[1,2,3],\"2\":[],\"3\":[4]}}"; -// -// List groupJsons = List.of(new GroupJson(3, 1L, "group 1", "groupclusterurl")); -// GroupClusterJson groupClusterJson = new GroupClusterJson(1L, "test cluster", -// 3, 5, OffsetDateTime.now(), groupJsons, "courseurl"); -// when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) -// .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); -// when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any())) -// .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); -// when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)) -// .thenReturn(groupClusterJson); -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isOk()); -// -// when(commonDatabaseActions.removeGroup(anyLong())) -// .thenThrow(new RuntimeException("TEST ERROR")); -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isInternalServerError()); -// -// // a group that is too big -// request = "{\"clusterGroupMembers\":{\"1\":[1,2,3,6],\"2\":[],\"3\":[4]}}"; -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isBadRequest()); -// // too many groups -// request = "{\"clusterGroupMembers\":{\"1\":[1,2,3],\"2\":[],\"3\":[4],\"4\":[],\"5\":[6],\"6\":[]}}"; -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isBadRequest()); -// -// when(entityToJsonConverter.clusterEntityToClusterJson(groupClusterEntity)) -// .thenReturn(null); -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isNotFound()); -// -// when(clusterUtil.getGroupClusterEntityIfNotIndividual(anyLong(), any())) -// .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isIAmATeapot()); -// -// when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(anyLong(), any())) -// .thenReturn(new CheckResult<>(HttpStatus.UNAUTHORIZED, "", null)); -// mockMvc.perform(MockMvcRequestBuilders.put(ApiRoutes.CLUSTER_BASE_PATH+"/1/fill") -// .contentType(MediaType.APPLICATION_JSON) -// .content(request)) -// .andExpect(status().isUnauthorized()); -// } + @Test + public void testFillCluster() throws Exception { + String url = ApiRoutes.CLUSTER_BASE_PATH + "/" + groupClusterEntity.getId() + "/fill"; + String request = """ + { + "group1": [3, 2], + "group2": [4, 5] + } + """; + + long newGroupEntityId = 89L; + GroupEntity newGroupEntity = new GroupEntity("group1", groupClusterEntity.getId()); + newGroupEntity.setId(newGroupEntityId); + long newGroupEntityId2 = 221L; + GroupEntity newGroupEntity2 = new GroupEntity("group2", groupClusterEntity.getId()); + newGroupEntity2.setId(newGroupEntityId2); + /* All checks succeed */ + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + + when(groupRepository.findAllByClusterId(groupClusterEntity.getId())).thenReturn(List.of(groupEntity)); + when(clusterUtil.checkFillClusterJson(argThat( + json -> { + boolean check = json.getClusterGroupMembers().size() == 2; + check = check && json.getClusterGroupMembers().get("group1").length == 2; + check = check && json.getClusterGroupMembers().get("group1")[0] == 3; + check = check && json.getClusterGroupMembers().get("group1")[1] == 2; + check = check && json.getClusterGroupMembers().get("group2").length == 2; + check = check && json.getClusterGroupMembers().get("group2")[0] == 4; + check = check && json.getClusterGroupMembers().get("group2")[1] == 5; + return check; + } + ), eq(groupClusterEntity))) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + when(groupRepository.save(argThat( + g1 -> g1 != null && g1.getName().equals("group1") && g1.getClusterId() == groupClusterEntity.getId() + ))).thenReturn(newGroupEntity); + + when(groupRepository.save(argThat( + g2 -> g2 != null && g2.getName().equals("group2") && g2.getClusterId() == groupClusterEntity.getId() + ))).thenReturn(newGroupEntity2); + + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isOk()); + + verify(commonDatabaseActions, times(1)).removeGroup(groupEntity.getId()); + verify(groupMemberRepository, times(1)).addMemberToGroup(newGroupEntityId, 2); + verify(groupMemberRepository, times(1)).addMemberToGroup(newGroupEntityId, 3); + verify(groupMemberRepository, times(1)).addMemberToGroup(newGroupEntityId2, 4); + verify(groupMemberRepository, times(1)).addMemberToGroup(newGroupEntityId2, 5); + assertEquals(2, groupClusterEntity.getGroupAmount()); + verify(groupClusterRepository, times(1)).save(groupClusterEntity); + + /* Error when checking json */ + reset(clusterUtil); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + when(clusterUtil.checkFillClusterJson(any(), any())).thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + + /* Error when getting group cluster entity */ + reset(clusterUtil); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isIAmATeapot()); + + /* Unexepcted error */ + reset(clusterUtil); + when(clusterUtil.getGroupClusterEntityIfAdminAndNotIndividual(groupClusterEntity.getId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", groupClusterEntity)); + when(groupRepository.findAllByClusterId(groupClusterEntity.getId())).thenThrow(new RuntimeException()); + mockMvc.perform(MockMvcRequestBuilders.put(url) + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().isInternalServerError()); + + } @Test public void testPatchCluster() throws Exception { diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java index 3f7d690d..37588085 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/ClusterUtilTest.java @@ -3,14 +3,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.ugent.pidgeon.model.json.ClusterFillJson; import com.ugent.pidgeon.model.json.GroupClusterCreateJson; import com.ugent.pidgeon.model.json.GroupClusterUpdateJson; +import com.ugent.pidgeon.postgre.models.CourseUserEntity; import com.ugent.pidgeon.postgre.models.GroupClusterEntity; import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.CourseRelation; import com.ugent.pidgeon.postgre.models.types.UserRole; +import com.ugent.pidgeon.postgre.repository.CourseUserRepository; import com.ugent.pidgeon.postgre.repository.GroupClusterRepository; import java.util.Optional; import org.hibernate.annotations.Check; @@ -29,6 +38,8 @@ public class ClusterUtilTest { @Mock private GroupClusterRepository groupClusterRepository; @Mock + private CourseUserRepository courseUserRepository; + @Mock private CourseUtil courseUtil; @Spy @@ -251,4 +262,55 @@ void testCheckGroupClusterCreateJson() { assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); } + @Test + public void testCheckFillClusterJson() { + ClusterFillJson fillJson = new ClusterFillJson(); + CourseUserEntity enrolledCU = new CourseUserEntity(22L, 5L, CourseRelation.enrolled); + fillJson.addClusterGroupMembers("Group1", new Long[]{5L, 2L}); + fillJson.addClusterGroupMembers("Group2", new Long[]{3L, 10L}); + + when(courseUserRepository.findById(any())).thenReturn(Optional.of(enrolledCU)); + + CheckResult result = clusterUtil.checkFillClusterJson(fillJson, clusterEntity); + assertEquals(HttpStatus.OK, result.getStatus()); + + verify(courseUserRepository, times(1)).findById(argThat( + arg -> arg.getCourseId() == clusterEntity.getCourseId() && arg.getUserId() == 5L)); + verify(courseUserRepository, times(1)).findById(argThat( + arg -> arg.getCourseId() == clusterEntity.getCourseId() && arg.getUserId() == 2L)); + verify(courseUserRepository, times(1)).findById(argThat( + arg -> arg.getCourseId() == clusterEntity.getCourseId() && arg.getUserId() == 3L)); + verify(courseUserRepository, times(1)).findById(argThat( + arg -> arg.getCourseId() == clusterEntity.getCourseId() && arg.getUserId() == 10L)); + + /* User admin in course */ + CourseUserEntity courseAdminCU = new CourseUserEntity(22L, 5L, CourseRelation.course_admin); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == clusterEntity.getCourseId() && arg.getUserId() == 5L))) + .thenReturn(Optional.of(courseAdminCU)); + + result = clusterUtil.checkFillClusterJson(fillJson, clusterEntity); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* User not found in course */ + reset(courseUserRepository); + when(courseUserRepository.findById(any())).thenReturn(Optional.of(enrolledCU)); + when(courseUserRepository.findById(argThat( + arg -> arg.getCourseId() == clusterEntity.getCourseId() && arg.getUserId() == 3L))) + .thenReturn(Optional.empty()); + + result = clusterUtil.checkFillClusterJson(fillJson, clusterEntity); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + /* trying to add user twice */ + reset(courseUserRepository); + when(courseUserRepository.findById(any())).thenReturn(Optional.of(enrolledCU)); + fillJson.addClusterGroupMembers("Group3", new Long[]{5L, 4L}); + + result = clusterUtil.checkFillClusterJson(fillJson, clusterEntity); + assertEquals(HttpStatus.BAD_REQUEST, result.getStatus()); + + } + + } diff --git a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java index 2d482636..145b1cc9 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupUtilTest.java @@ -166,6 +166,12 @@ public void TestCanAddUserToGroup() { result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); assertEquals(HttpStatus.OK, result.getStatus()); + /* Group is already full but it's an admin adding someone else */ + when(groupRepository.countUsersInGroup(group.getId())).thenReturn(groupCluster.getMaxSize()); + result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + assertEquals(HttpStatus.OK, result.getStatus()); + when(groupRepository.countUsersInGroup(group.getId())).thenReturn(groupCluster.getMaxSize()-1); + /* User trying to add is admin */ doReturn(new CheckResult<>(HttpStatus.OK, "", null)). when(groupUtil).isAdminOfGroup(group.getId(), otherUser); @@ -179,7 +185,7 @@ public void TestCanAddUserToGroup() { /* Group is already full */ when(groupRepository.countUsersInGroup(group.getId())).thenReturn(groupCluster.getMaxSize()); - result = groupUtil.canAddUserToGroup(group.getId(), otherUserId, mockUser); + result = groupUtil.canAddUserToGroup(group.getId(), mockUser.getId(), mockUser); assertEquals(HttpStatus.FORBIDDEN, result.getStatus()); /* ClusterEntity is not found */ @@ -312,4 +318,6 @@ public void testCanGetProjectGroupData() throws Exception { result = groupUtil.canGetProjectGroupData(group.getId(), project.getId(), mockUser); assertEquals(HttpStatus.I_AM_A_TEAPOT, result.getStatus()); } -} + + + }