mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-22 15:35:03 +00:00
tests
This commit is contained in:
parent
2d18413676
commit
45e18b2611
@ -0,0 +1,208 @@
|
|||||||
|
package stirling.software.common.annotations;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
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 org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import stirling.software.common.aop.AutoJobAspect;
|
||||||
|
import stirling.software.common.model.api.PDFFile;
|
||||||
|
import stirling.software.common.service.FileOrUploadService;
|
||||||
|
import stirling.software.common.service.FileStorage;
|
||||||
|
import stirling.software.common.service.JobExecutorService;
|
||||||
|
import stirling.software.common.service.JobQueue;
|
||||||
|
import stirling.software.common.service.ResourceMonitor;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AutoJobPostMappingIntegrationTest {
|
||||||
|
|
||||||
|
private AutoJobAspect autoJobAspect;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private JobExecutorService jobExecutorService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FileOrUploadService fileOrUploadService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FileStorage fileStorage;
|
||||||
|
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ResourceMonitor resourceMonitor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private JobQueue jobQueue;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
autoJobAspect = new AutoJobAspect(
|
||||||
|
jobExecutorService,
|
||||||
|
request,
|
||||||
|
fileOrUploadService,
|
||||||
|
fileStorage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ProceedingJoinPoint joinPoint;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AutoJobPostMapping autoJobPostMapping;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Supplier<Object>> workCaptor;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Boolean> asyncCaptor;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Long> timeoutCaptor;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Boolean> queueableCaptor;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Integer> resourceWeightCaptor;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExecuteWithCustomParameters() throws Throwable {
|
||||||
|
// Given
|
||||||
|
PDFFile pdfFile = new PDFFile();
|
||||||
|
pdfFile.setFileId("test-file-id");
|
||||||
|
Object[] args = new Object[] { pdfFile };
|
||||||
|
|
||||||
|
when(joinPoint.getArgs()).thenReturn(args);
|
||||||
|
when(request.getParameter("async")).thenReturn("true");
|
||||||
|
when(autoJobPostMapping.timeout()).thenReturn(60000L);
|
||||||
|
when(autoJobPostMapping.retryCount()).thenReturn(3);
|
||||||
|
when(autoJobPostMapping.trackProgress()).thenReturn(true);
|
||||||
|
when(autoJobPostMapping.queueable()).thenReturn(true);
|
||||||
|
when(autoJobPostMapping.resourceWeight()).thenReturn(75);
|
||||||
|
|
||||||
|
MultipartFile mockFile = mock(MultipartFile.class);
|
||||||
|
when(fileStorage.retrieveFile("test-file-id")).thenReturn(mockFile);
|
||||||
|
|
||||||
|
|
||||||
|
when(jobExecutorService.runJobGeneric(
|
||||||
|
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
|
||||||
|
.thenReturn(ResponseEntity.ok("success"));
|
||||||
|
|
||||||
|
// When
|
||||||
|
Object result = autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(ResponseEntity.ok("success"), result);
|
||||||
|
|
||||||
|
verify(jobExecutorService).runJobGeneric(
|
||||||
|
asyncCaptor.capture(),
|
||||||
|
workCaptor.capture(),
|
||||||
|
timeoutCaptor.capture(),
|
||||||
|
queueableCaptor.capture(),
|
||||||
|
resourceWeightCaptor.capture());
|
||||||
|
|
||||||
|
assertTrue(asyncCaptor.getValue(), "Async should be true");
|
||||||
|
assertEquals(60000L, timeoutCaptor.getValue(), "Timeout should be 60000ms");
|
||||||
|
assertTrue(queueableCaptor.getValue(), "Queueable should be true");
|
||||||
|
assertEquals(75, resourceWeightCaptor.getValue(), "Resource weight should be 75");
|
||||||
|
|
||||||
|
// Test that file was resolved
|
||||||
|
assertNotNull(pdfFile.getFileInput(), "File input should be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRetryOnError() throws Throwable {
|
||||||
|
// Given
|
||||||
|
when(joinPoint.getArgs()).thenReturn(new Object[0]);
|
||||||
|
when(request.getParameter("async")).thenReturn("false");
|
||||||
|
when(autoJobPostMapping.timeout()).thenReturn(-1L);
|
||||||
|
when(autoJobPostMapping.retryCount()).thenReturn(2);
|
||||||
|
when(autoJobPostMapping.trackProgress()).thenReturn(false);
|
||||||
|
when(autoJobPostMapping.queueable()).thenReturn(false);
|
||||||
|
when(autoJobPostMapping.resourceWeight()).thenReturn(50);
|
||||||
|
|
||||||
|
// First call throws exception, second succeeds
|
||||||
|
when(joinPoint.proceed(any()))
|
||||||
|
.thenThrow(new RuntimeException("First attempt failed"))
|
||||||
|
.thenReturn(ResponseEntity.ok("retry succeeded"));
|
||||||
|
|
||||||
|
// Mock jobExecutorService to execute the work immediately
|
||||||
|
when(jobExecutorService.runJobGeneric(
|
||||||
|
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
|
||||||
|
.thenAnswer(invocation -> {
|
||||||
|
Supplier<Object> work = invocation.getArgument(1);
|
||||||
|
return work.get();
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
Object result = autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(ResponseEntity.ok("retry succeeded"), result);
|
||||||
|
|
||||||
|
// Verify that proceed was called twice (initial attempt + 1 retry)
|
||||||
|
verify(joinPoint, times(2)).proceed(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandlePDFFileWithAsyncRequests() throws Throwable {
|
||||||
|
// Given
|
||||||
|
PDFFile pdfFile = new PDFFile();
|
||||||
|
pdfFile.setFileInput(mock(MultipartFile.class));
|
||||||
|
Object[] args = new Object[] { pdfFile };
|
||||||
|
|
||||||
|
when(joinPoint.getArgs()).thenReturn(args);
|
||||||
|
when(request.getParameter("async")).thenReturn("true");
|
||||||
|
when(autoJobPostMapping.retryCount()).thenReturn(1);
|
||||||
|
|
||||||
|
when(fileStorage.storeFile(any(MultipartFile.class))).thenReturn("stored-file-id");
|
||||||
|
when(fileStorage.retrieveFile("stored-file-id")).thenReturn(mock(MultipartFile.class));
|
||||||
|
|
||||||
|
// Mock job executor to return a successful response
|
||||||
|
when(jobExecutorService.runJobGeneric(
|
||||||
|
anyBoolean(), any(Supplier.class), anyLong(), anyBoolean(), anyInt()))
|
||||||
|
.thenReturn(ResponseEntity.ok("success"));
|
||||||
|
|
||||||
|
// When
|
||||||
|
autoJobAspect.wrapWithJobExecution(joinPoint, autoJobPostMapping);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals("stored-file-id", pdfFile.getFileId(),
|
||||||
|
"FileId should be set to the stored file id");
|
||||||
|
assertNotNull(pdfFile.getFileInput(), "FileInput should be replaced with persistent file");
|
||||||
|
|
||||||
|
// Verify storage operations
|
||||||
|
verify(fileStorage).storeFile(any(MultipartFile.class));
|
||||||
|
verify(fileStorage).retrieveFile("stored-file-id");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.mockito.AdditionalAnswers.*;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
class FileStorageTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FileOrUploadService fileOrUploadService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private FileStorage fileStorage;
|
||||||
|
|
||||||
|
private MultipartFile mockFile;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
ReflectionTestUtils.setField(fileStorage, "tempDirPath", tempDir.toString());
|
||||||
|
|
||||||
|
// Create a mock MultipartFile
|
||||||
|
mockFile = mock(MultipartFile.class);
|
||||||
|
when(mockFile.getOriginalFilename()).thenReturn("test.pdf");
|
||||||
|
when(mockFile.getContentType()).thenReturn("application/pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStoreFile() throws IOException {
|
||||||
|
// Arrange
|
||||||
|
byte[] fileContent = "Test PDF content".getBytes();
|
||||||
|
when(mockFile.getBytes()).thenReturn(fileContent);
|
||||||
|
|
||||||
|
// Set up mock to handle transferTo by writing the file
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
java.io.File file = invocation.getArgument(0);
|
||||||
|
Files.write(file.toPath(), fileContent);
|
||||||
|
return null;
|
||||||
|
}).when(mockFile).transferTo(any(java.io.File.class));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String fileId = fileStorage.storeFile(mockFile);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertNotNull(fileId);
|
||||||
|
assertTrue(Files.exists(tempDir.resolve(fileId)));
|
||||||
|
verify(mockFile).transferTo(any(java.io.File.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStoreBytes() throws IOException {
|
||||||
|
// Arrange
|
||||||
|
byte[] fileContent = "Test PDF content".getBytes();
|
||||||
|
String originalName = "test.pdf";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String fileId = fileStorage.storeBytes(fileContent, originalName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertNotNull(fileId);
|
||||||
|
assertTrue(Files.exists(tempDir.resolve(fileId)));
|
||||||
|
assertArrayEquals(fileContent, Files.readAllBytes(tempDir.resolve(fileId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetrieveFile() throws IOException {
|
||||||
|
// Arrange
|
||||||
|
byte[] fileContent = "Test PDF content".getBytes();
|
||||||
|
String fileId = UUID.randomUUID().toString();
|
||||||
|
Path filePath = tempDir.resolve(fileId);
|
||||||
|
Files.write(filePath, fileContent);
|
||||||
|
|
||||||
|
MultipartFile expectedFile = mock(MultipartFile.class);
|
||||||
|
when(fileOrUploadService.toMockMultipartFile(eq(fileId), eq(fileContent)))
|
||||||
|
.thenReturn(expectedFile);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
MultipartFile result = fileStorage.retrieveFile(fileId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertSame(expectedFile, result);
|
||||||
|
verify(fileOrUploadService).toMockMultipartFile(eq(fileId), eq(fileContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetrieveBytes() throws IOException {
|
||||||
|
// Arrange
|
||||||
|
byte[] fileContent = "Test PDF content".getBytes();
|
||||||
|
String fileId = UUID.randomUUID().toString();
|
||||||
|
Path filePath = tempDir.resolve(fileId);
|
||||||
|
Files.write(filePath, fileContent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
byte[] result = fileStorage.retrieveBytes(fileId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertArrayEquals(fileContent, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetrieveFile_FileNotFound() {
|
||||||
|
// Arrange
|
||||||
|
String nonExistentFileId = "non-existent-file";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(IOException.class, () -> fileStorage.retrieveFile(nonExistentFileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetrieveBytes_FileNotFound() {
|
||||||
|
// Arrange
|
||||||
|
String nonExistentFileId = "non-existent-file";
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(IOException.class, () -> fileStorage.retrieveBytes(nonExistentFileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteFile() throws IOException {
|
||||||
|
// Arrange
|
||||||
|
byte[] fileContent = "Test PDF content".getBytes();
|
||||||
|
String fileId = UUID.randomUUID().toString();
|
||||||
|
Path filePath = tempDir.resolve(fileId);
|
||||||
|
Files.write(filePath, fileContent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
boolean result = fileStorage.deleteFile(fileId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(result);
|
||||||
|
assertFalse(Files.exists(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeleteFile_FileNotFound() {
|
||||||
|
// Arrange
|
||||||
|
String nonExistentFileId = "non-existent-file";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
boolean result = fileStorage.deleteFile(nonExistentFileId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFileExists() throws IOException {
|
||||||
|
// Arrange
|
||||||
|
byte[] fileContent = "Test PDF content".getBytes();
|
||||||
|
String fileId = UUID.randomUUID().toString();
|
||||||
|
Path filePath = tempDir.resolve(fileId);
|
||||||
|
Files.write(filePath, fileContent);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
boolean result = fileStorage.fileExists(fileId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFileExists_FileNotFound() {
|
||||||
|
// Arrange
|
||||||
|
String nonExistentFileId = "non-existent-file";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
boolean result = fileStorage.fileExists(nonExistentFileId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse(result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
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.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import stirling.software.common.model.job.JobProgress;
|
||||||
|
import stirling.software.common.model.job.JobResponse;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class JobExecutorServiceTest {
|
||||||
|
|
||||||
|
private JobExecutorService jobExecutorService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private TaskManager taskManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FileStorage fileStorage;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ResourceMonitor resourceMonitor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private JobQueue jobQueue;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<String> jobIdCaptor;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// Initialize the service manually with all its dependencies
|
||||||
|
jobExecutorService = new JobExecutorService(
|
||||||
|
taskManager,
|
||||||
|
fileStorage,
|
||||||
|
request,
|
||||||
|
resourceMonitor,
|
||||||
|
jobQueue,
|
||||||
|
30000L, // asyncRequestTimeoutMs
|
||||||
|
"30m" // sessionTimeout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRunSyncJobSuccessfully() throws Exception {
|
||||||
|
// Given
|
||||||
|
Supplier<Object> work = () -> "test-result";
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<?> response = jobExecutorService.runJobGeneric(false, work);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertEquals("test-result", response.getBody());
|
||||||
|
|
||||||
|
// Verify request attribute was set with jobId
|
||||||
|
verify(request).setAttribute(eq("jobId"), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRunAsyncJobSuccessfully() throws Exception {
|
||||||
|
// Given
|
||||||
|
Supplier<Object> work = () -> "test-result";
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<?> response = jobExecutorService.runJobGeneric(true, work);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertTrue(response.getBody() instanceof JobResponse);
|
||||||
|
JobResponse<?> jobResponse = (JobResponse<?>) response.getBody();
|
||||||
|
assertTrue(jobResponse.isAsync());
|
||||||
|
assertNotNull(jobResponse.getJobId());
|
||||||
|
|
||||||
|
// Verify task manager was called
|
||||||
|
verify(taskManager).createTask(jobIdCaptor.capture());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleSyncJobError() {
|
||||||
|
// Given
|
||||||
|
Supplier<Object> work = () -> {
|
||||||
|
throw new RuntimeException("Test error");
|
||||||
|
};
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<?> response = jobExecutorService.runJobGeneric(false, work);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, String> errorMap = (Map<String, String>) response.getBody();
|
||||||
|
assertEquals("Job failed: Test error", errorMap.get("error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldQueueJobWhenResourcesLimited() {
|
||||||
|
// Given
|
||||||
|
Supplier<Object> work = () -> "test-result";
|
||||||
|
CompletableFuture<ResponseEntity<?>> future = new CompletableFuture<>();
|
||||||
|
|
||||||
|
// Configure resourceMonitor to indicate job should be queued
|
||||||
|
when(resourceMonitor.shouldQueueJob(80)).thenReturn(true);
|
||||||
|
|
||||||
|
// Configure jobQueue to return our future
|
||||||
|
when(jobQueue.queueJob(anyString(), eq(80), any(), anyLong())).thenReturn(future);
|
||||||
|
|
||||||
|
// When
|
||||||
|
ResponseEntity<?> response = jobExecutorService.runJobGeneric(
|
||||||
|
true, work, 5000, true, 80);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertTrue(response.getBody() instanceof JobResponse);
|
||||||
|
|
||||||
|
// Verify job was queued
|
||||||
|
verify(jobQueue).queueJob(anyString(), eq(80), any(), eq(5000L));
|
||||||
|
verify(taskManager).createTask(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseCustomTimeoutWhenProvided() throws Exception {
|
||||||
|
// Given
|
||||||
|
Supplier<Object> work = () -> "test-result";
|
||||||
|
long customTimeout = 60000L;
|
||||||
|
|
||||||
|
// Use reflection to access the private executeWithTimeout method
|
||||||
|
java.lang.reflect.Method executeMethod = JobExecutorService.class
|
||||||
|
.getDeclaredMethod("executeWithTimeout", Supplier.class, long.class);
|
||||||
|
executeMethod.setAccessible(true);
|
||||||
|
|
||||||
|
// Create a spy on the JobExecutorService to verify method calls
|
||||||
|
JobExecutorService spy = Mockito.spy(jobExecutorService);
|
||||||
|
|
||||||
|
// When
|
||||||
|
spy.runJobGeneric(false, work, customTimeout);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify(spy).runJobGeneric(eq(false), any(Supplier.class), eq(customTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleTimeout() throws Exception {
|
||||||
|
// Given
|
||||||
|
Supplier<Object> work = () -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100); // Simulate long-running job
|
||||||
|
return "test-result";
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use reflection to access the private executeWithTimeout method
|
||||||
|
java.lang.reflect.Method executeMethod = JobExecutorService.class
|
||||||
|
.getDeclaredMethod("executeWithTimeout", Supplier.class, long.class);
|
||||||
|
executeMethod.setAccessible(true);
|
||||||
|
|
||||||
|
// When/Then
|
||||||
|
try {
|
||||||
|
executeMethod.invoke(jobExecutorService, work, 1L); // Very short timeout
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertTrue(e.getCause() instanceof TimeoutException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
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.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import stirling.software.common.model.job.JobProgress;
|
||||||
|
import stirling.software.common.service.ResourceMonitor.ResourceStatus;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class JobQueueTest {
|
||||||
|
|
||||||
|
private JobQueue jobQueue;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ResourceMonitor resourceMonitor;
|
||||||
|
|
||||||
|
|
||||||
|
private final AtomicReference<ResourceStatus> statusRef = new AtomicReference<>(ResourceStatus.OK);
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// Mark stubbing as lenient to avoid UnnecessaryStubbingException
|
||||||
|
lenient().when(resourceMonitor.calculateDynamicQueueCapacity(anyInt(), anyInt())).thenReturn(10);
|
||||||
|
lenient().when(resourceMonitor.getCurrentStatus()).thenReturn(statusRef);
|
||||||
|
|
||||||
|
// Initialize JobQueue with mocked ResourceMonitor
|
||||||
|
jobQueue = new JobQueue(resourceMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldQueueJob() {
|
||||||
|
String jobId = "test-job-1";
|
||||||
|
int resourceWeight = 50;
|
||||||
|
Supplier<Object> work = () -> "test-result";
|
||||||
|
long timeoutMs = 1000;
|
||||||
|
|
||||||
|
jobQueue.queueJob(jobId, resourceWeight, work, timeoutMs);
|
||||||
|
|
||||||
|
|
||||||
|
assertTrue(jobQueue.isJobQueued(jobId));
|
||||||
|
assertEquals(1, jobQueue.getTotalQueuedJobs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCancelJob() {
|
||||||
|
String jobId = "test-job-2";
|
||||||
|
Supplier<Object> work = () -> "test-result";
|
||||||
|
|
||||||
|
jobQueue.queueJob(jobId, 50, work, 1000);
|
||||||
|
boolean cancelled = jobQueue.cancelJob(jobId);
|
||||||
|
|
||||||
|
assertTrue(cancelled);
|
||||||
|
assertFalse(jobQueue.isJobQueued(jobId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetQueueStats() {
|
||||||
|
when(resourceMonitor.getCurrentStatus()).thenReturn(statusRef);
|
||||||
|
|
||||||
|
jobQueue.queueJob("job1", 50, () -> "ok", 1000);
|
||||||
|
jobQueue.queueJob("job2", 50, () -> "ok", 1000);
|
||||||
|
jobQueue.cancelJob("job2");
|
||||||
|
|
||||||
|
Map<String, Object> stats = jobQueue.getQueueStats();
|
||||||
|
|
||||||
|
assertEquals(2, stats.get("totalQueuedJobs"));
|
||||||
|
assertTrue(stats.containsKey("queuedJobs"));
|
||||||
|
assertTrue(stats.containsKey("resourceStatus"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCalculateQueueCapacity() {
|
||||||
|
when(resourceMonitor.calculateDynamicQueueCapacity(5, 2)).thenReturn(8);
|
||||||
|
int capacity = resourceMonitor.calculateDynamicQueueCapacity(5, 2);
|
||||||
|
assertEquals(8, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCheckIfJobIsQueued() {
|
||||||
|
String jobId = "job-123";
|
||||||
|
Supplier<Object> work = () -> "hello";
|
||||||
|
|
||||||
|
jobQueue.queueJob(jobId, 40, work, 500);
|
||||||
|
|
||||||
|
assertTrue(jobQueue.isJobQueued(jobId));
|
||||||
|
assertFalse(jobQueue.isJobQueued("nonexistent"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
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.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.lang.management.MemoryMXBean;
|
||||||
|
import java.lang.management.MemoryUsage;
|
||||||
|
import java.lang.management.OperatingSystemMXBean;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import stirling.software.common.service.ResourceMonitor.ResourceMetrics;
|
||||||
|
import stirling.software.common.service.ResourceMonitor.ResourceStatus;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ResourceMonitorTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private ResourceMonitor resourceMonitor;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private OperatingSystemMXBean osMXBean;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private MemoryMXBean memoryMXBean;
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private AtomicReference<ResourceStatus> currentStatus = new AtomicReference<>(ResourceStatus.OK);
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private AtomicReference<ResourceMetrics> latestMetrics = new AtomicReference<>(new ResourceMetrics());
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// Set thresholds for testing
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "memoryCriticalThreshold", 0.9);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "memoryHighThreshold", 0.75);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "cpuCriticalThreshold", 0.9);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "cpuHighThreshold", 0.75);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "osMXBean", osMXBean);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "memoryMXBean", memoryMXBean);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "currentStatus", currentStatus);
|
||||||
|
ReflectionTestUtils.setField(resourceMonitor, "latestMetrics", latestMetrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCalculateDynamicQueueCapacity() {
|
||||||
|
// Given
|
||||||
|
int baseCapacity = 10;
|
||||||
|
int minCapacity = 2;
|
||||||
|
|
||||||
|
// Mock current status as OK
|
||||||
|
currentStatus.set(ResourceStatus.OK);
|
||||||
|
|
||||||
|
// When
|
||||||
|
int capacity = resourceMonitor.calculateDynamicQueueCapacity(baseCapacity, minCapacity);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(baseCapacity, capacity, "With OK status, capacity should equal base capacity");
|
||||||
|
|
||||||
|
// Given
|
||||||
|
currentStatus.set(ResourceStatus.WARNING);
|
||||||
|
|
||||||
|
// When
|
||||||
|
capacity = resourceMonitor.calculateDynamicQueueCapacity(baseCapacity, minCapacity);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(6, capacity, "With WARNING status, capacity should be reduced to 60%");
|
||||||
|
|
||||||
|
// Given
|
||||||
|
currentStatus.set(ResourceStatus.CRITICAL);
|
||||||
|
|
||||||
|
// When
|
||||||
|
capacity = resourceMonitor.calculateDynamicQueueCapacity(baseCapacity, minCapacity);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(3, capacity, "With CRITICAL status, capacity should be reduced to 30%");
|
||||||
|
|
||||||
|
// Test minimum capacity enforcement
|
||||||
|
assertEquals(minCapacity, resourceMonitor.calculateDynamicQueueCapacity(1, minCapacity),
|
||||||
|
"Should never go below minimum capacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({
|
||||||
|
"10, OK, false", // Light job, OK status
|
||||||
|
"10, WARNING, false", // Light job, WARNING status
|
||||||
|
"10, CRITICAL, true", // Light job, CRITICAL status
|
||||||
|
"30, OK, false", // Medium job, OK status
|
||||||
|
"30, WARNING, true", // Medium job, WARNING status
|
||||||
|
"30, CRITICAL, true", // Medium job, CRITICAL status
|
||||||
|
"80, OK, true", // Heavy job, OK status
|
||||||
|
"80, WARNING, true", // Heavy job, WARNING status
|
||||||
|
"80, CRITICAL, true" // Heavy job, CRITICAL status
|
||||||
|
})
|
||||||
|
void shouldQueueJobBasedOnWeightAndStatus(int weight, ResourceStatus status, boolean shouldQueue) {
|
||||||
|
// Given
|
||||||
|
currentStatus.set(status);
|
||||||
|
|
||||||
|
// When
|
||||||
|
boolean result = resourceMonitor.shouldQueueJob(weight);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(shouldQueue, result,
|
||||||
|
String.format("For weight %d and status %s, shouldQueue should be %s",
|
||||||
|
weight, status, shouldQueue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resourceMetricsShouldDetectStaleState() {
|
||||||
|
// Given
|
||||||
|
Instant now = Instant.now();
|
||||||
|
Instant pastInstant = now.minusMillis(6000);
|
||||||
|
|
||||||
|
ResourceMetrics staleMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, pastInstant);
|
||||||
|
ResourceMetrics freshMetrics = new ResourceMetrics(0.5, 0.5, 1024, 2048, 4096, now);
|
||||||
|
|
||||||
|
// When/Then
|
||||||
|
assertTrue(staleMetrics.isStale(5000), "Metrics from 6 seconds ago should be stale with 5s threshold");
|
||||||
|
assertFalse(freshMetrics.isStale(5000), "Fresh metrics should not be stale");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,255 @@
|
|||||||
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
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.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import stirling.software.common.model.job.JobResult;
|
||||||
|
import stirling.software.common.model.job.JobStats;
|
||||||
|
|
||||||
|
class TaskManagerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private FileStorage fileStorage;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private TaskManager taskManager;
|
||||||
|
|
||||||
|
private AutoCloseable closeable;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
closeable = MockitoAnnotations.openMocks(this);
|
||||||
|
ReflectionTestUtils.setField(taskManager, "jobResultExpiryMinutes", 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() throws Exception {
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateTask() {
|
||||||
|
// Act
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(jobId, result.getJobId());
|
||||||
|
assertFalse(result.isComplete());
|
||||||
|
assertNotNull(result.getCreatedAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetResult() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
Object resultObject = "Test result";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
taskManager.setResult(jobId, resultObject);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertEquals(resultObject, result.getResult());
|
||||||
|
assertNotNull(result.getCompletedAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetFileResult() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
String fileId = "file-id";
|
||||||
|
String originalFileName = "test.pdf";
|
||||||
|
String contentType = "application/pdf";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
taskManager.setFileResult(jobId, fileId, originalFileName, contentType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertEquals(fileId, result.getFileId());
|
||||||
|
assertEquals(originalFileName, result.getOriginalFileName());
|
||||||
|
assertEquals(contentType, result.getContentType());
|
||||||
|
assertNotNull(result.getCompletedAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetError() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
String errorMessage = "Test error";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
taskManager.setError(jobId, errorMessage);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertEquals(errorMessage, result.getError());
|
||||||
|
assertNotNull(result.getCompletedAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetComplete_WithExistingResult() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
Object resultObject = "Test result";
|
||||||
|
taskManager.setResult(jobId, resultObject);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
taskManager.setComplete(jobId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertEquals(resultObject, result.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetComplete_WithoutExistingResult() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
taskManager.setComplete(jobId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isComplete());
|
||||||
|
assertEquals("Task completed successfully", result.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIsComplete() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
taskManager.createTask(jobId);
|
||||||
|
|
||||||
|
// Assert - not complete initially
|
||||||
|
assertFalse(taskManager.isComplete(jobId));
|
||||||
|
|
||||||
|
// Act - mark as complete
|
||||||
|
taskManager.setComplete(jobId);
|
||||||
|
|
||||||
|
// Assert - now complete
|
||||||
|
assertTrue(taskManager.isComplete(jobId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetJobStats() {
|
||||||
|
// Arrange
|
||||||
|
// 1. Create active job
|
||||||
|
String activeJobId = "active-job";
|
||||||
|
taskManager.createTask(activeJobId);
|
||||||
|
|
||||||
|
// 2. Create completed successful job with file
|
||||||
|
String successFileJobId = "success-file-job";
|
||||||
|
taskManager.createTask(successFileJobId);
|
||||||
|
taskManager.setFileResult(successFileJobId, "file-id", "test.pdf", "application/pdf");
|
||||||
|
|
||||||
|
// 3. Create completed successful job without file
|
||||||
|
String successJobId = "success-job";
|
||||||
|
taskManager.createTask(successJobId);
|
||||||
|
taskManager.setResult(successJobId, "Result");
|
||||||
|
|
||||||
|
// 4. Create failed job
|
||||||
|
String failedJobId = "failed-job";
|
||||||
|
taskManager.createTask(failedJobId);
|
||||||
|
taskManager.setError(failedJobId, "Error message");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
JobStats stats = taskManager.getJobStats();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(4, stats.getTotalJobs());
|
||||||
|
assertEquals(1, stats.getActiveJobs());
|
||||||
|
assertEquals(3, stats.getCompletedJobs());
|
||||||
|
assertEquals(1, stats.getFailedJobs());
|
||||||
|
assertEquals(2, stats.getSuccessfulJobs());
|
||||||
|
assertEquals(1, stats.getFileResultJobs());
|
||||||
|
assertNotNull(stats.getNewestActiveJobTime());
|
||||||
|
assertNotNull(stats.getOldestActiveJobTime());
|
||||||
|
assertTrue(stats.getAverageProcessingTimeMs() >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCleanupOldJobs() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
// 1. Create a recent completed job
|
||||||
|
String recentJobId = "recent-job";
|
||||||
|
taskManager.createTask(recentJobId);
|
||||||
|
taskManager.setResult(recentJobId, "Result");
|
||||||
|
|
||||||
|
// 2. Create an old completed job with file result
|
||||||
|
String oldJobId = "old-job";
|
||||||
|
taskManager.createTask(oldJobId);
|
||||||
|
JobResult oldJob = taskManager.getJobResult(oldJobId);
|
||||||
|
|
||||||
|
// Manually set the completion time to be older than the expiry
|
||||||
|
LocalDateTime oldTime = LocalDateTime.now().minusHours(1);
|
||||||
|
ReflectionTestUtils.setField(oldJob, "completedAt", oldTime);
|
||||||
|
ReflectionTestUtils.setField(oldJob, "complete", true);
|
||||||
|
ReflectionTestUtils.setField(oldJob, "fileId", "file-id");
|
||||||
|
ReflectionTestUtils.setField(oldJob, "originalFileName", "test.pdf");
|
||||||
|
ReflectionTestUtils.setField(oldJob, "contentType", "application/pdf");
|
||||||
|
|
||||||
|
when(fileStorage.deleteFile("file-id")).thenReturn(true);
|
||||||
|
|
||||||
|
// Obtain access to the private jobResults map
|
||||||
|
Map<String, JobResult> jobResultsMap = (Map<String, JobResult>) ReflectionTestUtils.getField(taskManager, "jobResults");
|
||||||
|
|
||||||
|
// 3. Create an active job
|
||||||
|
String activeJobId = "active-job";
|
||||||
|
taskManager.createTask(activeJobId);
|
||||||
|
|
||||||
|
// Verify all jobs are in the map
|
||||||
|
assertTrue(jobResultsMap.containsKey(recentJobId));
|
||||||
|
assertTrue(jobResultsMap.containsKey(oldJobId));
|
||||||
|
assertTrue(jobResultsMap.containsKey(activeJobId));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
taskManager.cleanupOldJobs();
|
||||||
|
|
||||||
|
// Assert - the old job should be removed
|
||||||
|
assertFalse(jobResultsMap.containsKey(oldJobId));
|
||||||
|
assertTrue(jobResultsMap.containsKey(recentJobId));
|
||||||
|
assertTrue(jobResultsMap.containsKey(activeJobId));
|
||||||
|
verify(fileStorage).deleteFile("file-id");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testShutdown() throws Exception {
|
||||||
|
// This mainly tests that the shutdown method doesn't throw exceptions
|
||||||
|
taskManager.shutdown();
|
||||||
|
|
||||||
|
// Verify the executor service is shutdown
|
||||||
|
// This is difficult to test directly, but we can verify it doesn't throw exceptions
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package stirling.software.common.controller;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -14,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import stirling.software.common.model.job.JobResult;
|
import stirling.software.common.model.job.JobResult;
|
||||||
import stirling.software.common.model.job.JobStats;
|
import stirling.software.common.model.job.JobStats;
|
||||||
import stirling.software.common.service.FileStorage;
|
import stirling.software.common.service.FileStorage;
|
||||||
|
import stirling.software.common.service.JobQueue;
|
||||||
import stirling.software.common.service.TaskManager;
|
import stirling.software.common.service.TaskManager;
|
||||||
|
|
||||||
/** REST controller for job-related endpoints */
|
/** REST controller for job-related endpoints */
|
||||||
@ -24,6 +26,7 @@ public class JobController {
|
|||||||
|
|
||||||
private final TaskManager taskManager;
|
private final TaskManager taskManager;
|
||||||
private final FileStorage fileStorage;
|
private final FileStorage fileStorage;
|
||||||
|
private final JobQueue jobQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the status of a job
|
* Get the status of a job
|
||||||
@ -37,6 +40,19 @@ public class JobController {
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the job is in the queue and add queue information
|
||||||
|
if (!result.isComplete() && jobQueue.isJobQueued(jobId)) {
|
||||||
|
int position = jobQueue.getJobPosition(jobId);
|
||||||
|
Map<String, Object> resultWithQueueInfo =
|
||||||
|
Map.of(
|
||||||
|
"jobResult",
|
||||||
|
result,
|
||||||
|
"queueInfo",
|
||||||
|
Map.of("inQueue", true, "position", position));
|
||||||
|
return ResponseEntity.ok(resultWithQueueInfo);
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +109,17 @@ public class JobController {
|
|||||||
return ResponseEntity.ok(stats);
|
return ResponseEntity.ok(stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get statistics about the job queue
|
||||||
|
*
|
||||||
|
* @return Queue statistics
|
||||||
|
*/
|
||||||
|
@GetMapping("/api/v1/general/job/queue/stats")
|
||||||
|
public ResponseEntity<?> getQueueStats() {
|
||||||
|
Map<String, Object> queueStats = jobQueue.getQueueStats();
|
||||||
|
return ResponseEntity.ok(queueStats);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manually trigger cleanup of old jobs
|
* Manually trigger cleanup of old jobs
|
||||||
*
|
*
|
||||||
@ -111,4 +138,59 @@ public class JobController {
|
|||||||
"removedJobs", removedCount,
|
"removedJobs", removedCount,
|
||||||
"remainingJobs", afterCount));
|
"remainingJobs", afterCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a job by its ID
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @return Response indicating whether the job was cancelled
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/api/v1/general/job/{jobId}")
|
||||||
|
public ResponseEntity<?> cancelJob(@PathVariable("jobId") String jobId) {
|
||||||
|
log.debug("Request to cancel job: {}", jobId);
|
||||||
|
|
||||||
|
// First check if the job is in the queue
|
||||||
|
boolean cancelled = false;
|
||||||
|
int queuePosition = -1;
|
||||||
|
|
||||||
|
if (jobQueue.isJobQueued(jobId)) {
|
||||||
|
queuePosition = jobQueue.getJobPosition(jobId);
|
||||||
|
cancelled = jobQueue.cancelJob(jobId);
|
||||||
|
log.info("Cancelled queued job: {} (was at position {})", jobId, queuePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not in queue or couldn't cancel, try to cancel in TaskManager
|
||||||
|
if (!cancelled) {
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
if (result != null && !result.isComplete()) {
|
||||||
|
// Mark as error with cancellation message
|
||||||
|
taskManager.setError(jobId, "Job was cancelled by user");
|
||||||
|
cancelled = true;
|
||||||
|
log.info("Marked job as cancelled in TaskManager: {}", jobId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelled) {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
Map.of(
|
||||||
|
"message",
|
||||||
|
"Job cancelled successfully",
|
||||||
|
"wasQueued",
|
||||||
|
queuePosition >= 0,
|
||||||
|
"queuePosition",
|
||||||
|
queuePosition >= 0 ? queuePosition : "n/a"));
|
||||||
|
} else {
|
||||||
|
// Job not found or already complete
|
||||||
|
JobResult result = taskManager.getJobResult(jobId);
|
||||||
|
if (result == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
} else if (result.isComplete()) {
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.body(Map.of("message", "Cannot cancel job that is already complete"));
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body(Map.of("message", "Failed to cancel job for unknown reason"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,365 @@
|
|||||||
|
package stirling.software.common.controller;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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 stirling.software.common.model.job.JobResult;
|
||||||
|
import stirling.software.common.model.job.JobStats;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private JobController controller;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
// 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ResponseEntity<?> response = controller.getJobResult(jobId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertEquals(resultObject, response.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.completeWithFile(fileId, originalFileName, contentType);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetJobResult_NonExistentJob() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = "non-existent-job";
|
||||||
|
when(taskManager.getJobResult(jobId)).thenReturn(null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ResponseEntity<?> response = controller.getJobResult(jobId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetJobResult_ErrorRetrievingFile() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
String jobId = "test-job-id";
|
||||||
|
String fileId = "file-id";
|
||||||
|
String originalFileName = "test.pdf";
|
||||||
|
String contentType = "application/pdf";
|
||||||
|
|
||||||
|
JobResult mockResult = new JobResult();
|
||||||
|
mockResult.setJobId(jobId);
|
||||||
|
mockResult.completeWithFile(fileId, originalFileName, contentType);
|
||||||
|
|
||||||
|
when(taskManager.getJobResult(jobId)).thenReturn(mockResult);
|
||||||
|
when(fileStorage.retrieveBytes(fileId)).thenThrow(new RuntimeException("File not found"));
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCancelJob_Running() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = "job-running";
|
||||||
|
JobResult jobResult = new JobResult();
|
||||||
|
jobResult.setJobId(jobId);
|
||||||
|
jobResult.setComplete(false);
|
||||||
|
|
||||||
|
when(jobQueue.isJobQueued(jobId)).thenReturn(false);
|
||||||
|
when(taskManager.getJobResult(jobId)).thenReturn(jobResult);
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCancelJob_NotFound() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = "non-existent-job";
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCancelJob_AlreadyComplete() {
|
||||||
|
// Arrange
|
||||||
|
String jobId = "completed-job";
|
||||||
|
JobResult jobResult = new JobResult();
|
||||||
|
jobResult.setJobId(jobId);
|
||||||
|
jobResult.setComplete(true);
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user