diff --git a/build.gradle b/build.gradle index a316f79..e233123 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,8 @@ dependencies { testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + //S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springframework.boot:spring-boot-starter' diff --git a/src/main/java/com/example/gamemate/domain/board/entity/Board.java b/src/main/java/com/example/gamemate/domain/board/entity/Board.java index c15f364..cf0ca69 100644 --- a/src/main/java/com/example/gamemate/domain/board/entity/Board.java +++ b/src/main/java/com/example/gamemate/domain/board/entity/Board.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; import java.util.List; @Entity @@ -36,7 +37,7 @@ public class Board extends BaseEntity { private User user; @OneToMany(mappedBy = "board") - private List boardImages; + private List boardImages = new ArrayList<>(); public Board(BoardCategory category, String title, String content, User user) { this.category = category; diff --git a/src/main/java/com/example/gamemate/global/exception/ApiException.java b/src/main/java/com/example/gamemate/global/exception/ApiException.java index d0085f8..42541fd 100644 --- a/src/main/java/com/example/gamemate/global/exception/ApiException.java +++ b/src/main/java/com/example/gamemate/global/exception/ApiException.java @@ -5,7 +5,15 @@ import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor public class ApiException extends RuntimeException { private final ErrorCode errorCode; + + public ApiException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } } diff --git a/src/test/java/com/example/gamemate/domain/board/entity/BoardTest.java b/src/test/java/com/example/gamemate/domain/board/entity/BoardTest.java new file mode 100644 index 0000000..06aaeb2 --- /dev/null +++ b/src/test/java/com/example/gamemate/domain/board/entity/BoardTest.java @@ -0,0 +1,45 @@ +package com.example.gamemate.domain.board.entity; + +import com.example.gamemate.domain.board.enums.BoardCategory; +import com.example.gamemate.domain.board.repository.BoardRepository; +import com.example.gamemate.domain.user.entity.User; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class BoardTest { + + @Test + @DisplayName("게시글 생성 테스트") + void createBoard() { + // given + User boardAuthorUser = new User("abc1234@naver", "테스트2", "테스트2","1234"); + ReflectionTestUtils.setField(boardAuthorUser, "id", 2L); + + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", boardAuthorUser); + + // when & then + assertThat(board).isNotNull(); + assertThat(board.getTitle()).isEqualTo("테스트 제목"); + } + + @Test + @DisplayName("게시글 업데이트 테스트") + void updateBoard() { + // given + User boardAuthorUser = new User("abc1234@naver", "테스트2", "테스트2","1234"); + ReflectionTestUtils.setField(boardAuthorUser, "id", 2L); + + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", boardAuthorUser); + + // when + board.updateBoard(BoardCategory.INFO, "수정 제목" ,"수정 내용"); + + // then + assertThat(board.getTitle()).isEqualTo("수정 제목"); + } +} \ No newline at end of file diff --git a/src/test/java/com/example/gamemate/domain/board/service/BoardServiceIntegrationTest.java b/src/test/java/com/example/gamemate/domain/board/service/BoardServiceIntegrationTest.java new file mode 100644 index 0000000..c184c50 --- /dev/null +++ b/src/test/java/com/example/gamemate/domain/board/service/BoardServiceIntegrationTest.java @@ -0,0 +1,170 @@ +package com.example.gamemate.domain.board.service; + +import com.example.gamemate.domain.board.dto.BoardFindAllResponseDto; +import com.example.gamemate.domain.board.dto.BoardFindOneResponseDto; +import com.example.gamemate.domain.board.dto.BoardRequestDto; +import com.example.gamemate.domain.board.dto.BoardResponseDto; +import com.example.gamemate.domain.board.entity.Board; +import com.example.gamemate.domain.board.enums.BoardCategory; +import com.example.gamemate.domain.board.enums.ListSize; +import com.example.gamemate.domain.board.repository.BoardRepository; +import com.example.gamemate.domain.user.entity.User; +import com.example.gamemate.domain.user.repository.UserRepository; +import org.jboss.marshalling.ObjectTable; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +@ExtendWith(SpringExtension.class) +class BoardServiceIntegrationTest { + + @Autowired + private BoardService boardService; + + @Autowired + private BoardViewService boardViewService; + + @Autowired + private BoardRepository boardRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + @Qualifier("viewCountRedisTemplate") // 조회수 RedisTemplate을 명시적으로 지정 + private StringRedisTemplate stringRedisTemplate; + + @Test + @DisplayName("게시글 생성 성공 - 통합 테스트") + void createBoard_success_integration() { + // given + User mockUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + userRepository.save(mockUser); + + BoardRequestDto requestDto = new BoardRequestDto(BoardCategory.FREE, "제목", "내용"); + + // when + BoardResponseDto responseDto = boardService.createBoard(mockUser,requestDto); + + // then + assertNotNull(responseDto); + assertEquals("제목", responseDto.getTitle()); + + Board savedBoard = boardRepository.findById(responseDto.getId()).orElseThrow(); + assertEquals("제목", savedBoard.getTitle()); + } + + @Test + @DisplayName("게시글 리스트 조회 - 통합 테스트") + void findAllBoards_success_integration() { + // given + User user1 = new User("abc123@naver", "테스트1", "테스트1", "1234"); + User user2 = new User("def456@naver", "테스트2", "테스트2", "1234"); + + userRepository.save(user1); + userRepository.save(user2); + + boardRepository.save(new Board(BoardCategory.FREE, "테스트 제목 1", "테스트 내용 1", user1)); + boardRepository.save(new Board(BoardCategory.FREE, "테스트 제목 2", "테스트 내용 2", user1)); + boardRepository.save(new Board(BoardCategory.INFO, "테스트 제목 3", "테스트 내용 3", user2)); + + int page = 0; + BoardCategory category = BoardCategory.FREE; + String title = "제목"; + String content = "내용"; + + Pageable pageable = PageRequest.of(page, ListSize.BOARD_LIST_SIZE.getSize(), Sort.by(Sort.Order.desc("createdAt"))); + + // when + List boardList = boardService.findAllBoards(page,category,title,content); + + // then + assertThat(boardList).hasSize(2); // 2개의 게시글이 조회되어야 함 + assertThat(boardList.get(0).getTitle()).isEqualTo("테스트 제목 2"); + assertThat(boardList.get(1).getTitle()).isEqualTo("테스트 제목 1"); + } + + @Test + @DisplayName("게시글 단건 조회- 로그인한 사용자") + void findBoardById_withLoginUser_success_integration() { + // given + User user1 = new User("abc123@naver", "테스트1", "테스트1", "1234"); + User createUser = userRepository.save(user1); + Board savedBoard = boardRepository.save(new Board(BoardCategory.FREE, "테스트 제목 1", "테스트 내용 1", user1)); + Long boardId = savedBoard.getId(); + + // when + BoardFindOneResponseDto result = boardService.findBoardById(boardId, user1); + + // then + // 게시글 조회 정상인지 확인 + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(boardId); + assertThat(result.getTitle()).isEqualTo("테스트 제목 1"); + + // Redis 확인 + String redisKey = "board:view:" + boardId + ":" + createUser.getId(); + Object viewCount = stringRedisTemplate.opsForValue().get(redisKey); + assertThat(viewCount).isNotNull(); + assertThat(viewCount).isEqualTo("1"); + } + + @Test + @DisplayName("로그인한 사용자가 본인 게시글 수정") + void updateBoard_withLoginUser_success_integration() { + // given + User user1 = new User("abc123@naver", "테스트1", "테스트1", "1234"); + User loginUser = userRepository.save(user1); + Board savedBoard = boardRepository.save(new Board(BoardCategory.FREE, "테스트 제목 1", "테스트 내용 1", loginUser)); + Long boardId = savedBoard.getId(); + + BoardRequestDto requestDto = new BoardRequestDto(BoardCategory.INFO, "수정 제목", "수정 내용"); + + // when + boardService.updateBoard(loginUser, boardId, requestDto); + + // then + assertThat(savedBoard.getTitle()).isEqualTo(requestDto.getTitle()); + } + + @Test + @DisplayName("로그인한 사용자가 본인 게시글 삭제") + void deleteBoard_withLoginUser_success_integration() { + // given + User user1 = new User("abc123@naver", "테스트1", "테스트1", "1234"); + User loginUser = userRepository.save(user1); + Board savedBoard = boardRepository.save(new Board(BoardCategory.FREE, "테스트 제목 1", "테스트 내용 1", loginUser)); + Long boardId = savedBoard.getId(); + + // when + boardService.deleteBoard(loginUser, boardId); + + // then + // 삭제된 글이 DB에 없어야한다 + Optional deletedBoard = boardRepository.findById(boardId); + assertThat(deletedBoard).isEmpty(); + + } +} \ No newline at end of file diff --git a/src/test/java/com/example/gamemate/domain/board/service/BoardServiceTest.java b/src/test/java/com/example/gamemate/domain/board/service/BoardServiceTest.java new file mode 100644 index 0000000..db7f055 --- /dev/null +++ b/src/test/java/com/example/gamemate/domain/board/service/BoardServiceTest.java @@ -0,0 +1,255 @@ +package com.example.gamemate.domain.board.service; + +import com.example.gamemate.domain.board.dto.BoardFindAllResponseDto; +import com.example.gamemate.domain.board.dto.BoardFindOneResponseDto; +import com.example.gamemate.domain.board.dto.BoardRequestDto; +import com.example.gamemate.domain.board.dto.BoardResponseDto; +import com.example.gamemate.domain.board.entity.Board; +import com.example.gamemate.domain.board.entity.QBoard; +import com.example.gamemate.domain.board.enums.BoardCategory; +import com.example.gamemate.domain.board.enums.ListSize; +import com.example.gamemate.domain.board.repository.BoardRepository; +import com.example.gamemate.domain.user.entity.User; +import com.example.gamemate.global.constant.ErrorCode; +import com.example.gamemate.global.exception.ApiException; +import io.swagger.v3.oas.annotations.media.DependentSchema; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +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.*; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@Slf4j +@ExtendWith(MockitoExtension.class) +class BoardServiceTest { + + @Mock + private BoardRepository boardRepository; + + @Mock + private BoardViewService boardViewService; + + @InjectMocks + private BoardService boardService; + + @Test + @DisplayName("게시글 생성 성공") + void createBoard_success() { + // Given + User mockUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + BoardRequestDto requestDto = new BoardRequestDto(BoardCategory.FREE, "제목", "내용"); + Board mockBoard = new Board(BoardCategory.FREE,"제목","내용", mockUser); + + when(boardRepository.save(any(Board.class))).thenReturn(mockBoard); + + //when + BoardResponseDto responseDto = boardService.createBoard(mockUser, requestDto); + + // then + assertNotNull(responseDto); + assertEquals(BoardCategory.FREE, responseDto.getCategory()); + assertEquals("제목", responseDto.getTitle()); + assertEquals("내용", responseDto.getContent()); + verify(boardRepository, times(1)).save(any(Board.class)); + } + + + @Test + @DisplayName("게시글 리스트 조회 성공") + void findAllBoards_success() { + // given + int page = 0; + BoardCategory category = BoardCategory.FREE; + String title = "제목"; + String content = "내용"; + + Board board1 = new Board(BoardCategory.FREE,"테스트 제목1","테스트 내용 1",null); + Board board2 = new Board(BoardCategory.FREE,"테스트 제목2","테스트 내용 2",null); + + Pageable pageable = PageRequest.of(page, ListSize.BOARD_LIST_SIZE.getSize(), Sort.by(Sort.Order.desc("createdAt"))); + Page boardPage = new PageImpl<>(List.of(board2, board1), pageable, 2); + + when(boardRepository.searchBoardQuerydsl(category, title, content, pageable)).thenReturn(boardPage).thenReturn(boardPage); + when(boardViewService.getViewCount(board1.getId())).thenReturn(100); + when(boardViewService.getViewCount(board2.getId())).thenReturn(50); + + // when + List result = boardService.findAllBoards(page, category, title, content); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).getTitle()).isEqualTo("테스트 제목2"); + assertThat(result.get(0).getViews()).isEqualTo(50); + assertThat(result.get(1).getTitle()).isEqualTo("테스트 제목1"); + + } + + @Test + @DisplayName("로그인한 사용자가 게시글을 조회하면 조회수가 증가하고 DTO가 반환됨") + void findBoardById_withLoginUser_success() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", loginUser); + + when(boardRepository.findById(boardId)).thenReturn(Optional.of(board)); + + // when + BoardFindOneResponseDto resultDto = boardService.findBoardById(boardId, loginUser); + + // then + verify(boardViewService).increaseViewCount(boardId, loginUser.getId()); + assertThat(resultDto).isNotNull(); + assertThat(resultDto.getCategory()).isEqualTo(BoardCategory.FREE); + assertThat(resultDto.getTitle()).isEqualTo("테스트 제목"); + } + + @Test + @DisplayName("비로그인 사용자가 게시글을 조회하면 조회수가 증가하고 DTO가 반환됨") + void findBoardById_withoutLoginUser_success() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", loginUser); + + when(boardRepository.findById(boardId)).thenReturn(Optional.of(board)); + + // when + BoardFindOneResponseDto resultDto = boardService.findBoardById(boardId, null); + + // then + verify(boardViewService).increaseViewCount(boardId, null); + assertThat(resultDto).isNotNull(); + assertThat(resultDto.getCategory()).isEqualTo(BoardCategory.FREE); + assertThat(resultDto.getTitle()).isEqualTo("테스트 제목"); + } + + @Test + @DisplayName("존재하지 않는 게시글을 조회하면 예외 발생") + void findBoardById_boardNotFound_throwsException() { + // given + Long boardId = 1L; + when(boardRepository.findById(boardId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> boardService.findBoardById(boardId, null)) + .isInstanceOf(ApiException.class) + .hasMessage(ErrorCode.BOARD_NOT_FOUND.getMessage()); + } + + @Test + @DisplayName("로그인한 사용자가 본인의 게시글 수정") + void updateBoard_withLoginUser_success() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + ReflectionTestUtils.setField(loginUser, "id", 1L); + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", loginUser); + + BoardRequestDto requestDto = new BoardRequestDto(BoardCategory.INFO, "수정 제목", "수정 내용"); + + when(boardRepository.findById(boardId)).thenReturn(Optional.of(board)); + + // when + boardService.updateBoard(loginUser, boardId, requestDto); + + // then + assertThat(board.getCategory()).isEqualTo(BoardCategory.INFO); + assertThat(board.getTitle()).isEqualTo("수정 제목"); + assertThat(board.getContent()).isEqualTo("수정 내용"); + verify(boardRepository, times(1)).save(any(Board.class)); + + } + + @Test + @DisplayName("다른 사용자의 게시글을 수정하려하면 예외발생") + void updateBoard_forbidden_throwsException() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + ReflectionTestUtils.setField(loginUser, "id", 1L); + + User boardAuthorUser = new User("abc1234@naver", "테스트2", "테스트2","1234"); + ReflectionTestUtils.setField(boardAuthorUser, "id", 2L); + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", boardAuthorUser); + + BoardRequestDto requestDto = new BoardRequestDto(BoardCategory.INFO, "수정 제목", "수정 내용"); + + when(boardRepository.findById(boardId)).thenReturn(Optional.of(board)); + + // when & then + assertThatThrownBy(() -> boardService.updateBoard(loginUser, boardId, requestDto)) + .isInstanceOf(ApiException.class) + .hasMessage(ErrorCode.FORBIDDEN.getMessage()); + + } + + @Test + @DisplayName("로그인한 사용자가 본인의 게시글 삭제") + void deleteBoard_withLoginUser_success() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + ReflectionTestUtils.setField(loginUser, "id", 1L); + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", loginUser); + + when(boardRepository.findById(boardId)).thenReturn(Optional.of(board)); + + // when + boardService.deleteBoard(loginUser, boardId); + + // then + verify(boardRepository, times(1)).delete(board); + } + + @Test + @DisplayName("존재하지 않는 게시글 삭제 시 예외 발생") + void deleteBoard_boardNotFound_throwsException() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + ReflectionTestUtils.setField(loginUser, "id", 1L); + + when(boardRepository.findById(boardId)).thenReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> boardService.deleteBoard(loginUser, boardId)) + .isInstanceOf(ApiException.class) + .hasMessage(ErrorCode.BOARD_NOT_FOUND.getMessage()); + verify(boardRepository, never()).delete(any(Board.class)); + } + + @Test + @DisplayName("다른 사용자의 게시글 삭제 시 예외 발생") + void deleteBoard_forbidden_throwsException() { + // given + Long boardId = 1L; + User loginUser = new User("abc123@naver", "테스트1", "테스트1","1234"); + ReflectionTestUtils.setField(loginUser, "id", 1L); + + User boardAuthorUser = new User("abc1234@naver", "테스트2", "테스트2","1234"); + ReflectionTestUtils.setField(boardAuthorUser, "id", 2L); + + Board board = new Board(BoardCategory.FREE, "테스트 제목", "테스트 내용", boardAuthorUser); + when(boardRepository.findById(boardId)).thenReturn(Optional.of(board)); + + // when & then + assertThatThrownBy(() -> boardService.deleteBoard(loginUser, boardId)) + .isInstanceOf(ApiException.class) + .hasMessage(ErrorCode.FORBIDDEN.getMessage()); + verify(boardRepository, never()).delete(any(Board.class)); + } +} \ No newline at end of file