From be325d78aa9bd58c6456fa6f4a32b1186d553840 Mon Sep 17 00:00:00 2001 From: Josip Date: Sat, 28 Jun 2025 16:33:06 +0200 Subject: [PATCH 1/2] Add unit test && test workflow --- .github/workflows/test.yml | 25 ++ backend/pom.xml | 28 +- .../AlgebraSocialNetworkApplicationTests.java | 11 - .../service/FriendsServiceTest.java | 184 +++++++++ .../service/PostServiceTest.java | 348 ++++++++++++++++++ .../service/UserServiceTest.java | 216 +++++++++++ 6 files changed, 800 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 backend/src/test/java/hr/algebra/socialnetwork/AlgebraSocialNetworkApplicationTests.java create mode 100644 backend/src/test/java/hr/algebra/socialnetwork/service/FriendsServiceTest.java create mode 100644 backend/src/test/java/hr/algebra/socialnetwork/service/PostServiceTest.java create mode 100644 backend/src/test/java/hr/algebra/socialnetwork/service/UserServiceTest.java diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7705f6e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Run Maven Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + - name: Run tests with Maven + run: mvn clean test + diff --git a/backend/pom.xml b/backend/pom.xml index f7dd4ec..8d8c389 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot @@ -59,6 +59,24 @@ aws-java-sdk-s3 1.12.664 + + org.mockito + mockito-core + 5.7.0 + test + + + org.mockito + mockito-junit-jupiter + 5.7.0 + test + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + org.springframework.boot spring-boot-starter-security @@ -102,6 +120,14 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + false + + org.springframework.boot spring-boot-maven-plugin diff --git a/backend/src/test/java/hr/algebra/socialnetwork/AlgebraSocialNetworkApplicationTests.java b/backend/src/test/java/hr/algebra/socialnetwork/AlgebraSocialNetworkApplicationTests.java deleted file mode 100644 index f868fde..0000000 --- a/backend/src/test/java/hr/algebra/socialnetwork/AlgebraSocialNetworkApplicationTests.java +++ /dev/null @@ -1,11 +0,0 @@ -package hr.algebra.socialnetwork; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class AlgebraSocialNetworkApplicationTests { - - @Test - void contextLoads() {} -} diff --git a/backend/src/test/java/hr/algebra/socialnetwork/service/FriendsServiceTest.java b/backend/src/test/java/hr/algebra/socialnetwork/service/FriendsServiceTest.java new file mode 100644 index 0000000..a3ad4db --- /dev/null +++ b/backend/src/test/java/hr/algebra/socialnetwork/service/FriendsServiceTest.java @@ -0,0 +1,184 @@ +package hr.algebra.socialnetwork.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import hr.algebra.socialnetwork.dto.UserSummaryDTO; +import hr.algebra.socialnetwork.mapper.UserSummaryDTOMapper; +import hr.algebra.socialnetwork.model.FriendRequest; +import hr.algebra.socialnetwork.model.RequestStatus; +import hr.algebra.socialnetwork.model.User; +import hr.algebra.socialnetwork.repository.FriendRequestRepository; +import hr.algebra.socialnetwork.repository.UserRepository; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class FriendsServiceTest { + + @Mock private UserRepository userRepository; + @Mock private FriendRequestRepository friendRequestRepository; + @Mock private UserSummaryDTOMapper userSummaryDTOMapper; + + @InjectMocks private FriendService friendService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void sendFriendRequest_WillSave_WhenValid() { + // GIVEN + String senderEmail = "milica@krmpotich.com"; + Long receiverId = 2L; + + User sender = new User(); + sender.setId(1L); + sender.setFriends(new HashSet<>()); + + User receiver = new User(); + receiver.setId(receiverId); + + when(userRepository.findByEmail(senderEmail)).thenReturn(Optional.of(sender)); + when(userRepository.findById(receiverId)).thenReturn(Optional.of(receiver)); + when(friendRequestRepository.existsBySenderAndReceiverAndStatus( + sender, receiver, RequestStatus.PENDING)) + .thenReturn(false); + + // WHEN + friendService.sendFriendRequest(senderEmail, receiverId); + + // THEN + verify(friendRequestRepository).save(any(FriendRequest.class)); + } + + @Test + void approveRequest_WillAddUsersAsFriends_WhenAuthorized() { + // GIVEN + String receiverEmail = "branko@example.com"; + Long requestId = 1L; + + User receiver = new User(); + receiver.setId(2L); + receiver.setFriends(new HashSet<>()); + + User sender = new User(); + sender.setId(1L); + sender.setFriends(new HashSet<>()); + + FriendRequest request = new FriendRequest(); + request.setId(requestId); + request.setSender(sender); + request.setReceiver(receiver); + request.setStatus(RequestStatus.PENDING); + + when(userRepository.findByEmail(receiverEmail)).thenReturn(Optional.of(receiver)); + when(friendRequestRepository.findById(requestId)).thenReturn(Optional.of(request)); + + // WHEN + friendService.approveRequest(receiverEmail, requestId); + + // THEN + assertTrue(sender.getFriends().contains(receiver)); + verify(userRepository).save(sender); + verify(friendRequestRepository).save(request); + } + + @Test + void removeFriend_WillMutuallyRemove_WhenUsersExist() { + // GIVEN + String requesterEmail = "milicah@example-svima.com"; + Long otherUserId = 2L; + + User requester = new User(); + requester.setId(1L); + User other = new User(); + other.setId(otherUserId); + + requester.setFriends(new HashSet<>(Set.of(other))); + other.setFriends(new HashSet<>(Set.of(requester))); + + when(userRepository.findByEmail(requesterEmail)).thenReturn(Optional.of(requester)); + when(userRepository.findById(otherUserId)).thenReturn(Optional.of(other)); + + // WHEN + friendService.removeFriend(requesterEmail, otherUserId); + + // THEN + assertFalse(requester.getFriends().contains(other)); + assertFalse(other.getFriends().contains(requester)); + verify(userRepository, times(2)).save(any()); + } + + @Test + void getFriends_WillReturnMappedFriends_WhenUserExists() { + // GIVEN + String email = "milicah@example.com"; + + User user = new User(); + user.setId(1L); + + User friend1 = new User(); + friend1.setId(2L); + friend1.setEmail("branko@example.com"); + friend1.setFirstName("Branko"); + friend1.setLastName("Mamic"); + + user.setFriends(Set.of(friend1)); + + UserSummaryDTO dto = + new UserSummaryDTO( + friend1.getId(), friend1.getFirstName(), friend1.getLastName(), friend1.getEmail()); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(userSummaryDTOMapper.apply(friend1)).thenReturn(dto); + + // WHEN + List result = friendService.getFriends(email); + + // THEN + assertEquals(1, result.size()); + assertEquals(dto, result.getFirst()); + } + + @Test + void declineRequest_WillMarkAsRejected_WhenAuthorized() { + // GIVEN + String receiverEmail = "milica@example-svima.com"; + Long requestId = 1L; + + User receiver = new User(); + receiver.setId(2L); + + User sender = new User(); + sender.setId(1L); + + FriendRequest request = new FriendRequest(); + request.setId(requestId); + request.setReceiver(receiver); + request.setSender(sender); + request.setStatus(RequestStatus.PENDING); + + when(userRepository.findByEmail(receiverEmail)).thenReturn(Optional.of(receiver)); + when(friendRequestRepository.findById(requestId)).thenReturn(Optional.of(request)); + + // WHEN + friendService.declineRequest(receiverEmail, requestId); + + // THEN + assertEquals(RequestStatus.REJECTED, request.getStatus()); + verify(friendRequestRepository).save(request); + } +} diff --git a/backend/src/test/java/hr/algebra/socialnetwork/service/PostServiceTest.java b/backend/src/test/java/hr/algebra/socialnetwork/service/PostServiceTest.java new file mode 100644 index 0000000..2cea184 --- /dev/null +++ b/backend/src/test/java/hr/algebra/socialnetwork/service/PostServiceTest.java @@ -0,0 +1,348 @@ +package hr.algebra.socialnetwork.service; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.startsWith; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import hr.algebra.socialnetwork.dto.PostDTO; +import hr.algebra.socialnetwork.exception.ResourceNotFoundException; +import hr.algebra.socialnetwork.mapper.CommentDTOMapper; +import hr.algebra.socialnetwork.mapper.PostDTOMapper; +import hr.algebra.socialnetwork.model.Post; +import hr.algebra.socialnetwork.model.User; +import hr.algebra.socialnetwork.payload.CreatePostRequest; +import hr.algebra.socialnetwork.payload.UpdatePostRequest; +import hr.algebra.socialnetwork.repository.*; +import hr.algebra.socialnetwork.s3.S3Service; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +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.web.multipart.MultipartFile; + +class PostServiceTest { + + @Mock private PostRepository postRepository; + @Mock private UserRepository userRepository; + @Mock private CommentRepository commentRepository; + @Mock private RatingRepository ratingRepository; + @Mock private PostDTOMapper postDTOMapper; + @Mock private CommentDTOMapper commentDTOMapper; + @Mock private S3Service s3Service; + + @InjectMocks private PostService postService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void getAllPosts_WillReturnPageOfPostDTOs() { + // GIVEN + Post post1 = new Post(); + Post post2 = new Post(); + Page posts = new PageImpl<>(List.of(post1, post2)); + Pageable pageable = PageRequest.of(0, 10); + + PostDTO dto1 = mock(PostDTO.class); + PostDTO dto2 = mock(PostDTO.class); + + when(postRepository.findAll(pageable)).thenReturn(posts); + when(postDTOMapper.apply(post1)).thenReturn(dto1); + when(postDTOMapper.apply(post2)).thenReturn(dto2); + + // WHEN + Page result = postService.getAllPosts(pageable); + + // THEN + assertEquals(2, result.getTotalElements()); + } + + @Test + void getPostById_WillReturnPostDTO_WhenPostExists() { + // GIVEN + Long postId = 1L; + Post post = new Post(); + PostDTO postDTO = new PostDTO(postId, "title", "content", null, 0.0, null, null, null); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(postDTOMapper.apply(post)).thenReturn(postDTO); + + // WHEN + PostDTO result = postService.getPostById(postId); + + // THEN + assertNotNull(result); + assertEquals(postDTO, result); + } + + @Test + void getPostById_WillThrowException_WhenPostNotFound() { + // GIVEN + Long postId = 100L; + when(postRepository.findById(postId)).thenReturn(Optional.empty()); + + // THEN + assertThrows(ResourceNotFoundException.class, () -> postService.getPostById(postId)); + } + + @Test + void getPostImage_WillReturnBytes_WhenImageIsSet() { + // GIVEN + Long postId = 1L; + Post post = new Post(); + post.setId(postId); + post.setImageId("image123"); + byte[] expectedBytes = new byte[] {10, 20}; + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(s3Service.getObject("post-images/1/image123.jpg")).thenReturn(expectedBytes); + + // WHEN + byte[] result = postService.getPostImage(postId); + + // THEN + assertArrayEquals(expectedBytes, result); + verify(s3Service).getObject("post-images/1/image123.jpg"); + } + + @Test + void getPostImage_WillThrow_WhenImageNotSetOrPostMissing() { + // CASE 1: POST NOT FOUND + when(postRepository.findById(1L)).thenReturn(Optional.empty()); + assertThrows(ResourceNotFoundException.class, () -> postService.getPostImage(1L)); + + // CASE 2: POST FOUND BUT IMAGE ID IS BLANK + Post post = new Post(); + post.setImageId(" "); + when(postRepository.findById(2L)).thenReturn(Optional.of(post)); + assertThrows(ResourceNotFoundException.class, () -> postService.getPostImage(2L)); + } + + @Test + void createPost_WillSaveAndReturnPostDTO_WhenUserExists() { + // GIVEN + String email = "milica@example-svima.com"; + User user = new User(); + user.setId(1L); + + CreatePostRequest request = new CreatePostRequest(); + request.setTitle("Test Title"); + request.setContent("Test Content"); + request.setImageId("img123"); + + Post savedPost = new Post(); + savedPost.setId(1L); + + PostDTO postDTO = + new PostDTO( + savedPost.getId(), + request.getTitle(), + request.getContent(), + request.getImageId(), + 0.0, + null, + null, + null); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(postRepository.save(any(Post.class))).thenReturn(savedPost); + when(postDTOMapper.apply(savedPost)).thenReturn(postDTO); + + // WHEN + PostDTO result = postService.createPost(email, request); + + // THEN + assertNotNull(result); + assertEquals(postDTO, result); + } + + @Test + void ratePost_WillCreateOrUpdateRating_AndReturnAverage() { + // GIVEN + Long postId = 1L; + String email = "milicakkk@example.com"; + int stars = 4; + + User user = new User(); + Post post = new Post(); + post.setId(postId); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(ratingRepository.findByPostAndUser(post, user)).thenReturn(Optional.empty()); + + // WHEN + double result = postService.ratePost(postId, email, stars); + + // THEN + verify(ratingRepository).save(any()); + assertEquals(post.getAverageRating(), result); + } + + @Test + void commentOnPost_WillSaveComment_WhenValidInput() { + // GIVEN + Long postId = 1L; + String email = "milicakkk@example.com"; + String content = "Nice post!"; + + Post post = new Post(); + User user = new User(); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + + // WHEN + postService.commentOnPost(postId, email, content); + + // THEN + verify(commentRepository).save(any()); + } + + @Test + void updatePostById_WillUpdateFields_WhenPostExists() { + // GIVEN + Long postId = 1L; + + UpdatePostRequest request = new UpdatePostRequest(); + request.setTitle("Milica Krmpotic u Zadru"); + request.setContent("Kamen za gnjecenje zelja"); + request.setImageId("newImageId"); + + Post post = new Post(); + post.setId(postId); + + Post savedPost = new Post(); + savedPost.setId(postId); + savedPost.setTitle(request.getTitle()); + savedPost.setContent(request.getContent()); + savedPost.setImageId(request.getImageId()); + + PostDTO postDTO = + new PostDTO( + postId, + request.getTitle(), + request.getContent(), + request.getImageId(), + 0.0, + null, + null, + null); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(postRepository.save(post)).thenReturn(savedPost); + when(postDTOMapper.apply(savedPost)).thenReturn(postDTO); + + // WHEN + PostDTO result = postService.updatePostById(postId, request); + + // THEN + assertNotNull(result); + assertEquals(postDTO, result); + assertEquals("Milica Krmpotic u Zadru", savedPost.getTitle()); + assertEquals("Kamen za gnjecenje zelja", savedPost.getContent()); + assertEquals("newImageId", savedPost.getImageId()); + } + + @Test + void uploadPostImage_WillUploadAndSetImage_WhenUserOwnsPost() throws IOException { + // GIVEN + Long postId = 1L; + String email = "milica@krmpotic.com"; + MultipartFile file = mock(MultipartFile.class); + byte[] fileBytes = new byte[] {1, 2, 3}; + + User user = new User(); + user.setId(1L); + Post post = new Post(); + post.setId(postId); + post.setUser(user); + + when(postRepository.findById(postId)).thenReturn(Optional.of(post)); + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(file.getBytes()).thenReturn(fileBytes); + + // WHEN + postService.uploadPostImage(postId, file, email); + + // THEN + verify(s3Service).putObject(startsWith("post-images/1/"), eq(fileBytes)); + verify(postRepository).save(post); + } + + @Test + void deletePostById_WillDelete_WhenExists() { + // GIVEN + Long postId = 1L; + when(postRepository.existsById(postId)).thenReturn(true); + + // WHEN + postService.deletePostById(postId); + + // THEN + verify(postRepository).deleteById(postId); + } + + @Test + void deletePostById_WillThrow_WhenPostDoesNotExist() { + // GIVEN + Long postId = 99L; + when(postRepository.existsById(postId)).thenReturn(false); + + // THEN + assertThrows(ResourceNotFoundException.class, () -> postService.deletePostById(postId)); + verify(postRepository, never()).deleteById(any()); + } + + @Test + void getFriendsPosts_WillReturnPosts_WhenFriendsExist() { + // GIVEN + String email = "milica@krmpotich.com"; + User friend = new User(); + friend.setId(2L); + + User user = new User(); + user.setId(1L); + user.setFriends(Set.of(friend)); + + Post friendPost = new Post(); + friendPost.setId(10L); + friendPost.setUser(friend); + + PostDTO friendPostDTO = + new PostDTO(10L, "Friend's Post", "Content", null, 0.0, null, null, null); + + Pageable pageable = PageRequest.of(0, 10); + Page postPage = new PageImpl<>(List.of(friendPost)); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(postRepository.findAllByUserIn(List.of(friend), pageable)).thenReturn(postPage); + when(postDTOMapper.apply(friendPost)).thenReturn(friendPostDTO); + + // WHEN + Page result = postService.getFriendsPosts(email, pageable); + + // THEN + assertEquals(1, result.getTotalElements()); + assertEquals(friendPostDTO, result.getContent().getFirst()); + } +} diff --git a/backend/src/test/java/hr/algebra/socialnetwork/service/UserServiceTest.java b/backend/src/test/java/hr/algebra/socialnetwork/service/UserServiceTest.java new file mode 100644 index 0000000..7640e9b --- /dev/null +++ b/backend/src/test/java/hr/algebra/socialnetwork/service/UserServiceTest.java @@ -0,0 +1,216 @@ +package hr.algebra.socialnetwork.service; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.startsWith; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import hr.algebra.socialnetwork.dto.UserDTO; +import hr.algebra.socialnetwork.exception.RequestValidationException; +import hr.algebra.socialnetwork.exception.ResourceNotFoundException; +import hr.algebra.socialnetwork.mapper.UserDTOMapper; +import hr.algebra.socialnetwork.mapper.UserSummaryDTOMapper; +import hr.algebra.socialnetwork.model.User; +import hr.algebra.socialnetwork.payload.UserUpdateRequest; +import hr.algebra.socialnetwork.repository.UserRepository; +import hr.algebra.socialnetwork.s3.S3Service; +import java.io.IOException; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.web.multipart.MultipartFile; + +class UserServiceTest { + + @Mock private UserRepository userRepository; + @Mock private UserDTOMapper userDTOMapper; + @Mock private UserSummaryDTOMapper userSummaryDTOMapper; + @Mock private S3Service s3Service; + + @InjectMocks private UserService userService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void getUserById_WillReturnUserDTO_WhenUserExists() { + // GIVEN + Long userId = 1L; + User user = new User(); + user.setId(userId); + UserDTO userDTO = mock(UserDTO.class); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(userDTOMapper.apply(user)).thenReturn(userDTO); + + // WHEN + UserDTO result = userService.getUserById(userId); + + // THEN + assertNotNull(result); + assertEquals(userDTO, result); + } + + @Test + void getUserById_WillThrowException_WhenUserDoesNotExist() { + // GIVEN + Long userId = 1L; + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + // THEN + assertThrows(ResourceNotFoundException.class, () -> userService.getUserById(userId)); + } + + @Test + void updateUserByEmail_WillUpdateAndReturnUserDTO_WhenChangesAreDetected() { + // GIVEN + String email = "test@example.com"; + UserUpdateRequest request = + new UserUpdateRequest(1L, "milicahaschanged@example.com", "Milicah", "Krmpotich"); + + User user = new User(); + user.setId(1L); + user.setEmail("test@example.com"); + user.setFirstName("OldFirst"); + user.setLastName("OldLast"); + + UserDTO expectedDTO = + new UserDTO( + user.getId(), request.email(), request.firstName(), request.lastName(), null, null); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + when(userRepository.save(user)).thenReturn(user); + when(userDTOMapper.apply(user)).thenReturn(expectedDTO); + + // WHEN + UserDTO result = userService.updateUserByEmail(email, request); + + // THEN + assertNotNull(result); + assertEquals(expectedDTO, result); + verify(userRepository).save(user); + } + + @Test + void updateUserByEmail_WillThrowValidationException_WhenNoChangesDetected() { + // GIVEN + String email = "same@example.com"; + UserUpdateRequest request = new UserUpdateRequest(1L, "same@example.com", "Same", "Name"); + + User user = new User(); + user.setId(1L); + user.setEmail("same@example.com"); + user.setFirstName("Same"); + user.setLastName("Name"); + + when(userRepository.findByEmail(email)).thenReturn(Optional.of(user)); + + // WHEN / THEN + assertThrows( + RequestValidationException.class, () -> userService.updateUserByEmail(email, request)); + + verify(userRepository, never()).save(any()); + } + + @Test + void uploadProfileImage_WillSaveImage_WhenValidUserIdAndFileProvided() throws IOException { + // GIVEN + Long userId = 1L; + User user = new User(); + user.setId(userId); + MultipartFile file = mock(MultipartFile.class); + byte[] fileBytes = new byte[] {1, 2, 3}; + when(file.getBytes()).thenReturn(fileBytes); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + // WHEN + userService.uploadProfileImage(userId, file); + + // THEN + verify(s3Service).putObject(startsWith("profile-images/" + userId), eq(fileBytes)); + verify(userRepository).save(user); + assertNotNull(user.getProfileImageId()); + } + + @Test + void uploadProfileImage_WillThrowException_WhenUserNotFound() { + // GIVEN + Long userId = 999L; + MultipartFile file = mock(MultipartFile.class); + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + // THEN + assertThrows( + ResourceNotFoundException.class, () -> userService.uploadProfileImage(userId, file)); + } + + @Test + void uploadProfileImage_WillThrowException_WhenIOExceptionOccurs() throws IOException { + // GIVEN + Long userId = 1L; + User user = new User(); + MultipartFile file = mock(MultipartFile.class); + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(file.getBytes()).thenThrow(new IOException("error")); + + // THEN + assertThrows(RuntimeException.class, () -> userService.uploadProfileImage(userId, file)); + } + + @Test + void getProfileImage_WillReturnImageBytes_WhenImageIdIsSet() { + // GIVEN + Long userId = 1L; + User user = new User(); + user.setId(userId); + user.setProfileImageId("abc123"); + + byte[] imageData = new byte[] {10, 20, 30}; + String expectedKey = "profile-images/1/abc123.jpg"; + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + when(s3Service.getObject(expectedKey)).thenReturn(imageData); + + // WHEN + byte[] result = userService.getProfileImage(userId); + + // THEN + assertArrayEquals(imageData, result); + verify(s3Service).getObject(expectedKey); + } + + @Test + void getProfileImage_WillThrow_WhenUserNotFound() { + // GIVEN + Long userId = 1L; + when(userRepository.findById(userId)).thenReturn(Optional.empty()); + + // THEN + assertThrows(ResourceNotFoundException.class, () -> userService.getProfileImage(userId)); + } + + @Test + void getProfileImage_WillThrow_WhenProfileImageIdIsBlank() { + // GIVEN + Long userId = 1L; + User user = new User(); + user.setId(userId); + user.setProfileImageId(" "); // Blank ID + + when(userRepository.findById(userId)).thenReturn(Optional.of(user)); + + // THEN + assertThrows(ResourceNotFoundException.class, () -> userService.getProfileImage(userId)); + } +} From 0a3fb3796f2a3ac184787cac022b3a90ffc63f05 Mon Sep 17 00:00:00 2001 From: Josip Date: Sat, 28 Jun 2025 16:36:32 +0200 Subject: [PATCH 2/2] Set backend as working dir for unit tests --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7705f6e..cfebedd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,5 +21,6 @@ jobs: distribution: 'temurin' - name: Run tests with Maven + working-directory: backend run: mvn clean test