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 f2685ffe..cf07c916 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 @@ -15,8 +15,19 @@ import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.*; import com.ugent.pidgeon.util.*; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; @@ -90,6 +101,15 @@ public ResponseEntity getSubmission(@PathVariable("submissionid") long submis return ResponseEntity.ok(submissionJson); } + private Map> getLatestSubmissionsForProject(long projectId) { + List groupIds = projectRepository.findGroupIdsByProjectId(projectId); + return groupIds.stream() + .collect(Collectors.toMap( + groupId -> groupId, + groupId -> submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectId, groupId) + )); + } + /** * Function to get all submissions * @@ -110,35 +130,35 @@ public ResponseEntity getSubmissions(@PathVariable("projectid") long projecti return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage()); } - List projectGroupIds = projectRepository.findGroupIdsByProjectId(projectid); - List res = projectGroupIds.stream().map(groupId -> { - GroupEntity group = groupRepository.findById(groupId).orElse(null); + Map> submissions = getLatestSubmissionsForProject(projectid); + List res = new ArrayList<>(); + for (Map.Entry> entry : submissions.entrySet()) { + GroupEntity group = groupRepository.findById(entry.getKey()).orElse(null); if (group == null) { throw new RuntimeException("Group not found"); } GroupJson groupjson = entityToJsonConverter.groupEntityToJson(group, false); - GroupFeedbackEntity groupFeedbackEntity = groupFeedbackRepository.getGroupFeedback(groupId, projectid); + GroupFeedbackEntity groupFeedbackEntity = groupFeedbackRepository.getGroupFeedback(entry.getKey(), projectid); GroupFeedbackJson groupFeedbackJson; if (groupFeedbackEntity == null) { groupFeedbackJson = null; } else { groupFeedbackJson = entityToJsonConverter.groupFeedbackEntityToJson(groupFeedbackEntity); } - SubmissionEntity submission = submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(projectid, groupId).orElse(null); + SubmissionEntity submission = entry.getValue().orElse(null); if (submission == null) { - return new LastGroupSubmissionJson(null, groupjson, groupFeedbackJson); + res.add(new LastGroupSubmissionJson(null, groupjson, groupFeedbackJson)); + continue; } + res.add(new LastGroupSubmissionJson(entityToJsonConverter.getSubmissionJson(submission), groupjson, groupFeedbackJson)); + } - return new LastGroupSubmissionJson(entityToJsonConverter.getSubmissionJson(submission), groupjson, groupFeedbackJson); - - }).toList(); return ResponseEntity.ok(res); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); } } - /** * Function to submit a file * @@ -306,6 +326,55 @@ public ResponseEntity getSubmissionFile(@PathVariable("submissionid") long su return Filehandler.getZipFileAsResponse(Path.of(file.getPath()), file.getName()); } + @GetMapping(ApiRoutes.PROJECT_BASE_PATH + "/{projectid}/submissions/files") + @Roles({UserRole.teacher, UserRole.student}) + public ResponseEntity getSubmissionsFiles(@PathVariable("projectid") long projectid, @RequestParam(value = "artifacts", required = false) Boolean artifacts, Auth auth) { + try { + CheckResult checkResult = projectUtil.isProjectAdmin(projectid, auth.getUserEntity()); + if (!checkResult.getStatus().equals(HttpStatus.OK)) { + return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage()); + } + + Path tempDir = Files.createTempDirectory("SELAB6CANDELETEallsubmissions"); + Path mainZipPath = tempDir.resolve("main.zip"); + try (ZipOutputStream mainZipOut = new ZipOutputStream(Files.newOutputStream(mainZipPath))) { + Map> submissions = getLatestSubmissionsForProject(projectid); + for (Map.Entry> entry : submissions.entrySet()) { + SubmissionEntity submission = entry.getValue().orElse(null); + if (submission == null) { + continue; + } + FileEntity file = fileRepository.findById(submission.getFileId()).orElse(null); + if (file == null) { + continue; + } + + // Create the group-specific zip file in a temporary location + Path groupZipPath = tempDir.resolve("group-" + submission.getGroupId() + ".zip"); + try (ZipOutputStream groupZipOut = new ZipOutputStream(Files.newOutputStream(groupZipPath))) { + File submissionZip = Path.of(file.getPath()).toFile(); + Filehandler.addExistingZip(groupZipOut, "files.zip", submissionZip); + + if (artifacts != null && artifacts) { + Path artifactPath = Filehandler.getSubmissionArtifactPath(projectid, submission.getGroupId(), submission.getId()); + File artifactZip = artifactPath.toFile(); + if (artifactZip.exists()) { + Filehandler.addExistingZip(groupZipOut, "artifacts.zip", artifactZip); + } + } + + } + + Filehandler.addExistingZip(mainZipOut, "group-" + submission.getGroupId() + ".zip", groupZipPath.toFile()); + } + } + + return Filehandler.getZipFileAsResponse(mainZipPath, "allsubmissions.zip"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); + } + } + @GetMapping(ApiRoutes.SUBMISSION_BASE_PATH + "/{submissionid}/artifacts") //Route to get a submission @Roles({UserRole.teacher, UserRole.student}) public ResponseEntity getSubmissionArtifacts(@PathVariable("submissionid") long submissionid, Auth auth) { 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 ba304571..961f335f 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 @@ -9,6 +9,8 @@ import com.ugent.pidgeon.postgre.models.types.DockerTestState; import com.ugent.pidgeon.postgre.models.types.DockerTestType; import com.ugent.pidgeon.postgre.repository.*; +import java.io.File; +import java.nio.file.Path; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -252,6 +254,14 @@ else if (submission.getDockerTestType().equals(DockerTestType.SIMPLE)) { } else { feedback = new DockerTestFeedbackJson(DockerTestType.TEMPLATE, submission.getDockerFeedback(), submission.getDockerAccepted()); } + + boolean artifactsExist; + if (submission.getGroupId() != null) { + Path artifactPath = Filehandler.getSubmissionArtifactPath(submission.getProjectId(), submission.getGroupId(), submission.getId()); + artifactsExist = new File(artifactPath.toString()).exists(); + } else { + artifactsExist = false; + } return new SubmissionJson( submission.getId(), ApiRoutes.PROJECT_BASE_PATH + "/" + submission.getProjectId(), @@ -264,7 +274,7 @@ else if (submission.getDockerTestType().equals(DockerTestType.SIMPLE)) { submission.getStructureFeedback(), feedback, submission.getDockerTestState().toString(), - ApiRoutes.SUBMISSION_BASE_PATH + "/" + submission.getId() + "/artifacts" + artifactsExist ? ApiRoutes.SUBMISSION_BASE_PATH + "/" + submission.getId() + "/artifacts" : null ); } 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 a06e5399..94e6a031 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 @@ -39,7 +39,7 @@ public static File saveFile(Path directory, MultipartFile file, String filename) try { // Create a temporary file and save the uploaded file to it - File tempFile = File.createTempFile("uploaded-zip-", ".zip"); + File tempFile = File.createTempFile("SELAB6CANDELETEuploaded-zip-", ".zip"); file.transferTo(tempFile); // Check if the file is a ZIP file @@ -213,4 +213,15 @@ public static ResponseEntity getZipFileAsResponse(Path path, String filename) .headers(headers) .body(zipFile); } + + + public static void addExistingZip(ZipOutputStream groupZipOut, String zipFileName, File zipFile) throws IOException { + ZipEntry zipEntry = new ZipEntry(zipFileName); + groupZipOut.putNextEntry(zipEntry); + + // Read the content of the zip file and write it to the group zip output stream + Files.copy(zipFile.toPath(), groupZipOut); + + groupZipOut.closeEntry(); + } } 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 03e21232..b1dc7e52 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,15 +100,15 @@ public CheckResult checkGroupFeedbackUpdateJson(UpdateGroupScoreRequest re return new CheckResult<>(projectCheck.getStatus(), projectCheck.getMessage(), null); } Integer maxScore = projectCheck.getData().getMaxScore(); - if ((request.getScore() == null && maxScore != null) || request.getFeedback() == null) { - return new CheckResult<>(HttpStatus.BAD_REQUEST, "Score and feedback need to be provided", null); + if (request.getFeedback() == null) { + return new CheckResult<>(HttpStatus.BAD_REQUEST, "Feedbacks need to be provided", null); } if (request.getScore() != null && request.getScore() < 0) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "Score can't be lower than 0", null); } - if (maxScore != null && request.getScore() > maxScore) { + if (maxScore != null && request.getScore() != null && request.getScore() > maxScore) { return new CheckResult<>(HttpStatus.BAD_REQUEST, "Score can't be higher than the defined max score (" + maxScore + ")", 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 31ba426a..58e92fa5 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 @@ -19,16 +19,24 @@ import com.ugent.pidgeon.postgre.models.types.DockerTestType; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.ugent.pidgeon.postgre.repository.*; import com.ugent.pidgeon.util.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; 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.ZipFile; +import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,6 +51,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.time.OffsetDateTime; @@ -54,6 +63,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -112,10 +122,10 @@ public class SubmissionControllerTest extends ControllerTest { public static File createTestFile() throws IOException { // Create a temporary directory - File tempDir = Files.createTempDirectory("test-dir").toFile(); + File tempDir = Files.createTempDirectory("SELAB6CANDELETEtest-dir").toFile(); // Create a temporary file within the directory - File tempFile = File.createTempFile("test-file", ".zip", tempDir); + File tempFile = File.createTempFile("SELAB6CANDELETEtest-file", ".zip", tempDir); // Create some content to write into the zip file String content = "Hello, this is a test file!"; @@ -565,4 +575,355 @@ public void testGetAdminSubmissions() { e.printStackTrace(); } } + + @Test + public void testGetSubmissionsFiles() { + String url = ApiRoutes.PROJECT_BASE_PATH + "/" + submission.getProjectId() + "/submissions/files"; + + /* Create temp zip file for submission */ + File file = null; + try { + file = createTestFile(); + } catch (IOException e) { + e.printStackTrace(); + } + assertNotNull(file); + fileEntity.setPath(file.getAbsolutePath()); + + + /* All checks succeed */ + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.OK, "", null)); + + when(projectRepository.findGroupIdsByProjectId(submission.getProjectId())).thenReturn(groupIds); + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(submission.getProjectId(), groupEntity.getId())).thenReturn(Optional.of(submission)); + when(fileRepository.findById(submission.getFileId())).thenReturn(Optional.of(fileEntity)); + + try { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=allsubmissions.zip")) + .andReturn(); + + byte[] content = mvcResult.getResponse().getContentAsByteArray(); + + boolean groupzipfound = false; + boolean fileszipfound = false; + /* Check contents of file */ + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().equals("group-" + submission.getGroupId() + ".zip")) { + groupzipfound = true; + /* Check if there is a zipfile inside the zipfile with name 'files.zip' */ + + // Create a new ByteArrayOutputStream to store the content of the group zip file + ByteArrayOutputStream groupZipContent = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = zis.read(buffer)) != -1) { + groupZipContent.write(buffer, 0, bytesRead); + } + + byte[] groupZipContentBytes = groupZipContent.toByteArray(); + + ByteArrayInputStream groupZipByteStream = new ByteArrayInputStream(groupZipContentBytes); + + // Create a new ZipInputStream using the ByteArrayInputStream + try (ZipInputStream groupZipInputStream = new ZipInputStream(groupZipByteStream)) { + ZipEntry groupEntry; + while ((groupEntry = groupZipInputStream.getNextEntry()) != null) { + if (groupEntry.getName().equals("files.zip")) { + fileszipfound = true; + } + } + } + } + } + } + assertTrue(groupzipfound); + assertTrue(fileszipfound); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* With arifact */ + url += "?artifacts=true"; + // Create artifact tempfile + File artifactFile = null; + try { + artifactFile = createTestFile(); + } catch (IOException e) { + e.printStackTrace(); + } + assertNotNull(artifactFile); + + try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { + mockedFileHandler.when(() -> Filehandler. + getSubmissionArtifactPath(submission.getProjectId(), groupEntity.getId(), submission.getId())) + .thenReturn(Path.of(artifactFile.getAbsolutePath())); + mockedFileHandler.when(() -> Filehandler.addExistingZip(any(), any(), any())) + .thenCallRealMethod(); + mockedFileHandler.when(() -> Filehandler.getZipFileAsResponse(any(), any())) + .thenCallRealMethod(); + mockedFileHandler.when(() -> Filehandler.getFileAsResource(any())) + .thenCallRealMethod(); + + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=allsubmissions.zip")) + .andReturn(); + + byte[] content = mvcResult.getResponse().getContentAsByteArray(); + + boolean groupzipfound = false; + boolean fileszipfound = false; + boolean artifactzipfound = false; + + /* Check contents of file */ + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().equals("group-" + submission.getGroupId() + ".zip")) { + groupzipfound = true; + /* Check if there is a zipfile inside the zipfile with name 'files.zip' */ + + // Create a new ByteArrayOutputStream to store the content of the group zip file + ByteArrayOutputStream groupZipContent = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = zis.read(buffer)) != -1) { + groupZipContent.write(buffer, 0, bytesRead); + } + + byte[] groupZipContentBytes = groupZipContent.toByteArray(); + + ByteArrayInputStream groupZipByteStream = new ByteArrayInputStream(groupZipContentBytes); + + // Create a new ZipInputStream using the ByteArrayInputStream + try (ZipInputStream groupZipInputStream = new ZipInputStream(groupZipByteStream)) { + ZipEntry groupEntry; + while ((groupEntry = groupZipInputStream.getNextEntry()) != null) { + if (groupEntry.getName().equals("files.zip")) { + fileszipfound = true; + } else if (groupEntry.getName().equals("artifacts.zip")) { + artifactzipfound = true; + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + assertTrue(groupzipfound); + assertTrue(fileszipfound); + assertTrue(artifactzipfound); + } catch (Exception e) { + e.printStackTrace(); + } + + /* With artifact but no artifact file, should just return the zip without an artifacts.zip */ + try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { + mockedFileHandler.when(() -> Filehandler. + getSubmissionArtifactPath(submission.getProjectId(), groupEntity.getId(), submission.getId())) + .thenReturn(Path.of("nonexistent")); + mockedFileHandler.when(() -> Filehandler.addExistingZip(any(), any(), any())) + .thenCallRealMethod(); + mockedFileHandler.when(() -> Filehandler.getZipFileAsResponse(any(), any())) + .thenCallRealMethod(); + mockedFileHandler.when(() -> Filehandler.getFileAsResource(any())) + .thenCallRealMethod(); + + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=allsubmissions.zip")) + .andReturn(); + + byte[] content = mvcResult.getResponse().getContentAsByteArray(); + + boolean groupzipfound = false; + boolean fileszipfound = false; + boolean artifactzipfound = false; + + /* Check contents of file */ + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().equals("group-" + submission.getGroupId() + ".zip")) { + groupzipfound = true; + /* Check if there is a zipfile inside the zipfile with name 'files.zip' */ + + // Create a new ByteArrayOutputStream to store the content of the group zip file + ByteArrayOutputStream groupZipContent = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = zis.read(buffer)) != -1) { + groupZipContent.write(buffer, 0, bytesRead); + } + + byte[] groupZipContentBytes = groupZipContent.toByteArray(); + + ByteArrayInputStream groupZipByteStream = new ByteArrayInputStream(groupZipContentBytes); + + // Create a new ZipInputStream using the ByteArrayInputStream + try (ZipInputStream groupZipInputStream = new ZipInputStream(groupZipByteStream)) { + ZipEntry groupEntry; + while ((groupEntry = groupZipInputStream.getNextEntry()) != null) { + if (groupEntry.getName().equals("files.zip")) { + fileszipfound = true; + } else if (groupEntry.getName().equals("artifacts.zip")) { + artifactzipfound = true; + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + assertTrue(groupzipfound); + assertTrue(fileszipfound); + assertFalse(artifactzipfound); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* With artifact parameter false */ + url = url.replace("?artifacts=true", "?artifacts=false"); + + try { + + + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=allsubmissions.zip")) + .andReturn(); + + byte[] content = mvcResult.getResponse().getContentAsByteArray(); + + boolean groupzipfound = false; + boolean fileszipfound = false; + boolean artifactzipfound = false; + /* Check contents of file */ + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().equals("group-" + submission.getGroupId() + ".zip")) { + groupzipfound = true; + /* Check if there is a zipfile inside the zipfile with name 'files.zip' */ + + // Create a new ByteArrayOutputStream to store the content of the group zip file + ByteArrayOutputStream groupZipContent = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = zis.read(buffer)) != -1) { + groupZipContent.write(buffer, 0, bytesRead); + } + + byte[] groupZipContentBytes = groupZipContent.toByteArray(); + + ByteArrayInputStream groupZipByteStream = new ByteArrayInputStream(groupZipContentBytes); + + // Create a new ZipInputStream using the ByteArrayInputStream + try (ZipInputStream groupZipInputStream = new ZipInputStream(groupZipByteStream)) { + ZipEntry groupEntry; + while ((groupEntry = groupZipInputStream.getNextEntry()) != null) { + if (groupEntry.getName().equals("files.zip")) { + fileszipfound = true; + } else if (groupEntry.getName().equals("artifacts.zip")) { + artifactzipfound = true; + } + } + } + } + } + } + assertTrue(groupzipfound); + assertTrue(fileszipfound); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* File not found, should return empty zip */ + when(fileRepository.findById(submission.getFileId())).thenReturn(Optional.empty()); + + try { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=allsubmissions.zip")) + .andReturn(); + + byte[] content = mvcResult.getResponse().getContentAsByteArray(); + + boolean zipfound = false; + /* Check contents of file */ + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + zipfound = true; + } + } + assertFalse(zipfound); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* Submission not found, should return empty zip */ + when(submissionRepository.findLatestsSubmissionIdsByProjectAndGroupId(submission.getProjectId(), groupEntity.getId())).thenReturn(Optional.empty()); + + try { + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=allsubmissions.zip")) + .andReturn(); + + byte[] content = mvcResult.getResponse().getContentAsByteArray(); + + boolean zipfound = false; + /* Check contents of file */ + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(content))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + zipfound = true; + } + } + assertFalse(zipfound); + + } catch (Exception e) { + e.printStackTrace(); + } + + /* Not admin */ + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())) + .thenReturn(new CheckResult<>(HttpStatus.I_AM_A_TEAPOT, "", null)); + + try { + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isIAmATeapot()); + } catch (Exception e) { + e.printStackTrace(); + } + + /* Unexecpted error */ + when(projectUtil.isProjectAdmin(submission.getProjectId(), getMockUser())) + .thenThrow(new RuntimeException()); + + try { + mockMvc.perform(MockMvcRequestBuilders.get(url)) + .andExpect(status().isInternalServerError()); + } catch (Exception e) { + e.printStackTrace(); + } + } } \ No newline at end of file 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 7a7cfb6a..686e9144 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 @@ -709,10 +709,10 @@ public void testDeleteTest() throws Exception { public static File createTestFile() throws IOException { // Create a temporary directory - File tempDir = Files.createTempDirectory("test-dir").toFile(); + File tempDir = Files.createTempDirectory("SELAB6CANDELETEtest-dir").toFile(); // Create a temporary file within the directory - File tempFile = File.createTempFile("test-file", ".zip", tempDir); + File tempFile = File.createTempFile("SELAB6CANDELETEtest-file", ".zip", tempDir); // Create some content to write into the zip file String content = "Hello, this is a test file!"; 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 f02c545a..2fcc809b 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 @@ -24,6 +24,8 @@ import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.*; import com.ugent.pidgeon.postgre.repository.GroupRepository.UserReference; +import java.io.File; +import java.io.IOException; import java.time.OffsetDateTime; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -31,6 +33,7 @@ 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; @@ -47,6 +50,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; 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; @@ -550,62 +554,79 @@ public void testCourseEntityToCourseReference() { @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()); - - /* Group id is null */ - submissionEntity.setGroupId(null); - result = entityToJsonConverter.getSubmissionJson(submissionEntity); - assertNull(result.getGroupUrl()); + try (MockedStatic mockedFileHandler = mockStatic(Filehandler.class)) { + /* Create temp file for artifacts */ + File file = File.createTempFile("SELAB2CANDELETEtest", "zip"); + mockedFileHandler.when(() -> Filehandler.getSubmissionArtifactPath(submissionEntity.getProjectId(), submissionEntity.getGroupId(), submissionEntity.getId())) + .thenReturn(file.toPath()); + 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()); + + /* No artifacts */ + file.delete(); + result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertNull(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()); + + /* Group id is null */ + submissionEntity.setGroupId(null); + result = entityToJsonConverter.getSubmissionJson(submissionEntity); + assertNull(result.getGroupUrl()); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Test 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 a7dcc193..94b7a153 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 @@ -12,16 +12,22 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; 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.logging.Logger; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,7 +64,7 @@ public void cleanup() throws Exception { @BeforeEach public void setUp() throws IOException { - tempDir = Files.createTempDirectory("test"); + tempDir = Files.createTempDirectory("SELAB6CANDELETEtest"); fileContent = Files.readAllBytes(testFilePath.resolve(basicZipFileName)); file = new MockMultipartFile( basicZipFileName, fileContent @@ -115,17 +121,17 @@ public void testSaveFile_fileNull() { @Test public void testDeleteLocation() throws Exception { - Path testDir = Files.createTempDirectory("test"); - Path tempFile = Files.createTempFile(testDir, "test", ".txt"); + Path testDir = Files.createTempDirectory("SELAB6CANDELETEtest"); + Path tempFile = Files.createTempFile(testDir, "SELAB6CANDELETEtest", ".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"); + Path testDir = Files.createTempDirectory("SELAB6CANDELETEtest"); + Path tempFile = Files.createTempFile(testDir, "SELAB6CANDELETEtest", ".txt"); + Files.createTempFile(testDir, "SELAB6CANDELETEtest2", ".txt"); Filehandler.deleteLocation(new File(tempFile.toString())); assertTrue(Files.exists(testDir)); } @@ -278,7 +284,7 @@ public void testGetSubmissionArtifactPath_groupIdIsNull() { @Test public void testGetFileAsResource_FileExists() { try { - File tempFile = Files.createTempFile("testFile", ".txt").toFile(); + File tempFile = Files.createTempFile("SELAB6CANDELETEtestFile", ".txt").toFile(); Resource resource = Filehandler.getFileAsResource(tempFile.toPath()); @@ -302,8 +308,8 @@ public void testGetFileAsResource_FileDoesNotExist() { @Test public void testCopyFilesAsZip() throws IOException { List files = new ArrayList<>(); - File tempFile1 = Files.createTempFile("tempFile1", ".txt").toFile(); - File tempFile2 = Files.createTempFile("tempFile2", ".txt").toFile(); + File tempFile1 = Files.createTempFile("SELAB6CANDELETEtempFile1", ".txt").toFile(); + File tempFile2 = Files.createTempFile("SELAB6CANDELETEtempFile2", ".txt").toFile(); try { files.add(tempFile1); @@ -331,9 +337,9 @@ public void testCopyFilesAsZip() throws IOException { @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(); + File tempFile1 = Files.createTempFile("SELAB6CANDELETEtempFile1", ".txt").toFile(); + File tempFile2 = Files.createTempFile("SELAB6CANDELETEtempFile2", ".txt").toFile(); + File zipFile = Files.createTempFile(tempDir, "SELAB6CANDELETEfiles", ".zip").toFile(); try { files.add(tempFile1); @@ -373,9 +379,9 @@ private static File createTempFileWithContent(String prefix, String suffix, int @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(); + File tempFile1 = createTempFileWithContent("SELAB6CANDELETEtempFile1", ".txt", 4095); + File tempFile2 = Files.createTempFile("SELAB6CANDELETEtempFile2", ".txt").toFile(); + File zipFile = Files.createTempFile(tempDir, "SELAB6CANDELETEfiles", ".zip").toFile(); zipFile.setWritable(false); try { @@ -403,8 +409,8 @@ public void testCopyFilesAsZip_zipFileAlreadyExistNonWriteable() throws IOExcept @Test public void testGetZipFileAsResponse() throws IOException { List files = new ArrayList<>(); - File tempFile1 = Files.createTempFile("tempFile1", ".txt").toFile(); - File tempFile2 = Files.createTempFile("tempFile2", ".txt").toFile(); + File tempFile1 = Files.createTempFile("SELAB6CANDELETEtempFile1", ".txt").toFile(); + File tempFile2 = Files.createTempFile("SELAB6CANDELETEtempFile2", ".txt").toFile(); try { files.add(tempFile1); @@ -435,4 +441,44 @@ public void testGetZipFileAsResponse_fileDoesNotExist() { assertEquals(404, response.getStatusCodeValue()); } + @Test + public void testAddExistingZip() throws IOException { + // Create zip file + String zipFileName = "existingZipFile.zip"; + File tempZipFile = Files.createTempFile("SELAB6CANDELETEexistingZip", ".zip").toFile(); + + // Populate the zip file with some content + try (ZipOutputStream tempZipOutputStream = new ZipOutputStream(new FileOutputStream(tempZipFile))) { + ZipEntry entry = new ZipEntry("testFile.txt"); + tempZipOutputStream.putNextEntry(entry); + tempZipOutputStream.write("Test content".getBytes()); + tempZipOutputStream.closeEntry(); + Filehandler.addExistingZip(tempZipOutputStream, zipFileName, tempZipFile); + } + + + + + + // Check if the zip file contains the entry + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(tempZipFile))) { + ZipEntry entry; + boolean found = false; + boolean originalFound = false; + while ((entry = zis.getNextEntry()) != null) { + Logger.getGlobal().info("Entry: " + entry.getName()); + if (entry.getName().equals(zipFileName)) { + found = true; + } else if (entry.getName().equals("testFile.txt")) { + originalFound = true; + } + } + assertTrue(found); + assertTrue(originalFound); + } + } + + + + } 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 index 506d1607..21bb85fc 100644 --- a/backend/app/src/test/java/com/ugent/pidgeon/util/GroupFeedbackUtilTest.java +++ b/backend/app/src/test/java/com/ugent/pidgeon/util/GroupFeedbackUtilTest.java @@ -200,13 +200,7 @@ public void testCheckGroupFeedbackUpdateJson() { /* 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())); 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 9ad7ca11..00b2b6bf 100644 Binary files a/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip and b/backend/app/src/test/test-cases/DockerSubmissionTestTest/d__test.zip differ