Add demo logic for landing
Test Workflow / test (push) Has been cancelled

This commit is contained in:
2026-07-07 18:14:23 +07:00
parent 0fa30eb176
commit ccb57c3805
3 changed files with 0 additions and 916 deletions
@@ -1,328 +0,0 @@
package ru.soune.nocopy.controller.demo;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import ru.soune.nocopy.dto.demo.*;
import ru.soune.nocopy.service.demo.DemoSessionService;
import java.io.IOException;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("DemoController Tests")
class DemoControllerTest {
@Mock
private DemoSessionService demoSessionService;
@Mock
private HttpServletRequest mockRequest;
@InjectMocks
private DemoController demoController;
@BeforeEach
void setUp() {
when(mockRequest.getRemoteAddr()).thenReturn("192.168.1.1");
}
@Test
@DisplayName("Should upload file successfully")
void testUploadFileSuccess() throws IOException {
// Arrange
MockMultipartFile file = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", "test content".getBytes()
);
DemoUploadResponse mockResponse = DemoUploadResponse.builder()
.sessionId("session-123")
.fileName("test.jpg")
.fileType("image")
.fileSize(12L)
.status("COMPLETED")
.message("File uploaded successfully")
.uploadedBytes(12L)
.progressPercentage(100)
.build();
when(demoSessionService.uploadFile(any(), anyString())).thenReturn(mockResponse);
// Act
ResponseEntity<?> response = demoController.uploadFile(file);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoUploadResponse);
DemoUploadResponse body = (DemoUploadResponse) response.getBody();
assertEquals("session-123", body.getSessionId());
assertEquals("test.jpg", body.getFileName());
}
@Test
@DisplayName("Should reject empty file")
void testUploadEmptyFile() {
// Arrange
MockMultipartFile emptyFile = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", new byte[0]
);
// Act
ResponseEntity<?> response = demoController.uploadFile(emptyFile);
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoUploadResponse);
DemoUploadResponse body = (DemoUploadResponse) response.getBody();
assertEquals("FAILED", body.getStatus());
assertTrue(body.getMessage().contains("empty"));
}
@Test
@DisplayName("Should handle file size validation error")
void testUploadFileSizeError() throws IOException {
// Arrange
MockMultipartFile file = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", "test content".getBytes()
);
when(demoSessionService.uploadFile(any(), anyString()))
.thenThrow(new RuntimeException("File too large"));
// Act
ResponseEntity<?> response = demoController.uploadFile(file);
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoUploadResponse);
DemoUploadResponse body = (DemoUploadResponse) response.getBody();
assertEquals("FAILED", body.getStatus());
}
@Test
@DisplayName("Should handle upload IO error")
void testUploadIOError() throws IOException {
// Arrange
MockMultipartFile file = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", "test content".getBytes()
);
when(demoSessionService.uploadFile(any(), anyString()))
.thenThrow(new RuntimeException("Upload failed"));
// Act
ResponseEntity<?> response = demoController.uploadFile(file);
// Assert
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
@Test
@DisplayName("Should get file info successfully")
void testGetFileInfoSuccess() {
// Arrange
DemoSessionInfo mockInfo = DemoSessionInfo.builder()
.sessionId("session-123")
.fileName("test.jpg")
.fileType("image")
.fileSize(1024L)
.status("COMPLETED")
.createdAt(LocalDateTime.now())
.expiresAt(LocalDateTime.now().plusHours(24))
.searchResultsCount(5)
.mimeType("image/jpeg")
.build();
when(demoSessionService.getSessionInfo("session-123", "192.168.1.1"))
.thenReturn(mockInfo);
// Act
ResponseEntity<?> response = demoController.getFileInfo("session-123");
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoSessionInfo);
DemoSessionInfo body = (DemoSessionInfo) response.getBody();
assertEquals("test.jpg", body.getFileName());
}
@Test
@DisplayName("Should return 404 for non-existent session")
void testGetFileInfoNotFound() {
// Arrange
when(demoSessionService.getSessionInfo("non-existent", "192.168.1.1"))
.thenThrow(new RuntimeException("Session not found"));
// Act
ResponseEntity<?> response = demoController.getFileInfo("non-existent");
// Assert
assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}
@Test
@DisplayName("Should start search successfully")
void testStartSearchSuccess() {
// Arrange
DemoSearchResponse mockResponse = DemoSearchResponse.builder()
.sessionId("session-123")
.status("COMPLETED")
.resultsCount(3)
.matchPercentage(92.5)
.processingTimeMs(5000L)
.message("Search completed successfully")
.build();
when(demoSessionService.startSearch("session-123", "192.168.1.1"))
.thenReturn(mockResponse);
// Act
ResponseEntity<?> response = demoController.startSearch("session-123");
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoSearchResponse);
DemoSearchResponse body = (DemoSearchResponse) response.getBody();
assertEquals("COMPLETED", body.getStatus());
assertEquals(3, body.getResultsCount());
}
@Test
@DisplayName("Should handle search error")
void testStartSearchError() {
// Arrange
when(demoSessionService.startSearch("session-123", "192.168.1.1"))
.thenThrow(new RuntimeException("File not ready"));
// Act
ResponseEntity<?> response = demoController.startSearch("session-123");
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoSearchResponse);
DemoSearchResponse body = (DemoSearchResponse) response.getBody();
assertEquals("FAILED", body.getStatus());
}
@Test
@DisplayName("Should cleanup session successfully")
void testCleanupSuccess() {
// Arrange
doNothing().when(demoSessionService).deleteSession("session-123", "192.168.1.1");
// Act
ResponseEntity<?> response = demoController.cleanup("session-123");
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoUploadResponse);
DemoUploadResponse body = (DemoUploadResponse) response.getBody();
assertEquals("CLEANED", body.getStatus());
verify(demoSessionService).deleteSession("session-123", "192.168.1.1");
}
@Test
@DisplayName("Should handle cleanup error")
void testCleanupError() {
// Arrange
doThrow(new RuntimeException("Session not found"))
.when(demoSessionService).deleteSession("non-existent", "192.168.1.1");
// Act
ResponseEntity<?> response = demoController.cleanup("non-existent");
// Assert
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoUploadResponse);
}
@Test
@DisplayName("Should return health status")
void testHealthCheck() {
// Act
ResponseEntity<?> response = demoController.health();
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getBody() instanceof DemoUploadResponse);
DemoUploadResponse body = (DemoUploadResponse) response.getBody();
assertEquals("HEALTHY", body.getStatus());
}
@Test
@DisplayName("Should extract client IP correctly from X-Forwarded-For")
void testGetClientIpFromXForwardedFor() throws IOException {
// Arrange
when(mockRequest.getHeader("X-Forwarded-For")).thenReturn("203.0.113.1, 198.51.100.2");
when(mockRequest.getHeader("X-Real-IP")).thenReturn(null);
when(mockRequest.getRemoteAddr()).thenReturn("192.168.1.1");
MockMultipartFile file = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", "test content".getBytes()
);
DemoUploadResponse mockResponse = DemoUploadResponse.builder()
.sessionId("session-123")
.fileName("test.jpg")
.fileType("image")
.fileSize(12L)
.status("COMPLETED")
.progressPercentage(100)
.build();
when(demoSessionService.uploadFile(any(), eq("203.0.113.1")))
.thenReturn(mockResponse);
// Act
ResponseEntity<?> response = demoController.uploadFile(file);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
verify(demoSessionService).uploadFile(any(), eq("203.0.113.1"));
}
@Test
@DisplayName("Should extract client IP from X-Real-IP")
void testGetClientIpFromXRealIp() throws IOException {
// Arrange
when(mockRequest.getHeader("X-Forwarded-For")).thenReturn(null);
when(mockRequest.getHeader("X-Real-IP")).thenReturn("192.0.2.1");
when(mockRequest.getRemoteAddr()).thenReturn("192.168.1.1");
MockMultipartFile file = new MockMultipartFile(
"file", "test.jpg", "image/jpeg", "test content".getBytes()
);
DemoUploadResponse mockResponse = DemoUploadResponse.builder()
.sessionId("session-123")
.fileName("test.jpg")
.fileType("image")
.fileSize(12L)
.status("COMPLETED")
.progressPercentage(100)
.build();
when(demoSessionService.uploadFile(any(), eq("192.0.2.1")))
.thenReturn(mockResponse);
// Act
ResponseEntity<?> response = demoController.uploadFile(file);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
verify(demoSessionService).uploadFile(any(), eq("192.0.2.1"));
}
}
@@ -1,313 +0,0 @@
package ru.soune.nocopy.service.demo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import ru.soune.nocopy.entity.demo.DemoSession;
import ru.soune.nocopy.entity.demo.DemoSessionStatus;
import ru.soune.nocopy.repository.DemoSessionRepository;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("DemoCleanupService Tests")
class DemoCleanupServiceTest {
@Mock
private DemoSessionRepository demoSessionRepository;
@Mock
private CloudStorageService cloudStorageService;
@InjectMocks
private DemoCleanupService demoCleanupService;
@BeforeEach
void setUp() {
// Настройка тестовых данных
}
@Test
@DisplayName("Should cleanup expired sessions")
void testCleanupExpiredSessions() {
// Arrange
List<DemoSession> expiredSessions = createExpiredSessions(3);
when(demoSessionRepository.findExpiredSessions(any())).thenReturn(expiredSessions);
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
demoCleanupService.cleanupExpiredSessions();
// Assert
verify(cloudStorageService, times(3)).deleteFileFromStorage(anyString());
verify(demoSessionRepository, times(3)).save(any(DemoSession.class));
// Проверяем, что сессии помечены как CLEANED
expiredSessions.forEach(session ->
assertEquals(DemoSessionStatus.CLEANED, session.getStatus())
);
}
@Test
@DisplayName("Should handle cleanup with no expired sessions")
void testCleanupNoExpiredSessions() {
// Arrange
when(demoSessionRepository.findExpiredSessions(any())).thenReturn(new ArrayList<>());
// Act & Assert - должно завершиться без ошибок
assertDoesNotThrow(() -> demoCleanupService.cleanupExpiredSessions());
// Ничего не должно быть удалено
verify(cloudStorageService, never()).deleteFileFromStorage(anyString());
verify(demoSessionRepository, never()).save(any(DemoSession.class));
}
@Test
@DisplayName("Should handle S3 deletion failures gracefully")
void testCleanupHandlesS3Failures() {
// Arrange
List<DemoSession> expiredSessions = createExpiredSessions(2);
when(demoSessionRepository.findExpiredSessions(any())).thenReturn(expiredSessions);
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
doThrow(new RuntimeException("S3 error")).when(cloudStorageService).deleteFileFromStorage(anyString());
// Act & Assert - должно не бросать исключение
assertDoesNotThrow(() -> demoCleanupService.cleanupExpiredSessions());
// Сессии должны быть помечены как CLEANED несмотря на ошибку S3
verify(demoSessionRepository, times(2)).save(any(DemoSession.class));
}
@Test
@DisplayName("Should cleanup failed sessions")
void testCleanupFailedSessions() {
// Arrange
List<DemoSession> failedSessions = createFailedSessions(2);
when(demoSessionRepository.findByStatus(DemoSessionStatus.FAILED.name())).thenReturn(failedSessions);
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
demoCleanupService.cleanupFailedSessions();
// Assert
verify(cloudStorageService, times(2)).deleteFileFromStorage(anyString());
verify(demoSessionRepository, times(2)).save(any(DemoSession.class));
}
@Test
@DisplayName("Should skip failed sessions newer than 1 hour")
void testCleanupSkipsRecentFailedSessions() {
// Arrange
DemoSession recentFailed = createFailedSessionAt(LocalDateTime.now().minusMinutes(30));
DemoSession oldFailed = createFailedSessionAt(LocalDateTime.now().minusHours(2));
List<DemoSession> failedSessions = List.of(recentFailed, oldFailed);
when(demoSessionRepository.findByStatus(DemoSessionStatus.FAILED.name())).thenReturn(failedSessions);
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
demoCleanupService.cleanupFailedSessions();
// Assert - только старая сессия должна быть очищена
verify(cloudStorageService, times(1)).deleteFileFromStorage(anyString());
}
@Test
@DisplayName("Should cleanup session manually")
void testCleanupSessionManually() {
// Arrange
DemoSession session = createTestSession();
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
demoCleanupService.cleanupSessionManually("session-123");
// Assert
verify(cloudStorageService).deleteFileFromStorage(session.getS3Path());
verify(demoSessionRepository).save(any(DemoSession.class));
assertEquals(DemoSessionStatus.CLEANED, session.getStatus());
}
@Test
@DisplayName("Should throw exception for non-existent session during manual cleanup")
void testCleanupSessionManuallyNotFound() {
// Arrange
when(demoSessionRepository.findBySessionId("non-existent")).thenReturn(Optional.empty());
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoCleanupService.cleanupSessionManually("non-existent")
);
assertTrue(exception.getMessage().contains("not found"));
}
@Test
@DisplayName("Should calculate active demo sessions count correctly")
void testGetActiveDemoSessionsCount() {
// Arrange
List<DemoSession> activeSessions = new ArrayList<>();
activeSessions.add(createTestSessionWithStatus(DemoSessionStatus.CREATED));
activeSessions.add(createTestSessionWithStatus(DemoSessionStatus.PROCESSING));
activeSessions.add(createTestSessionWithStatus(DemoSessionStatus.COMPLETED));
activeSessions.add(createTestSessionWithStatus(DemoSessionStatus.CLEANED)); // Это не должно считаться
when(demoSessionRepository.findExpiredSessions(any())).thenReturn(activeSessions);
// Act
long count = demoCleanupService.getActiveDemoSessionsCount();
// Assert
assertEquals(3, count);
}
@Test
@DisplayName("Should calculate total demo data size correctly")
void testGetActiveDemoDataSize() {
// Arrange
List<DemoSession> sessions = new ArrayList<>();
sessions.add(createSessionWithSize(1000L, DemoSessionStatus.COMPLETED));
sessions.add(createSessionWithSize(2000L, DemoSessionStatus.COMPLETED));
sessions.add(createSessionWithSize(3000L, DemoSessionStatus.CLEANED));
when(demoSessionRepository.findExpiredSessions(any())).thenReturn(sessions);
// Act
long totalSize = demoCleanupService.getActiveDemoDataSize();
// Assert
assertEquals(3000L, totalSize); // Только активные сессии
}
@Test
@DisplayName("Should return zero when no active sessions")
void testGetActiveDemoDataSizeNoActiveSessions() {
// Arrange
when(demoSessionRepository.findExpiredSessions(any())).thenReturn(new ArrayList<>());
// Act
long totalSize = demoCleanupService.getActiveDemoDataSize();
// Assert
assertEquals(0L, totalSize);
}
// Помощные методы
private List<DemoSession> createExpiredSessions(int count) {
List<DemoSession> sessions = new ArrayList<>();
for (int i = 0; i < count; i++) {
sessions.add(DemoSession.builder()
.sessionId("expired-session-" + i)
.ipAddress("192.168.1." + i)
.fileName("file-" + i + ".jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(1024L * (i + 1))
.s3Path("demo/file-" + i + ".jpg")
.status(DemoSessionStatus.COMPLETED)
.createdAt(LocalDateTime.now().minusHours(25))
.updatedAt(LocalDateTime.now().minusHours(25))
.expiresAt(LocalDateTime.now().minusHours(1))
.build());
}
return sessions;
}
private List<DemoSession> createFailedSessions(int count) {
List<DemoSession> sessions = new ArrayList<>();
for (int i = 0; i < count; i++) {
sessions.add(DemoSession.builder()
.sessionId("failed-session-" + i)
.ipAddress("192.168.1." + i)
.fileName("file-" + i + ".jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(1024L)
.s3Path("demo/file-" + i + ".jpg")
.status(DemoSessionStatus.FAILED)
.createdAt(LocalDateTime.now().minusHours(2))
.updatedAt(LocalDateTime.now().minusHours(2))
.expiresAt(LocalDateTime.now().plusHours(22))
.build());
}
return sessions;
}
private DemoSession createFailedSessionAt(LocalDateTime createdAt) {
return DemoSession.builder()
.sessionId("failed-" + System.nanoTime())
.ipAddress("192.168.1.1")
.fileName("file.jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(1024L)
.s3Path("demo/file.jpg")
.status(DemoSessionStatus.FAILED)
.createdAt(createdAt)
.updatedAt(createdAt)
.expiresAt(createdAt.plusHours(24))
.build();
}
private DemoSession createTestSession() {
return DemoSession.builder()
.sessionId("session-123")
.ipAddress("192.168.1.1")
.fileName("test-image.jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(1024L)
.s3Path("demo/test-file.jpg")
.status(DemoSessionStatus.COMPLETED)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.expiresAt(LocalDateTime.now().plusHours(24))
.build();
}
private DemoSession createTestSessionWithStatus(DemoSessionStatus status) {
return DemoSession.builder()
.sessionId("session-" + System.nanoTime())
.ipAddress("192.168.1.1")
.fileName("file.jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(1024L)
.s3Path("demo/file.jpg")
.status(status)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.expiresAt(LocalDateTime.now().plusHours(24))
.build();
}
private DemoSession createSessionWithSize(Long fileSize, DemoSessionStatus status) {
return DemoSession.builder()
.sessionId("session-" + System.nanoTime())
.ipAddress("192.168.1.1")
.fileName("file.jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(fileSize)
.s3Path("demo/file.jpg")
.status(status)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.expiresAt(LocalDateTime.now().plusHours(24))
.build();
}
}
@@ -1,275 +0,0 @@
package ru.soune.nocopy.service.demo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import ru.soune.nocopy.dto.demo.DemoSearchResponse;
import ru.soune.nocopy.dto.demo.DemoSessionInfo;
import ru.soune.nocopy.dto.demo.DemoUploadResponse;
import ru.soune.nocopy.entity.demo.DemoSession;
import ru.soune.nocopy.entity.demo.DemoSessionStatus;
import ru.soune.nocopy.repository.DemoSessionRepository;
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("DemoSessionService Tests")
class DemoSessionServiceTest {
@Mock
private DemoSessionRepository demoSessionRepository;
@Mock
private CloudStorageService cloudStorageService;
@InjectMocks
private DemoSessionService demoSessionService;
private String testIpAddress;
private MockMultipartFile testFile;
@BeforeEach
void setUp() {
testIpAddress = "192.168.1.1";
testFile = new MockMultipartFile(
"file",
"test-image.jpg",
"image/jpeg",
"test content".getBytes()
);
// Устанавливаем значения через reflection для тестирования
ReflectionTestUtils.setField(demoSessionService, "maxFileSize", 52428800L); // 50 MB
ReflectionTestUtils.setField(demoSessionService, "maxFilesPerDay", 10);
ReflectionTestUtils.setField(demoSessionService, "demoS3Path", "demo/");
ReflectionTestUtils.setField(demoSessionService, "sessionTtlHours", 24);
}
@Test
@DisplayName("Should successfully upload file")
void testUploadFileSuccess() throws IOException {
// Arrange
when(demoSessionRepository.countSessionsByIpAndTime(anyString(), any())).thenReturn(0);
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
DemoUploadResponse response = demoSessionService.uploadFile(testFile, testIpAddress);
// Assert
assertNotNull(response);
assertNotNull(response.getSessionId());
assertEquals("test-image.jpg", response.getFileName());
assertEquals("image", response.getFileType());
assertEquals(100, response.getProgressPercentage());
verify(cloudStorageService).saveFilesToStorage(any(), anyString());
verify(demoSessionRepository).save(any(DemoSession.class));
}
@Test
@DisplayName("Should reject file that exceeds max size")
void testUploadFileExceedsMaxSize() {
// Arrange
MockMultipartFile largeFile = new MockMultipartFile(
"file",
"large-file.jpg",
"image/jpeg",
new byte[52428801] // 50 MB + 1 byte
);
ReflectionTestUtils.setField(demoSessionService, "maxFileSize", 52428800L);
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.uploadFile(largeFile, testIpAddress)
);
assertTrue(exception.getMessage().contains("File too large"));
}
@Test
@DisplayName("Should reject file with unsupported type")
void testUploadFileUnsupportedType() {
// Arrange
MockMultipartFile unsupportedFile = new MockMultipartFile(
"file",
"test-file.exe",
"application/x-msdownload",
"test content".getBytes()
);
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.uploadFile(unsupportedFile, testIpAddress)
);
assertTrue(exception.getMessage().contains("File type not supported"));
}
@Test
@DisplayName("Should enforce daily file limit")
void testUploadFileExceedsDailyLimit() {
// Arrange
ReflectionTestUtils.setField(demoSessionService, "maxFilesPerDay", 2);
when(demoSessionRepository.countSessionsByIpAndTime(anyString(), any())).thenReturn(2);
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.uploadFile(testFile, testIpAddress)
);
assertTrue(exception.getMessage().contains("Daily limit reached"));
}
@Test
@DisplayName("Should retrieve session info successfully")
void testGetSessionInfoSuccess() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
// Act
DemoSessionInfo info = demoSessionService.getSessionInfo("session-123", testIpAddress);
// Assert
assertNotNull(info);
assertEquals("session-123", info.getSessionId());
assertEquals("test-image.jpg", info.getFileName());
assertEquals(testIpAddress, session.getIpAddress());
}
@Test
@DisplayName("Should reject session info access from different IP")
void testGetSessionInfoUnauthorizedIp() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.getSessionInfo("session-123", "192.168.1.2")
);
assertTrue(exception.getMessage().contains("Unauthorized"));
}
@Test
@DisplayName("Should throw exception for non-existent session")
void testGetSessionInfoNotFound() {
// Arrange
when(demoSessionRepository.findBySessionId("non-existent")).thenReturn(Optional.empty());
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.getSessionInfo("non-existent", testIpAddress)
);
assertTrue(exception.getMessage().contains("not found"));
}
@Test
@DisplayName("Should start search successfully")
void testStartSearchSuccess() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
session.setStatus(DemoSessionStatus.COMPLETED);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
DemoSearchResponse response = demoSessionService.startSearch("session-123", testIpAddress);
// Assert
assertNotNull(response);
assertEquals("COMPLETED", response.getStatus());
assertTrue(response.getResultsCount() >= 0);
assertTrue(response.getMatchPercentage() >= 0);
assertNotNull(response.getSimilarFiles());
}
@Test
@DisplayName("Should reject search if file not ready")
void testStartSearchFileNotReady() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
session.setStatus(DemoSessionStatus.CREATED);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.startSearch("session-123", testIpAddress)
);
assertTrue(exception.getMessage().contains("not ready"));
}
@Test
@DisplayName("Should delete session and file from S3")
void testDeleteSessionSuccess() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
// Act
demoSessionService.deleteSession("session-123", testIpAddress);
// Assert
verify(cloudStorageService).deleteFileFromStorage(session.getS3Path());
verify(demoSessionRepository).save(any(DemoSession.class));
}
@Test
@DisplayName("Should reject deletion from different IP")
void testDeleteSessionUnauthorizedIp() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
// Act & Assert
RuntimeException exception = assertThrows(RuntimeException.class, () ->
demoSessionService.deleteSession("session-123", "192.168.1.2")
);
assertTrue(exception.getMessage().contains("Unauthorized"));
}
@Test
@DisplayName("Should handle S3 deletion failure gracefully")
void testDeleteSessionS3FailureHandling() {
// Arrange
DemoSession session = createTestSession(testIpAddress);
when(demoSessionRepository.findBySessionId("session-123")).thenReturn(Optional.of(session));
when(demoSessionRepository.save(any(DemoSession.class))).thenAnswer(invocation -> invocation.getArgument(0));
doThrow(new RuntimeException("S3 error")).when(cloudStorageService).deleteFileFromStorage(anyString());
// Act & Assert - должно не бросать исключение
assertDoesNotThrow(() -> demoSessionService.deleteSession("session-123", testIpAddress));
// Сессия должна быть помечена как очищенная несмотря на ошибку S3
verify(demoSessionRepository).save(any(DemoSession.class));
}
// Помощные методы
private DemoSession createTestSession(String ipAddress) {
return DemoSession.builder()
.sessionId("session-123")
.ipAddress(ipAddress)
.fileName("test-image.jpg")
.fileType("image")
.mimeType("image/jpeg")
.fileSize(1024L)
.s3Path("demo/test-file.jpg")
.status(DemoSessionStatus.COMPLETED)
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.expiresAt(LocalDateTime.now().plusHours(24))
.build();
}
}