diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index a4b4bac..c402990 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -22,13 +22,15 @@ jobs:
ref: main
fetch-depth: 0
- - name: Save fly.toml from deploy branch
+ - name: Save files from deploy branch
run: |
git fetch origin deploy || echo "No deploy branch yet"
mkdir temp
if git ls-remote --exit-code origin deploy; then
- git checkout origin/deploy -- fly.toml || echo "No fly.toml to copy"
- cp fly.toml temp/ || echo "No fly.toml found to copy"
+ for file in fly.toml .dockerignore; do
+ git checkout origin/deploy -- "$file" 2>/dev/null || echo "No $file to copy"
+ [ -f "$file" ] && cp "$file" temp/ || echo "$file not found"
+ done
fi
- name: Create/overwrite deploy branch
@@ -36,16 +38,18 @@ jobs:
git checkout -B deploy
git reset --hard main
- - name: Restore fly.toml
+ - name: Restore files
run: |
- if [ -f temp/fly.toml ]; then
- cp temp/fly.toml fly.toml
- fi
+ for file in fly.toml .dockerignore; do
+ if [ -f temp/$file ]; then
+ cp temp/$file $file
+ fi
+ done
- name: Commit and push to deploy
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
- git commit -m "Sync main -> deploy (keeping fly.toml)" || echo "No changes to commit"
- git push -f origin deploy
+ git commit -m "Sync main -> deploy (keeping fly.toml and .dockerignore)" || echo "No changes to commit"
+ git push -f origin deploy
\ No newline at end of file
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..2240980
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,23 @@
+name: Run Unit Tests
+
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ - name: Run unit tests
+ run: mvn test
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 401a7ce..d1ecfb8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -113,6 +113,12 @@
jackson-databind
2.15.2
+
+ org.mockito
+ mockito-core
+ 5.20.0
+ test
+
diff --git a/src/test/java/br/com/notehub/implementation/user/UserCreationTest.java b/src/test/java/br/com/notehub/implementation/user/UserCreationTest.java
new file mode 100644
index 0000000..9ddb517
--- /dev/null
+++ b/src/test/java/br/com/notehub/implementation/user/UserCreationTest.java
@@ -0,0 +1,156 @@
+package br.com.notehub.implementation.user;
+
+import br.com.notehub.application.implementation.user.UserServiceImpl;
+import br.com.notehub.domain.history.UserHistoryService;
+import br.com.notehub.domain.token.TokenService;
+import br.com.notehub.domain.user.User;
+import br.com.notehub.domain.user.UserRepository;
+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.dao.DataIntegrityViolationException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class UserCreationTest {
+
+ @InjectMocks
+ private UserServiceImpl service;
+
+ @Mock
+ private UserRepository repository;
+
+ @Mock
+ private TokenService tokenService;
+
+ @Mock
+ private UserHistoryService historian;
+
+ @Mock
+ private PasswordEncoder encoder;
+
+ private User user;
+
+ private User createUser(String email, String username) {
+ User u = new User(email, username, username.toUpperCase(), "123");
+ u.setId(UUID.randomUUID());
+ u.setActive(true);
+ return u;
+ }
+
+ private void mockExistsByEmail(User u, boolean condition) {
+ when(repository.existsByEmail(u.getEmail())).thenReturn(condition);
+ }
+
+ private void mockExistsByUsername(User u, boolean condition) {
+ when(repository.existsByUsername(u.getUsername())).thenReturn(condition);
+ }
+
+ private void mockSave(User u) {
+ when(repository.save(u)).thenReturn(u);
+ }
+
+ @BeforeEach
+ void setup() {
+ user = createUser("tester@notehub.com.br", "tester");
+ }
+
+ @Test
+ void shouldEncodePasswordAndSaveUser_whenDataIsValid() {
+
+ mockExistsByEmail(user, false);
+ mockExistsByUsername(user, false);
+ mockSave(user);
+ when(encoder.encode(user.getPassword())).thenReturn("encoded");
+
+ User result = service.create(user);
+
+ verify(repository).existsByEmail(user.getEmail());
+ verify(repository).existsByUsername(user.getUsername());
+ verify(encoder).encode("123");
+ verify(repository).save(user);
+
+ assertThat(result).isEqualTo(user);
+ assertThat(result.getEmail()).isEqualTo(user.getEmail());
+ assertThat(result.getUsername()).isEqualTo(user.getUsername());
+ assertThat(result.getDisplayName()).isEqualTo(user.getDisplayName());
+ assertThat(result.getPassword()).isEqualTo("encoded");
+
+ }
+
+ @Test
+ void shouldThrowException_whenEmailAlreadyExists() {
+
+ mockExistsByEmail(user, true);
+ mockExistsByUsername(user, false);
+
+ assertThatThrownBy(() -> service.create(user))
+ .isInstanceOf(DataIntegrityViolationException.class)
+ .hasMessage("email");
+
+ verify(repository, never()).save(user);
+
+ }
+
+ @Test
+ void shouldThrowException_whenUsernameAlreadyExists() {
+
+ mockExistsByEmail(user, false);
+ mockExistsByUsername(user, true);
+
+ assertThatThrownBy(() -> service.create(user))
+ .isInstanceOf(DataIntegrityViolationException.class)
+ .hasMessage("username");
+
+ verify(repository, never()).save(user);
+
+ }
+
+ @Test
+ void shouldThrowException_whenEmailAndUsernameAlreadyExist() {
+
+ mockExistsByEmail(user, true);
+ mockExistsByUsername(user, true);
+
+ assertThatThrownBy(() -> service.create(user))
+ .isInstanceOf(DataIntegrityViolationException.class)
+ .hasMessage("both");
+
+ verify(repository, never()).save(user);
+
+ }
+
+ @Test
+ void shouldReturnToken_whenGeneratingActivationToken() {
+ when(tokenService.generateActivationToken(user)).thenReturn("token");
+ String token = service.generateActivationToken(user);
+ assertThat(token).isEqualTo("token");
+ }
+
+ @Test
+ void shouldSetActiveTrueAndLogHistory_whenActivatingUser() {
+
+ User inactive = createUser("inactive@notehub.com.br", "inactive");
+ inactive.setActive(false);
+
+ when(repository.findById(inactive.getId())).thenReturn(Optional.of(inactive));
+ mockSave(inactive);
+
+ service.activate(inactive.getId());
+
+ verify(repository).save(inactive);
+ verify(historian).setHistory(inactive, "active", "false", "true");
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/br/com/notehub/implementation/user/UserDeletionTest.java b/src/test/java/br/com/notehub/implementation/user/UserDeletionTest.java
new file mode 100644
index 0000000..ddf169a
--- /dev/null
+++ b/src/test/java/br/com/notehub/implementation/user/UserDeletionTest.java
@@ -0,0 +1,84 @@
+package br.com.notehub.implementation.user;
+
+import br.com.notehub.application.implementation.user.UserServiceImpl;
+import br.com.notehub.domain.note.NoteService;
+import br.com.notehub.domain.user.User;
+import br.com.notehub.domain.user.UserRepository;
+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.security.authentication.BadCredentialsException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class UserDeletionTest {
+
+ @InjectMocks
+ private UserServiceImpl service;
+
+ @Mock
+ private UserRepository repository;
+
+ @Mock
+ private NoteService noteService;
+
+ @Mock
+ private PasswordEncoder encoder;
+
+ private User user;
+
+ private void mockFindById(User u) {
+ when(repository.findById(u.getId())).thenReturn(Optional.of(u));
+ }
+
+ private void mockMatches(boolean condition) {
+ when(encoder.matches(anyString(), anyString())).thenReturn(condition);
+ }
+
+ @BeforeEach
+ void setup() {
+ user = new User("tester@notehub.com.br", "tester", "TESTER", "123");
+ user.setId(UUID.randomUUID());
+ user.setActive(true);
+ }
+
+ @Test
+ void shouldDeleteUser_whenPasswordMatches() {
+
+ mockFindById(user);
+ mockMatches(true);
+
+ service.delete(user.getId(), user.getPassword());
+
+ verify(repository).findById(user.getId());
+ verify(repository).delete(user);
+ verify(noteService).deleteAllUserHiddenNotes(user);
+
+ }
+
+ @Test
+ void shouldThrowBadCredentialsException_whenPasswordDoesNotMatch() {
+
+ mockFindById(user);
+ mockMatches(false);
+
+ assertThatThrownBy(() ->
+ service.delete(user.getId(), user.getPassword()))
+ .isInstanceOf(BadCredentialsException.class);
+
+ verify(repository).findById(user.getId());
+ verify(repository, never()).delete(user);
+ verify(noteService, never()).deleteAllUserHiddenNotes(user);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/br/com/notehub/implementation/user/UserEditionTest.java b/src/test/java/br/com/notehub/implementation/user/UserEditionTest.java
new file mode 100644
index 0000000..46f8277
--- /dev/null
+++ b/src/test/java/br/com/notehub/implementation/user/UserEditionTest.java
@@ -0,0 +1,101 @@
+package br.com.notehub.implementation.user;
+
+import br.com.notehub.application.implementation.user.UserServiceImpl;
+import br.com.notehub.domain.history.UserHistoryService;
+import br.com.notehub.domain.user.User;
+import br.com.notehub.domain.user.UserRepository;
+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.dao.DataIntegrityViolationException;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class UserEditionTest {
+
+ @InjectMocks
+ private UserServiceImpl service;
+
+ @Mock
+ private UserRepository repository;
+
+ @Mock
+ private UserHistoryService historian;
+
+ private User user;
+
+ private User updateUser(String name, boolean profilePrivate) {
+ return new User(name, name.toUpperCase(), null, null, null, profilePrivate);
+ }
+
+ private void mockFindById(User user) {
+ when(repository.findById(user.getId())).thenReturn(Optional.of(user));
+ }
+
+ @BeforeEach
+ void setup() {
+ user = new User("tester@notehub.com.br", "tester", "TESTER", "123");
+ user.setId(UUID.randomUUID());
+ user.setActive(true);
+ }
+
+ @Test
+ void shouldSaveHistoryAndPersistChanges_whenDataIsModified() {
+
+ User updated = updateUser("updated", true);
+
+ mockFindById(user);
+
+ assertThat(updated).isEqualTo(service.edit(user.getId(), updated));
+
+ verify(repository, times(6)).findById(user.getId());
+ verify(repository, times(3)).save(user);
+ verify(historian).setHistory(user, "username", "tester", "updated");
+ verify(historian).setHistory(user, "display_name", "TESTER", "UPDATED");
+ verify(historian).setHistory(user, "profile_private", "false", "true");
+
+ }
+
+ @Test
+ void shouldSkipSaveAndHistory_whenUserDataIsUnchanged() {
+
+ User updated = updateUser("tester", false);
+
+ mockFindById(user);
+
+ assertThat(updated).isEqualTo(service.edit(user.getId(), updated));
+
+ verify(repository, times(6)).findById(user.getId());
+ verify(repository, never()).save(user);
+ verify(historian, never()).setHistory(any(), any(), any(), any());
+
+ }
+
+ @Test
+ void shouldThrowDataIntegrityViolationException_whenUsernameAlreadyExists() {
+
+ User updated = updateUser("tester", false);
+
+ mockFindById(user);
+ when(repository.findByUsername("tester")).thenReturn(Optional.of(updated));
+
+ assertThatThrownBy(() -> service.edit(user.getId(), updated))
+ .isInstanceOf(DataIntegrityViolationException.class)
+ .hasMessage("username");
+
+ verify(repository).findById(user.getId());
+ verify(repository, never()).save(user);
+ verify(historian, never()).setHistory(any(), any(), any(), any());
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/br/com/notehub/implementation/user/UserRelationshipTest.java b/src/test/java/br/com/notehub/implementation/user/UserRelationshipTest.java
new file mode 100644
index 0000000..c0d5e0b
--- /dev/null
+++ b/src/test/java/br/com/notehub/implementation/user/UserRelationshipTest.java
@@ -0,0 +1,149 @@
+package br.com.notehub.implementation.user;
+
+import br.com.notehub.application.counter.Counter;
+import br.com.notehub.application.dto.notification.MessageNotification;
+import br.com.notehub.application.implementation.user.UserServiceImpl;
+import br.com.notehub.domain.notification.NotificationService;
+import br.com.notehub.domain.user.User;
+import br.com.notehub.domain.user.UserRepository;
+import br.com.notehub.infra.exception.CustomExceptions;
+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 java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class UserRelationshipTest {
+
+ @InjectMocks
+ private UserServiceImpl service;
+
+ @Mock
+ private UserRepository repository;
+
+ @Mock
+ private NotificationService notifier;
+
+ @Mock
+ private Counter counter;
+
+ private User follower;
+ private User following;
+
+ private User createUser(String email, String username) {
+ User u = new User(email, username, username.toUpperCase(), "123");
+ u.setId(UUID.randomUUID());
+ u.setActive(true);
+ return u;
+ }
+
+ private void mockfindByIdWithFollowersAndFollowing(User u) {
+ when(repository.findByIdWithFollowersAndFollowing(u.getId())).thenReturn(Optional.of(u));
+ }
+
+ private void mockfindByUsernameWithFollowersAndFollowing(User u) {
+ when(repository.findByUsernameWithFollowersAndFollowing(u.getUsername())).thenReturn(Optional.of(u));
+ }
+
+ @BeforeEach
+ void setup() {
+ follower = createUser("follower@notehub.com.br", "follower");
+ following = createUser("following@notehub.com.br", "following");
+ }
+
+ @Test
+ void shouldFollowUserAndSendNotification_whenNotAlreadyFollowing() {
+
+ mockfindByIdWithFollowersAndFollowing(follower);
+ mockfindByUsernameWithFollowersAndFollowing(following);
+
+ service.follow(follower.getId(), following.getUsername());
+
+ verify(repository).findByIdWithFollowersAndFollowing(follower.getId());
+ verify(repository).findByUsernameWithFollowersAndFollowing(following.getUsername());
+ verify(counter).updateFollowersAndFollowingCount(eq(follower), eq(following), eq(true));
+ verify(notifier).notify(eq(follower), eq(following), eq(follower), eq(MessageNotification.of(follower)));
+
+ }
+
+ @Test
+ void shouldUnfollowUserWithoutNotification_whenAlreadyFollowing() {
+
+ mockfindByIdWithFollowersAndFollowing(follower);
+ mockfindByUsernameWithFollowersAndFollowing(following);
+
+ follower.getFollowing().add(following);
+ following.getFollowers().add(follower);
+ service.unfollow(follower.getId(), following.getUsername());
+
+ verify(repository).findByIdWithFollowersAndFollowing(follower.getId());
+ verify(repository).findByUsernameWithFollowersAndFollowing(following.getUsername());
+ verify(counter).updateFollowersAndFollowingCount(eq(follower), eq(following), eq(false));
+ verify(notifier, never()).notify(any(), any(), any(), any());
+
+ }
+
+ @Test
+ void shouldThrowSelfFollowException_whenTryingToFollowSelf() {
+
+ mockfindByIdWithFollowersAndFollowing(follower);
+ mockfindByUsernameWithFollowersAndFollowing(follower);
+
+ assertThatThrownBy(() ->
+ service.follow(follower.getId(), follower.getUsername()))
+ .isInstanceOf(CustomExceptions.SelfFollowException.class);
+
+ verify(repository).findByIdWithFollowersAndFollowing(follower.getId());
+ verify(repository).findByUsernameWithFollowersAndFollowing(follower.getUsername());
+ verify(counter, never()).updateFollowersAndFollowingCount(any(), any(), any(Boolean.class));
+ verify(notifier, never()).notify(any(), any(), any(), any());
+
+ }
+
+ @Test
+ void shouldThrowAlreadyFollowingException_whenFollowingUserAgain() {
+
+ mockfindByIdWithFollowersAndFollowing(follower);
+ mockfindByUsernameWithFollowersAndFollowing(following);
+
+ follower.getFollowing().add(following);
+ following.getFollowers().add(follower);
+
+ assertThatThrownBy(() ->
+ service.follow(follower.getId(), following.getUsername()))
+ .isInstanceOf(CustomExceptions.AlreadyFollowingException.class);
+
+ verify(repository).findByIdWithFollowersAndFollowing(follower.getId());
+ verify(repository).findByUsernameWithFollowersAndFollowing(following.getUsername());
+ verify(counter, never()).updateFollowersAndFollowingCount(any(), any(), any(Boolean.class));
+ verify(notifier, never()).notify(any(), any(), any(), any());
+
+ }
+
+ @Test
+ void shouldThrowNotFollowingException_whenUnfollowingUserNotFollowed() {
+
+ mockfindByIdWithFollowersAndFollowing(follower);
+ mockfindByUsernameWithFollowersAndFollowing(following);
+
+ assertThatThrownBy(() ->
+ service.unfollow(follower.getId(), following.getUsername()))
+ .isInstanceOf(CustomExceptions.NotFollowingException.class);
+
+ verify(repository).findByIdWithFollowersAndFollowing(follower.getId());
+ verify(repository).findByUsernameWithFollowersAndFollowing(following.getUsername());
+ verify(counter, never()).updateFollowersAndFollowingCount(any(), any(), any(Boolean.class));
+ verify(notifier, never()).notify(any(), any(), any(), any());
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/br/com/notehub/implementation/user/UserRetrievalTest.java b/src/test/java/br/com/notehub/implementation/user/UserRetrievalTest.java
new file mode 100644
index 0000000..7025495
--- /dev/null
+++ b/src/test/java/br/com/notehub/implementation/user/UserRetrievalTest.java
@@ -0,0 +1,325 @@
+package br.com.notehub.implementation.user;
+
+import br.com.notehub.application.implementation.user.UserServiceImpl;
+import br.com.notehub.domain.history.UserHistoryService;
+import br.com.notehub.domain.user.Subscription;
+import br.com.notehub.domain.user.User;
+import br.com.notehub.domain.user.UserRepository;
+import jakarta.persistence.EntityNotFoundException;
+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.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.access.AccessDeniedException;
+
+import java.util.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class UserRetrievalTest {
+
+ @InjectMocks
+ private UserServiceImpl service;
+
+ @Mock
+ private UserRepository repository;
+
+ @Mock
+ private UserHistoryService historian;
+
+ private User user;
+ private Pageable pageable;
+
+ private User createUser(String email, String username) {
+ User u = new User(email, username, username.toUpperCase(), "123");
+ u.setId(UUID.randomUUID());
+ u.setActive(true);
+ return u;
+ }
+
+ private void mockFindById(User u) {
+ when(repository.findById(u.getId())).thenReturn(Optional.of(u));
+ }
+
+ private void mockFindByUsername(User u) {
+ when(repository.findByUsername(u.getUsername())).thenReturn(Optional.of(u));
+ }
+
+ @BeforeEach
+ void setup() {
+ user = createUser("tester@notehub.com.br", "tester");
+ pageable = PageRequest.of(0, 10);
+ }
+
+ @Test
+ void shouldReturnUser_whenUsernameWasFoundAndUserIsActive() {
+
+ mockFindByUsername(user);
+
+ User result = service.getUser(user.getUsername());
+
+ verify(repository).findByUsername(user.getUsername());
+
+ assertThat(result).isEqualTo(user);
+
+ }
+
+ @Test
+ void shouldThrowEntityNotFoundException_whenUsernameWasNotFoundOrUserIsInactive() {
+
+ when(repository.findByUsername(anyString())).thenReturn(Optional.empty());
+
+ assertThatThrownBy(() -> service.getUser(user.getUsername()))
+ .isInstanceOf(EntityNotFoundException.class);
+
+ verify(repository).findByUsername(user.getUsername());
+
+ }
+
+ @Test
+ void shouldReturnListOfActiveUsers() {
+
+ when(repository.findAllByActiveTrue()).thenReturn(List.of(user));
+
+ List result = service.getAllActiveUsers();
+
+ verify(repository).findAllByActiveTrue();
+
+ assertThat(result).containsExactly(user);
+
+ }
+
+ @Test
+ void shouldReturnPageOfActiveUsersByQuery() {
+
+ String query = "tester";
+ Page page = new PageImpl<>(List.of(user));
+
+ when(repository.findAllActiveUsersByUsernameOrDisplayName(pageable, query)).thenReturn(page);
+
+ Page result = service.findAll(pageable, query);
+
+ verify(repository).findAllActiveUsersByUsernameOrDisplayName(pageable, query);
+
+ assertThat(result).isEqualTo(page);
+
+ }
+
+ @Test
+ void shouldReturnPageOfUsersThatRequestedUserIsFollowing() {
+
+ User requesting = user;
+ User target = createUser("target@mail.com", "target");
+ User u1 = createUser("a@mail.com", "a");
+ User u2 = createUser("b@mail.com", "b");
+
+ List users = List.of(u1, u2);
+ Set ids = Set.of(u1.getId(), u2.getId());
+ target.getFollowing().addAll(users);
+
+ Page expected = new PageImpl<>(users);
+
+ mockFindById(requesting);
+ mockFindByUsername(target);
+
+ when(repository.findAllByIdIn(
+ eq(pageable),
+ eq("q"),
+ argThat(list -> new HashSet<>(list).equals(ids))
+ )).thenReturn(expected);
+
+ Page result = service.getUserFollowing(pageable, "q", requesting.getId(), target.getUsername());
+
+ verify(repository).findById(requesting.getId());
+ verify(repository).findByUsername(target.getUsername());
+ verify(repository).findAllByIdIn(
+ eq(pageable),
+ eq("q"),
+ argThat(list -> new HashSet<>(list).equals(ids))
+ );
+
+ assertThat(result).isEqualTo(expected);
+
+ }
+
+ @Test
+ void shouldReturnPageOfUsersThatFollowsRequestedUser() {
+
+ User requesting = user;
+ User target = createUser("target@mail.com", "target");
+ User u1 = createUser("a@mail.com", "a");
+ User u2 = createUser("b@mail.com", "b");
+
+ List users = List.of(u1, u2);
+ Set ids = Set.of(u1.getId(), u2.getId());
+ target.getFollowers().addAll(users);
+
+ Page expected = new PageImpl<>(users);
+
+ mockFindById(requesting);
+ mockFindByUsername(target);
+
+ when(repository.findAllByIdIn(
+ eq(pageable),
+ eq("q"),
+ argThat(list -> new HashSet<>(list).equals(ids))
+ )).thenReturn(expected);
+
+ Page result = service.getUserFollowers(pageable, "q", requesting.getId(), target.getUsername());
+
+ verify(repository).findById(requesting.getId());
+ verify(repository).findByUsername(target.getUsername());
+ verify(repository).findAllByIdIn(
+ eq(pageable),
+ eq("q"),
+ argThat(list -> new HashSet<>(list).equals(ids))
+ );
+
+ assertThat(result).isEqualTo(expected);
+
+ }
+
+ @Test
+ void shouldThrowEntityNotFoundException_whenRequestingUserNotFound() {
+
+ UUID id = UUID.randomUUID();
+
+ when(repository.findById(id)).thenReturn(Optional.empty());
+
+ assertThatThrownBy(() ->
+ service.getUserFollowing(pageable, "q", id, "someone"))
+ .isInstanceOf(EntityNotFoundException.class);
+
+ verify(repository).findById(id);
+ verify(repository, never()).findByUsername(anyString());
+ verify(repository, never()).findAllByIdIn(any(), anyString(), anyList());
+
+ }
+
+ @Test
+ void shouldThrowEntityNotFoundException_whenRequestedUserNotFound() {
+
+ mockFindById(user);
+ when(repository.findByUsername(anyString())).thenReturn(Optional.empty());
+
+ assertThatThrownBy(() ->
+ service.getUserFollowing(pageable, "q", user.getId(), "unknown"))
+ .isInstanceOf(EntityNotFoundException.class);
+
+ verify(repository).findById(user.getId());
+ verify(repository).findByUsername("unknown");
+ verify(repository, never()).findAllByIdIn(any(), anyString(), anyList());
+
+ }
+
+ @Test
+ void shouldThrowAccessDeniedException_whenAccessIsNotAllowed() {
+
+ User requesting = createUser("req@mail.com", "req");
+ User target = createUser("tar@mail.com", "tar");
+ target.setProfilePrivate(true);
+
+ mockFindById(requesting);
+ mockFindByUsername(target);
+
+ assertThatThrownBy(() ->
+ service.getUserFollowing(pageable, "q", requesting.getId(), "tar"))
+ .isInstanceOf(AccessDeniedException.class);
+
+ verify(repository).findById(requesting.getId());
+ verify(repository).findByUsername("tar");
+ verify(repository, never()).findAllByIdIn(any(), anyString(), anyList());
+
+ }
+
+ @Test
+ void shouldReturnStringSetOfAllUserMutuals() {
+
+ User u1 = createUser("a@mail.com", "a");
+ User u2 = createUser("b@mail.com", "b");
+
+ List users = List.of(u1, u2);
+
+ user.getFollowers().addAll(users);
+ user.getFollowing().addAll(users);
+
+ when(repository.findByIdWithFollowersAndFollowing(user.getId())).thenReturn(Optional.of(user));
+
+ Set mutuals = service.getUserMutualConnections(user.getId());
+
+ verify(repository).findByIdWithFollowersAndFollowing(user.getId());
+
+ assertThat(mutuals).containsExactlyInAnyOrder("a", "b");
+
+ }
+
+ @Test
+ void shouldReturnEmptySet_whenUserHasNoMutualConnections() {
+
+ user.getFollowers().add(createUser("a@mail.com", "a"));
+ user.getFollowing().add(createUser("b@mail.com", "b"));
+
+ when(repository.findByIdWithFollowersAndFollowing(user.getId())).thenReturn(Optional.of(user));
+
+ Set mutuals = service.getUserMutualConnections(user.getId());
+
+ verify(repository).findByIdWithFollowersAndFollowing(user.getId());
+
+ assertThat(mutuals).isEmpty();
+
+ }
+
+ @Test
+ void shouldReturnListOfUserUsernameHistory() {
+
+ List expected = List.of("tester", "new");
+
+ mockFindByUsername(user);
+ when(historian.getLastFiveUserDisplayName(user)).thenReturn(expected);
+
+ List history = service.getUserDisplayNameHistory(user.getUsername());
+
+ assertThat(history).containsExactlyElementsOf(expected);
+
+ }
+
+ @Test
+ void shouldReturnListOfUserSubscriptions() {
+
+ mockFindById(user);
+
+ Set expected = Set.of(Subscription.MAINTENANCE, Subscription.RELEASE);
+ Set subscriptions = service.getUserSubscriptions(user.getId());
+
+ verify(repository).findById(user.getId());
+
+ assertThat(subscriptions).containsExactlyInAnyOrderElementsOf(expected);
+
+ }
+
+ @Test
+ void shouldCleanUsersWithExpiredActivationTime() {
+
+ User u1 = createUser("a@mail.com", "a");
+ User u2 = createUser("b@mail.com", "b");
+ List expired = List.of(u1, u2);
+
+ when(repository.findUsersWithExpiredActivationTime(any())).thenReturn(expired);
+
+ service.cleanUsersWithExpiredActivationTime();
+
+ verify(repository).findUsersWithExpiredActivationTime(any());
+ verify(repository).deleteAll(expired);
+
+ }
+
+}
\ No newline at end of file