package ai.decompile.workspace.service; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import ai.decompile.common.exception.NotFoundException; import ai.decompile.workspace.event.BinaryUploadedEvent; import ai.decompile.workspace.model.dto.BinaryResponse; import ai.decompile.workspace.model.entity.Binary; import ai.decompile.workspace.model.entity.Project; import ai.decompile.workspace.model.entity.Workspace; import ai.decompile.workspace.model.repository.BinaryRepository; import ai.decompile.workspace.service.storage.FileStorage; import java.io.IOException; import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; 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.context.ApplicationEventPublisher; import org.springframework.data.jpa.domain.Specification; import org.springframework.mock.web.MockMultipartFile; @ExtendWith(MockitoExtension.class) class BinaryServiceTest { @Mock private ProjectService projectService; @Mock private BinaryRepository binaryRepository; @Mock private FileStorage fileStorage; @Mock private ApplicationEventPublisher eventPublisher; @InjectMocks private BinaryService binaryService; private UUID projectId; private UUID binaryId; private Project project; private Binary binary; private byte[] fileContent; @BeforeEach void setUp() { projectId = UUID.randomUUID(); binaryId = UUID.randomUUID(); var workspace = Workspace.builder() .id(UUID.randomUUID()) .name("WS") .createdAt(Instant.now()) .updatedAt(Instant.now()) .build(); project = Project.builder() .id(projectId) .name("Test Project") .workspace(workspace) .createdAt(Instant.now()) .updatedAt(Instant.now()) .build(); binary = Binary.builder() .id(binaryId) .filename("test.bin") .fileSize(100L) .filePath("abc_test.bin") .project(project) .createdAt(Instant.now()) .updatedAt(Instant.now()) .build(); fileContent = new byte[] {1, 2, 3, 4}; project.setBinaries(List.of(binary)); } @Test void getBinariesShouldReturnEmptyList() { when(projectService.getProject(projectId)).thenReturn(project); when(binaryRepository.findAll(any(Specification.class))).thenReturn(List.of()); List result = binaryService.getBinaries(projectId, null); assertTrue(result.isEmpty()); verify(projectService).getProject(projectId); verify(binaryRepository).findAll(any(Specification.class)); } @Test void getBinariesShouldReturnList() { when(projectService.getProject(projectId)).thenReturn(project); when(binaryRepository.findAll(any(Specification.class))).thenReturn(List.of(binary)); List result = binaryService.getBinaries(projectId, null); assertEquals(1, result.size()); assertEquals(binaryId, result.getFirst().id()); assertEquals("test.bin", result.getFirst().filename()); assertEquals(projectId, result.getFirst().projectId()); assertEquals(100L, result.getFirst().fileSize()); } @Test void getBinariesShouldFilterByFilename() { when(projectService.getProject(projectId)).thenReturn(project); when(binaryRepository.findAll(any(Specification.class))).thenReturn(List.of(binary)); List result = binaryService.getBinaries(projectId, "test"); assertEquals(1, result.size()); assertEquals("test.bin", result.getFirst().filename()); verify(binaryRepository).findAll(any(Specification.class)); } @Test void getBinariesShouldThrowNotFoundWhenProjectDoesNotExist() { when(projectService.getProject(projectId)) .thenThrow(new NotFoundException("Project with id '" + projectId + "' not found")); assertThrows(NotFoundException.class, () -> binaryService.getBinaries(projectId, null)); verify(binaryRepository, never()).findAll(any(Specification.class)); } @Test void uploadBinaryShouldSucceed() throws IOException { var file = new MockMultipartFile("file", "test.exe", "application/octet-stream", fileContent); when(projectService.getProject(projectId)).thenReturn(project); when(fileStorage.store(any(), eq("test.exe"))).thenReturn("stored_test.exe"); when(binaryRepository.save(any(Binary.class))).thenReturn(binary); BinaryResponse result = binaryService.uploadBinary(projectId, file, null); assertEquals(binaryId, result.id()); assertEquals("test.bin", result.filename()); verify(projectService).getProject(projectId); verify(fileStorage).store(any(), eq("test.exe")); verify(binaryRepository).save(any(Binary.class)); verify(eventPublisher).publishEvent(any(BinaryUploadedEvent.class)); } @Test void uploadBinaryShouldThrowRuntimeExceptionOnStorageFailure() throws IOException { var file = new MockMultipartFile("file", "test.exe", "application/octet-stream", fileContent); when(projectService.getProject(projectId)).thenReturn(project); when(fileStorage.store(any(), eq("test.exe"))).thenThrow(new IOException("disk full")); assertThrows(RuntimeException.class, () -> binaryService.uploadBinary(projectId, file, null)); verify(binaryRepository, never()).save(any()); verify(eventPublisher, never()).publishEvent(any()); } @Test void deleteBinaryShouldSucceed() throws IOException { when(binaryRepository.findById(binaryId)).thenReturn(Optional.of(binary)); binaryService.deleteBinary(binaryId); verify(fileStorage).delete(binary.getFilePath()); verify(binaryRepository).delete(binary); } @Test void deleteBinaryShouldThrowNotFound() throws IOException { when(binaryRepository.findById(binaryId)).thenReturn(Optional.empty()); assertThrows(NotFoundException.class, () -> binaryService.deleteBinary(binaryId)); verify(fileStorage, never()).delete(any()); verify(binaryRepository, never()).delete((Binary) any()); } }