fix(bookdrop): fix 'Select All' query logic and improve filename fallback for missing metadata (#1828)

* fix(bookdrop): fix 'Select All' query logic and improve filename fallback for missing metadata

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>

* fix(tests): update BookDropServiceTest and PathPatternResolverTest to use findAllIds method

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>

---------

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
Balázs Szücs
2025-12-12 16:32:57 +01:00
committed by GitHub
parent 1a7d92c60d
commit 62ea319536
7 changed files with 108 additions and 9 deletions

View File

@@ -31,5 +31,8 @@ public interface BookdropFileRepository extends JpaRepository<BookdropFileEntity
@Query("SELECT f.id FROM BookdropFileEntity f WHERE f.id NOT IN :excludedIds")
List<Long> findAllExcludingIdsFlat(@Param("excludedIds") List<Long> excludedIds);
@Query("SELECT f.id FROM BookdropFileEntity f")
List<Long> findAllIds();
}

View File

@@ -186,7 +186,12 @@ public class BookDropService {
AtomicInteger failedCount,
AtomicInteger totalFilesProcessed) {
List<Long> excludedIds = Optional.ofNullable(request.getExcludedIds()).orElse(List.of());
List<Long> allIds = bookdropFileRepository.findAllExcludingIdsFlat(excludedIds);
List<Long> allIds;
if (excludedIds.isEmpty()) {
allIds = bookdropFileRepository.findAllIds();
} else {
allIds = bookdropFileRepository.findAllExcludingIdsFlat(excludedIds);
}
log.info("SelectAll: Total files to finalize (after exclusions): {}, Excluded IDs: {}", allIds.size(), excludedIds);
processFileChunks(allIds, metadataById, defaultLibraryId, defaultPathId, results, failedCount, totalFilesProcessed);

View File

@@ -55,9 +55,19 @@ public class PathPatternResolver {
return filename;
}
String filenameBase = "Untitled";
if (filename != null && !filename.isBlank()) {
int lastDot = filename.lastIndexOf('.');
if (lastDot > 0) {
filenameBase = filename.substring(0, lastDot);
} else {
filenameBase = filename;
}
}
String title = sanitize(metadata != null && metadata.getTitle() != null
? metadata.getTitle()
: "Untitled");
: filenameBase);
String subtitle = sanitize(metadata != null ? metadata.getSubtitle() : "");

View File

