From ccb57c3805301cd411b3d032bb688de532973b17 Mon Sep 17 00:00:00 2001 From: backdev-1 Date: Tue, 7 Jul 2026 18:14:23 +0700 Subject: [PATCH] Add demo logic for landing --- .../controller/demo/DemoControllerTest.java | 328 ------------------ .../service/demo/DemoCleanupServiceTest.java | 313 ----------------- .../service/demo/DemoSessionServiceTest.java | 275 --------------- 3 files changed, 916 deletions(-) delete mode 100644 src/test/java/ru/soune/nocopy/controller/demo/DemoControllerTest.java delete mode 100644 src/test/java/ru/soune/nocopy/service/demo/DemoCleanupServiceTest.java delete mode 100644 src/test/java/ru/soune/nocopy/service/demo/DemoSessionServiceTest.java diff --git a/src/test/java/ru/soune/nocopy/controller/demo/DemoControllerTest.java b/src/test/java/ru/soune/nocopy/controller/demo/DemoControllerTest.java deleted file mode 100644 index 70fc9df..0000000 --- a/src/test/java/ru/soune/nocopy/controller/demo/DemoControllerTest.java +++ /dev/null @@ -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")); - } -} diff --git a/src/test/java/ru/soune/nocopy/service/demo/DemoCleanupServiceTest.java b/src/test/java/ru/soune/nocopy/service/demo/DemoCleanupServiceTest.java deleted file mode 100644 index 43037eb..0000000 --- a/src/test/java/ru/soune/nocopy/service/demo/DemoCleanupServiceTest.java +++ /dev/null @@ -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 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 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 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 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 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 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 createExpiredSessions(int count) { - List 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 createFailedSessions(int count) { - List 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(); - } -} diff --git a/src/test/java/ru/soune/nocopy/service/demo/DemoSessionServiceTest.java b/src/test/java/ru/soune/nocopy/service/demo/DemoSessionServiceTest.java deleted file mode 100644 index 281beca..0000000 --- a/src/test/java/ru/soune/nocopy/service/demo/DemoSessionServiceTest.java +++ /dev/null @@ -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(); - } -}