commit 7b891bc9df242d78fd882718963f387127843648
Author: Jakub Smid <jaksmid@seznam.cz>
Date:   Wed Mar 30 17:05:57 2022 +0200

    Added another unit tests
     - for catalog, title page and user service
    re #9363

diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/alternativename/AlternativeName.java b/backend/src/main/java/cz/zcu/kiv/backendapi/alternativename/AlternativeName.java
index bdbd596..656293d 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/alternativename/AlternativeName.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/alternativename/AlternativeName.java
@@ -1,10 +1,7 @@
 package cz.zcu.kiv.backendapi.alternativename;
 
 import cz.zcu.kiv.backendapi.catalog.CatalogEntry;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import javax.persistence.*;
 import java.io.Serializable;
@@ -14,6 +11,7 @@ import java.io.Serializable;
  */
 @Data
 @EqualsAndHashCode(exclude = "catalog")
+@ToString(exclude = "catalog")
 @NoArgsConstructor
 @AllArgsConstructor
 @Entity
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/bibliography/Bibliography.java b/backend/src/main/java/cz/zcu/kiv/backendapi/bibliography/Bibliography.java
index cfceada..c55aa20 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/bibliography/Bibliography.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/bibliography/Bibliography.java
@@ -1,10 +1,7 @@
 package cz.zcu.kiv.backendapi.bibliography;
 
 import cz.zcu.kiv.backendapi.catalog.CatalogEntry;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import javax.persistence.*;
 import java.io.Serializable;
@@ -14,6 +11,7 @@ import java.io.Serializable;
  */
 @Data
 @EqualsAndHashCode(exclude = "catalog")