@@ -173,7 +173,7 @@ class PathPatternResolverTest {
@Test void allPlaceholdersMissing_yieldsJustExtension() {
var book = createBook(null, null, null, null, null, null, null, null, null, "file.cbz");
String pattern = "{title}-{authors}-{series}-{year}-{language}-{publisher}-{isbn}";
assertThat(PathPatternResolver.resolvePattern(book, pattern)).isEqualTo("Untitled------.cbz");
assertThat(PathPatternResolver.resolvePattern(book, pattern)).isEqualTo("file------.cbz");
}
@Test void patternWithBackslashes_isSanitized() {

View File

@@ -0,0 +1,81 @@
package com.adityachandel.booklore.service.bookdrop;
import com.adityachandel.booklore.config.AppProperties;
import com.adityachandel.booklore.model.dto.request.BookdropFinalizeRequest;
import com.adityachandel.booklore.model.dto.response.BookdropFinalizeResult;
import com.adityachandel.booklore.repository.BookdropFileRepository;
import com.adityachandel.booklore.repository.LibraryRepository;
import com.adityachandel.booklore.service.NotificationService;
import com.adityachandel.booklore.service.file.FileMovingHelper;
import com.adityachandel.booklore.service.monitoring.MonitoringRegistrationService;
import com.fasterxml.jackson.databind.ObjectMapper;
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 java.util.Collections;
import java.util.List;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class BookDropServiceFinalizeTest {
@Mock
private BookdropFileRepository bookdropFileRepository;
@Mock
private BookdropMonitoringService bookdropMonitoringService;
@Mock
private LibraryRepository libraryRepository;
@Mock
private MonitoringRegistrationService monitoringRegistrationService;
@Mock
private NotificationService notificationService;
@Mock
private ObjectMapper objectMapper;
@Mock
private FileMovingHelper fileMovingHelper;
@Mock
private AppProperties appProperties;
@Mock
private BookdropNotificationService bookdropNotificationService;
@InjectMocks
private BookDropService bookDropService;
@Test
void finalizeImport_selectAll_emptyExcludedIds_shouldCallFindAllIds() {
BookdropFinalizeRequest request = new BookdropFinalizeRequest();
request.setSelectAll(true);
request.setExcludedIds(Collections.emptyList());
request.setDefaultLibraryId(1L);
request.setDefaultPathId(1L);
when(bookdropFileRepository.findAllIds()).thenReturn(List.of(1L, 2L));
when(bookdropFileRepository.findAllById(anyList())).thenReturn(Collections.emptyList()); // Mock chunk processing
bookDropService.finalizeImport(request);
verify(bookdropFileRepository).findAllIds();
verify(bookdropFileRepository, never()).findAllExcludingIdsFlat(anyList());
}
@Test
void finalizeImport_selectAll_withExcludedIds_shouldCallFindAllExcludingIdsFlat() {
BookdropFinalizeRequest request = new BookdropFinalizeRequest();
request.setSelectAll(true);
request.setExcludedIds(List.of(3L));
request.setDefaultLibraryId(1L);
request.setDefaultPathId(1L);
when(bookdropFileRepository.findAllExcludingIdsFlat(anyList())).thenReturn(List.of(1L, 2L));
when(bookdropFileRepository.findAllById(anyList())).thenReturn(Collections.emptyList()); // Mock chunk processing
bookDropService.finalizeImport(request);
verify(bookdropFileRepository).findAllExcludingIdsFlat(List.of(3L));
verify(bookdropFileRepository, never()).findAllIds();
}
}

View File

@@ -286,7 +286,7 @@ class BookDropServiceTest {
request.setDefaultLibraryId(999L);
request.setDefaultPathId(1L);
when(bookdropFileRepository.findAllExcludingIdsFlat(any())).thenReturn(List.of(1L));
when(bookdropFileRepository.findAllIds()).thenReturn(List.of(1L));
when(bookdropFileRepository.findAllById(any())).thenReturn(List.of(bookdropFileEntity));
when(libraryRepository.findById(999L)).thenReturn(Optional.empty());
@@ -387,7 +387,7 @@ class BookDropServiceTest {
missingFileEntity.setOriginalMetadata("{\"title\":\"Missing Book\"}");
missingFileEntity.setFetchedMetadata(null);
when(bookdropFileRepository.findAllExcludingIdsFlat(any())).thenReturn(List.of(2L));
when(bookdropFileRepository.findAllIds()).thenReturn(List.of(2L));
when(bookdropFileRepository.findAllById(any())).thenReturn(List.of(missingFileEntity));
when(libraryRepository.findById(1L)).thenReturn(Optional.of(libraryEntity));
@@ -419,7 +419,7 @@ class BookDropServiceTest {
request.setDefaultLibraryId(1L);
request.setDefaultPathId(1L);
when(bookdropFileRepository.findAllExcludingIdsFlat(any())).thenReturn(List.of(1L));
when(bookdropFileRepository.findAllIds()).thenReturn(List.of(1L));
when(bookdropFileRepository.findAllById(any())).thenReturn(List.of(bookdropFileEntity));
when(libraryRepository.findById(1L)).thenReturn(Optional.of(libraryEntity));
@@ -451,7 +451,7 @@ class BookDropServiceTest {
request.setDefaultPathId(1L);
request.setExcludedIds(List.of());
when(bookdropFileRepository.findAllExcludingIdsFlat(any())).thenReturn(List.of(1L));
when(bookdropFileRepository.findAllIds()).thenReturn(List.of(1L));
when(bookdropFileRepository.findAllById(any())).thenReturn(List.of(bookdropFileEntity));
when(libraryRepository.findById(1L)).thenReturn(Optional.of(libraryEntity));

View File

@@ -184,7 +184,7 @@ class PathPatternResolverTest {
void testResolvePattern_nullMetadata() {
String result = PathPatternResolver.resolvePattern((BookMetadata) null, "{title}", "original.pdf");
assertEquals("Untitled.pdf", result);
assertEquals("original.pdf", result);
}
@Test
@@ -195,7 +195,7 @@ class PathPatternResolverTest {
String result = PathPatternResolver.resolvePattern(metadata, "{title}", "original.pdf");
assertEquals("Untitled.pdf", result);
assertEquals("original.pdf", result);
}
@Test