diff --git a/src/main/resources/templates/parts/pager.ftl b/src/main/resources/templates/parts/pager.ftl
index fbfbb8b..a789ef4 100644
--- a/src/main/resources/templates/parts/pager.ftl
+++ b/src/main/resources/templates/parts/pager.ftl
@@ -30,7 +30,7 @@
<#else>
- ${p}
+ ${p}
#if>
#list>
@@ -47,7 +47,7 @@
<#else>
- ${c}
+ ${c}
#if>
#list>
diff --git a/src/test/java/net/sytes/kashey/sweater/LoginTest.java b/src/test/java/net/sytes/kashey/sweater/controller/LoginTest.java
similarity index 98%
rename from src/test/java/net/sytes/kashey/sweater/LoginTest.java
rename to src/test/java/net/sytes/kashey/sweater/controller/LoginTest.java
index 3d9927d..9f4fa4e 100644
--- a/src/test/java/net/sytes/kashey/sweater/LoginTest.java
+++ b/src/test/java/net/sytes/kashey/sweater/controller/LoginTest.java
@@ -1,4 +1,4 @@
-package net.sytes.kashey.sweater;
+package net.sytes.kashey.sweater.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/test/java/net/sytes/kashey/sweater/MainControllerTest.java b/src/test/java/net/sytes/kashey/sweater/controller/MainControllerTest.java
similarity index 98%
rename from src/test/java/net/sytes/kashey/sweater/MainControllerTest.java
rename to src/test/java/net/sytes/kashey/sweater/controller/MainControllerTest.java
index f03a1ab..8413fe2 100644
--- a/src/test/java/net/sytes/kashey/sweater/MainControllerTest.java
+++ b/src/test/java/net/sytes/kashey/sweater/controller/MainControllerTest.java
@@ -1,4 +1,4 @@
-package net.sytes.kashey.sweater;
+package net.sytes.kashey.sweater.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
diff --git a/src/test/java/net/sytes/kashey/sweater/service/CustomUserDetailsServiceTest.java b/src/test/java/net/sytes/kashey/sweater/service/CustomUserDetailsServiceTest.java
new file mode 100644
index 0000000..6a031b1
--- /dev/null
+++ b/src/test/java/net/sytes/kashey/sweater/service/CustomUserDetailsServiceTest.java
@@ -0,0 +1,230 @@
+package net.sytes.kashey.sweater.service;
+
+import net.sytes.kashey.sweater.domain.Role;
+import net.sytes.kashey.sweater.domain.User;
+import net.sytes.kashey.sweater.repository.UserRepository;
+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.security.crypto.password.PasswordEncoder;
+
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class CustomUserDetailsServiceTest {
+
+ @Mock
+ UserRepository userRepository;
+
+ @Mock
+ EmailService emailService;
+
+ @Mock
+ PasswordEncoder passwordEncoder;
+
+ @InjectMocks
+ CustomUserDetailsService customUserDetailsService;
+
+ @Test
+ public void shouldAddUserWhenNewUserProvided() {
+
+ User user = new User();
+ user.setUsername("Dummy");
+ user.setEmail("test@gmail.com");
+
+ String isUserAdded = customUserDetailsService.addUser(user);
+
+ assertThat(isUserAdded).isEmpty();
+ assertThat(user.getActivationCode()).isNotNull();
+ assertThat(user.getRoles()).contains(Role.USER);
+ verify(userRepository, times(1))
+ .save(user);
+ verify(emailService, times(1))
+ .send(eq(user.getEmail()), anyString(), anyString());
+ }
+
+ @Test
+ public void shouldNotAddUserWhenUsernameIsEmpty() {
+
+ User user = new User();
+
+ String isUserAdded = customUserDetailsService.addUser(user);
+
+ assertThat(isUserAdded).contains("User name must not be empty!");
+ verify(userRepository, times(0))
+ .save(any(User.class));
+ verify(emailService, times(0))
+ .send(anyString(), anyString(), anyString());
+ }
+
+ @Test
+ public void shouldNotAddUserWhenUserAlreadyExists() {
+
+ User user = new User("Dummy");
+ when(userRepository.findByUsername(user.getUsername()))
+ .thenReturn(Optional.of(user));
+
+ String isUserAdded = customUserDetailsService.addUser(user);
+
+ assertThat(isUserAdded).contains("User already exists!");
+ verify(userRepository, times(0))
+ .save(user);
+ verify(emailService, times(0))
+ .send(anyString(), anyString(), anyString());
+ }
+
+ @Test
+ void shouldActivateUserWhenValidActivationCodeProvided() {
+
+ User user = new User();
+ user.setActivationCode("dummy_activation_code");
+ when(userRepository.findByActivationCode(user.getActivationCode()))
+ .thenReturn(user);
+
+ boolean isUserActivated = customUserDetailsService.activateUser(user.getActivationCode());
+
+ assertThat(isUserActivated).isTrue();
+ assertThat(user.getActivationCode()).isNull();
+ verify(userRepository, times(1))
+ .save(user);
+ }
+
+ @Test
+ void shouldNotActivateUserWhenInvalidActivationCodeProvided() {
+
+ boolean isUserActivated = customUserDetailsService.activateUser("dummy_activation_code");
+
+ assertThat(isUserActivated).isFalse();
+ verify(userRepository, times(0))
+ .save(any(User.class));
+ }
+
+ @Test
+ public void shouldUpdateUsernameAndRolesWhenValidDataProvided() {
+
+ User user = new User();
+ user.setUsername("oldUser");
+ user.setRoles(new HashSet<>(Collections.singleton(Role.USER)));
+
+ Map form = new HashMap<>();
+ form.put("ADMIN", "true");
+ form.put("USER", "true");
+
+ customUserDetailsService.updateUser("newUser", form, user);
+
+ assertThat(user.getUsername()).isEqualTo("newUser");
+ assertThat(user.getRoles()).containsExactlyInAnyOrder(Role.ADMIN, Role.USER);
+ verify(userRepository, times(1)).save(user);
+ }
+
+ @Test
+ public void shouldUpdateEmailAndGenerateActivationCodeWhenEmailChanged() {
+
+ User user = new User();
+ user.setEmail("old@gmail.com");
+
+ customUserDetailsService.updateProfile(user, null, "new@gmail.com");
+
+ assertThat(user.getEmail()).isEqualTo("new@gmail.com");
+ assertThat(user.getActivationCode()).isNotNull();
+ verify(userRepository, times(1)).save(user);
+ verify(emailService, times(1)).send(anyString(), anyString(), anyString());
+ }
+
+ @Test
+ public void shouldUpdatePasswordWhenProvided() {
+
+ User user = new User();
+ user.setPassword("oldPassword");
+
+ customUserDetailsService.updateProfile(user, "newPassword", null);
+
+ assertThat(user.getPassword()).isEqualTo("newPassword");
+ verify(userRepository, times(1)).save(user);
+ verify(emailService, times(0)).send(anyString(), anyString(), anyString());
+ }
+
+ @Test
+ public void shouldUpdateBothEmailAndPasswordWhenBothProvided() {
+
+ User user = new User();
+ user.setEmail("old@gmail.com");
+ user.setPassword("oldPassword");
+
+ customUserDetailsService.updateProfile(user, "newPassword", "new@gmail.com");
+
+ assertThat(user.getEmail()).isEqualTo("new@gmail.com");
+ assertThat(user.getPassword()).isEqualTo("newPassword");
+ assertThat(user.getActivationCode()).isNotNull();
+ verify(userRepository, times(1)).save(user);
+ verify(emailService, times(1)).send(anyString(), anyString(), anyString());
+ }
+
+ @Test
+ public void shouldReturnAllUsers() {
+
+ User user1 = new User("Dummy1");
+ User user2 = new User("Dummy2");
+ List users = Arrays.asList(user1, user2);
+ when(userRepository.findAll()).thenReturn(users);
+
+ List result = customUserDetailsService.findAll();
+
+ assertThat(result).hasSize(2);
+ assertThat(result).contains(user1, user2);
+ verify(userRepository, times(1)).findAll();
+ }
+
+ @Test
+ public void shouldReturnEmptyListWhenNoUsersFound() {
+
+ when(userRepository.findAll()).thenReturn(Collections.emptyList());
+
+ List result = customUserDetailsService.findAll();
+
+ assertThat(result).isEmpty();
+ verify(userRepository, times(1)).findAll();
+ }
+
+ @Test
+ public void shouldSubscribeUser() {
+
+ User subscriber = new User("Subscriber");
+ User userChannel = new User("UserChannel");
+
+ customUserDetailsService.subscribe(subscriber, userChannel);
+
+ assertThat(userChannel.getSubscribers()).contains(subscriber);
+ verify(userRepository, times(1)).save(userChannel);
+ }
+
+ @Test
+ public void shouldUnsubscribeUser() {
+
+ User subscriber = new User("Subscriber");
+ User userChannel = new User("UserChannel");
+ userChannel.getSubscribers().add(subscriber);
+
+ customUserDetailsService.unsubscribe(subscriber, userChannel);
+
+ assertThat(userChannel.getSubscribers()).doesNotContain(subscriber);
+ verify(userRepository, times(1)).save(userChannel);
+ }
+
+ @Test
+ public void shouldNotFailUnsubscribeIfUserIsNotSubscribed() {
+
+ User subscriber = new User("Subscriber");
+ User userChannel = new User("UserChannel");
+
+ customUserDetailsService.unsubscribe(subscriber, userChannel);
+
+ assertThat(userChannel.getSubscribers()).isEmpty();
+ verify(userRepository, times(1)).save(userChannel);
+ }
+}
\ No newline at end of file