+@ToString(exclude = "catalog")
 @NoArgsConstructor
 @AllArgsConstructor
 @Entity
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImpl.java b/backend/src/main/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImpl.java
index b82279b..cb96ab1 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImpl.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImpl.java
@@ -88,11 +88,11 @@ public class CatalogEntryServiceImpl implements ICatalogEntryService {
 
     @Override
     public void deleteCatalogEntry(UUID id) {
-        CatalogEntry catalogEntry = catalogEntryRepository.findById(id).orElseThrow(() -> {
+        if (!catalogEntryRepository.existsById(id)) {
             log.error(CATALOG_ENTRY_NOT_FOUND);
             throw new ApiRequestException(CATALOG_ENTRY_NOT_FOUND, HttpStatus.NOT_FOUND);
-        });
-        catalogEntryRepository.delete(catalogEntry);
+        }
+        catalogEntryRepository.deleteById(id);
         log.info("Catalog entry deleted");
     }
 
@@ -126,8 +126,8 @@ public class CatalogEntryServiceImpl implements ICatalogEntryService {
     /**
      * Converts catalog DTO to catalog entity
      *
-     * @param catalogEntryDto    catalog DTO
-     * @param catalogEntry catalog entity
+     * @param catalogEntryDto catalog DTO
+     * @param catalogEntry    catalog entity
      */
     private void convertDtoToEntity(CatalogEntryDto catalogEntryDto, CatalogEntry catalogEntry) {
         catalogEntry.setName(catalogEntryDto.getName());
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/country/Country.java b/backend/src/main/java/cz/zcu/kiv/backendapi/country/Country.java
index bb14878..6886362 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/country/Country.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/country/Country.java
@@ -1,10 +1,7 @@
 package cz.zcu.kiv.backendapi.country;
 
 import cz.zcu.kiv.backendapi.catalog.CatalogEntry;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import javax.persistence.*;
 import java.io.Serializable;
@@ -14,6 +11,7 @@ import java.io.Serializable;
  */
 @Data
 @EqualsAndHashCode(exclude = "catalog")
+@ToString(exclude = "catalog")
 @NoArgsConstructor
 @AllArgsConstructor
 @Entity
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java b/backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java
index 6ea5494..cc8e497 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java
@@ -3,7 +3,7 @@ package cz.zcu.kiv.backendapi.security;
 import cz.zcu.kiv.backendapi.security.jwt.JwtTokenVerifier;
 import cz.zcu.kiv.backendapi.security.jwt.JwtUsernameAndPasswordAuthenticationFilter;
 import cz.zcu.kiv.backendapi.security.jwt.JwtUtils;
-import cz.zcu.kiv.backendapi.user.Permission;
+import cz.zcu.kiv.backendapi.user.permission.Permission;
 import cz.zcu.kiv.backendapi.user.Role;
 import lombok.RequiredArgsConstructor;
 import org.springframework.context.annotation.Bean;
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/IUserService.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/IUserService.java
index 61e0f29..f319856 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/IUserService.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/IUserService.java
@@ -42,10 +42,10 @@ public interface IUserService {
     /**
      * Changes password of the user
      *
-     * @param oldPassword old poasswort
-     * @param passwordDto password DTO containing new password and confirmation password
+     * @param oldPassword old password
+     * @param newPassword new password
      */
-    void changePassword(String oldPassword, PasswordDto passwordDto);
+    void changePassword(String oldPassword, String newPassword);
 
     /**
      * Updates permissions to given user
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/Permission.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/Permission.java
deleted file mode 100644
index dc6c7a2..0000000
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/Permission.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package cz.zcu.kiv.backendapi.user;
-
-import lombok.Getter;
-
-/**
- * Enum of permission with given name end byte value which is used to extract permission from number
- */
-@Getter
-public enum Permission {
-    READ("READ", (byte) 0x1),
-    WRITE("WRITE", (byte) 0x2),
-    DELETE("DELETE", (byte) 0x4);
-
-    /**
-     * Permission name
-     */
-    private final String name;
-
-    /**
-     * Hexadecimal number containing only one bit set to 1 (corresponds to given permission)
-     */
-    private final byte bit;
-
-    /**
-     * Creates new permission with given name and byte value
-     *
-     * @param name name
-     * @param bit  byte value
-     */
-    Permission(String name, byte bit) {
-        this.name = name;
-        this.bit = bit;
-    }
-}
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserController.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserController.java
index 381a739..a642cbe 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserController.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserController.java
@@ -66,7 +66,7 @@ public class UserController {
      */
     @PatchMapping("/user/change-password")
     public void changePassword(@Valid @NotNull(message = "Old password must not be null") @RequestParam String oldPassword, @RequestBody @Valid PasswordDto passwordDto) {
-        userService.changePassword(oldPassword, passwordDto);
+        userService.changePassword(oldPassword, passwordDto.getPassword());
     }
 
     /**
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserDto.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserDto.java
index 58fec75..8b54941 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserDto.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserDto.java
@@ -3,8 +3,10 @@ package cz.zcu.kiv.backendapi.user;
 import cz.zcu.kiv.backendapi.user.password.PasswordDto;
 import cz.zcu.kiv.backendapi.validation.PasswordMatches;
 import cz.zcu.kiv.backendapi.validation.ValidEmail;
+import lombok.AllArgsConstructor;
 import lombok.Data;
 import cz.zcu.kiv.backendapi.user.permission.PermissionDto;
+import lombok.NoArgsConstructor;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotEmpty;
@@ -15,6 +17,8 @@ import javax.validation.constraints.NotNull;
  * User data object
  */
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 @PasswordMatches
 public class UserDto {
     /**
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserEntity.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserEntity.java
index 62e222a..26e87c0 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserEntity.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserEntity.java
@@ -1,6 +1,6 @@
 package cz.zcu.kiv.backendapi.user;
 
-import lombok.AllArgsConstructor;
+import cz.zcu.kiv.backendapi.user.permission.Permission;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import org.hibernate.annotations.Type;
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserServiceImpl.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserServiceImpl.java
index ef9df9a..ed90aed 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserServiceImpl.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/UserServiceImpl.java
@@ -2,6 +2,7 @@ package cz.zcu.kiv.backendapi.user;
 
 import cz.zcu.kiv.backendapi.exception.ApiRequestException;
 import cz.zcu.kiv.backendapi.user.password.PasswordDto;
+import cz.zcu.kiv.backendapi.user.permission.Permission;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
@@ -52,10 +53,11 @@ public class UserServiceImpl implements IUserService, UserDetailsService {
 
     @Override
     public UserEntity getUserByName(String username) {
-        return userRepository.findByEmail(username).orElseThrow(() -> {
-            log.error(String.format(USER_NOT_FOUND, username));
-            throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username));
-        });
+        return userRepository.findByEmail(username).
+                orElseThrow(() -> {
+                    log.error(String.format(USER_NOT_FOUND, username));
+                    throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username));
+                });
     }
 
     @Override
@@ -71,12 +73,11 @@ public class UserServiceImpl implements IUserService, UserDetailsService {
 
     @Override
     public void updatePermissions(String username, PermissionDto permissionDto) {
-        UserEntity userEntity = userRepository.findByEmail(username).orElseThrow(() ->{
+        UserEntity userEntity = userRepository.findByEmail(username).orElseThrow(() -> {
             log.error(String.format(USER_NOT_FOUND, username));
             throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username));
         });
         userEntity.setPermissions(getPermissionsFromDto(permissionDto));
-        userEntity.setEmail(username);
         userRepository.save(userEntity);
     }
 
@@ -93,11 +94,11 @@ public class UserServiceImpl implements IUserService, UserDetailsService {
     //TODO maybe check if user is not deleting himself - or it might be ok
     @Override
     public void deleteUser(String username) {
-        UserEntity userEntity = userRepository.findByEmail(username).orElseThrow(() ->{
+        if (!userRepository.existsById(username)) {
             log.error(String.format(USER_NOT_FOUND, username));
             throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username));
-        });
-        userRepository.delete(userEntity);
+        }
+        userRepository.deleteById(username);
     }
 
     @Override
@@ -106,17 +107,16 @@ public class UserServiceImpl implements IUserService, UserDetailsService {
     }
 
     @Override
-    public void changePassword(String oldPassword, PasswordDto passwordDto) {
+    public void changePassword(String oldPassword, String newPassword) {
         UserEntity loggedUser = getUserByName((String) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
         if (!bCryptPasswordEncoder.matches(oldPassword, loggedUser.getPassword())) {
             throw new ApiRequestException("Old password does not match", HttpStatus.BAD_REQUEST);
         }
-        loggedUser.setPassword(bCryptPasswordEncoder.encode(passwordDto.getPassword()));
+        loggedUser.setPassword(bCryptPasswordEncoder.encode(newPassword));
         userRepository.save(loggedUser);
     }
 
 
-
     /**
      * Converts user DTO to user entity
      *
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/password/PasswordDto.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/password/PasswordDto.java
index 171d927..b7c82c4 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/password/PasswordDto.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/password/PasswordDto.java
@@ -2,6 +2,7 @@ package cz.zcu.kiv.backendapi.user.password;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import cz.zcu.kiv.backendapi.validation.PasswordMatches;
+import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -15,6 +16,7 @@ import javax.validation.constraints.Size;
 @Data
 @PasswordMatches
 @NoArgsConstructor
+@AllArgsConstructor
 public class PasswordDto {
     /**
      * Minimal length for password
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/Permission.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/Permission.java
new file mode 100644
index 0000000..c822219
--- /dev/null
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/Permission.java
@@ -0,0 +1,34 @@
+package cz.zcu.kiv.backendapi.user.permission;
+
+import lombok.Getter;
+
+/**
+ * Enum of permission with given name end byte value which is used to extract permission from number
+ */
+@Getter
+public enum Permission {
+    READ("READ", (byte) 0x1),
+    WRITE("WRITE", (byte) 0x2),
+    DELETE("DELETE", (byte) 0x4);
+
+    /**
+     * Permission name
+     */
+    private final String name;
+
+    /**
+     * Hexadecimal number containing only one bit set to 1 (corresponds to given permission)
+     */
+    private final byte bit;
+
+    /**
+     * Creates new permission with given name and byte value
+     *
+     * @param name name
+     * @param bit  byte value
+     */
+    Permission(String name, byte bit) {
+        this.name = name;
+        this.bit = bit;
+    }
+}
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/PermissionDto.java b/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/PermissionDto.java
index eb54d15..dfc1667 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/PermissionDto.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/user/permission/PermissionDto.java
@@ -1,11 +1,15 @@
 package cz.zcu.kiv.backendapi.user.permission;
 
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 /**
  * Permission DTO
  */
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class PermissionDto {
     /**
      * Whether user has 'READ' authority
diff --git a/backend/src/main/java/cz/zcu/kiv/backendapi/writtenform/WrittenForm.java b/backend/src/main/java/cz/zcu/kiv/backendapi/writtenform/WrittenForm.java
index 32cd62d..ecb66c5 100644
--- a/backend/src/main/java/cz/zcu/kiv/backendapi/writtenform/WrittenForm.java
+++ b/backend/src/main/java/cz/zcu/kiv/backendapi/writtenform/WrittenForm.java
@@ -1,10 +1,7 @@
 package cz.zcu.kiv.backendapi.writtenform;
 
 import cz.zcu.kiv.backendapi.catalog.CatalogEntry;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
+import lombok.*;
 
 import javax.persistence.*;
 import java.io.Serializable;
@@ -14,6 +11,7 @@ import java.io.Serializable;
  */
 @Data
 @EqualsAndHashCode(exclude = "catalog")
+@ToString(exclude = "catalog")
 @NoArgsConstructor
 @AllArgsConstructor
 @Entity
diff --git a/backend/src/test/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImplTest.java b/backend/src/test/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImplTest.java
new file mode 100644
index 0000000..6b1d0f5
--- /dev/null
+++ b/backend/src/test/java/cz/zcu/kiv/backendapi/catalog/CatalogEntryServiceImplTest.java
@@ -0,0 +1,313 @@
+package cz.zcu.kiv.backendapi.catalog;
+
+import cz.zcu.kiv.backendapi.alternativename.AlternativeName;
+import cz.zcu.kiv.backendapi.bibliography.Bibliography;
+import cz.zcu.kiv.backendapi.country.Country;
+import cz.zcu.kiv.backendapi.exception.ApiRequestException;
+import cz.zcu.kiv.backendapi.type.Type;
+import cz.zcu.kiv.backendapi.type.TypeServiceImpl;
+import cz.zcu.kiv.backendapi.writtenform.WrittenForm;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.*;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class CatalogEntryServiceImplTest {
+
+    @Mock
+    private CatalogEntryRepository catalogEntryRepository;
+
+    @Mock
+    private TypeServiceImpl typeService;
+
+
+    private CatalogEntryServiceImpl underTest;
+
+    @BeforeEach
+    void setUp() {
+        underTest = new CatalogEntryServiceImpl(catalogEntryRepository, typeService);
+    }
+
+    @Test
+    void saveCatalog() {
+        // given
+        Type type = new Type("type");
+
+        CatalogEntry catalogEntry = new CatalogEntry();
+        catalogEntry.setName("aaacbbbbbbbaa");
+        catalogEntry.setBibliography(Set.of(new Bibliography("bibl", catalogEntry)));
+        catalogEntry.setTypes(Set.of(type));
+        catalogEntry.setAlternativeNames(Set.of(new AlternativeName("altName", catalogEntry)));
+        catalogEntry.setCountries(Set.of(new Country("aaaabbaa", catalogEntry)));
+        catalogEntry.setWrittenForms(Set.of(new WrittenForm("written", catalogEntry)));
+        catalogEntry.setCertainty(0);
+        catalogEntry.setLatitude(0.1);
+        catalogEntry.setLongitude(0.2);
+
+        CatalogEntry catalogEntry2 = new CatalogEntry();
+        catalogEntry2.setName("name");
+        catalogEntry2.setBibliography(Set.of(new Bibliography("bibl", catalogEntry2)));
+        catalogEntry2.setTypes(Set.of(type));
+        catalogEntry2.setAlternativeNames(Set.of(new AlternativeName("aaaabbbbbbaa", catalogEntry2)));
+        catalogEntry2.setCountries(Set.of(new Country("aaaabbcccefaa", catalogEntry2)));
+        catalogEntry2.setWrittenForms(Set.of(new WrittenForm("written", catalogEntry2)));
+        catalogEntry2.setCertainty(1);
+        catalogEntry2.setLatitude(1.1);
+        catalogEntry2.setLongitude(1.2);
+
+        List<CatalogEntry> catalog = List.of(catalogEntry, catalogEntry2);
+
+        given(typeService.getTypeByName(anyString())).willReturn(Optional.of(type));
+
+        // when
+        underTest.saveCatalog(catalog);
+
+        // then
+        verify(typeService, never()).saveType(type);
+        verify(catalogEntryRepository).save(catalogEntry);
+        verify(catalogEntryRepository).save(catalogEntry2);
+    }
+
+    @Test
+    void saveCatalogEntry() {
+        // given
+        CatalogEntryDto catalogEntryDto = new CatalogEntryDto();
+        catalogEntryDto.setName("name");
+        catalogEntryDto.setBibliography(Set.of("bibl"));
+        catalogEntryDto.setTypes(Set.of("type"));
+        catalogEntryDto.setAlternativeNames(Set.of("altName"));
+        catalogEntryDto.setCountries(Set.of("country"));
+        catalogEntryDto.setWrittenForms(Set.of("written"));
+        catalogEntryDto.setCertainty(0);
+        catalogEntryDto.setLatitude(0.1);
+        catalogEntryDto.setLongitude(0.2);
+
+        Type type = new Type("type");
+
+        CatalogEntry catalogEntry = new CatalogEntry();
+        catalogEntry.setName("name");
+        catalogEntry.setBibliography(Set.of(new Bibliography("bibl", catalogEntry)));
+        catalogEntry.setTypes(Set.of(type));
+        catalogEntry.setAlternativeNames(Set.of(new AlternativeName("altName", catalogEntry)));
+        catalogEntry.setCountries(Set.of(new Country("country", catalogEntry)));
+        catalogEntry.setWrittenForms(Set.of(new WrittenForm("written", catalogEntry)));
+        catalogEntry.setCertainty(0);
+        catalogEntry.setLatitude(0.1);
+        catalogEntry.setLongitude(0.2);
+
+        given(typeService.getTypeByName(anyString())).willReturn(Optional.of(type));
+
+        // when
+        underTest.saveCatalogEntry(catalogEntryDto);
+
+        // then
+        ArgumentCaptor<CatalogEntry> argumentCaptor = ArgumentCaptor.forClass(CatalogEntry.class);
+
+        verify(catalogEntryRepository).save(argumentCaptor.capture());
+        verify(typeService, never()).saveType(type);
+
+        CatalogEntry capturedEntry = argumentCaptor.getValue();
+
+        assertThat(capturedEntry).isEqualTo(catalogEntry);
+
+    }
+
+    @Test
+    void testCanUpdateCatalogEntry() {
+        // given
+        UUID id = UUID.randomUUID();
+
+        CatalogEntryDto catalogEntryDto = new CatalogEntryDto();
+        catalogEntryDto.setName("name");
+        catalogEntryDto.setBibliography(Set.of("bibl"));
+        catalogEntryDto.setTypes(Set.of("type"));
+        catalogEntryDto.setAlternativeNames(Set.of("altName"));
+        catalogEntryDto.setCountries(Set.of("country"));
+        catalogEntryDto.setWrittenForms(Set.of("written"));
+        catalogEntryDto.setCertainty(0);
+        catalogEntryDto.setLatitude(0.1);
+        catalogEntryDto.setLongitude(0.2);
+
+        Type type = new Type("type");
+        Type typeOld = new Type("old");
+
+        CatalogEntry catalogEntry = new CatalogEntry();
+        catalogEntry.setName("name");
+        catalogEntry.setBibliography(Set.of(new Bibliography("bibl", catalogEntry)));
+        catalogEntry.setTypes(Set.of(type));
+        catalogEntry.setAlternativeNames(Set.of(new AlternativeName("altName", catalogEntry)));
+        catalogEntry.setCountries(Set.of(new Country("country", catalogEntry)));
+        catalogEntry.setWrittenForms(Set.of(new WrittenForm("written", catalogEntry)));
+        catalogEntry.setCertainty(0);
+        catalogEntry.setLatitude(0.1);
+        catalogEntry.setLongitude(0.2);
+
+        CatalogEntry oldCatalogEntry = new CatalogEntry();
+        oldCatalogEntry.setName("old");
+        oldCatalogEntry.setBibliography(Set.of(new Bibliography("old", oldCatalogEntry)));
+        oldCatalogEntry.setTypes(Set.of(typeOld));
+        oldCatalogEntry.setAlternativeNames(Set.of(new AlternativeName("old", oldCatalogEntry)));
+        oldCatalogEntry.setCountries(Set.of(new Country("old", oldCatalogEntry)));
+        oldCatalogEntry.setWrittenForms(Set.of(new WrittenForm("old", catalogEntry)));
+        oldCatalogEntry.setCertainty(10);
+        oldCatalogEntry.setLatitude(10.1);
+        oldCatalogEntry.setLongitude(10.2);
+
+        given(catalogEntryRepository.findById(id)).willReturn(Optional.of(oldCatalogEntry));
+        given(typeService.getTypeByName(anyString())).willReturn(Optional.empty());
+
+        // when
+
+        underTest.updateCatalogEntry(id, catalogEntryDto);
+
+        // then
+        verify(typeService).saveType(type);
+
+        ArgumentCaptor<CatalogEntry> argumentCaptor = ArgumentCaptor.forClass(CatalogEntry.class);
+
+        verify(catalogEntryRepository).save(argumentCaptor.capture());
+
+        CatalogEntry capturedCatalogEntry = argumentCaptor.getValue();
+
+        assertThat(capturedCatalogEntry).isEqualTo(catalogEntry);
+    }
+
+    @Test
+    void testCanNotUpdateCatalogEntry() {
+        // given
+        UUID id = UUID.randomUUID();
+
+        CatalogEntryDto catalogEntryDto = new CatalogEntryDto();
+        catalogEntryDto.setName("name");
+        catalogEntryDto.setBibliography(Set.of("bibl"));
+        catalogEntryDto.setTypes(Set.of("type"));
+        catalogEntryDto.setAlternativeNames(Set.of("altName"));
+        catalogEntryDto.setCountries(Set.of("country"));
+        catalogEntryDto.setWrittenForms(Set.of("written"));
+        catalogEntryDto.setCertainty(0);
+        catalogEntryDto.setLatitude(0.1);
+        catalogEntryDto.setLongitude(0.2);
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.updateCatalogEntry(id, catalogEntryDto))
+                .isInstanceOf(ApiRequestException.class)
+                .hasMessageContaining("Catalog entry not found");
+
+        verify(catalogEntryRepository, never()).save(any());
+        verify(typeService, never()).saveType(any());
+    }
+
+    @Test
+    void testCanDeleteCatalogEntry() {
+        // given
+        UUID id = UUID.randomUUID();
+        given(catalogEntryRepository.existsById(id)).willReturn(true);
+
+        // when
+        underTest.deleteCatalogEntry(id);
+
+        // then
+        verify(catalogEntryRepository).deleteById(id);
+    }
+
+    @Test
+    void testCanNotDeleteCatalogEntry() {
+        // given
+        UUID id = UUID.randomUUID();
+        given(catalogEntryRepository.existsById(id)).willReturn(false);
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.deleteCatalogEntry(id))
+                .isInstanceOf(ApiRequestException.class)
+                .hasMessageContaining("Catalog entry not found");
+
+        verify(catalogEntryRepository, never()).deleteById(any());
+    }
+
+    @Test
+    void getCatalog() {
+        // given
+        String name = "aaa?bb*aa";
+        String country = "aaa?bb*aa";
+        String type = "aaa?bb*aa";
+        String nameChanged = "aaa_bb%aa";
+        String countryChanged = "aaa_bb%aa";
+        String typeChanged = "aaa_bb%aa";
+
+        Type typeEntity = new Type(type);
+
+        CatalogEntry catalogEntry = new CatalogEntry();
+        catalogEntry.setName("aaacbbbbbbbaa");
+        catalogEntry.setBibliography(Set.of(new Bibliography("bibl", catalogEntry)));
+        catalogEntry.setTypes(Set.of(typeEntity));
+        catalogEntry.setAlternativeNames(Set.of(new AlternativeName("altName", catalogEntry)));
+        catalogEntry.setCountries(Set.of(new Country("aaaabbaa", catalogEntry)));
+        catalogEntry.setWrittenForms(Set.of(new WrittenForm("written", catalogEntry)));
+        catalogEntry.setCertainty(0);
+        catalogEntry.setLatitude(0.1);
+        catalogEntry.setLongitude(0.2);
+
+        CatalogEntry catalogEntry2 = new CatalogEntry();
+        catalogEntry2.setName("name");
+        catalogEntry2.setBibliography(Set.of(new Bibliography("bibl", catalogEntry2)));
+        catalogEntry2.setTypes(Set.of(typeEntity));
+        catalogEntry2.setAlternativeNames(Set.of(new AlternativeName("aaaabbbbbbaa", catalogEntry2)));
+        catalogEntry2.setCountries(Set.of(new Country("aaaabbcccefaa", catalogEntry2)));
+        catalogEntry2.setWrittenForms(Set.of(new WrittenForm("written", catalogEntry2)));
+        catalogEntry2.setCertainty(1);
+        catalogEntry2.setLatitude(1.1);
+        catalogEntry2.setLongitude(1.2);
+
+        CatalogEntryDto catalogEntryDto = new CatalogEntryDto();
+        catalogEntryDto.setName("aaacbbbbbbbaa");
+        catalogEntryDto.setBibliography(Set.of("bibl"));
+        catalogEntryDto.setTypes(Set.of("aaa?bb*aa"));
+        catalogEntryDto.setAlternativeNames(Set.of("altName"));
+        catalogEntryDto.setCountries(Set.of("aaaabbaa"));
+        catalogEntryDto.setWrittenForms(Set.of("written"));
+        catalogEntryDto.setCertainty(0);
+        catalogEntryDto.setLatitude(0.1);
+        catalogEntryDto.setLongitude(0.2);
+
+        CatalogEntryDto catalogEntryDto2 = new CatalogEntryDto();
+        catalogEntryDto2.setName("name");
+        catalogEntryDto2.setBibliography(Set.of("bibl"));
+        catalogEntryDto2.setTypes(Set.of("aaa?bb*aa"));
+        catalogEntryDto2.setAlternativeNames(Set.of("aaaabbbbbbaa"));
+        catalogEntryDto2.setCountries(Set.of("aaaabbcccefaa"));
+        catalogEntryDto2.setWrittenForms(Set.of("written"));
+        catalogEntryDto2.setCertainty(1);
+        catalogEntryDto2.setLatitude(1.1);
+        catalogEntryDto2.setLongitude(1.2);
+
+        given(catalogEntryRepository.filterCatalog(nameChanged, countryChanged, typeChanged)).willReturn(Set.of(catalogEntry, catalogEntry2));
+
+        // when
+        List<CatalogEntryDto> results = underTest.getCatalog(name, country, type);
+
+        // then
+        assertThat(results.size()).isEqualTo(2);
+        assertTrue(results.contains(catalogEntryDto));
+        assertTrue(results.contains(catalogEntryDto2));
+
+        verify(catalogEntryRepository).filterCatalog(nameChanged, countryChanged, typeChanged);
+    }
+}
\ No newline at end of file
diff --git a/backend/src/test/java/cz/zcu/kiv/backendapi/titlepage/TitlePageServiceImplementationTest.java b/backend/src/test/java/cz/zcu/kiv/backendapi/titlepage/TitlePageServiceImplementationTest.java
index ef9d791..36300dc 100644
--- a/backend/src/test/java/cz/zcu/kiv/backendapi/titlepage/TitlePageServiceImplementationTest.java
+++ b/backend/src/test/java/cz/zcu/kiv/backendapi/titlepage/TitlePageServiceImplementationTest.java
@@ -44,7 +44,7 @@ class TitlePageServiceImplementationTest {
 
         TitlePage capturedTitlePage = argumentCaptor.getValue();
 
-        titlePage.setId(id);
+        assertThat(capturedTitlePage.getId()).isEqualTo(id);
 
         assertThat(capturedTitlePage).isEqualTo(titlePage);
 
diff --git a/backend/src/test/java/cz/zcu/kiv/backendapi/user/UserServiceImplTest.java b/backend/src/test/java/cz/zcu/kiv/backendapi/user/UserServiceImplTest.java
new file mode 100644
index 0000000..7fcbd4f
--- /dev/null
+++ b/backend/src/test/java/cz/zcu/kiv/backendapi/user/UserServiceImplTest.java
@@ -0,0 +1,328 @@
+package cz.zcu.kiv.backendapi.user;
+
+import cz.zcu.kiv.backendapi.exception.ApiRequestException;
+import cz.zcu.kiv.backendapi.user.password.PasswordDto;
+import cz.zcu.kiv.backendapi.user.permission.PermissionDto;
+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.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class UserServiceImplTest {
+
+    @Mock
+    private UserRepository userRepository;
+
+    @Mock
+    private BCryptPasswordEncoder bCryptPasswordEncoder;
+
+    private UserServiceImpl underTest;
+
+    @BeforeEach
+    void setUp() {
+        underTest = new UserServiceImpl(userRepository, bCryptPasswordEncoder);
+    }
+
+    @Test
+    void testCanLoadUserByUsername() {
+        // given
+        String email = "test@test.com";
+        UserEntity userEntity = new UserEntity("John Doe", email, "password", (byte) 1, false);
+        given(userRepository.findByEmail(email)).willReturn(Optional.of(userEntity));
+
+        // when
+        UserEntity user = (UserEntity) underTest.loadUserByUsername(email);
+
+        // then
+        verify(userRepository).findByEmail(email);
+        assertThat(user).isEqualTo(userEntity);
+    }
+
+    @Test
+    void testCanNotLoadUserByUsername() {
+        String email = "test@test.com";
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.loadUserByUsername(email))
+                .isInstanceOf(UsernameNotFoundException.class)
+                .hasMessageContaining("User with username " + email + " not found");
+
+        verify(userRepository).findByEmail(email);
+    }
+
+    @Test
+    void testCanGetUserByName() {
+        // given
+        String email = "test@test.com";
+        UserEntity userEntity = new UserEntity("John Doe", email, "password", (byte) 1, false);
+        given(userRepository.findByEmail(email)).willReturn(Optional.of(userEntity));
+
+        // when
+        UserEntity user = underTest.getUserByName(email);
+
+        // then
+        verify(userRepository).findByEmail(email);
+        assertThat(user).isEqualTo(userEntity);
+    }
+
+    @Test
+    void testCanNotGetUserByName() {
+        String email = "test@test.com";
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.getUserByName(email))
+                .isInstanceOf(UsernameNotFoundException.class)
+                .hasMessageContaining("User with username " + email + " not found");
+
+        verify(userRepository).findByEmail(email);
+    }
+
+    @Test
+    void canRegisterNewUser() {
+        // given
+        String email = "test@test.com";
+        String password = "password";
+        UserDto userDto = new UserDto("John Doe", email, new PermissionDto(), new PasswordDto(password, password));
+        UserEntity userEntity = new UserEntity(userDto.getName(), userDto.getEmail(), bCryptPasswordEncoder.encode(password), (byte) 0, false);
+        // when
+        underTest.registerNewUser(userDto);
+
+        // then
+        ArgumentCaptor<UserEntity> argumentCaptor = ArgumentCaptor.forClass(UserEntity.class);
+
+        verify(userRepository).save(argumentCaptor.capture());
+
+        UserEntity capturedUser = argumentCaptor.getValue();
+
+        assertThat(capturedUser).isEqualTo(userEntity);
+    }
+
+    @Test
+    void canNotRegisterNewUser() {
+        // given
+        String email = "test@test.com";
+        String password = "password";
+        UserDto userDto = new UserDto("John Doe", email, new PermissionDto(), new PasswordDto(password, password));
+
+
+        given(userRepository.findByEmail(anyString())).willReturn(Optional.of(new UserEntity()));
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.registerNewUser(userDto))
+                .isInstanceOf(ApiRequestException.class)
+                .hasMessageContaining("User with username " + email + " already exists");
+
+        verify(userRepository, never()).save(any());
+    }
+
+    @Test
+    void testCanUpdatePermissions() {
+        // given
+        String email = "test@test.com";
+        String password = "password";
+        UserEntity userEntity = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(password), (byte) 0, false);
+        UserEntity userEntityWithChangedPermissions = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(password), (byte) 7, false);
+        PermissionDto permissionDto = new PermissionDto(true, true, true);
+        given(userRepository.findByEmail(email)).willReturn(Optional.of(userEntity));
+
+        // when
+        underTest.updatePermissions(email, permissionDto);
+
+        // then
+        ArgumentCaptor<UserEntity> argumentCaptor = ArgumentCaptor.forClass(UserEntity.class);
+
+        verify(userRepository).save(argumentCaptor.capture());
+
+        UserEntity capturedUser = argumentCaptor.getValue();
+
+        assertThat(capturedUser).isEqualTo(userEntityWithChangedPermissions);
+    }
+
+    @Test
+    void testCanNotUpdatePermissions() {
+        // given
+        String email = "test@test.com";
+        PermissionDto permissionDto = new PermissionDto();
+        given(userRepository.findByEmail(anyString())).willReturn(Optional.empty());
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.updatePermissions(email, permissionDto))
+                .isInstanceOf(UsernameNotFoundException.class)
+                .hasMessageContaining("User with username " + email + " not found");
+
+        verify(userRepository, never()).save(any());
+    }
+
+    @Test
+    void testCanResetPassword() {
+        // given
+        String email = "test@test.com";
+        String password = "password";
+        String newPassword = "password123";
+        UserEntity userEntity = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(password), (byte) 0, false);
+        UserEntity userEntityWithChangedPassword = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(newPassword), (byte) 0, false);
+        given(userRepository.findByEmail(email)).willReturn(Optional.of(userEntity));
+
+        // when
+        underTest.resetPassword(email, newPassword);
+
+        // then
+        ArgumentCaptor<UserEntity> argumentCaptor = ArgumentCaptor.forClass(UserEntity.class);
+
+        verify(userRepository).save(argumentCaptor.capture());
+
+        UserEntity capturedUser = argumentCaptor.getValue();
+
+        assertThat(capturedUser).isEqualTo(userEntityWithChangedPassword);
+    }
+
+    @Test
+    void testCanNotResetPassword() {
+        // given
+        String email = "test@test.com";
+        String newPassword = "password123";
+        given(userRepository.findByEmail(anyString())).willReturn(Optional.empty());
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.resetPassword(email, newPassword))
+                .isInstanceOf(UsernameNotFoundException.class)
+                .hasMessageContaining("User with username " + email + " not found");
+
+        verify(userRepository, never()).save(any());
+    }
+
+    @Test
+    void testCanDeleteUser() {
+        // given
+        String email = "test@test.com";
+        given(userRepository.existsById(email)).willReturn(true);
+        // when
+        underTest.deleteUser(email);
+
+        // then
+        verify(userRepository).deleteById(email);
+    }
+
+    @Test
+    void testCanNotDeleteUser() {
+        // given
+        String email = "test@test.com";
+        given(userRepository.existsById(email)).willReturn(false);
+        // when
+        assertThatThrownBy(() -> underTest.deleteUser(email))
+                .isInstanceOf(UsernameNotFoundException.class)
+                .hasMessageContaining("User with username " + email + " not found");
+
+        // then
+        verify(userRepository, never()).deleteById(email);
+    }
+
+    @Test
+    void getAllUsers() {
+        // given
+        UserEntity userEntity1 = new UserEntity("first", "first@test.com", "password", (byte) 0, false);
+        UserEntity userEntity2 = new UserEntity("second", "second@test.com", "password2", (byte) 1, false);
+        UserEntity userEntity3 = new UserEntity("third", "third@test.com", "password3", (byte) 7, true);
+
+        UserDto userDto1 = new UserDto("first", "first@test.com", new PermissionDto(), null);
+        UserDto userDto2 = new UserDto("second", "second@test.com", new PermissionDto(true, false, false), null);
+        UserDto userDto3 = new UserDto("third", "third@test.com", new PermissionDto(true, true, true), null);
+
+        given(userRepository.findAll()).willReturn(List.of(userEntity1, userEntity2, userEntity3));
+
+        // when
+        List<UserDto> allUsers = underTest.getAllUsers();
+
+        // then
+        assertThat(allUsers.size()).isEqualTo(3);
+        assertTrue(allUsers.contains(userDto1));
+        assertTrue(allUsers.contains(userDto2));
+        assertTrue(allUsers.contains(userDto3));
+
+
+        verify(userRepository).findAll();
+    }
+
+    @Test
+    void testCanChangePassword() {
+        // given
+        String email = "test@test.com";
+        String oldPassword = "password";
+        String newPassword = "password123";
+        given(bCryptPasswordEncoder.encode(oldPassword)).willReturn("encodedOldPassword");
+        given(bCryptPasswordEncoder.encode(newPassword)).willReturn("encodedNewPassword");
+        UserEntity userEntity = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(oldPassword), (byte) 0, false);
+        UserEntity userEntityWithChangedPassword = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(newPassword), (byte) 0, false);
+        given(bCryptPasswordEncoder.matches(any(), any())).willReturn(true);
+        SecurityContext securityContext = mock(SecurityContext.class);
+        Authentication authentication = mock(Authentication.class);
+        given(securityContext.getAuthentication()).willReturn(authentication);
+        SecurityContextHolder.setContext(securityContext);
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, null, Collections.emptySet());
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        given(SecurityContextHolder.getContext().getAuthentication().getPrincipal()).willReturn(email);
+        given(userRepository.findByEmail(email)).willReturn(Optional.of(userEntity));
+        // when
+        underTest.changePassword(oldPassword, newPassword);
+
+        // then
+        ArgumentCaptor<UserEntity> argumentCaptor = ArgumentCaptor.forClass(UserEntity.class);
+
+        verify(userRepository).save(argumentCaptor.capture());
+
+        UserEntity capturedUser = argumentCaptor.getValue();
+
+        assertThat(capturedUser).isEqualTo(userEntityWithChangedPassword);
+    }
+
+    @Test
+    void testCanNotChangePassword() {
+        // given
+        String email = "test@test.com";
+        String oldPassword = "password";
+        String newPassword = "password123";
+        UserEntity userEntity = new UserEntity("John Doe", email, bCryptPasswordEncoder.encode(oldPassword), (byte) 0, false);
+        given(bCryptPasswordEncoder.matches(any(), any())).willReturn(false);
+        SecurityContext securityContext = mock(SecurityContext.class);
+        Authentication authentication = mock(Authentication.class);
+        given(securityContext.getAuthentication()).willReturn(authentication);
+        SecurityContextHolder.setContext(securityContext);
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, null, Collections.emptySet());
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        given(SecurityContextHolder.getContext().getAuthentication().getPrincipal()).willReturn(email);
+        given(userRepository.findByEmail(email)).willReturn(Optional.of(userEntity));
+
+        // when
+        // then
+        assertThatThrownBy(() -> underTest.changePassword(oldPassword, newPassword))
+                .isInstanceOf(ApiRequestException.class)
+                .hasMessageContaining("Old password does not match");
+
+        verify(userRepository, never()).save(any());
+    }
+}
\ No newline at end of file
