mirror of
https://github.com/booklore-app/booklore.git
synced 2026-02-18 00:17:53 +01:00
feat(kobo-sync): Sync Shelves and Magic Shelves to Kobo Tags (#2236)
* feat(kobo-sync): sync shelves and magic shelves to kobo tags * refactor(kobo-sync): replace `EntityNotFoundException` with `NoSuchElementException`, update timestamps handling, and add unit tests for `generateTags` --------- Co-authored-by: ACX <8075870+acx10@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package org.booklore.model.dto.kobo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
|
||||
@Builder
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class KoboTag {
|
||||
private String created;
|
||||
private String lastModified;
|
||||
private String id;
|
||||
private String name;
|
||||
private String type;
|
||||
private List<KoboTagItem> items;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
|
||||
@Builder
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static class KoboTagItem {
|
||||
private String revisionId;
|
||||
private String type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.booklore.model.dto.kobo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class KoboTagWrapper implements Entitlement {
|
||||
|
||||
private WrappedTag changedTag;
|
||||
private WrappedTag deletedTag;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
|
||||
public static class WrappedTag {
|
||||
private KoboTag tag;
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,21 @@ package org.booklore.service.kobo;
|
||||
|
||||
import org.booklore.config.security.service.AuthenticationService;
|
||||
import org.booklore.mapper.KoboReadingStateMapper;
|
||||
import org.booklore.model.dto.Book;
|
||||
import org.booklore.model.dto.kobo.*;
|
||||
import org.booklore.model.dto.settings.KoboSettings;
|
||||
import org.booklore.model.entity.*;
|
||||
import org.booklore.model.enums.BookFileType;
|
||||
import org.booklore.model.enums.KoboBookFormat;
|
||||
import org.booklore.model.enums.KoboReadStatus;
|
||||
import org.booklore.model.enums.ShelfType;
|
||||
import org.booklore.repository.KoboReadingStateRepository;
|
||||
import org.booklore.repository.MagicShelfRepository;
|
||||
import org.booklore.repository.ShelfRepository;
|
||||
import org.booklore.repository.UserBookProgressRepository;
|
||||
import org.booklore.service.book.BookQueryService;
|
||||
import org.booklore.service.appsettings.AppSettingService;
|
||||
import org.booklore.service.opds.MagicShelfBookService;
|
||||
import org.booklore.util.kobo.KoboUrlBuilder;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -20,10 +25,7 @@ import org.springframework.stereotype.Service;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -42,6 +44,9 @@ public class KoboEntitlementService {
|
||||
private final KoboReadingStateMapper readingStateMapper;
|
||||
private final AuthenticationService authenticationService;
|
||||
private final KoboReadingStateBuilder readingStateBuilder;
|
||||
private final ShelfRepository shelfRepository;
|
||||
private final MagicShelfRepository magicShelfRepository;
|
||||
private final MagicShelfBookService magicShelfBookService;
|
||||
|
||||
public List<NewEntitlement> generateNewEntitlements(Set<Long> bookIds, String token) {
|
||||
List<BookEntity> books = bookQueryService.findAllWithMetadataByIds(bookIds);
|
||||
@@ -105,6 +110,71 @@ public class KoboEntitlementService {
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<KoboTagWrapper> generateTags() {
|
||||
Long userId = authenticationService.getAuthenticatedUser().getId();
|
||||
List<Long> koboBookIDs = shelfRepository.findByUserIdAndName(userId, ShelfType.KOBO.getName())
|
||||
.orElseThrow(() -> new NoSuchElementException("Kobo shelf not found for user: " + userId))
|
||||
.getBookEntities().stream().filter(koboCompatibilityService::isBookSupportedForKobo)
|
||||
.map(BookEntity::getId)
|
||||
.toList();
|
||||
|
||||
List<KoboTagWrapper> tags = new ArrayList<>();
|
||||
// Shelves
|
||||
shelfRepository.findByUserId(userId).stream()
|
||||
.filter(shelf -> !Objects.equals(shelf.getName(), ShelfType.KOBO.getName()))
|
||||
.map(shelf -> buildKoboTag("BL-S-" + shelf.getId(), shelf.getName(), null, null,
|
||||
shelf.getBookEntities().stream().map(BookEntity::getId).toList(), koboBookIDs))
|
||||
.forEach(tags::add);
|
||||
|
||||
// Magic Shelves
|
||||
magicShelfRepository.findAllByUserId(userId).stream()
|
||||
.map(magicShelf -> {
|
||||
List<Long> bookIds = magicShelfBookService.getBooksByMagicShelfId(userId, magicShelf.getId(), 0, Integer.MAX_VALUE)
|
||||
.stream().map(Book::getId).toList();
|
||||
return buildKoboTag("BL-MS-" + magicShelf.getId(), magicShelf.getName(),
|
||||
magicShelf.getCreatedAt().atOffset(ZoneOffset.UTC).toString(), magicShelf.getUpdatedAt().atOffset(ZoneOffset.UTC).toString(),
|
||||
bookIds, koboBookIDs);
|
||||
})
|
||||
.forEach(tags::add);
|
||||
|
||||
log.info("Synced {} tags to Kobo", tags.size());
|
||||
return tags;
|
||||
}
|
||||
|
||||
private KoboTagWrapper buildKoboTag(String id, String name, String created, String modified, List<Long> bookIds, List<Long> koboFilterIds) {
|
||||
List<KoboTag.KoboTagItem> items = bookIds.stream()
|
||||
.filter(koboFilterIds::contains)
|
||||
.map(bookId -> KoboTag.KoboTagItem.builder()
|
||||
.revisionId(bookId.toString())
|
||||
.type("ProductRevisionTagItem")
|
||||
.build())
|
||||
.toList();
|
||||
|
||||
if (items.isEmpty()) {
|
||||
return KoboTagWrapper.builder()
|
||||
.deletedTag(KoboTagWrapper.WrappedTag.builder()
|
||||
.tag(KoboTag.builder()
|
||||
.id(id)
|
||||
.lastModified(modified)
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
return KoboTagWrapper.builder()
|
||||
.changedTag(KoboTagWrapper.WrappedTag.builder()
|
||||
.tag(KoboTag.builder()
|
||||
.id(id)
|
||||
.name(name)
|
||||
.created(created)
|
||||
.lastModified(modified)
|
||||
.type("UserTag")
|
||||
.items(items)
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ChangedReadingState buildChangedReadingState(UserBookProgressEntity progress, String timestamp, OffsetDateTime now) {
|
||||
String entitlementId = String.valueOf(progress.getBook().getId());
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ public class KoboLibrarySyncService {
|
||||
|
||||
if (!shouldContinueSync) {
|
||||
entitlements.addAll(syncReadingStatesToKobo(user.getId(), currSnapshot.getId()));
|
||||
entitlements.addAll(entitlementService.generateTags());
|
||||
}
|
||||
} else {
|
||||
int maxRemaining = 5;
|
||||
@@ -104,6 +105,7 @@ public class KoboLibrarySyncService {
|
||||
|
||||
if (!shouldContinueSync) {
|
||||
entitlements.addAll(syncReadingStatesToKobo(user.getId(), currSnapshot.getId()));
|
||||
entitlements.addAll(entitlementService.generateTags());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,46 @@
|
||||
package org.booklore.service;
|
||||
|
||||
import org.booklore.config.security.service.AuthenticationService;
|
||||
import org.booklore.model.dto.Book;
|
||||
import org.booklore.model.dto.BookLoreUser;
|
||||
import org.booklore.model.dto.kobo.KoboBookMetadata;
|
||||
import org.booklore.model.dto.kobo.KoboTag;
|
||||
import org.booklore.model.dto.kobo.KoboTagWrapper;
|
||||
import org.booklore.model.dto.settings.KoboSettings;
|
||||
import org.booklore.model.entity.BookFileEntity;
|
||||
import org.booklore.model.entity.BookEntity;
|
||||
import org.booklore.model.entity.BookMetadataEntity;
|
||||
import org.booklore.model.entity.MagicShelfEntity;
|
||||
import org.booklore.model.entity.ShelfEntity;
|
||||
import org.booklore.model.enums.BookFileType;
|
||||
import org.booklore.model.enums.KoboBookFormat;
|
||||
import org.booklore.model.enums.ShelfType;
|
||||
import org.booklore.repository.MagicShelfRepository;
|
||||
import org.booklore.repository.ShelfRepository;
|
||||
import org.booklore.service.appsettings.AppSettingService;
|
||||
import org.booklore.service.book.BookQueryService;
|
||||
import org.booklore.service.kobo.KoboCompatibilityService;
|
||||
import org.booklore.service.kobo.KoboEntitlementService;
|
||||
import org.booklore.service.opds.MagicShelfBookService;
|
||||
import org.booklore.util.kobo.KoboUrlBuilder;
|
||||
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.data.domain.PageImpl;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -39,9 +58,29 @@ class KoboEntitlementServiceTest {
|
||||
@Mock
|
||||
private KoboCompatibilityService koboCompatibilityService;
|
||||
|
||||
@Mock
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@Mock
|
||||
private ShelfRepository shelfRepository;
|
||||
|
||||
@Mock
|
||||
private MagicShelfRepository magicShelfRepository;
|
||||
|
||||
@Mock
|
||||
private MagicShelfBookService magicShelfBookService;
|
||||
|
||||
@InjectMocks
|
||||
private KoboEntitlementService koboEntitlementService;
|
||||
|
||||
private BookLoreUser user;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
BookLoreUser.UserPermissions permissions = new BookLoreUser.UserPermissions();
|
||||
user = BookLoreUser.builder().permissions(permissions).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMetadataForBook_shouldUseCompatibilityServiceFilter() {
|
||||
long bookId = 1L;
|
||||
@@ -116,4 +155,245 @@ class KoboEntitlementServiceTest {
|
||||
appSettings.setKoboSettings(koboSettings);
|
||||
return appSettings;
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldReturnTagsForShelvesAndMagicShelves() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
BookEntity book2 = createEpubBookEntity(2L);
|
||||
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1, book2)).build();
|
||||
ShelfEntity userShelf = ShelfEntity.builder().id(101L).name("My Favorites").bookEntities(Set.of(book1)).build();
|
||||
|
||||
MagicShelfEntity magicShelf = MagicShelfEntity.builder()
|
||||
.id(201L)
|
||||
.userId(user.getId())
|
||||
.name("Sci-Fi Books")
|
||||
.icon("pi-book")
|
||||
.filterJson("{}")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf, userShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of(magicShelf));
|
||||
when(magicShelfBookService.getBooksByMagicShelfId(eq(user.getId()), eq(201L), eq(0), eq(Integer.MAX_VALUE)))
|
||||
.thenReturn(new PageImpl<>(List.of(Book.builder().id(2L).build())));
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertEquals(2, tags.size());
|
||||
|
||||
// Verify shelf tag
|
||||
KoboTagWrapper shelfTag = tags.stream()
|
||||
.filter(t -> t.getChangedTag() != null && t.getChangedTag().getTag().getId().equals("BL-S-101"))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals("My Favorites", shelfTag.getChangedTag().getTag().getName());
|
||||
assertEquals("UserTag", shelfTag.getChangedTag().getTag().getType());
|
||||
assertEquals(1, shelfTag.getChangedTag().getTag().getItems().size());
|
||||
assertEquals("1", shelfTag.getChangedTag().getTag().getItems().getFirst().getRevisionId());
|
||||
|
||||
// Verify magic shelf tag
|
||||
KoboTagWrapper magicShelfTag = tags.stream()
|
||||
.filter(t -> t.getChangedTag() != null && t.getChangedTag().getTag().getId().equals("BL-MS-201"))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertEquals("Sci-Fi Books", magicShelfTag.getChangedTag().getTag().getName());
|
||||
assertEquals(1, magicShelfTag.getChangedTag().getTag().getItems().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldExcludeKoboShelfFromTags() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1)).build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of());
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertTrue(tags.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldReturnDeletedTagWhenNoMatchingBooks() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
BookEntity book2 = createEpubBookEntity(2L);
|
||||
|
||||
// Kobo shelf only has book1
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1)).build();
|
||||
// User shelf only has book2, which is not in Kobo shelf
|
||||
ShelfEntity userShelf = ShelfEntity.builder().id(101L).name("My Favorites").bookEntities(Set.of(book2)).build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf, userShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of());
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertEquals(1, tags.size());
|
||||
|
||||
// Should be a deleted tag since book2 is not in Kobo shelf
|
||||
KoboTagWrapper deletedTag = tags.getFirst();
|
||||
assertNotNull(deletedTag.getDeletedTag());
|
||||
assertNull(deletedTag.getChangedTag());
|
||||
assertEquals("BL-S-101", deletedTag.getDeletedTag().getTag().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldFilterBooksNotSupportedForKobo() {
|
||||
BookEntity supportedBook = createEpubBookEntity(1L);
|
||||
BookEntity unsupportedBook = createEpubBookEntity(2L);
|
||||
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(supportedBook, unsupportedBook)).build();
|
||||
ShelfEntity userShelf = ShelfEntity.builder().id(101L).name("My Favorites").bookEntities(Set.of(supportedBook, unsupportedBook)).build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(supportedBook)).thenReturn(true);
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(unsupportedBook)).thenReturn(false);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf, userShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of());
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertEquals(1, tags.size());
|
||||
|
||||
KoboTagWrapper shelfTag = tags.getFirst();
|
||||
assertNotNull(shelfTag.getChangedTag());
|
||||
// Only supported book should be included
|
||||
assertEquals(1, shelfTag.getChangedTag().getTag().getItems().size());
|
||||
assertEquals("1", shelfTag.getChangedTag().getTag().getItems().getFirst().getRevisionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldSetCorrectTagItemType() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1)).build();
|
||||
ShelfEntity userShelf = ShelfEntity.builder().id(101L).name("Reading").bookEntities(Set.of(book1)).build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf, userShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of());
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertEquals(1, tags.size());
|
||||
|
||||
KoboTag.KoboTagItem item = tags.getFirst().getChangedTag().getTag().getItems().getFirst();
|
||||
assertEquals("ProductRevisionTagItem", item.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldUseMagicShelfTimestamps() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1)).build();
|
||||
|
||||
LocalDateTime createdAt = LocalDateTime.of(2024, 1, 15, 10, 30, 0);
|
||||
LocalDateTime updatedAt = LocalDateTime.of(2024, 6, 20, 14, 45, 0);
|
||||
MagicShelfEntity magicShelf = MagicShelfEntity.builder()
|
||||
.id(201L)
|
||||
.userId(user.getId())
|
||||
.name("Fantasy")
|
||||
.icon("pi-book")
|
||||
.filterJson("{}")
|
||||
.createdAt(createdAt)
|
||||
.updatedAt(updatedAt)
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of(magicShelf));
|
||||
when(magicShelfBookService.getBooksByMagicShelfId(eq(user.getId()), eq(201L), eq(0), eq(Integer.MAX_VALUE)))
|
||||
.thenReturn(new PageImpl<>(List.of(Book.builder().id(1L).build())));
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertEquals(1, tags.size());
|
||||
|
||||
KoboTag tag = tags.getFirst().getChangedTag().getTag();
|
||||
assertEquals(createdAt.atOffset(ZoneOffset.UTC).toString(), tag.getCreated());
|
||||
assertEquals(updatedAt.atOffset(ZoneOffset.UTC).toString(), tag.getLastModified());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldHandleEmptyShelvesAndMagicShelves() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1)).build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of());
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertTrue(tags.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateTags_shouldIncludeMultipleBooksInSingleTag() {
|
||||
BookEntity book1 = createEpubBookEntity(1L);
|
||||
BookEntity book2 = createEpubBookEntity(2L);
|
||||
BookEntity book3 = createEpubBookEntity(3L);
|
||||
|
||||
ShelfEntity koboShelf = ShelfEntity.builder().id(100L).name(ShelfType.KOBO.getName()).bookEntities(Set.of(book1, book2, book3)).build();
|
||||
ShelfEntity userShelf = ShelfEntity.builder().id(101L).name("Collection").bookEntities(Set.of(book1, book2, book3)).build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.findByUserIdAndName(user.getId(), ShelfType.KOBO.getName()))
|
||||
.thenReturn(Optional.of(koboShelf));
|
||||
when(koboCompatibilityService.isBookSupportedForKobo(any(BookEntity.class))).thenReturn(true);
|
||||
when(shelfRepository.findByUserId(user.getId())).thenReturn(List.of(koboShelf, userShelf));
|
||||
when(magicShelfRepository.findAllByUserId(user.getId())).thenReturn(List.of());
|
||||
|
||||
List<KoboTagWrapper> tags = koboEntitlementService.generateTags();
|
||||
|
||||
assertEquals(1, tags.size());
|
||||
|
||||
KoboTag tag = tags.getFirst().getChangedTag().getTag();
|
||||
assertEquals(3, tag.getItems().size());
|
||||
|
||||
Set<String> revisionIds = new HashSet<>();
|
||||
tag.getItems().forEach(item -> revisionIds.add(item.getRevisionId()));
|
||||
assertTrue(revisionIds.contains("1"));
|
||||
assertTrue(revisionIds.contains("2"));
|
||||
assertTrue(revisionIds.contains("3"));
|
||||
}
|
||||
|
||||
private BookEntity createEpubBookEntity(Long id) {
|
||||
BookEntity book = new BookEntity();
|
||||
book.setId(id);
|
||||
book.setBookType(BookFileType.EPUB);
|
||||
book.setFileSizeKb(1024L);
|
||||
|
||||
BookMetadataEntity metadata = new BookMetadataEntity();
|
||||
metadata.setTitle("Test EPUB Book " + id);
|
||||
metadata.setDescription("A test EPUB book");
|
||||
metadata.setBookId(id);
|
||||
book.setMetadata(metadata);
|
||||
|
||||
return book;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user