mirror of
https://github.com/booklore-app/booklore.git
synced 2026-02-18 00:17:53 +01:00
feat(icons): Made icons for libraries and shelves optional with no default icons. (#2599)
* feat(icons): Made icons for libraries and shelves optional with no default icon displayed. * Added tests for making sure the API handles nullable icons for Shelves and Libraries. * Fixed some issues identified during PR review. * Rebased on develop.
This commit is contained in:
@@ -15,7 +15,6 @@ public class MagicShelf {
|
||||
@Size(max = 255, message = "Shelf name must not exceed 255 characters")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "Icon must not be blank")
|
||||
@Size(max = 64, message = "Icon must not exceed 64 characters")
|
||||
private String icon;
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ public class CreateLibraryRequest {
|
||||
@NotBlank(message = "Library name must not be empty.")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "Library icon must not be empty.")
|
||||
private String icon;
|
||||
|
||||
@NotNull(message = "Library icon type must not be null.")
|
||||
private IconType iconType;
|
||||
|
||||
@NotEmpty(message = "Library paths must not be empty.")
|
||||
|
||||
@@ -16,10 +16,7 @@ public class ShelfCreateRequest {
|
||||
@NotBlank(message = "Shelf name must not be empty.")
|
||||
private String name;
|
||||
|
||||
@NotBlank(message = "Shelf icon must not be empty.")
|
||||
private String icon;
|
||||
|
||||
@NotNull(message = "Shelf icon type must not be null.")
|
||||
private IconType iconType;
|
||||
|
||||
private boolean publicShelf;
|
||||
|
||||
@@ -44,9 +44,8 @@ public class LibraryEntity {
|
||||
private String icon;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "icon_type", nullable = false)
|
||||
@Builder.Default
|
||||
private IconType iconType = IconType.PRIME_NG;
|
||||
@Column(name = "icon_type")
|
||||
private IconType iconType;
|
||||
|
||||
@Column(name = "file_naming_pattern")
|
||||
private String fileNamingPattern;
|
||||
@@ -65,10 +64,4 @@ public class LibraryEntity {
|
||||
@Builder.Default
|
||||
private LibraryOrganizationMode organizationMode = LibraryOrganizationMode.AUTO_DETECT;
|
||||
|
||||
@PrePersist
|
||||
public void ensureIconType() {
|
||||
if (this.iconType == null) {
|
||||
this.iconType = IconType.PRIME_NG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,11 @@ public class MagicShelfEntity {
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String icon;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "icon_type", nullable = false)
|
||||
@Builder.Default
|
||||
private IconType iconType = IconType.PRIME_NG;
|
||||
@Column(name = "icon_type")
|
||||
private IconType iconType;
|
||||
|
||||
@Column(name = "filter_json", columnDefinition = "json", nullable = false)
|
||||
private String filterJson;
|
||||
@@ -54,11 +52,4 @@ public class MagicShelfEntity {
|
||||
public void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PrePersist
|
||||
public void ensureIconType() {
|
||||
if (this.iconType == null) {
|
||||
this.iconType = IconType.PRIME_NG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,8 @@ public class ShelfEntity {
|
||||
private String icon;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "icon_type", nullable = false)
|
||||
@Builder.Default
|
||||
private IconType iconType = IconType.PRIME_NG;
|
||||
@Column(name = "icon_type")
|
||||
private IconType iconType;
|
||||
|
||||
@Column(name = "is_public", nullable = false)
|
||||
@Builder.Default
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.booklore.model.enums.IconType;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -24,6 +25,7 @@ public class UserDefaultsService {
|
||||
.user(user)
|
||||
.name("Favorites")
|
||||
.icon("heart")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.build();
|
||||
shelfRepository.save(shelf);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE library MODIFY COLUMN icon VARCHAR(64) NULL;
|
||||
ALTER TABLE library MODIFY COLUMN icon_type VARCHAR(255) NULL;
|
||||
|
||||
ALTER TABLE magic_shelf MODIFY COLUMN icon VARCHAR(64) NULL;
|
||||
ALTER TABLE magic_shelf MODIFY COLUMN icon_type VARCHAR(255) NULL;
|
||||
|
||||
ALTER TABLE shelf MODIFY COLUMN icon VARCHAR(64) NULL;
|
||||
ALTER TABLE shelf MODIFY COLUMN icon_type VARCHAR(255) NULL;
|
||||
@@ -0,0 +1,191 @@
|
||||
package org.booklore.service;
|
||||
|
||||
import org.booklore.config.security.service.AuthenticationService;
|
||||
import org.booklore.model.dto.BookLoreUser;
|
||||
import org.booklore.model.dto.MagicShelf;
|
||||
import org.booklore.model.entity.MagicShelfEntity;
|
||||
import org.booklore.model.enums.IconType;
|
||||
import org.booklore.repository.MagicShelfRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MagicShelfServiceTest {
|
||||
|
||||
@Mock
|
||||
private MagicShelfRepository magicShelfRepository;
|
||||
@Mock
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@InjectMocks
|
||||
private MagicShelfService magicShelfService;
|
||||
|
||||
private BookLoreUser user;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
BookLoreUser.UserPermissions permissions = new BookLoreUser.UserPermissions();
|
||||
permissions.setAdmin(true);
|
||||
user = BookLoreUser.builder().id(1L).isDefaultPassword(false).permissions(permissions).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShelf_withNullIcon_shouldPersistNullIconValues() {
|
||||
MagicShelf dto = new MagicShelf();
|
||||
dto.setName("Unread Books");
|
||||
dto.setIcon(null);
|
||||
dto.setIconType(null);
|
||||
dto.setFilterJson("{\"status\": \"unread\"}");
|
||||
dto.setIsPublic(false);
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(magicShelfRepository.existsByUserIdAndName(1L, "Unread Books")).thenReturn(false);
|
||||
when(magicShelfRepository.save(any(MagicShelfEntity.class))).thenAnswer(invocation -> {
|
||||
MagicShelfEntity entity = invocation.getArgument(0);
|
||||
entity.setId(1L);
|
||||
return entity;
|
||||
});
|
||||
|
||||
MagicShelf result = magicShelfService.createOrUpdateShelf(dto);
|
||||
|
||||
ArgumentCaptor<MagicShelfEntity> captor = ArgumentCaptor.forClass(MagicShelfEntity.class);
|
||||
verify(magicShelfRepository).save(captor.capture());
|
||||
|
||||
MagicShelfEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
assertEquals("Unread Books", saved.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShelf_withIcon_shouldPersistIconValues() {
|
||||
MagicShelf dto = new MagicShelf();
|
||||
dto.setName("Favorites");
|
||||
dto.setIcon("star");
|
||||
dto.setIconType(IconType.PRIME_NG);
|
||||
dto.setFilterJson("{\"rating\": 5}");
|
||||
dto.setIsPublic(false);
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(magicShelfRepository.existsByUserIdAndName(1L, "Favorites")).thenReturn(false);
|
||||
when(magicShelfRepository.save(any(MagicShelfEntity.class))).thenAnswer(invocation -> {
|
||||
MagicShelfEntity entity = invocation.getArgument(0);
|
||||
entity.setId(1L);
|
||||
return entity;
|
||||
});
|
||||
|
||||
MagicShelf result = magicShelfService.createOrUpdateShelf(dto);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("star", result.getIcon());
|
||||
assertEquals(IconType.PRIME_NG, result.getIconType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateShelf_withNullIcon_shouldClearIconValues() {
|
||||
MagicShelfEntity existing = MagicShelfEntity.builder()
|
||||
.id(1L)
|
||||
.userId(1L)
|
||||
.name("Old Shelf")
|
||||
.icon("star")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.filterJson("{\"status\": \"reading\"}")
|
||||
.build();
|
||||
|
||||
MagicShelf dto = new MagicShelf();
|
||||
dto.setId(1L);
|
||||
dto.setName("Updated Shelf");
|
||||
dto.setIcon(null);
|
||||
dto.setIconType(null);
|
||||
dto.setFilterJson("{\"status\": \"updated\"}");
|
||||
dto.setIsPublic(false);
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(magicShelfRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(magicShelfRepository.save(any(MagicShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
magicShelfService.createOrUpdateShelf(dto);
|
||||
|
||||
ArgumentCaptor<MagicShelfEntity> captor = ArgumentCaptor.forClass(MagicShelfEntity.class);
|
||||
verify(magicShelfRepository).save(captor.capture());
|
||||
|
||||
MagicShelfEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
assertEquals("Updated Shelf", saved.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateShelf_withIcon_shouldPreserveIconValues() {
|
||||
MagicShelfEntity existing = MagicShelfEntity.builder()
|
||||
.id(1L)
|
||||
.userId(1L)
|
||||
.name("Old Shelf")
|
||||
.icon("star")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.filterJson("{\"status\": \"reading\"}")
|
||||
.build();
|
||||
|
||||
MagicShelf dto = new MagicShelf();
|
||||
dto.setId(1L);
|
||||
dto.setName("Updated Shelf");
|
||||
dto.setIcon("bookmark");
|
||||
dto.setIconType(IconType.CUSTOM_SVG);
|
||||
dto.setFilterJson("{\"status\": \"updated\"}");
|
||||
dto.setIsPublic(false);
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(magicShelfRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(magicShelfRepository.save(any(MagicShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
magicShelfService.createOrUpdateShelf(dto);
|
||||
|
||||
ArgumentCaptor<MagicShelfEntity> captor = ArgumentCaptor.forClass(MagicShelfEntity.class);
|
||||
verify(magicShelfRepository).save(captor.capture());
|
||||
|
||||
MagicShelfEntity saved = captor.getValue();
|
||||
assertEquals("bookmark", saved.getIcon());
|
||||
assertEquals(IconType.CUSTOM_SVG, saved.getIconType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateShelf_fromIconToNull_shouldAllowRemovingIcon() {
|
||||
MagicShelfEntity existing = MagicShelfEntity.builder()
|
||||
.id(1L)
|
||||
.userId(1L)
|
||||
.name("Shelf With Icon")
|
||||
.icon("heart")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.filterJson("{}")
|
||||
.build();
|
||||
|
||||
MagicShelf dto = new MagicShelf();
|
||||
dto.setId(1L);
|
||||
dto.setName("Shelf With Icon");
|
||||
dto.setIcon(null);
|
||||
dto.setIconType(null);
|
||||
dto.setFilterJson("{}");
|
||||
dto.setIsPublic(false);
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(magicShelfRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(magicShelfRepository.save(any(MagicShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
MagicShelf result = magicShelfService.createOrUpdateShelf(dto);
|
||||
|
||||
assertNull(result.getIcon());
|
||||
assertNull(result.getIconType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package org.booklore.service;
|
||||
|
||||
import org.booklore.config.security.service.AuthenticationService;
|
||||
import org.booklore.mapper.ShelfMapper;
|
||||
import org.booklore.model.dto.BookLoreUser;
|
||||
import org.booklore.model.dto.Shelf;
|
||||
import org.booklore.model.dto.request.ShelfCreateRequest;
|
||||
import org.booklore.model.entity.BookLoreUserEntity;
|
||||
import org.booklore.model.entity.ShelfEntity;
|
||||
import org.booklore.model.enums.IconType;
|
||||
import org.booklore.repository.BookRepository;
|
||||
import org.booklore.repository.ShelfRepository;
|
||||
import org.booklore.repository.UserRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ShelfServiceTest {
|
||||
|
||||
@Mock
|
||||
private ShelfRepository shelfRepository;
|
||||
@Mock
|
||||
private BookRepository bookRepository;
|
||||
@Mock
|
||||
private ShelfMapper shelfMapper;
|
||||
@Mock
|
||||
private AuthenticationService authenticationService;
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@InjectMocks
|
||||
private ShelfService shelfService;
|
||||
|
||||
private BookLoreUser user;
|
||||
private BookLoreUserEntity userEntity;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
user = BookLoreUser.builder().id(1L).isDefaultPassword(false).build();
|
||||
userEntity = BookLoreUserEntity.builder().id(1L).username("testuser").build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShelf_withNullIcon_shouldPersistNullIconValues() {
|
||||
ShelfCreateRequest request = ShelfCreateRequest.builder()
|
||||
.name("My Shelf")
|
||||
.icon(null)
|
||||
.iconType(null)
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.existsByUserIdAndName(1L, "My Shelf")).thenReturn(false);
|
||||
when(userRepository.findById(1L)).thenReturn(Optional.of(userEntity));
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfMapper.toShelf(any(ShelfEntity.class))).thenReturn(Shelf.builder().name("My Shelf").build());
|
||||
|
||||
shelfService.createShelf(request);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
assertEquals("My Shelf", saved.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShelf_withIcon_shouldPersistIconValues() {
|
||||
ShelfCreateRequest request = ShelfCreateRequest.builder()
|
||||
.name("My Shelf")
|
||||
.icon("heart")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.existsByUserIdAndName(1L, "My Shelf")).thenReturn(false);
|
||||
when(userRepository.findById(1L)).thenReturn(Optional.of(userEntity));
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfMapper.toShelf(any(ShelfEntity.class))).thenReturn(
|
||||
Shelf.builder().name("My Shelf").icon("heart").iconType(IconType.PRIME_NG).build());
|
||||
|
||||
shelfService.createShelf(request);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertEquals("heart", saved.getIcon());
|
||||
assertEquals(IconType.PRIME_NG, saved.getIconType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateShelf_withNullIcon_shouldClearIconValues() {
|
||||
ShelfEntity existingShelf = ShelfEntity.builder()
|
||||
.id(1L)
|
||||
.name("Old Shelf")
|
||||
.icon("star")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.user(userEntity)
|
||||
.build();
|
||||
|
||||
ShelfCreateRequest request = ShelfCreateRequest.builder()
|
||||
.name("Updated Shelf")
|
||||
.icon(null)
|
||||
.iconType(null)
|
||||
.build();
|
||||
|
||||
when(shelfRepository.findById(1L)).thenReturn(Optional.of(existingShelf));
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfMapper.toShelf(any(ShelfEntity.class))).thenReturn(Shelf.builder().name("Updated Shelf").build());
|
||||
|
||||
shelfService.updateShelf(1L, request);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
assertEquals("Updated Shelf", saved.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateShelf_withIcon_shouldPreserveIconValues() {
|
||||
ShelfEntity existingShelf = ShelfEntity.builder()
|
||||
.id(1L)
|
||||
.name("Old Shelf")
|
||||
.icon("star")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.user(userEntity)
|
||||
.build();
|
||||
|
||||
ShelfCreateRequest request = ShelfCreateRequest.builder()
|
||||
.name("Updated Shelf")
|
||||
.icon("bookmark")
|
||||
.iconType(IconType.CUSTOM_SVG)
|
||||
.build();
|
||||
|
||||
when(shelfRepository.findById(1L)).thenReturn(Optional.of(existingShelf));
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfMapper.toShelf(any(ShelfEntity.class))).thenReturn(
|
||||
Shelf.builder().name("Updated Shelf").icon("bookmark").iconType(IconType.CUSTOM_SVG).build());
|
||||
|
||||
shelfService.updateShelf(1L, request);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertEquals("bookmark", saved.getIcon());
|
||||
assertEquals(IconType.CUSTOM_SVG, saved.getIconType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createShelf_withCustomSvgIcon_shouldPersistCorrectIconType() {
|
||||
ShelfCreateRequest request = ShelfCreateRequest.builder()
|
||||
.name("SVG Shelf")
|
||||
.icon("<svg>...</svg>")
|
||||
.iconType(IconType.CUSTOM_SVG)
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(shelfRepository.existsByUserIdAndName(1L, "SVG Shelf")).thenReturn(false);
|
||||
when(userRepository.findById(1L)).thenReturn(Optional.of(userEntity));
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(shelfMapper.toShelf(any(ShelfEntity.class))).thenReturn(
|
||||
Shelf.builder().name("SVG Shelf").icon("<svg>...</svg>").iconType(IconType.CUSTOM_SVG).build());
|
||||
|
||||
shelfService.createShelf(request);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertEquals("<svg>...</svg>", saved.getIcon());
|
||||
assertEquals(IconType.CUSTOM_SVG, saved.getIconType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package org.booklore.service.library;
|
||||
|
||||
import org.booklore.config.security.service.AuthenticationService;
|
||||
import org.booklore.mapper.LibraryMapper;
|
||||
import org.booklore.model.dto.BookLoreUser;
|
||||
import org.booklore.model.dto.Library;
|
||||
import org.booklore.model.dto.LibraryPath;
|
||||
import org.booklore.model.dto.request.CreateLibraryRequest;
|
||||
import org.booklore.model.entity.LibraryEntity;
|
||||
import org.booklore.model.entity.LibraryPathEntity;
|
||||
import org.booklore.model.enums.IconType;
|
||||
import org.booklore.repository.LibraryRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.booklore.mapper.BookMapper;
|
||||
import org.booklore.repository.BookRepository;
|
||||
import org.booklore.repository.LibraryPathRepository;
|
||||
import org.booklore.repository.UserRepository;
|
||||
import org.booklore.model.entity.BookLoreUserEntity;
|
||||
import org.booklore.service.NotificationService;
|
||||
import org.booklore.service.monitoring.MonitoringService;
|
||||
import org.booklore.util.FileService;
|
||||
|
||||
import java.util.Collections;
|
||||
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.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LibraryServiceIconTest {
|
||||
|
||||
@Mock
|
||||
private LibraryRepository libraryRepository;
|
||||
@Mock
|
||||
private LibraryPathRepository libraryPathRepository;
|
||||
@Mock
|
||||
private BookRepository bookRepository;
|
||||
@Mock
|
||||
private LibraryProcessingService libraryProcessingService;
|
||||
@Mock
|
||||
private BookMapper bookMapper;
|
||||
@Mock
|
||||
private LibraryMapper libraryMapper;
|
||||
@Mock
|
||||
private NotificationService notificationService;
|
||||
@Mock
|
||||
private FileService fileService;
|
||||
@Mock
|
||||
private MonitoringService monitoringService;
|
||||
@Mock
|
||||
private AuthenticationService authenticationService;
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@InjectMocks
|
||||
private LibraryService libraryService;
|
||||
|
||||
private BookLoreUser user;
|
||||
private BookLoreUserEntity userEntity;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
user = BookLoreUser.builder().id(1L).isDefaultPassword(false).build();
|
||||
userEntity = BookLoreUserEntity.builder().id(1L).username("testuser").build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLibrary_withNullIcon_shouldClearIconValues() {
|
||||
LibraryEntity existing = LibraryEntity.builder()
|
||||
.id(1L)
|
||||
.name("My Library")
|
||||
.icon("book")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.libraryPaths(new ArrayList<>())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
CreateLibraryRequest request = CreateLibraryRequest.builder()
|
||||
.name("Updated Library")
|
||||
.icon(null)
|
||||
.iconType(null)
|
||||
.paths(Collections.emptyList())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
when(libraryRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(libraryRepository.save(any(LibraryEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(libraryMapper.toLibrary(any(LibraryEntity.class))).thenReturn(Library.builder().name("Updated Library").build());
|
||||
|
||||
libraryService.updateLibrary(request, 1L);
|
||||
|
||||
ArgumentCaptor<LibraryEntity> captor = ArgumentCaptor.forClass(LibraryEntity.class);
|
||||
verify(libraryRepository).save(captor.capture());
|
||||
|
||||
LibraryEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
assertEquals("Updated Library", saved.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLibrary_withIcon_shouldPreserveIconValues() {
|
||||
LibraryEntity existing = LibraryEntity.builder()
|
||||
.id(1L)
|
||||
.name("My Library")
|
||||
.icon("book")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.libraryPaths(new ArrayList<>())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
CreateLibraryRequest request = CreateLibraryRequest.builder()
|
||||
.name("Updated Library")
|
||||
.icon("folder")
|
||||
.iconType(IconType.CUSTOM_SVG)
|
||||
.paths(Collections.emptyList())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
when(libraryRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(libraryRepository.save(any(LibraryEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(libraryMapper.toLibrary(any(LibraryEntity.class))).thenReturn(
|
||||
Library.builder().name("Updated Library").icon("folder").iconType(IconType.CUSTOM_SVG).build());
|
||||
|
||||
libraryService.updateLibrary(request, 1L);
|
||||
|
||||
ArgumentCaptor<LibraryEntity> captor = ArgumentCaptor.forClass(LibraryEntity.class);
|
||||
verify(libraryRepository).save(captor.capture());
|
||||
|
||||
LibraryEntity saved = captor.getValue();
|
||||
assertEquals("folder", saved.getIcon());
|
||||
assertEquals(IconType.CUSTOM_SVG, saved.getIconType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLibrary_fromIconToNull_shouldAllowRemovingIcon() {
|
||||
LibraryEntity existing = LibraryEntity.builder()
|
||||
.id(1L)
|
||||
.name("Library With Icon")
|
||||
.icon("star")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.libraryPaths(new ArrayList<>())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
CreateLibraryRequest request = CreateLibraryRequest.builder()
|
||||
.name("Library With Icon")
|
||||
.icon(null)
|
||||
.iconType(null)
|
||||
.paths(Collections.emptyList())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
when(libraryRepository.findById(1L)).thenReturn(Optional.of(existing));
|
||||
when(libraryRepository.save(any(LibraryEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
when(libraryMapper.toLibrary(any(LibraryEntity.class))).thenReturn(Library.builder().name("Library With Icon").build());
|
||||
|
||||
libraryService.updateLibrary(request, 1L);
|
||||
|
||||
ArgumentCaptor<LibraryEntity> captor = ArgumentCaptor.forClass(LibraryEntity.class);
|
||||
verify(libraryRepository).save(captor.capture());
|
||||
|
||||
LibraryEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLibrary_withNullIcon_shouldPersistNullIconValues() {
|
||||
CreateLibraryRequest request = CreateLibraryRequest.builder()
|
||||
.name("No Icon Library")
|
||||
.icon(null)
|
||||
.iconType(null)
|
||||
.paths(Collections.emptyList())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(userRepository.findById(1L)).thenReturn(Optional.of(userEntity));
|
||||
when(libraryRepository.save(any(LibraryEntity.class))).thenAnswer(invocation -> {
|
||||
LibraryEntity entity = invocation.getArgument(0);
|
||||
entity.setId(1L);
|
||||
return entity;
|
||||
});
|
||||
when(libraryMapper.toLibrary(any(LibraryEntity.class))).thenReturn(Library.builder().name("No Icon Library").build());
|
||||
|
||||
libraryService.createLibrary(request);
|
||||
|
||||
ArgumentCaptor<LibraryEntity> captor = ArgumentCaptor.forClass(LibraryEntity.class);
|
||||
verify(libraryRepository).save(captor.capture());
|
||||
|
||||
LibraryEntity saved = captor.getValue();
|
||||
assertNull(saved.getIcon());
|
||||
assertNull(saved.getIconType());
|
||||
assertEquals("No Icon Library", saved.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createLibrary_withIcon_shouldPersistIconValues() {
|
||||
CreateLibraryRequest request = CreateLibraryRequest.builder()
|
||||
.name("Icon Library")
|
||||
.icon("book")
|
||||
.iconType(IconType.PRIME_NG)
|
||||
.paths(Collections.emptyList())
|
||||
.watch(false)
|
||||
.build();
|
||||
|
||||
when(authenticationService.getAuthenticatedUser()).thenReturn(user);
|
||||
when(userRepository.findById(1L)).thenReturn(Optional.of(userEntity));
|
||||
when(libraryRepository.save(any(LibraryEntity.class))).thenAnswer(invocation -> {
|
||||
LibraryEntity entity = invocation.getArgument(0);
|
||||
entity.setId(1L);
|
||||
return entity;
|
||||
});
|
||||
when(libraryMapper.toLibrary(any(LibraryEntity.class))).thenReturn(
|
||||
Library.builder().name("Icon Library").icon("book").iconType(IconType.PRIME_NG).build());
|
||||
|
||||
libraryService.createLibrary(request);
|
||||
|
||||
ArgumentCaptor<LibraryEntity> captor = ArgumentCaptor.forClass(LibraryEntity.class);
|
||||
verify(libraryRepository).save(captor.capture());
|
||||
|
||||
LibraryEntity saved = captor.getValue();
|
||||
assertEquals("book", saved.getIcon());
|
||||
assertEquals(IconType.PRIME_NG, saved.getIconType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.booklore.service.user;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.booklore.model.entity.BookLoreUserEntity;
|
||||
import org.booklore.model.entity.ShelfEntity;
|
||||
import org.booklore.model.enums.IconType;
|
||||
import org.booklore.repository.ShelfRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserDefaultsServiceTest {
|
||||
|
||||
@Mock
|
||||
private ShelfRepository shelfRepository;
|
||||
@Mock
|
||||
private ObjectMapper objectMapper;
|
||||
@Mock
|
||||
private DefaultUserSettingsProvider defaultSettingsProvider;
|
||||
|
||||
@InjectMocks
|
||||
private UserDefaultsService userDefaultsService;
|
||||
|
||||
@Test
|
||||
void addDefaultShelves_shouldCreateFavoritesShelfWithIcon() {
|
||||
BookLoreUserEntity user = BookLoreUserEntity.builder()
|
||||
.id(1L)
|
||||
.username("testuser")
|
||||
.build();
|
||||
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
userDefaultsService.addDefaultShelves(user);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertEquals("Favorites", saved.getName());
|
||||
assertEquals("heart", saved.getIcon());
|
||||
assertEquals(IconType.PRIME_NG, saved.getIconType());
|
||||
assertEquals(user, saved.getUser());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addDefaultShelves_shouldSetExplicitIconType() {
|
||||
BookLoreUserEntity user = BookLoreUserEntity.builder()
|
||||
.id(2L)
|
||||
.username("anotheruser")
|
||||
.build();
|
||||
|
||||
when(shelfRepository.save(any(ShelfEntity.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
|
||||
userDefaultsService.addDefaultShelves(user);
|
||||
|
||||
ArgumentCaptor<ShelfEntity> captor = ArgumentCaptor.forClass(ShelfEntity.class);
|
||||
verify(shelfRepository).save(captor.capture());
|
||||
|
||||
ShelfEntity saved = captor.getValue();
|
||||
assertNotNull(saved.getIconType(), "Default shelf must have an explicit iconType since entity no longer has a default");
|
||||
assertNotNull(saved.getIcon(), "Default shelf must have an explicit icon since entity no longer has a default");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user