diff --git a/src/test/java/com/soyeon/nubim/domain/album/AlbumControllerV1Test.java b/src/test/java/com/soyeon/nubim/domain/album/AlbumControllerV1Test.java new file mode 100644 index 0000000..6aea196 --- /dev/null +++ b/src/test/java/com/soyeon/nubim/domain/album/AlbumControllerV1Test.java @@ -0,0 +1,379 @@ +package com.soyeon.nubim.domain.album; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.soyeon.nubim.domain.album.dto.AlbumCreateRequestDto; +import com.soyeon.nubim.domain.album.dto.AlbumCreateResponseDto; +import com.soyeon.nubim.domain.album.dto.AlbumReadResponseDto; +import com.soyeon.nubim.domain.album.dto.AlbumUpdateRequestDto; +import com.soyeon.nubim.domain.album.dto.LocationCreateRequestDto; +import com.soyeon.nubim.domain.album.dto.LocationCreateResponseDto; +import com.soyeon.nubim.domain.album.dto.LocationReadResponseDto; +import com.soyeon.nubim.domain.album.dto.LocationUpdateRequestDto; +import com.soyeon.nubim.domain.album.exception.AlbumNotFoundException; +import com.soyeon.nubim.domain.user.exception.UserNotFoundException; +import com.soyeon.nubim.security.SecurityConfig; +import com.soyeon.nubim.security.jwt.JwtAuthenticationFilter; +import com.soyeon.nubim.security.jwt.JwtTokenProvider; + +@WebMvcTest(controllers = AlbumControllerV1.class) +@Import({SecurityConfig.class, JwtAuthenticationFilter.class, JwtTokenProvider.class}) +@TestPropertySource(properties = { + "jwt.secret=testsecretkeytestsecretkeytestsecretkeytestsecretkey", + "logging.level.com.soyeon.nubim=DEBUG", +}) +class AlbumControllerV1Test { + + @Autowired + private MockMvc mockMvc; + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private ObjectMapper objectMapper; + @MockBean + private AlbumService albumService; + @MockBean + private AlbumPhotoUploadUrlService albumPhotoUploadUrlService; + + @Autowired + private JwtTokenProvider jwtTokenProvider; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + private AlbumCreateRequestDto createRequestDto; + private AlbumCreateResponseDto createResponseDto; + private AlbumReadResponseDto readResponseDto; + private AlbumUpdateRequestDto updateRequestDto; + + private String accessToken; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .addFilter(new CharacterEncodingFilter("UTF-8", true)) + .addFilter(jwtAuthenticationFilter) + .alwaysDo(print()) + .build(); + + accessToken = jwtTokenProvider.generateAccessToken("1L", "testUser@email.com", "USER"); + + Long albumId = 1L; + Long userId = 1L; + String description = "create album test"; + Map photoUrls = Map.of(1, "https://test01.jpg", + 2, "https://test02.jpg", + 3, "https://test03.jpg", + 4, "https://test04.jpg"); + + List locationsRequest = List.of( + LocationCreateRequestDto.builder() + .latitude(37.5665) + .longitude(126.9780) + .visitedAt(LocalDateTime.now()) + .placeName("서울") + .build(), + LocationCreateRequestDto.builder() + .latitude(35.1796) + .longitude(129.0756) + .visitedAt(LocalDateTime.now().minusDays(1)) + .placeName("서울") + .build(), + LocationCreateRequestDto.builder() + .latitude(33.4996) + .longitude(126.5312) + .visitedAt(LocalDateTime.now().minusDays(2)) + .placeName("서울") + .build() + ); + + List locationsResponse = List.of( + LocationCreateResponseDto.builder() + .locationId(1L) + .albumId(1L) + .latitude(37.5665) + .longitude(126.9780) + .visitedAt(LocalDateTime.now()) + .placeName("서울") + .build(), + LocationCreateResponseDto.builder() + .locationId(2L) + .albumId(1L) + .latitude(35.1796) + .longitude(129.0756) + .visitedAt(LocalDateTime.now().minusDays(1)) + .placeName("서울") + .build(), + LocationCreateResponseDto.builder() + .locationId(3L) + .albumId(1L) + .latitude(33.4996) + .longitude(126.5312) + .visitedAt(LocalDateTime.now().minusDays(2)) + .placeName("서울") + .build() + ); + + createRequestDto = AlbumCreateRequestDto.builder() + .description(description) + .photoUrls(photoUrls) + .locations(locationsRequest) + .build(); + + createResponseDto = AlbumCreateResponseDto.builder() + .albumId(albumId) + .userId(userId) + .description(description) + .photoUrls(photoUrls) + .locations(locationsResponse) + .build(); + + readResponseDto = AlbumReadResponseDto.builder() + .albumId(albumId) + .userId(1L) + .description("read album test") + .photoUrls(Map.of(1, "https://test01.jpg", 2, "https://test02.jpg")) + .locations(List.of( + LocationReadResponseDto.builder() + .locationId(1L) + .albumId(albumId) + .latitude(37.5665) + .longitude(126.9780) + .visitedAt(LocalDateTime.now()) + .placeName("서울") + .build() + )) + .build(); + + updateRequestDto = AlbumUpdateRequestDto.builder() + .description("update album test") + .photoUrls(Map.of(1, "https://change01.jpg", 2, "https://change01.jpg")) + .locations(List.of( + LocationUpdateRequestDto.builder() + .latitude(1.0) + .longitude(2.0) + .visitedAt(LocalDateTime.now()) + .placeName("경기도") + .build()) + ) + .build(); + } + + @Test + @DisplayName("앨범 생성 - 성공 case") + void createAlbum() throws Exception { + when(albumService.createAlbum(any(AlbumCreateRequestDto.class))).thenReturn(createResponseDto); + + MvcResult result = mockMvc.perform(post("/v1/albums") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createRequestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.albumId").value(createResponseDto.getAlbumId())) + .andExpect(jsonPath("$.description").value(createResponseDto.getDescription())) + .andExpect(jsonPath("$.photoUrls").isNotEmpty()) + .andExpect(jsonPath("$.locations").isNotEmpty()) + .andReturn(); + + String content = result.getResponse().getContentAsString(); + AlbumCreateResponseDto actualCreateResponseDto = objectMapper.readValue(content, AlbumCreateResponseDto.class); + + assertEquals(createResponseDto.getPhotoUrls().size(), actualCreateResponseDto.getPhotoUrls().size()); + for (Map.Entry photoUrl : createResponseDto.getPhotoUrls().entrySet()) { + assertEquals(photoUrl.getValue(), actualCreateResponseDto.getPhotoUrls().get(photoUrl.getKey())); + } + + assertEquals(createResponseDto.getLocations().size(), actualCreateResponseDto.getLocations().size()); + for (int i = 0; i < createResponseDto.getLocations().size(); i++) { + LocationCreateResponseDto expectedLocation = createResponseDto.getLocations().get(i); + LocationCreateResponseDto actualLocation = actualCreateResponseDto.getLocations().get(i); + + assertEquals(expectedLocation.getLatitude(), actualLocation.getLatitude()); + assertEquals(expectedLocation.getLongitude(), actualLocation.getLongitude()); + assertEquals(expectedLocation.getVisitedAt(), actualLocation.getVisitedAt()); + assertEquals(expectedLocation.getPlaceName(), actualLocation.getPlaceName()); + } + } + + @Test + @DisplayName("앨범 생성 - 실패 case : access token 이 없는 경우") + void createAlbum_Unauthorized() throws Exception { + when(albumService.createAlbum(any(AlbumCreateRequestDto.class))).thenReturn(createResponseDto); + + mockMvc.perform(post("/v1/albums") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(createRequestDto))) + .andExpect(status().isUnauthorized()); + } + + @Test + @DisplayName("앨범 검색 - 성공 case") + void getAlbumWithLocations() throws Exception { + Long albumId = 1L; + + when(albumService.findByIdWithLocations(albumId)).thenReturn(readResponseDto); + + mockMvc.perform(get("/v1/albums/{albumId}", albumId) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.userId").value(readResponseDto.getUserId())) + .andExpect(jsonPath("$.albumId").value(albumId)) + .andExpect(jsonPath("$.description").value(readResponseDto.getDescription())) + .andExpect(jsonPath("$.photoUrls").isNotEmpty()) + .andExpect(jsonPath("$.locations").isNotEmpty()) + .andExpect( + jsonPath("$.locations[0].placeName").value(readResponseDto.getLocations().get(0).getPlaceName())); + + verify(albumService).findByIdWithLocations(albumId); + } + + @Test + @DisplayName("앨범 검색 - 실패 case : 존재 하지 않는 앨범 ID 검색") + void getAlbumWithLocations_NotFound() throws Exception { + Long albumId = 999L; + + when(albumService.findByIdWithLocations(albumId)).thenThrow(new AlbumNotFoundException(albumId)); + + mockMvc.perform(get("/v1/albums/{albumId}", albumId) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + + verify(albumService).findByIdWithLocations(albumId); + } + + @Test + @DisplayName("닉네임으로 앨범 검색 - 성공 case") + void getUserAlbums() throws Exception { + String nickname = "testUser"; + + when(albumService.findAlbumsByUserNickname(nickname)).thenReturn(List.of(readResponseDto)); + + MvcResult result = mockMvc.perform(get("/v1/albums/user/{nickname}", nickname) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isOk()) + .andReturn(); + + String content = result.getResponse().getContentAsString(); + List actualAlbums + = objectMapper.readValue(content, new TypeReference<>() {}); + + assertEquals(1, actualAlbums.size()); + assertEquals(readResponseDto.getAlbumId(), actualAlbums.get(0).getAlbumId()); + assertEquals(readResponseDto.getDescription(), actualAlbums.get(0).getDescription()); + assertEquals(readResponseDto.getPhotoUrls().size(), actualAlbums.get(0).getPhotoUrls().size()); + for (Map.Entry photoUrl : readResponseDto.getPhotoUrls().entrySet()) { + assertEquals(photoUrl.getValue(), actualAlbums.get(0).getPhotoUrls().get(photoUrl.getKey())); + } + + assertEquals(readResponseDto.getLocations().size(), actualAlbums.get(0).getLocations().size()); + for (int i = 0; i < readResponseDto.getLocations().size(); i++) { + LocationReadResponseDto expectedLocation = readResponseDto.getLocations().get(i); + LocationReadResponseDto actualLocation = actualAlbums.get(0).getLocations().get(i); + assertEquals(expectedLocation.getLatitude(), actualLocation.getLatitude()); + assertEquals(expectedLocation.getLongitude(), actualLocation.getLongitude()); + assertEquals(expectedLocation.getVisitedAt(), actualLocation.getVisitedAt()); + assertEquals(expectedLocation.getPlaceName(), actualLocation.getPlaceName()); + } + } + + @Test + @DisplayName("닉네임으로 앨범 검색 - 실패 case : 존재하지 않는 닉네임") + void getUserAlbums_NotFound() throws Exception { + String nickname = "testUser"; + + when(albumService.findAlbumsByUserNickname(nickname)).thenThrow( + new UserNotFoundException(nickname, "nickname")); + + mockMvc.perform(get("/v1/albums/user/{nickname}", nickname) + .header("Authorization", "Bearer " + accessToken)) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("앨범 업데이트 - 성공 case") + void updateAlbum() throws Exception { + Long albumId = 1L; + + List locationUpdateList = updateRequestDto.getLocations(); + List locationReadList = new ArrayList<>(locationUpdateList.size()); + for (int i = 0; i < locationUpdateList.size(); i++) { + LocationUpdateRequestDto locationUpdateRequest = locationUpdateList.get(i); + locationReadList.add(LocationReadResponseDto.builder() + .locationId((long)(i + 1)) + .albumId(albumId) + .longitude(locationUpdateRequest.getLongitude()) + .latitude(locationUpdateRequest.getLatitude()) + .visitedAt(locationUpdateRequest.getVisitedAt()) + .placeName(locationUpdateRequest.getPlaceName()) + .build()); + } + + AlbumReadResponseDto updateResponseDto = AlbumReadResponseDto.builder() + .albumId(readResponseDto.getAlbumId()) + .userId(readResponseDto.getUserId()) + .description(updateRequestDto.getDescription()) + .photoUrls(updateRequestDto.getPhotoUrls()) + .locations(locationReadList) + .build(); + + when(albumService.updateAlbum(any(Long.class), any(AlbumUpdateRequestDto.class))).thenReturn(updateResponseDto); + + MvcResult result = mockMvc.perform(put("/v1/albums/{albumId}", albumId) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateRequestDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.albumId").value(albumId)) + .andExpect(jsonPath("$.userId").value(readResponseDto.getUserId())) + .andExpect(jsonPath("$.description").value(updateRequestDto.getDescription())) + .andExpect(jsonPath("$.photoUrls").isNotEmpty()) + .andExpect(jsonPath("$.locations").isNotEmpty()) + .andReturn(); + + String content = result.getResponse().getContentAsString(); + AlbumReadResponseDto actualReadResponseDto = objectMapper.readValue(content, AlbumReadResponseDto.class); + + for (Map.Entry photoUrl : updateRequestDto.getPhotoUrls().entrySet()) { + assertEquals(photoUrl.getValue(), actualReadResponseDto.getPhotoUrls().get(photoUrl.getKey())); + } + + assertEquals(updateRequestDto.getLocations().size(), actualReadResponseDto.getLocations().size()); + for (int i = 0; i < updateRequestDto.getLocations().size(); i++) { + LocationUpdateRequestDto expectedLocation = updateRequestDto.getLocations().get(i); + LocationReadResponseDto actualLocation = actualReadResponseDto.getLocations().get(i); + + assertEquals(expectedLocation.getLatitude(), actualLocation.getLatitude()); + assertEquals(expectedLocation.getLongitude(), actualLocation.getLongitude()); + assertEquals(expectedLocation.getVisitedAt(), actualLocation.getVisitedAt()); + assertEquals(expectedLocation.getPlaceName(), actualLocation.getPlaceName()); + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/soyeon/nubim/domain/album/AlbumRepositoryTest.java b/src/test/java/com/soyeon/nubim/domain/album/AlbumRepositoryTest.java new file mode 100644 index 0000000..679ad8f --- /dev/null +++ b/src/test/java/com/soyeon/nubim/domain/album/AlbumRepositoryTest.java @@ -0,0 +1,138 @@ +package com.soyeon.nubim.domain.album; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import com.soyeon.nubim.common.enums.Role; +import com.soyeon.nubim.domain.album.exception.AlbumNotFoundException; +import com.soyeon.nubim.domain.user.User; +import com.soyeon.nubim.domain.user.UserRepository; + +import jakarta.persistence.EntityManager; + +@SpringBootTest +@ActiveProfiles("local") +@Transactional +class AlbumRepositoryTest { + + @Autowired + private AlbumRepository albumRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private EntityManager entityManager; + + private Album album; + private User user; + + @BeforeEach + void setUp() { + user = new User(); + user.setRole(Role.USER); + user.setUsername("testUser"); + user.setNickname("testNickname"); + user.setEmail("test@email.com"); + + album = new Album(); + album.setPhotoUrls(Map.of()); + + Location location1 = Location.builder() + .latitude(0.0) + .longitude(0.0) + .visitedAt(LocalDateTime.now()) + .build(); + Location location2 = Location.builder() + .latitude(0.0) + .longitude(0.0) + .visitedAt(LocalDateTime.now()) + .build(); + + album.setLocations(List.of(location1, location2)); + album.bindLocations(); + + album.setUser(user); + + userRepository.save(user); + albumRepository.save(album); + } + + @Test + @DisplayName("앨범을 생성한 사용자의 ID 검색 - 성공 case") + void findAlbumOwnerIdByAlbumId() { + Optional ownerId = albumRepository.findAlbumOwnerIdByAlbumId(album.getAlbumId()); + + assertTrue(ownerId.isPresent()); + assertEquals(user.getUserId(), ownerId.get()); + } + + @Test + @DisplayName("앨범과 연관된 장소를 한번에 검색 - 성공 case") + void findByIdWithLocations() { + Optional foundAlbum = albumRepository.findByIdWithLocations(album.getAlbumId()); + assertTrue(foundAlbum.isPresent()); + assertEquals(2, foundAlbum.get().getLocations().size()); + } + + @Test + @DisplayName("사용자로 앨범 검색 - 성공 case") + void findByUser() { + List albums = albumRepository.findByUser(user); + assertFalse(albums.isEmpty()); + assertEquals(album.getAlbumId(), albums.get(0).getAlbumId()); + } + + @Test + @DisplayName("사용자의 이메일로 앨범 검색 - 성공 case") + void findAlbumsByEmail() { + List albums = albumRepository.findAlbumsByEmail(user.getEmail()); + assertFalse(albums.isEmpty()); + assertEquals(album.getAlbumId(), albums.get(0).getAlbumId()); + } + + @Test + @DisplayName("게시물과 연결되지 않은 앨범 검색 - 성공 case") + void findUnlinkedAlbumsByEmail() { + List unlinkedAlbums = albumRepository.findUnlinkedAlbumsByEmail(user.getEmail()); + assertFalse(unlinkedAlbums.isEmpty()); + assertEquals(album.getAlbumId(), unlinkedAlbums.get(0).getAlbumId()); + + album.setPostLinked(true); + unlinkedAlbums = albumRepository.findUnlinkedAlbumsByEmail(user.getEmail()); + assertTrue(unlinkedAlbums.isEmpty()); + } + + @Test + @DisplayName("앨범과 연관된 경로 삭제 - 성공 case") + void deleteLocationsByAlbumId() { + albumRepository.deleteLocationsByAlbumId(album.getAlbumId()); + entityManager.clear(); + + Album refreshedAlbum = albumRepository.findById(album.getAlbumId()).orElseThrow(); + assertTrue(refreshedAlbum.getLocations().isEmpty()); + } + + @Test + @DisplayName("앨범 삭제 테스트 - 성공 case : 삭제된 앨범은 조회 시 NotFoundException을 발생시킨다") + void deleteByAlbumId() { + albumRepository.deleteByAlbumId(album.getAlbumId()); + + assertThrows(AlbumNotFoundException.class, () -> + albumRepository.findByIdWithLocations(album.getAlbumId()).orElseThrow( + ()-> new AlbumNotFoundException(album.getAlbumId()) + )); + } +} \ No newline at end of file diff --git a/src/test/java/com/soyeon/nubim/domain/album/AlbumServiceTest.java b/src/test/java/com/soyeon/nubim/domain/album/AlbumServiceTest.java new file mode 100644 index 0000000..0a07320 --- /dev/null +++ b/src/test/java/com/soyeon/nubim/domain/album/AlbumServiceTest.java @@ -0,0 +1,165 @@ +package com.soyeon.nubim.domain.album; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.soyeon.nubim.common.util.aws.S3ImageDeleter; +import com.soyeon.nubim.domain.album.dto.AlbumCreateRequestDto; +import com.soyeon.nubim.domain.album.dto.AlbumCreateResponseDto; +import com.soyeon.nubim.domain.album.dto.AlbumReadResponseDto; +import com.soyeon.nubim.domain.album.dto.AlbumUpdateRequestDto; +import com.soyeon.nubim.domain.album.dto.LocationUpdateRequestDto; +import com.soyeon.nubim.domain.album.exception.AlbumNotFoundException; +import com.soyeon.nubim.domain.album.mapper.AlbumMapper; +import com.soyeon.nubim.domain.album.mapper.LocationMapper; +import com.soyeon.nubim.domain.post.PostRepository; +import com.soyeon.nubim.domain.user.User; +import com.soyeon.nubim.domain.user.UserService; + +class AlbumServiceTest { + + @Mock + private AlbumValidator albumValidator; + @Mock + private AlbumRepository albumRepository; + @Mock + private AlbumMapper albumMapper; + @Mock + private UserService userService; + @Mock + private LocationMapper locationMapper; + @Mock + private S3ImageDeleter s3ImageDeleter; + @Mock + private PostRepository postRepository; + + @InjectMocks + private AlbumService albumService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("앨범 id로 검색 - 성공 case") + void findById() { + Long albumId = 1L; + Album expectedAlbum = new Album(); + when(albumRepository.findById(albumId)).thenReturn(Optional.of(expectedAlbum)); + + Album result = albumService.findById(albumId); + + assertNotNull(result); + assertEquals(expectedAlbum, result); + } + + @Test + @DisplayName("앨범 id로 검색 - 실패 case : 앨범이 없는 경우") + void findById_shouldThrowException_whenAlbumNotFound() { + Long albumId = 1L; + when(albumRepository.findById(albumId)).thenReturn(Optional.empty()); + + assertThrows(AlbumNotFoundException.class, () -> albumService.findById(albumId)); + } + + @Test + @DisplayName("앨범 생성 - 성공 case") + void createAlbum() { + AlbumCreateRequestDto requestDto = AlbumCreateRequestDto.builder().build(); + User currentUser = new User(); + Album album = new Album(); + Album savedAlbum = new Album(); + AlbumCreateResponseDto expectedResponseDto = AlbumCreateResponseDto.builder().build(); + + when(userService.getCurrentUser()).thenReturn(currentUser); + when(albumMapper.toEntity(requestDto, currentUser)).thenReturn(album); + when(albumRepository.save(album)).thenReturn(savedAlbum); + when(albumMapper.toAlbumCreateResponseDto(savedAlbum)).thenReturn(expectedResponseDto); + + AlbumCreateResponseDto result = albumService.createAlbum(requestDto); + + assertNotNull(result); + assertEquals(expectedResponseDto, result); + verify(albumRepository).save(album); + } + + @Test + void findByIdWithLocations_shouldReturnAlbumWithLocations_whenAlbumExists() { + // Given + Long albumId = 1L; + Album album = new Album(); + AlbumReadResponseDto expectedResponseDto = AlbumReadResponseDto.builder().build(); + + when(albumRepository.findByIdWithLocations(albumId)).thenReturn(Optional.of(album)); + when(albumMapper.toAlbumReadResponseDto(album)).thenReturn(expectedResponseDto); + + // When + AlbumReadResponseDto result = albumService.findByIdWithLocations(albumId); + + // Then + assertNotNull(result); + assertEquals(expectedResponseDto, result); + } + + @Test + void updateAlbum_shouldUpdateAndReturnAlbum() { + // Given + Long albumId = 1L; + Long userId = 1L; + AlbumUpdateRequestDto updateRequestDto = AlbumUpdateRequestDto.builder() + .description("New description") + .photoUrls(Map.of()) + .locations(List.of(LocationUpdateRequestDto.builder().build())) + .build(); + + Album existingAlbum = new Album(); + existingAlbum.setPhotoUrls(new HashMap<>()); + + when(userService.getCurrentUserId()).thenReturn(userId); + when(albumRepository.findByIdWithLocations(albumId)).thenReturn(Optional.of(existingAlbum)); + when(albumRepository.save(existingAlbum)).thenReturn(existingAlbum); + when(albumMapper.toAlbumReadResponseDto(existingAlbum)).thenReturn(AlbumReadResponseDto.builder().build()); + + // When + AlbumReadResponseDto result = albumService.updateAlbum(albumId, updateRequestDto); + + // Then + assertNotNull(result); + verify(albumValidator).validateAlbumOwner(albumId, userId); + verify(albumRepository).deleteLocationsByAlbumId(albumId); + verify(albumRepository).save(existingAlbum); + } + + @Test + void deleteAlbum_shouldDeleteAlbumAndRelatedData() { + // Given + Long albumId = 1L; + Long userId = 1L; + Album album = new Album(); + + when(userService.getCurrentUserId()).thenReturn(userId); + when(albumRepository.findByIdWithLocations(albumId)).thenReturn(Optional.of(album)); + + // When + albumService.deleteAlbum(albumId); + + // Then + verify(albumValidator).validateAlbumOwner(albumId, userId); + verify(albumRepository).deleteLocationsByAlbumId(albumId); + verify(albumRepository).deleteByAlbumId(albumId); + verify(postRepository).deletePostByDeletedAlbumId(albumId); + } +} \ No newline at end of file