Update JobControllerTest.java

This commit is contained in:
Ludy87 2025-08-10 00:59:55 +02:00
parent 1fb9c18530
commit 2cbc733daa
No known key found for this signature in database
GPG Key ID: 92696155E0220F94

View File

@ -1,401 +1,516 @@
package stirling.software.common.controller;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Map;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;
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.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import stirling.software.common.model.job.JobResult;
import stirling.software.common.model.job.ResultFile;
import stirling.software.common.service.FileStorage;
import stirling.software.common.service.JobQueue;
import stirling.software.common.service.TaskManager;
class JobControllerTest {
@Mock private TaskManager taskManager;
@Mock private FileStorage fileStorage;
@Mock private JobQueue jobQueue;
@Mock private HttpServletRequest request;
private MockHttpSession session;
@InjectMocks private JobController controller;
private TaskManager taskManager;
private FileStorage fileStorage;
private JobQueue jobQueue;
private HttpServletRequest request;
private HttpSession session;
private MockMvc mvc;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
// Setup mock session for tests
session = new MockHttpSession();
void setup() {
taskManager = mock(TaskManager.class);
fileStorage = mock(FileStorage.class);
jobQueue = mock(JobQueue.class);
request = mock(HttpServletRequest.class);
session = mock(HttpSession.class);
when(request.getSession()).thenReturn(session);
JobController controller = new JobController(taskManager, fileStorage, jobQueue, request);
mvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
void testGetJobStatus_ExistingJob() {
// Arrange
String jobId = "test-job-id";
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
// Act
ResponseEntity<?> response = controller.getJobStatus(jobId);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(mockResult, response.getBody());
void getJobStatus_notFound_returns404() throws Exception {
when(taskManager.getJobResult("abc")).thenReturn(null);
mvc.perform(get("/api/v1/general/job/{jobId}", "abc")).andExpect(status().isNotFound());
}
@Test
void testGetJobStatus_ExistingJobInQueue() {
// Arrange
String jobId = "test-job-id";
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
mockResult.setComplete(false);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
when(jobQueue.isJobQueued(jobId)).thenReturn(true);
when(jobQueue.getJobPosition(jobId)).thenReturn(3);
void getJobStatus_inQueue_addsQueueInfo() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(false);
when(taskManager.getJobResult("j1")).thenReturn(result);
when(jobQueue.isJobQueued("j1")).thenReturn(true);
when(jobQueue.getJobPosition("j1")).thenReturn(3);
// Act
ResponseEntity<?> response = controller.getJobStatus(jobId);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
@SuppressWarnings("unchecked")
Map<String, Object> responseBody = (Map<String, Object>) response.getBody();
assertEquals(mockResult, responseBody.get("jobResult"));
@SuppressWarnings("unchecked")
Map<String, Object> queueInfo = (Map<String, Object>) responseBody.get("queueInfo");
assertTrue((Boolean) queueInfo.get("inQueue"));
assertEquals(3, queueInfo.get("position"));
mvc.perform(get("/api/v1/general/job/{jobId}", "j1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.queueInfo.inQueue").value(true))
.andExpect(jsonPath("$.queueInfo.position").value(3));
}
@Test
void testGetJobStatus_NonExistentJob() {
// Arrange
String jobId = "non-existent-job";
when(taskManager.getJobResult(jobId)).thenReturn(null);
// Act
ResponseEntity<?> response = controller.getJobStatus(jobId);
// Assert
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
void getJobStatus_complete_returnsResult() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(taskManager.getJobResult("j2")).thenReturn(result);
mvc.perform(get("/api/v1/general/job/{jobId}", "j2")).andExpect(status().isOk());
}
@Test
void testGetJobResult_CompletedSuccessfulWithObject() {
// Arrange
String jobId = "test-job-id";
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
mockResult.setComplete(true);
String resultObject = "Test result";
mockResult.completeWithResult(resultObject);
void getJobStatus_inProgress_notQueued_returnsResult() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(false);
when(taskManager.getJobResult("JSTAT")).thenReturn(result);
when(jobQueue.isJobQueued("JSTAT")).thenReturn(false);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
// Act
ResponseEntity<?> response = controller.getJobResult(jobId);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(resultObject, response.getBody());
mvc.perform(get("/api/v1/general/job/{jobId}", "JSTAT")).andExpect(status().isOk());
}
@Test
void testGetJobResult_CompletedSuccessfulWithFile() throws Exception {
// Arrange
String jobId = "test-job-id";
String fileId = "file-id";
String originalFileName = "test.pdf";
String contentType = "application/pdf";
byte[] fileContent = "Test file content".getBytes();
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
mockResult.completeWithSingleFile(
fileId, originalFileName, contentType, fileContent.length);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
when(fileStorage.retrieveBytes(fileId)).thenReturn(fileContent);
// Act
ResponseEntity<?> response = controller.getJobResult(jobId);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(contentType, response.getHeaders().getFirst("Content-Type"));
assertTrue(
response.getHeaders().getFirst("Content-Disposition").contains(originalFileName));
assertEquals(fileContent, response.getBody());
void getJobResult_notFound() throws Exception {
when(taskManager.getJobResult("x")).thenReturn(null);
mvc.perform(get("/api/v1/general/job/{jobId}/result", "x"))
.andExpect(status().isNotFound());
}
@Test
void testGetJobResult_CompletedWithError() {
// Arrange
String jobId = "test-job-id";
String errorMessage = "Test error";
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
mockResult.failWithError(errorMessage);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
// Act
ResponseEntity<?> response = controller.getJobResult(jobId);
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue(response.getBody().toString().contains(errorMessage));
void getJobResult_notComplete_returns400() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(false);
when(taskManager.getJobResult("j")).thenReturn(result);
mvc.perform(get("/api/v1/general/job/{jobId}/result", "j"))
.andExpect(status().isBadRequest())
.andExpect(
content()
.string(
org.hamcrest.Matchers.containsString(
"Job is not complete yet")));
}
@Test
void testGetJobResult_IncompleteJob() {
// Arrange
String jobId = "test-job-id";
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
mockResult.setComplete(false);
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
// Act
ResponseEntity<?> response = controller.getJobResult(jobId);
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue(response.getBody().toString().contains("not complete"));
void getJobResult_error_returns400() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn("oops");
when(taskManager.getJobResult("j")).thenReturn(result);
mvc.perform(get("/api/v1/general/job/{jobId}/result", "j"))
.andExpect(status().isBadRequest())
.andExpect(
content().string(org.hamcrest.Matchers.containsString("Job failed: oops")));
}
@Test
void testGetJobResult_NonExistentJob() {
// Arrange
String jobId = "non-existent-job";
when(taskManager.getJobResult(jobId)).thenReturn(null);
void getJobResult_multipleFiles_returnsJsonList() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn(null);
when(result.hasMultipleFiles()).thenReturn(true);
// Act
ResponseEntity<?> response = controller.getJobResult(jobId);
ResultFile f1 = mock(ResultFile.class);
ResultFile f2 = mock(ResultFile.class);
when(result.getAllResultFiles()).thenReturn(List.of(f1, f2));
when(taskManager.getJobResult("j")).thenReturn(result);
// Assert
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
mvc.perform(get("/api/v1/general/job/{jobId}/result", "j"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.jobId").value("j"))
.andExpect(jsonPath("$.hasMultipleFiles").value(true))
.andExpect(jsonPath("$.files.length()").value(2));
}
@Test
void testGetJobResult_ErrorRetrievingFile() throws Exception {
// Arrange
String jobId = "test-job-id";
String fileId = "file-id";
String originalFileName = "test.pdf";
String contentType = "application/pdf";
void getJobResult_singleFile_streamsBytes_withHeaders() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn(null);
when(result.hasMultipleFiles()).thenReturn(false);
when(result.hasFiles()).thenReturn(true);
JobResult mockResult = new JobResult();
mockResult.setJobId(jobId);
mockResult.completeWithSingleFile(fileId, originalFileName, contentType, 1024L);
ResultFile rf = mock(ResultFile.class);
when(rf.getFileId()).thenReturn("fid");
when(rf.getFileName()).thenReturn("report.pdf");
when(rf.getContentType()).thenReturn("application/pdf");
when(result.getAllResultFiles()).thenReturn(List.of(rf));
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
when(fileStorage.retrieveBytes(fileId)).thenThrow(new RuntimeException("File not found"));
when(taskManager.getJobResult("job9")).thenReturn(result);
when(fileStorage.retrieveBytes("fid"))
.thenReturn("PDFDATA".getBytes(StandardCharsets.UTF_8));
// Act
ResponseEntity<?> response = controller.getJobResult(jobId);
// Assert
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
assertTrue(response.getBody().toString().contains("Error retrieving file"));
}
/*
* @Test void testGetJobStats() { // Arrange JobStats mockStats =
* JobStats.builder() .totalJobs(10) .activeJobs(3) .completedJobs(7) .build();
*
* when(taskManager.getJobStats()).thenReturn(mockStats);
*
* // Act ResponseEntity<?> response = controller.getJobStats();
*
* // Assert assertEquals(HttpStatus.OK, response.getStatusCode());
* assertEquals(mockStats, response.getBody()); }
*
* @Test void testCleanupOldJobs() { // Arrange when(taskManager.getJobStats())
* .thenReturn(JobStats.builder().totalJobs(10).build())
* .thenReturn(JobStats.builder().totalJobs(7).build());
*
* // Act ResponseEntity<?> response = controller.cleanupOldJobs();
*
* // Assert assertEquals(HttpStatus.OK, response.getStatusCode());
*
* @SuppressWarnings("unchecked") Map<String, Object> responseBody =
* (Map<String, Object>) response.getBody(); assertEquals("Cleanup complete",
* responseBody.get("message")); assertEquals(3,
* responseBody.get("removedJobs")); assertEquals(7,
* responseBody.get("remainingJobs"));
*
* verify(taskManager).cleanupOldJobs(); }
*
* @Test void testGetQueueStats() { // Arrange Map<String, Object>
* mockQueueStats = Map.of( "queuedJobs", 5, "queueCapacity", 10,
* "resourceStatus", "OK" );
*
* when(jobQueue.getQueueStats()).thenReturn(mockQueueStats);
*
* // Act ResponseEntity<?> response = controller.getQueueStats();
*
* // Assert assertEquals(HttpStatus.OK, response.getStatusCode());
* assertEquals(mockQueueStats, response.getBody());
* verify(jobQueue).getQueueStats(); }
*/
@Test
void testCancelJob_InQueue() {
// Arrange
String jobId = "job-in-queue";
// Setup user session with job authorization
java.util.Set<String> userJobIds = new java.util.HashSet<>();
userJobIds.add(jobId);
session.setAttribute("userJobIds", userJobIds);
when(jobQueue.isJobQueued(jobId)).thenReturn(true);
when(jobQueue.getJobPosition(jobId)).thenReturn(2);
when(jobQueue.cancelJob(jobId)).thenReturn(true);
// Act
ResponseEntity<?> response = controller.cancelJob(jobId);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
@SuppressWarnings("unchecked")
Map<String, Object> responseBody = (Map<String, Object>) response.getBody();
assertEquals("Job cancelled successfully", responseBody.get("message"));
assertTrue((Boolean) responseBody.get("wasQueued"));
assertEquals(2, responseBody.get("queuePosition"));
verify(jobQueue).cancelJob(jobId);
verify(taskManager, never()).setError(anyString(), anyString());
mvc.perform(get("/api/v1/general/job/{jobId}/result", "job9"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Type", "application/pdf"))
.andExpect(
header().string(
"Content-Disposition",
org.hamcrest.Matchers.containsString(
"filename=\"report.pdf\"")))
.andExpect(
header().string(
"Content-Disposition",
org.hamcrest.Matchers.containsString("filename*=")))
.andExpect(content().bytes("PDFDATA".getBytes(StandardCharsets.UTF_8)));
}
@Test
void testCancelJob_Running() {
// Arrange
String jobId = "job-running";
JobResult jobResult = new JobResult();
jobResult.setJobId(jobId);
jobResult.setComplete(false);
void getJobResult_singleFile_retrievalError_returns500() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn(null);
when(result.hasMultipleFiles()).thenReturn(false);
when(result.hasFiles()).thenReturn(true);
// Setup user session with job authorization
java.util.Set<String> userJobIds = new java.util.HashSet<>();
userJobIds.add(jobId);
session.setAttribute("userJobIds", userJobIds);
ResultFile rf = mock(ResultFile.class);
when(rf.getFileId()).thenReturn("fid");
when(rf.getFileName()).thenReturn("x.pdf");
when(rf.getContentType()).thenReturn("application/pdf");
when(result.getAllResultFiles()).thenReturn(List.of(rf));
when(jobQueue.isJobQueued(jobId)).thenReturn(false);
when(taskManager.getJobResult(jobId)).thenReturn(jobResult);
when(taskManager.getJobResult("job10")).thenReturn(result);
when(fileStorage.retrieveBytes("fid")).thenThrow(new RuntimeException("IO boom"));
// Act
ResponseEntity<?> response = controller.cancelJob(jobId);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
@SuppressWarnings("unchecked")
Map<String, Object> responseBody = (Map<String, Object>) response.getBody();
assertEquals("Job cancelled successfully", responseBody.get("message"));
assertFalse((Boolean) responseBody.get("wasQueued"));
assertEquals("n/a", responseBody.get("queuePosition"));
verify(jobQueue, never()).cancelJob(jobId);
verify(taskManager).setError(jobId, "Job was cancelled by user");
mvc.perform(get("/api/v1/general/job/{jobId}/result", "job10"))
.andExpect(status().isInternalServerError())
.andExpect(
content()
.string(
org.hamcrest.Matchers.containsString(
"Error retrieving file:")));
}
@Test
void testCancelJob_NotFound() {
// Arrange
String jobId = "non-existent-job";
void getJobResult_noFiles_returns_result_payload() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn(null);
when(result.hasMultipleFiles()).thenReturn(false);
when(result.hasFiles()).thenReturn(false); // -> triggert den Fallback
when(result.getResult()).thenReturn("SIMPLE-RESULT");
// Setup user session with job authorization
java.util.Set<String> userJobIds = new java.util.HashSet<>();
userJobIds.add(jobId);
session.setAttribute("userJobIds", userJobIds);
when(taskManager.getJobResult("job11")).thenReturn(result);
when(jobQueue.isJobQueued(jobId)).thenReturn(false);
when(taskManager.getJobResult(jobId)).thenReturn(null);
// Act
ResponseEntity<?> response = controller.cancelJob(jobId);
// Assert
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
mvc.perform(get("/api/v1/general/job/{jobId}/result", "job11"))
.andExpect(status().isOk())
.andExpect(content().string("SIMPLE-RESULT"));
}
@Test
void testCancelJob_AlreadyComplete() {
// Arrange
String jobId = "completed-job";
JobResult jobResult = new JobResult();
jobResult.setJobId(jobId);
jobResult.setComplete(true);
void cancelJob_unauthorized_returns403() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(null);
// Setup user session with job authorization
java.util.Set<String> userJobIds = new java.util.HashSet<>();
userJobIds.add(jobId);
session.setAttribute("userJobIds", userJobIds);
when(jobQueue.isJobQueued(jobId)).thenReturn(false);
when(taskManager.getJobResult(jobId)).thenReturn(jobResult);
// Act
ResponseEntity<?> response = controller.cancelJob(jobId);
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
@SuppressWarnings("unchecked")
Map<String, Object> responseBody = (Map<String, Object>) response.getBody();
assertEquals("Cannot cancel job that is already complete", responseBody.get("message"));
mvc.perform(delete("/api/v1/general/job/{jobId}", "J1"))
.andExpect(status().isForbidden())
.andExpect(
jsonPath("$.message").value("You are not authorized to cancel this job"));
}
@Test
void testCancelJob_Unauthorized() {
// Arrange
String jobId = "unauthorized-job";
void cancelJob_inQueue_success() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(Set.of("J2"));
when(jobQueue.isJobQueued("J2")).thenReturn(true);
when(jobQueue.getJobPosition("J2")).thenReturn(5);
when(jobQueue.cancelJob("J2")).thenReturn(true);
// Setup user session with other job IDs but not this one
java.util.Set<String> userJobIds = new java.util.HashSet<>();
userJobIds.add("other-job-1");
userJobIds.add("other-job-2");
session.setAttribute("userJobIds", userJobIds);
mvc.perform(delete("/api/v1/general/job/{jobId}", "J2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Job cancelled successfully"))
.andExpect(jsonPath("$.wasQueued").value(true))
.andExpect(jsonPath("$.queuePosition").value(5));
}
// Act
ResponseEntity<?> response = controller.cancelJob(jobId);
@Test
void cancelJob_taskManager_cancel_success_when_not_queued() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(Set.of("J3"));
when(jobQueue.isJobQueued("J3")).thenReturn(false);
// Assert
assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(false);
when(taskManager.getJobResult("J3")).thenReturn(result);
@SuppressWarnings("unchecked")
Map<String, Object> responseBody = (Map<String, Object>) response.getBody();
assertEquals("You are not authorized to cancel this job", responseBody.get("message"));
mvc.perform(delete("/api/v1/general/job/{jobId}", "J3"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Job cancelled successfully"))
.andExpect(jsonPath("$.wasQueued").value(false))
.andExpect(jsonPath("$.queuePosition").value("n/a"));
// Verify no cancellation attempts were made
verify(jobQueue, never()).isJobQueued(anyString());
verify(jobQueue, never()).cancelJob(anyString());
verify(taskManager, never()).getJobResult(anyString());
verify(taskManager, never()).setError(anyString(), anyString());
verify(taskManager).setError(eq("J3"), any(String.class));
}
@Test
void cancelJob_notFound_returns404() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(Set.of("J4"));
when(jobQueue.isJobQueued("J4")).thenReturn(false);
when(taskManager.getJobResult("J4")).thenReturn(null);
mvc.perform(delete("/api/v1/general/job/{jobId}", "J4")).andExpect(status().isNotFound());
}
@Test
void cancelJob_alreadyComplete_returns400() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(Set.of("J5"));
when(jobQueue.isJobQueued("J5")).thenReturn(false);
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(taskManager.getJobResult("J5")).thenReturn(result);
mvc.perform(delete("/api/v1/general/job/{jobId}", "J5"))
.andExpect(status().isBadRequest())
.andExpect(
jsonPath("$.message").value("Cannot cancel job that is already complete"));
}
@Test
void cancelJob_unknown_failure_returns500() throws Exception {
// User besitzt den Job
when(session.getAttribute("userJobIds")).thenReturn(java.util.Set.of("J6"));
// Nicht in Queue
when(jobQueue.isJobQueued("J6")).thenReturn(false);
// 1. Aufruf im Mittelteil: completed -> kein setError -> cancelled bleibt false
JobResult completed = mock(JobResult.class);
when(completed.isComplete()).thenReturn(true);
// 2. Aufruf im finalen else: nicht complete -> 500 "unknown reason"
JobResult notComplete = mock(JobResult.class);
when(notComplete.isComplete()).thenReturn(false);
when(taskManager.getJobResult("J6"))
.thenReturn(completed) // erster Aufruf (inside if (!cancelled))
.thenReturn(notComplete); // zweiter Aufruf (final else)
mvc.perform(delete("/api/v1/general/job/{jobId}", "J6"))
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.message").value("Failed to cancel job for unknown reason"));
}
@Test
void cancelJob_unauthorized_wrong_attribute_type_returns403() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(java.util.List.of("JX")); // kein Set
mvc.perform(delete("/api/v1/general/job/{jobId}", "JX"))
.andExpect(status().isForbidden())
.andExpect(
jsonPath("$.message").value("You are not authorized to cancel this job"));
}
@Test
void cancelJob_inQueue_cancelFalse_then_taskManager_setsError_ok() throws Exception {
when(session.getAttribute("userJobIds")).thenReturn(java.util.Set.of("JQ"));
when(jobQueue.isJobQueued("JQ")).thenReturn(true);
when(jobQueue.getJobPosition("JQ")).thenReturn(2);
when(jobQueue.cancelJob("JQ")).thenReturn(false); // zwingt den zweiten Pfad
JobResult notComplete = mock(JobResult.class);
when(notComplete.isComplete()).thenReturn(false);
when(taskManager.getJobResult("JQ")).thenReturn(notComplete);
mvc.perform(delete("/api/v1/general/job/{jobId}", "JQ"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Job cancelled successfully"))
.andExpect(jsonPath("$.wasQueued").value(true))
.andExpect(jsonPath("$.queuePosition").value(2));
verify(taskManager).setError(eq("JQ"), any(String.class));
}
@Test
void getJobFiles_notFound() throws Exception {
when(taskManager.getJobResult("JJ")).thenReturn(null);
mvc.perform(get("/api/v1/general/job/{jobId}/result/files", "JJ"))
.andExpect(status().isNotFound());
}
@Test
void getJobFiles_notComplete() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(false);
when(taskManager.getJobResult("JJ")).thenReturn(result);
mvc.perform(get("/api/v1/general/job/{jobId}/result/files", "JJ"))
.andExpect(status().isBadRequest())
.andExpect(
content()
.string(
org.hamcrest.Matchers.containsString(
"Job is not complete yet")));
}
@Test
void getJobFiles_error() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn("E");
when(taskManager.getJobResult("JJ")).thenReturn(result);
mvc.perform(get("/api/v1/general/job/{jobId}/result/files", "JJ"))
.andExpect(status().isBadRequest())
.andExpect(content().string(org.hamcrest.Matchers.containsString("Job failed: E")));
}
@Test
void getJobFiles_success() throws Exception {
JobResult result = mock(JobResult.class);
when(result.isComplete()).thenReturn(true);
when(result.getError()).thenReturn(null);
ResultFile f1 = mock(ResultFile.class);
ResultFile f2 = mock(ResultFile.class);
when(result.getAllResultFiles()).thenReturn(List.of(f1, f2));
when(taskManager.getJobResult("JJ")).thenReturn(result);
mvc.perform(get("/api/v1/general/job/{jobId}/result/files", "JJ"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.jobId").value("JJ"))
.andExpect(jsonPath("$.fileCount").value(2))
.andExpect(jsonPath("$.files.length()").value(2));
}
@Test
void getFileMetadata_notFound_when_file_missing() throws Exception {
when(fileStorage.fileExists("F1")).thenReturn(false);
mvc.perform(get("/api/v1/general/files/{fileId}/metadata", "F1"))
.andExpect(status().isNotFound());
}
@Test
void getFileMetadata_found_with_metadata() throws Exception {
when(fileStorage.fileExists("F2")).thenReturn(true);
ResultFile rf = mock(ResultFile.class);
when(rf.getFileId()).thenReturn("F2");
when(rf.getFileName()).thenReturn("name.pdf");
when(rf.getContentType()).thenReturn("application/pdf");
when(taskManager.findResultFileByFileId("F2")).thenReturn(rf);
mvc.perform(get("/api/v1/general/files/{fileId}/metadata", "F2"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.fileId").value("F2"))
.andExpect(jsonPath("$.fileName").value("name.pdf"))
.andExpect(jsonPath("$.contentType").value("application/pdf"));
}
@Test
void getFileMetadata_found_without_metadata_returns_basic_info() throws Exception {
when(fileStorage.fileExists("F3")).thenReturn(true);
when(taskManager.findResultFileByFileId("F3")).thenReturn(null);
when(fileStorage.getFileSize("F3")).thenReturn(123L);
mvc.perform(get("/api/v1/general/files/{fileId}/metadata", "F3"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.fileId").value("F3"))
.andExpect(jsonPath("$.fileName").value("unknown"))
.andExpect(jsonPath("$.contentType").value("application/octet-stream"))
.andExpect(jsonPath("$.fileSize").value(123));
}
@Test
void getFileMetadata_error_on_fileExists_returns500() throws Exception {
when(fileStorage.fileExists("FERR2")).thenThrow(new RuntimeException("fail-exists"));
mvc.perform(get("/api/v1/general/files/{fileId}/metadata", "FERR2"))
.andExpect(status().isInternalServerError())
.andExpect(
content()
.string(
org.hamcrest.Matchers.containsString(
"Error retrieving file metadata:")));
}
@Test
void downloadFile_notFound() throws Exception {
when(fileStorage.fileExists("FX")).thenReturn(false);
mvc.perform(get("/api/v1/general/files/{fileId}", "FX")).andExpect(status().isNotFound());
}
@Test
void downloadFile_success_with_metadata() throws Exception {
when(fileStorage.fileExists("FY")).thenReturn(true);
when(fileStorage.retrieveBytes("FY")).thenReturn("DATA".getBytes(StandardCharsets.UTF_8));
ResultFile rf = mock(ResultFile.class);
when(rf.getFileName()).thenReturn("out.txt");
when(rf.getContentType()).thenReturn("text/plain");
when(taskManager.findResultFileByFileId("FY")).thenReturn(rf);
mvc.perform(get("/api/v1/general/files/{fileId}", "FY"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Type", "text/plain"))
.andExpect(
header().string(
"Content-Disposition",
org.hamcrest.Matchers.containsString("out.txt")))
.andExpect(content().bytes("DATA".getBytes(StandardCharsets.UTF_8)));
}
@Test
void downloadFile_error_returns500() throws Exception {
when(fileStorage.fileExists("FZ")).thenReturn(true);
when(fileStorage.retrieveBytes("FZ")).thenThrow(new RuntimeException("oops"));
mvc.perform(get("/api/v1/general/files/{fileId}", "FZ"))
.andExpect(status().isInternalServerError())
.andExpect(
content()
.string(
org.hamcrest.Matchers.containsString(
"Error retrieving file:")));
}
@Test
void downloadFile_filename_encoding_fallback_on_exception() throws Exception {
when(fileStorage.fileExists("FENC")).thenReturn(true);
when(fileStorage.retrieveBytes("FENC"))
.thenReturn("X".getBytes(java.nio.charset.StandardCharsets.UTF_8));
// ResultFile mit NULL-Dateiname -> URLEncoder.encode(...) wirft NPE -> Catch-Fallback
ResultFile rf = mock(ResultFile.class);
when(rf.getFileName()).thenReturn(null);
when(rf.getContentType()).thenReturn("text/plain");
when(taskManager.findResultFileByFileId("FENC")).thenReturn(rf);
mvc.perform(get("/api/v1/general/files/{fileId}", "FENC"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Type", "text/plain"))
// Fallback-Header: nur filename=..., kein filename*=
.andExpect(header().string("Content-Disposition", "attachment; filename=\"null\""))
.andExpect(content().bytes("X".getBytes(java.nio.charset.StandardCharsets.UTF_8)));
}
@Test
void downloadFile_success_without_metadata_defaults() throws Exception {
when(fileStorage.fileExists("FD")).thenReturn(true);
when(fileStorage.retrieveBytes("FD"))
.thenReturn("D".getBytes(java.nio.charset.StandardCharsets.UTF_8));
// keine Metadata
when(taskManager.findResultFileByFileId("FD")).thenReturn(null);
mvc.perform(get("/api/v1/general/files/{fileId}", "FD"))
.andExpect(status().isOk())
.andExpect(header().string("Content-Type", "application/octet-stream"))
.andExpect(
header().string(
"Content-Disposition",
org.hamcrest.Matchers.allOf(
org.hamcrest.Matchers.containsString(
"filename=\"download\""),
org.hamcrest.Matchers.containsString(
"filename*="))))
.andExpect(content().bytes("D".getBytes(java.nio.charset.StandardCharsets.UTF_8)));
}
}