second tests

This commit is contained in:
RM
2026-10-05 14:58:33 +03:00
parent db7bcd1d54
commit 9813f2489e

View File

@@ -1,30 +1,67 @@
package com.pledge.torgi.controller;
import com.pledge.torgi.model.MissedData;
import com.pledge.torgi.model.TorgiEvent;
import com.pledge.torgi.repositories.MissedDataRepository;
import com.pledge.torgi.repositories.TorgiEventRepository;
import com.pledge.torgi.service.DownloadHeadlessService;
import com.pledge.torgi.service.Lot;
import com.pledge.torgi.service.SendEmailService;
import com.pledge.torgi.utils.StartParams;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import static org.mockito.ArgumentMatchers.any;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class DownloadHeadlessControllerTest {
private static final String CADASTRAL = "77:01:0001001:1234";
private static final String EVENT_KEY = "Notice1_1_PUBLISHED_" + CADASTRAL;
@Mock
private TorgiEventRepository eventRepository;
@Mock
private MissedDataRepository missedDataRepository;
@BeforeEach
void setUp() {
lenient().when(eventRepository.findAllByOrderByCreatedAtDesc(any(PageRequest.class)))
.thenReturn(new PageImpl<>(List.of()));
}
@Test
void constructor_shouldLoadExistingKeys() {
// given
@@ -41,4 +78,408 @@ class DownloadHeadlessControllerTest {
// then
verify(eventRepository).findAllByOrderByCreatedAtDesc(any(PageRequest.class));
}
@Test
void constructor_shouldLoadAllKeysIntoContentSet() {
// given
TorgiEvent event1 = new TorgiEvent();
event1.setKey("key-1");
TorgiEvent event2 = new TorgiEvent();
event2.setKey("key-2");
when(eventRepository.findAllByOrderByCreatedAtDesc(any(PageRequest.class)))
.thenReturn(new PageImpl<>(List.of(event1, event2)));
// when
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// then
assertThat(getContent(controller)).containsExactlyInAnyOrder("key-1", "key-2");
}
@Test
void process_shouldSaveNewEvents() {
// given
Lot lot = new Lot();
lot.setStatus("PUBLISHED");
lot.setNotice("Notice1");
lot.setLotNumber(1);
lot.setCadastral(CADASTRAL);
lot.setFullInfo("Full info");
Map<String, Lot> downloaded = Map.of(EVENT_KEY, lot);
try (MockedConstruction<DownloadHeadlessService> ignored = mockConstruction(DownloadHeadlessService.class,
(mock, context) -> when(mock.downloadExcel(anyInt(), anyString())).thenReturn(downloaded))) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.process();
// then
ArgumentCaptor<List<TorgiEvent>> captor = ArgumentCaptor.forClass(List.class);
verify(eventRepository).saveAll(captor.capture());
List<TorgiEvent> saved = captor.getValue();
assertThat(saved).hasSize(1);
assertThat(saved.get(0).getKey()).isEqualTo(EVENT_KEY);
assertThat(saved.get(0).getStatus()).isEqualTo("PUBLISHED");
assertThat(saved.get(0).getNotice()).isEqualTo("Notice1");
assertThat(saved.get(0).getLotNumber()).isEqualTo(1);
assertThat(saved.get(0).getCadastral()).isEqualTo(CADASTRAL);
assertThat(saved.get(0).getFullInfo()).isEqualTo("Full info");
assertThat(getContent(controller)).contains(EVENT_KEY);
}
}
@Test
void process_shouldSkipAlreadyKnownKeys() {
// given
TorgiEvent existing = new TorgiEvent();
existing.setKey(EVENT_KEY);
when(eventRepository.findAllByOrderByCreatedAtDesc(any(PageRequest.class)))
.thenReturn(new PageImpl<>(List.of(existing)));
Map<String, Lot> downloaded = Map.of(EVENT_KEY, new Lot());
try (MockedConstruction<DownloadHeadlessService> ignored = mockConstruction(DownloadHeadlessService.class,
(mock, context) -> when(mock.downloadExcel(anyInt(), anyString())).thenReturn(downloaded))) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.process();
// then
ArgumentCaptor<List<TorgiEvent>> captor = ArgumentCaptor.forClass(List.class);
verify(eventRepository).saveAll(captor.capture());
assertThat(captor.getValue()).isEmpty();
assertThat(getContent(controller)).containsExactly(EVENT_KEY);
}
}
@Test
void sendNewContent_shouldCreateDocumentSendArchiveAndCleanUp() throws Exception {
// given
StartParams params = new StartParams();
params.setFrom("from@example.com");
params.setToken("token");
List<String> cadastral = List.of(CADASTRAL);
try (MockedConstruction<DownloadHeadlessService> serviceConstruction = mockConstruction(DownloadHeadlessService.class);
MockedConstruction<SendEmailService> emailConstruction = mockConstruction(SendEmailService.class)) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.sendNewContent(params, cadastral, "to@example.com");
// then
DownloadHeadlessService service = serviceConstruction.constructed().get(0);
ArgumentCaptor<LocalDate> dateCaptor = ArgumentCaptor.forClass(LocalDate.class);
verify(service).createNewDocument(eq(eventRepository), eq(cadastral), dateCaptor.capture());
LocalDate now = dateCaptor.getValue();
SendEmailService emailService = emailConstruction.constructed().get(0);
verify(emailService).sendMail(eq("from@example.com"), eq("to@example.com"),
eq("Новая Выгрузка по торгам от " + now),
eq("Новая Выгрузка по торгам от " + now),
argThat((File file) -> file != null && file.getName().equals(now + ".zip")));
assertThat(new File(now + ".zip")).doesNotExist();
assertThat(new File(now.toString())).doesNotExist();
}
}
@Test
void sendArchive_shouldSwallowEmailException() throws Exception {
// given
StartParams params = new StartParams();
params.setFrom("from@example.com");
params.setToken("token");
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
try (MockedConstruction<SendEmailService> ignored = mockConstruction(SendEmailService.class,
(mock, context) -> doThrow(new RuntimeException("smtp failure"))
.when(mock).sendMail(anyString(), anyString(), anyString(), anyString(), any(File.class)))) {
// when / then
assertThatCode(() -> invokeSendArchive(controller, params, LocalDate.now(), "to@example.com"))
.doesNotThrowAnyException();
SendEmailService emailService = ignored.constructed().get(0);
verify(emailService).sendMail(anyString(), anyString(), anyString(), anyString(), any(File.class));
}
}
@Test
void richData_shouldSaveNewEventsForMissedCadastral() {
// given
MissedData missed = new MissedData();
missed.setCadastral(CADASTRAL);
when(missedDataRepository.findAll(any(PageRequest.class))).thenReturn(new PageImpl<>(List.of(missed)));
when(eventRepository.findAllByFullInfoLikeOrderByCreatedAtDesc("%" + CADASTRAL + "%")).thenReturn(Collections.emptyList());
Lot lot = new Lot();
lot.setStatus("PUBLISHED");
lot.setNotice("Notice1");
lot.setLotNumber(1);
lot.setCadastral(CADASTRAL);
Map<String, Lot> downloaded = Map.of(EVENT_KEY, lot);
try (MockedConstruction<DownloadHeadlessService> ignored = mockConstruction(DownloadHeadlessService.class,
(mock, context) -> when(mock.downloadExcel(anyInt(), anyString())).thenReturn(downloaded))) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.richData();
// then
ArgumentCaptor<List<TorgiEvent>> captor = ArgumentCaptor.forClass(List.class);
verify(eventRepository).saveAll(captor.capture());
assertThat(captor.getValue()).hasSize(1);
assertThat(captor.getValue().get(0).getKey()).isEqualTo(EVENT_KEY);
verify(missedDataRepository).delete(missed);
}
}
@Test
void richData_shouldSkipKnownEventsForCadastral() {
// given
MissedData missed = new MissedData();
missed.setCadastral(CADASTRAL);
when(missedDataRepository.findAll(any(PageRequest.class))).thenReturn(new PageImpl<>(List.of(missed)));
TorgiEvent existing = new TorgiEvent();
existing.setKey(EVENT_KEY);
when(eventRepository.findAllByFullInfoLikeOrderByCreatedAtDesc("%" + CADASTRAL + "%")).thenReturn(List.of(existing));
Map<String, Lot> downloaded = Map.of(EVENT_KEY, new Lot());
try (MockedConstruction<DownloadHeadlessService> ignored = mockConstruction(DownloadHeadlessService.class,
(mock, context) -> when(mock.downloadExcel(anyInt(), anyString())).thenReturn(downloaded))) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.richData();
// then
ArgumentCaptor<List<TorgiEvent>> captor = ArgumentCaptor.forClass(List.class);
verify(eventRepository).saveAll(captor.capture());
assertThat(captor.getValue()).isEmpty();
verify(missedDataRepository).delete(missed);
}
}
@Test
void richData_shouldDeleteMissedDataWhenDownloadedContentEmpty() {
// given
MissedData missed = new MissedData();
missed.setCadastral(CADASTRAL);
when(missedDataRepository.findAll(any(PageRequest.class))).thenReturn(new PageImpl<>(List.of(missed)));
try (MockedConstruction<DownloadHeadlessService> ignored = mockConstruction(DownloadHeadlessService.class,
(mock, context) -> when(mock.downloadExcel(anyInt(), anyString())).thenReturn(Collections.emptyMap()))) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.richData();
// then
verify(eventRepository, never()).saveAll(anyList());
verify(missedDataRepository).delete(missed);
}
}
@Test
void richData_shouldAbortProcessingOnRepositoryError() {
// given
MissedData missed = new MissedData();
missed.setCadastral(CADASTRAL);
when(missedDataRepository.findAll(any(PageRequest.class))).thenReturn(new PageImpl<>(List.of(missed)));
when(eventRepository.findAllByFullInfoLikeOrderByCreatedAtDesc(anyString()))
.thenThrow(new RuntimeException("Database error"));
Map<String, Lot> downloaded = Map.of(EVENT_KEY, new Lot());
try (MockedConstruction<DownloadHeadlessService> ignored = mockConstruction(DownloadHeadlessService.class,
(mock, context) -> when(mock.downloadExcel(anyInt(), anyString())).thenReturn(downloaded))) {
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
controller.richData();
// then
verify(eventRepository, never()).saveAll(anyList());
verify(missedDataRepository, never()).delete(any());
}
}
@Test
void richData_shouldHandleExceptionGracefully() {
// given
when(missedDataRepository.findAll(any(PageRequest.class))).thenThrow(new RuntimeException("Database error"));
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when / then
assertThatCode(controller::richData).doesNotThrowAnyException();
}
@Test
void deleteDirectory_shouldDeleteNestedStructure(@TempDir File tempDir) throws Exception {
// given
File nested = new File(tempDir, "nested");
File deeper = new File(nested, "deeper");
assertThat(deeper.mkdirs()).isTrue();
writeFile(new File(deeper, "file.txt"), "content");
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
// when
boolean result = invokeDeleteDirectory(controller, nested);
// then
assertThat(result).isTrue();
assertThat(nested).doesNotExist();
assertThat(tempDir).exists();
}
@Test
void zipFile_shouldAddFileEntry(@TempDir File tempDir) throws Exception {
// given
File file = new File(tempDir, "data.txt");
writeFile(file, "hello");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// when
try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {
invokeZipFile(file, "dir/data.txt", zipOut);
}
// then
try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
ZipEntry entry = zipIn.getNextEntry();
assertThat(entry).isNotNull();
assertThat(entry.getName()).isEqualTo("dir/data.txt");
assertThat(new String(zipIn.readAllBytes(), StandardCharsets.UTF_8)).isEqualTo("hello");
assertThat(zipIn.getNextEntry()).isNull();
}
}
@Test
void zipFile_shouldAddDirectoryEntry(@TempDir File tempDir) throws Exception {
// given
File dir = new File(tempDir, "mydir");
assertThat(dir.mkdirs()).isTrue();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// when
try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {
invokeZipFile(dir, "mydir", zipOut);
}
// then
try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
ZipEntry entry = zipIn.getNextEntry();
assertThat(entry).isNotNull();
assertThat(entry.getName()).isEqualTo("mydir/");
assertThat(entry.isDirectory()).isTrue();
assertThat(zipIn.getNextEntry()).isNull();
}
}
@Test
void createArchive_shouldCreateZipFileForDirectory() throws Exception {
// given
LocalDate now = LocalDate.of(1999, 1, 1);
File sourceDir = new File(now.toString());
File nested = new File(sourceDir, "nested");
assertThat(nested.mkdirs()).isTrue();
writeFile(new File(nested, "data.txt"), "archive-content");
DownloadHeadlessController controller = new DownloadHeadlessController(eventRepository, missedDataRepository);
try {
// when
invokeCreateArchive(controller, now);
// then
File zip = new File(now + ".zip");
assertThat(zip).exists();
assertThat(readEntryNames(zip)).containsExactlyInAnyOrder(
"1999-01-01/", "1999-01-01/nested/", "1999-01-01/nested/data.txt");
} finally {
new File(now + ".zip").delete();
deleteRecursively(sourceDir);
}
}
// Helpers
@SuppressWarnings("unchecked")
private Set<String> getContent(DownloadHeadlessController controller) {
try {
Field field = DownloadHeadlessController.class.getDeclaredField("content");
field.setAccessible(true);
return (Set<String>) field.get(controller);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void invokeCreateArchive(DownloadHeadlessController controller, LocalDate now) {
try {
Method method = DownloadHeadlessController.class.getDeclaredMethod("createArchive", LocalDate.class);
method.setAccessible(true);
method.invoke(controller, now);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean invokeDeleteDirectory(DownloadHeadlessController controller, File directory) {
try {
Method method = DownloadHeadlessController.class.getDeclaredMethod("deleteDirectory", File.class);
method.setAccessible(true);
return (boolean) method.invoke(controller, directory);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void invokeSendArchive(DownloadHeadlessController controller, StartParams params, LocalDate now, String to) {
try {
Method method = DownloadHeadlessController.class.getDeclaredMethod("sendArchive", StartParams.class, LocalDate.class, String.class);
method.setAccessible(true);
method.invoke(controller, params, now, to);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void invokeZipFile(File fileToZip, String fileName, ZipOutputStream zipOut) {
try {
Method method = DownloadHeadlessController.class.getDeclaredMethod("zipFile", File.class, String.class, ZipOutputStream.class);
method.setAccessible(true);
method.invoke(null, fileToZip, fileName, zipOut);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void writeFile(File file, String content) throws IOException {
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
}
private List<String> readEntryNames(File zipFile) throws IOException {
List<String> names = new ArrayList<>();
try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry entry;
while ((entry = zipIn.getNextEntry()) != null) {
names.add(entry.getName());
}
}
return names;
}
private void deleteRecursively(File file) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursively(child);
}
}
file.delete();
}
}