mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Async (#3773)
# Description of Changes This pull request introduces a job management system with enhanced capabilities for handling asynchronous tasks, file operations, and progress tracking. Key changes include the addition of new annotations and aspects for job execution, file management services, and models for job progress and results. ### Job Execution Enhancements: * [`common/src/main/java/stirling/software/common/annotations/AutoJobPostMapping.java`](diffhunk://#diff-570304f67b974d5bd30a28d05d34759b86bcb4a35148d779e2b46904e8dd2904R1-R47): Added a custom annotation to simplify job handling for POST requests, including support for retries, progress tracking, and resource management. * [`common/src/main/java/stirling/software/common/aop/AutoJobAspect.java`](diffhunk://#diff-5f725b1d99dbc47dfe9b1d07f37382ca7c81d587725dc35a62c644d1a25f9869R1-R231): Implemented an aspect to integrate job execution logic, handling retries, asynchronous processing, and file management seamlessly. ### File Management: * [`common/src/main/java/stirling/software/common/service/FileStorage.java`](diffhunk://#diff-f382e12c197ad6f7c5b01b1cea912e9b141a4b4e4ab7f12baafa1b69cb112962R1-R152): Added a service for storing, retrieving, and managing files using unique IDs, enabling persistent file handling for jobs. * [`common/src/main/java/stirling/software/common/service/FileOrUploadService.java`](diffhunk://#diff-e0637404eea2b1c1413cf5f3247208a9196b14388a90a896314d3e9c2949c893R1-R78): Added utility methods for converting files to `MultipartFile` and resolving file paths. ### Job Models: * [`common/src/main/java/stirling/software/common/model/job/JobProgress.java`](diffhunk://#diff-edc765f0e32ef4cb5a03dd3badafad450336a5248221ecc27976eb692280f003R1-R15): Introduced a model to represent job progress, including completion percentage and status messages. * [`common/src/main/java/stirling/software/common/model/job/JobResult.java`](diffhunk://#diff-b34316aa0ebfd849f41086339ae0323cb5cc2066b8200c38c6a39564e17b88f3R1-R94): Added a model to encapsulate job results, supporting both file-based and object-based outcomes. * [`common/src/main/java/stirling/software/common/model/job/JobResponse.java`](diffhunk://#diff-b02e9f86d44beda10ceb66650c79d1e032acd6f6a609887fb5f5596713048ab1R1-R14): Created a model for job responses, including async execution details and job IDs. * [`common/src/main/java/stirling/software/common/model/job/JobStats.java`](diffhunk://#diff-6067e6bd9e44d9dc40419d2435fa24d6753ec51e3baf7967dbcbc1a51e95e8afR1-R43): Added a model for tracking job statistics, such as total jobs, success rates, and average processing times. ### Other Changes: * [`common/src/main/java/stirling/software/common/model/api/PDFFile.java`](diffhunk://#diff-d2419d05a852acf8f8d0bd5c3673bbdd8e385b2d5cf1d80fbd8b66691ebd2cb2L17-R24): Updated the `PDFFile` model to include a `fileId` field for server-side file references, enhancing flexibility in file handling. * [`common/build.gradle`](diffhunk://#diff-824c1e8ad11e20caed0bec7162a99779b9a4bcf1178d99fae3e39f69889f8959R31): Added the `spring-boot-starter-aop` dependency to enable aspect-oriented programming. --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: a <a>
This commit is contained in:
co-authored by
Copilot
a <a>
parent
ee8030c1c4
commit
7d7f1272e4
+208
@@ -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,287 @@
|
||||
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
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddNote() {
|
||||
// Arrange
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
taskManager.createTask(jobId);
|
||||
String note = "Test note";
|
||||
|
||||
// Act
|
||||
boolean result = taskManager.addNote(jobId, note);
|
||||
|
||||
// Assert
|
||||
assertTrue(result);
|
||||
JobResult jobResult = taskManager.getJobResult(jobId);
|
||||
assertNotNull(jobResult);
|
||||
assertNotNull(jobResult.getNotes());
|
||||
assertEquals(1, jobResult.getNotes().size());
|
||||
assertEquals(note, jobResult.getNotes().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddNote_NonExistentJob() {
|
||||
// Arrange
|
||||
String jobId = "non-existent-job";
|
||||
String note = "Test note";
|
||||
|
||||
// Act
|
||||
boolean result = taskManager.addNote(jobId, note);
|
||||
|
||||
// Assert
|
||||
assertFalse(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
class SpringContextHolderTest {
|
||||
|
||||
private ApplicationContext mockApplicationContext;
|
||||
private SpringContextHolder contextHolder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockApplicationContext = mock(ApplicationContext.class);
|
||||
contextHolder = new SpringContextHolder();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetApplicationContext() {
|
||||
// Act
|
||||
contextHolder.setApplicationContext(mockApplicationContext);
|
||||
|
||||
// Assert
|
||||
assertTrue(SpringContextHolder.isInitialized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBean_ByType() {
|
||||
// Arrange
|
||||
contextHolder.setApplicationContext(mockApplicationContext);
|
||||
TestBean expectedBean = new TestBean();
|
||||
when(mockApplicationContext.getBean(TestBean.class)).thenReturn(expectedBean);
|
||||
|
||||
// Act
|
||||
TestBean result = SpringContextHolder.getBean(TestBean.class);
|
||||
|
||||
// Assert
|
||||
assertSame(expectedBean, result);
|
||||
verify(mockApplicationContext).getBean(TestBean.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testGetBean_ApplicationContextNotSet() {
|
||||
// Don't set application context
|
||||
|
||||
// Act
|
||||
TestBean result = SpringContextHolder.getBean(TestBean.class);
|
||||
|
||||
// Assert
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetBean_BeanNotFound() {
|
||||
// Arrange
|
||||
contextHolder.setApplicationContext(mockApplicationContext);
|
||||
when(mockApplicationContext.getBean(TestBean.class)).thenThrow(new org.springframework.beans.BeansException("Bean not found") {});
|
||||
|
||||
// Act
|
||||
TestBean result = SpringContextHolder.getBean(TestBean.class);
|
||||
|
||||
// Assert
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
// Simple test class
|
||||
private static class TestBean {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user