diff --git a/back/build.gradle.kts b/back/build.gradle.kts index d5aa642..a9f6af0 100644 --- a/back/build.gradle.kts +++ b/back/build.gradle.kts @@ -1,3 +1,6 @@ +import org.gradle.kotlin.dsl.annotationProcessor +import org.gradle.kotlin.dsl.testAnnotationProcessor + plugins { java id("org.springframework.boot") version "3.5.5" @@ -43,6 +46,14 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + + // QueryDSL + implementation("io.github.openfeign.querydsl:querydsl-jpa:7.0") + annotationProcessor("io.github.openfeign.querydsl:querydsl-apt:7.0:jpa") + annotationProcessor("jakarta.persistence:jakarta.persistence-api") + testAnnotationProcessor("io.github.openfeign.querydsl:querydsl-apt:7.0:jpa") + testAnnotationProcessor("jakarta.persistence:jakarta.persistence-api") } tasks.withType { diff --git a/back/src/main/java/com/back/domain/post/controller/PostController.java b/back/src/main/java/com/back/domain/post/controller/PostController.java index 5bbbbe4..3d8bbe1 100644 --- a/back/src/main/java/com/back/domain/post/controller/PostController.java +++ b/back/src/main/java/com/back/domain/post/controller/PostController.java @@ -2,6 +2,7 @@ import com.back.domain.post.dto.PostRequest; import com.back.domain.post.dto.PostResponse; +import com.back.domain.post.dto.PostSearchCondition; import com.back.domain.post.service.PostService; import com.back.global.common.ApiResponse; import com.back.global.common.PageResponse; @@ -36,8 +37,9 @@ public ApiResponse createPost( // 게시글 목록 조회 @GetMapping - public ApiResponse> getPosts(Pageable pageable) { - Page responses = postService.getPosts(pageable); + public ApiResponse> getPosts( + @ModelAttribute PostSearchCondition condition, Pageable pageable) { + Page responses = postService.getPosts(condition, pageable); return ApiResponse.success(PageResponse.of(responses), "성공적으로 조회되었습니다.", HttpStatus.OK); } diff --git a/back/src/main/java/com/back/domain/post/dto/PostResponse.java b/back/src/main/java/com/back/domain/post/dto/PostResponse.java index 31008eb..4e367e8 100644 --- a/back/src/main/java/com/back/domain/post/dto/PostResponse.java +++ b/back/src/main/java/com/back/domain/post/dto/PostResponse.java @@ -18,6 +18,7 @@ public record PostResponse( Long id, String title, String content, + String author, PostCategory category, boolean hide, int likeCount, diff --git a/back/src/main/java/com/back/domain/post/dto/PostSearchCondition.java b/back/src/main/java/com/back/domain/post/dto/PostSearchCondition.java new file mode 100644 index 0000000..663b488 --- /dev/null +++ b/back/src/main/java/com/back/domain/post/dto/PostSearchCondition.java @@ -0,0 +1,17 @@ +package com.back.domain.post.dto; + +import com.back.domain.post.enums.PostCategory; +import com.back.domain.post.enums.SearchType; + +/** + * 검색 조건 + * @param category (CHAT, SCENARIO, POLL) + * @param searchType (TITLE, TITLE_CONTENT, AUTHOR) + * @param keyword (검색어) + */ +public record PostSearchCondition( + PostCategory category, + SearchType searchType, + String keyword +) { +} diff --git a/back/src/main/java/com/back/domain/post/enums/SearchType.java b/back/src/main/java/com/back/domain/post/enums/SearchType.java new file mode 100644 index 0000000..52b2e23 --- /dev/null +++ b/back/src/main/java/com/back/domain/post/enums/SearchType.java @@ -0,0 +1,11 @@ +package com.back.domain.post.enums; + +/** + * 검색 타입 + * TITLE - 제목 + * TITLE_CONTENT - 제목 + 내용 + * AUTHOR - 작성자 + */ +public enum SearchType { + TITLE, TITLE_CONTENT, AUTHOR +} diff --git a/back/src/main/java/com/back/domain/post/mapper/PostMapper.java b/back/src/main/java/com/back/domain/post/mapper/PostMapper.java index c1c1cd3..99cd724 100644 --- a/back/src/main/java/com/back/domain/post/mapper/PostMapper.java +++ b/back/src/main/java/com/back/domain/post/mapper/PostMapper.java @@ -26,6 +26,7 @@ public static PostResponse toResponse(Post post) { post.getId(), post.getTitle(), post.getContent(), + post.getUser().getNickname(), post.getCategory(), post.isHide(), post.getLikeCount(), diff --git a/back/src/main/java/com/back/domain/post/repository/PostRepository.java b/back/src/main/java/com/back/domain/post/repository/PostRepository.java index 0fbf138..fa6dd72 100644 --- a/back/src/main/java/com/back/domain/post/repository/PostRepository.java +++ b/back/src/main/java/com/back/domain/post/repository/PostRepository.java @@ -8,5 +8,5 @@ * 게시글 엔티티에 대한 데이터베이스 접근을 담당하는 JpaRepository. */ @Repository -public interface PostRepository extends JpaRepository { +public interface PostRepository extends JpaRepository, PostRepositoryCustom { } \ No newline at end of file diff --git a/back/src/main/java/com/back/domain/post/repository/PostRepositoryCustom.java b/back/src/main/java/com/back/domain/post/repository/PostRepositoryCustom.java new file mode 100644 index 0000000..fa3da10 --- /dev/null +++ b/back/src/main/java/com/back/domain/post/repository/PostRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.back.domain.post.repository; + +import com.back.domain.post.dto.PostSearchCondition; +import com.back.domain.post.entity.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface PostRepositoryCustom { + Page searchPosts(PostSearchCondition postSearchCondition, Pageable pageable); +} diff --git a/back/src/main/java/com/back/domain/post/repository/PostRepositoryCustomImpl.java b/back/src/main/java/com/back/domain/post/repository/PostRepositoryCustomImpl.java new file mode 100644 index 0000000..10dcd56 --- /dev/null +++ b/back/src/main/java/com/back/domain/post/repository/PostRepositoryCustomImpl.java @@ -0,0 +1,74 @@ +package com.back.domain.post.repository; + +import com.back.domain.post.dto.PostSearchCondition; +import com.back.domain.post.entity.Post; +import com.back.domain.post.enums.PostCategory; +import com.back.domain.post.enums.SearchType; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import java.util.List; + +import static com.back.domain.post.entity.QPost.post; +import static com.back.domain.user.entity.QUser.user; + +@RequiredArgsConstructor +@Repository +public class PostRepositoryCustomImpl implements PostRepositoryCustom { + private final JPAQueryFactory queryFactory; + + @Override + public Page searchPosts(PostSearchCondition condition, Pageable pageable) { + List posts = queryFactory + .selectFrom(post) + .leftJoin(post.user, user).fetchJoin() + .where(getCategoryCondition(condition.category()), + getSearchCondition(condition.keyword(), condition.searchType())) + .orderBy(post.createdDate.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery count = queryFactory + .select(post.count()) + .from(post) + .where( + getCategoryCondition(condition.category()), + getSearchCondition(condition.keyword(), condition.searchType()) + ); + + return PageableExecutionUtils.getPage(posts, pageable, count::fetchOne); + } + + /** + * 1차 필터링 (CHAT, SCENARIO, POLL) + * category 조건이 null이 아니면 필터링 조건 추가 + */ + private BooleanExpression getCategoryCondition(PostCategory category) { + return category != null ? post.category.eq(category) : null; + } + + /** + * 2차 필터링 (TITLE, TITLE_CONTENT, AUTHOR) + * fixme 현재 like 기반 검색 - 성능 최적화를 위해 추후에 수정 예정 + */ + private BooleanExpression getSearchCondition(String searchKeyword, SearchType searchType) { + if (!StringUtils.hasText(searchKeyword) || searchType == null) { + return null; + } + + return switch (searchType) { + case TITLE -> post.title.containsIgnoreCase(searchKeyword); + case TITLE_CONTENT -> post.title.containsIgnoreCase(searchKeyword) + .or(post.content.containsIgnoreCase(searchKeyword)); + case AUTHOR -> post.user.nickname.containsIgnoreCase(searchKeyword); + }; + } +} diff --git a/back/src/main/java/com/back/domain/post/service/PostService.java b/back/src/main/java/com/back/domain/post/service/PostService.java index 5e48028..01ce950 100644 --- a/back/src/main/java/com/back/domain/post/service/PostService.java +++ b/back/src/main/java/com/back/domain/post/service/PostService.java @@ -2,6 +2,7 @@ import com.back.domain.post.dto.PostRequest; import com.back.domain.post.dto.PostResponse; +import com.back.domain.post.dto.PostSearchCondition; import com.back.domain.post.entity.Post; import com.back.domain.post.mapper.PostMapper; import com.back.domain.post.repository.PostRepository; @@ -48,8 +49,8 @@ public PostResponse getPost(Long postId) { .orElseThrow(() -> new ApiException(ErrorCode.POST_NOT_FOUND)); } - public Page getPosts(Pageable pageable) { - return postRepository.findAll(pageable) + public Page getPosts(PostSearchCondition condition, Pageable pageable) { + return postRepository.searchPosts(condition, pageable) .map(PostMapper::toResponse); } diff --git a/back/src/main/java/com/back/global/config/JpaConfig.java b/back/src/main/java/com/back/global/config/JpaConfig.java new file mode 100644 index 0000000..afa9149 --- /dev/null +++ b/back/src/main/java/com/back/global/config/JpaConfig.java @@ -0,0 +1,14 @@ +package com.back.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JpaConfig { + @Bean + public JPAQueryFactory jpaQueryFactory(EntityManager em) { + return new JPAQueryFactory(em); + } +} diff --git a/back/src/test/java/com/back/domain/post/controller/PostControllerTest.java b/back/src/test/java/com/back/domain/post/controller/PostControllerTest.java index d5ef6dc..c1e7000 100644 --- a/back/src/test/java/com/back/domain/post/controller/PostControllerTest.java +++ b/back/src/test/java/com/back/domain/post/controller/PostControllerTest.java @@ -3,14 +3,14 @@ import com.back.domain.post.dto.PostRequest; import com.back.domain.post.entity.Post; import com.back.domain.post.enums.PostCategory; +import com.back.domain.post.enums.SearchType; +import com.back.domain.post.fixture.PostFixture; import com.back.domain.post.repository.PostRepository; -import com.back.domain.user.entity.Gender; -import com.back.domain.user.entity.Mbti; -import com.back.domain.user.entity.Role; import com.back.domain.user.entity.User; import com.back.domain.user.repository.UserRepository; import com.back.global.exception.ErrorCode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -25,10 +25,8 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.stream.IntStream; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -50,45 +48,16 @@ class PostControllerTest { @Autowired private UserRepository userRepository; + private PostFixture fixture; private User testUser; private User anotherUser; @BeforeEach void setUp() { - testUser = User.builder() - .loginId("testLoginId") - .email("test@example.com") - .password("testPassword") - .beliefs("도전") - .gender(Gender.M) - .role(Role.USER) - .mbti(Mbti.ISFJ) - .birthdayAt(LocalDateTime.of(2000, 3, 1, 0, 0)) - .build(); - userRepository.save(testUser); - - anotherUser = User.builder() - .loginId("anotherLoginId") - .email("another@example.com") - .password("another") - .beliefs("도전") - .gender(Gender.F) - .role(Role.USER) - .mbti(Mbti.ISFJ) - .birthdayAt(LocalDateTime.of(2001, 4, 1, 0, 0)) - .build(); - userRepository.save(anotherUser); - - IntStream.rangeClosed(1, 5).forEach(i -> { - postRepository.save( - Post.builder() - .title("목록 게시글 " + i) - .content("목록 내용 " + i) - .category(PostCategory.CHAT) - .user(testUser) - .build() - ); - }); + fixture = new PostFixture(userRepository, postRepository); + testUser = fixture.createTestUser(); + anotherUser = fixture.createAnotherUser(); + fixture.createPostsForPaging(testUser, 5); } @Nested @@ -99,10 +68,10 @@ class CreatePost { @DisplayName("성공 - 정상 요청") void success() throws Exception { // given - PostRequest request = new PostRequest("테스트 게시글", "테스트 내용입니다.", PostCategory.CHAT); + PostRequest request = fixture.createPostRequest(); - // when - mockMvc.perform(post("/api/v1/posts") + // when & then + mockMvc.perform(post(PostFixture.API_BASE_PATH) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) @@ -114,19 +83,33 @@ void success() throws Exception { } @Test - @DisplayName("실패 - 유효성 검사 실패") - void fail_ValidationError() throws Exception { + @DisplayName("실패 - 유효성 검사 실패 (빈 제목)") + void fail_ValidationError_EmptyTitle() throws Exception { // given - PostRequest request = new PostRequest("", "테스트 내용입니다.", PostCategory.CHAT); + PostRequest request = fixture.createEmptyTitleRequest(); // when & then - mockMvc.perform(post("/api/v1/posts") + mockMvc.perform(post(PostFixture.API_BASE_PATH) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value(ErrorCode.INVALID_INPUT_VALUE.getCode())) .andExpect(jsonPath("$.message").exists()); } + + @Test + @DisplayName("실패 - 유효성 검사 실패 (빈 내용)") + void fail_ValidationError_EmptyContent() throws Exception { + // given + PostRequest request = fixture.createEmptyContentRequest(); + + // when & then + mockMvc.perform(post(PostFixture.API_BASE_PATH) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.INVALID_INPUT_VALUE.getCode())); + } } @Nested @@ -137,17 +120,10 @@ class GetPost { @DisplayName("성공 - 존재하는 게시글 조회") void success() throws Exception { // given - Post savedPost = postRepository.save( - Post.builder() - .title("조회 테스트 게시글") - .content("조회 테스트 내용입니다.") - .category(PostCategory.CHAT) - .user(userRepository.findAll().get(0)) - .build() - ); + Post savedPost = fixture.createPostForDetail(testUser); // when & then - mockMvc.perform(get("/api/v1/posts/{postId}", savedPost.getId())) + mockMvc.perform(get(PostFixture.API_BASE_PATH + "/{postId}", savedPost.getId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.title").value("조회 테스트 게시글")) .andExpect(jsonPath("$.data.content").value("조회 테스트 내용입니다.")) @@ -160,12 +136,12 @@ void success() throws Exception { @DisplayName("실패 - 존재하지 않는 게시글 ID") void fail_NotFound() throws Exception { // when & then - mockMvc.perform(get("/api/v1/posts/{postId}", 9999L)) + mockMvc.perform(get(PostFixture.API_BASE_PATH + "/{postId}", PostFixture.NON_EXISTENT_POST_ID)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.status").value(HttpStatus.NOT_FOUND.value())) .andExpect(jsonPath("$.code").value(ErrorCode.POST_NOT_FOUND.getCode())) .andExpect(jsonPath("$.message").value(ErrorCode.POST_NOT_FOUND.getMessage())) - .andExpect(jsonPath("$.path").value("/api/v1/posts/9999")); + .andExpect(jsonPath("$.path").value(PostFixture.API_BASE_PATH + "/" + PostFixture.NON_EXISTENT_POST_ID)); } } @@ -177,7 +153,7 @@ class GetPosts { @DisplayName("성공 - 페이징 파라미터가 없는 경우") void successWithDefaultParameters() throws Exception { // when & then - mockMvc.perform(get("/api/v1/posts")) + mockMvc.perform(get(PostFixture.API_BASE_PATH)) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.page").value(1)) .andExpect(jsonPath("$.data.size").value(5)) @@ -189,35 +165,58 @@ void successWithDefaultParameters() throws Exception { @DisplayName("성공 - page와 size 모두 지정") void successWithBothParameters() throws Exception { // when & then - mockMvc.perform(get("/api/v1/posts") + mockMvc.perform(get(PostFixture.API_BASE_PATH) .param("page", "1") .param("size", "5")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.items.length()").value(5)) - .andExpect(jsonPath("$.data.items[0].title").value("목록 게시글 1")) - .andExpect(jsonPath("$.data.items[1].title").value("목록 게시글 2")) + .andExpect(jsonPath("$.data.items[0].title").value("시나리오 게시글 2")) + .andExpect(jsonPath("$.data.items[1].title").value("시나리오 게시글 1")) + .andExpect(jsonPath("$.data.items[2].title").value("목록 게시글 5")) .andExpect(jsonPath("$.data.page").value(1)) .andExpect(jsonPath("$.data.size").value(5)) - .andExpect(jsonPath("$.data.totalElements").value(5)) - .andExpect(jsonPath("$.data.totalPages").value(1)) - .andExpect(jsonPath("$.data.last").value(true)) - .andExpect(jsonPath("$.status").value(200)) - .andExpect(jsonPath("$.message").value("성공적으로 조회되었습니다.")); + .andExpect(jsonPath("$.data.totalElements").value(7)) + .andExpect(jsonPath("$.data.totalPages").value(2)) + .andExpect(jsonPath("$.data.last").value(false)); } @Test - @DisplayName("성공 - 정렬 파라미터 포함") - void successWithSortParameters() throws Exception { + @DisplayName("성공 - 카테고리 필터링 적용") + void successWithCategoryFilter() throws Exception { // when & then - mockMvc.perform(get("/api/v1/posts") - .param("page", "1") - .param("size", "5") - .param("sort", "createdDate,desc") - .param("sort", "title,asc")) + mockMvc.perform(get(PostFixture.API_BASE_PATH) + .param("category", PostCategory.SCENARIO.name())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.page").value(1)) - .andExpect(jsonPath("$.data.size").value(5)) - .andExpect(jsonPath("$.status").value(200)); + .andExpect(jsonPath("$.data.items[*].category").value( + Matchers.everyItem(Matchers.equalTo("SCENARIO")) + )) + .andExpect(jsonPath("$.data.items.length()").value(2)) + .andDo(print()); + } + + @Test + @DisplayName("성공 - 제목 + 내용 검색") + void successWithTitleContentSearch() throws Exception { + // when & then + mockMvc.perform(get(PostFixture.API_BASE_PATH) + .param("searchType", SearchType.TITLE_CONTENT.name()) + .param("keyword", "시나리오")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.items.length()").value(2)) + .andDo(print()); + } + + @Test + @DisplayName("성공 - 작성자 검색") + void successWithAuthorSearch() throws Exception { + // when & then + mockMvc.perform(get(PostFixture.API_BASE_PATH) + .param("searchType", SearchType.AUTHOR.name()) + .param("keyword", "작성자1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.items[*].author", + Matchers.everyItem(Matchers.containsStringIgnoringCase("작성자1")))) + .andDo(print()); } } @@ -231,21 +230,13 @@ class UpdatePost { "UPDATE users SET id = 1 WHERE login_id = 'testLoginId'" }) void success() throws Exception { - // given - ID=1인 사용자의 게시글 생성 + // given User user1 = userRepository.findById(1L).orElseThrow(); - Post savedPost = postRepository.save( - Post.builder() - .title("수정 전 제목") - .content("수정 전 내용") - .category(PostCategory.CHAT) - .user(user1) - .build() - ); - - PostRequest updateRequest = new PostRequest("수정된 제목", "수정된 내용", PostCategory.CHAT); + Post savedPost = fixture.createPostForUpdate(user1); + PostRequest updateRequest = fixture.createUpdateRequest(); // when & then - mockMvc.perform(put("/api/v1/posts/{postId}", savedPost.getId()) + mockMvc.perform(put(PostFixture.API_BASE_PATH + "/{postId}", savedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(updateRequest))) .andExpect(status().isOk()) @@ -261,21 +252,13 @@ void success() throws Exception { "UPDATE users SET id = 2 WHERE login_id = 'anotherLoginId'" }) void fail_UnauthorizedUser() throws Exception { - // given - ID=2인 사용자의 게시글 (ID=1 사용자가 수정 시도) + // given User user2 = userRepository.findById(2L).orElseThrow(); - Post savedPost = postRepository.save( - Post.builder() - .title("다른 사용자 게시글") - .content("다른 사용자 내용") - .category(PostCategory.CHAT) - .user(user2) - .build() - ); - - PostRequest updateRequest = new PostRequest("수정 시도", "수정 시도 내용", PostCategory.CHAT); + Post savedPost = fixture.createPostForUpdate(user2); + PostRequest updateRequest = fixture.createUpdateRequest(); // when & then - mockMvc.perform(put("/api/v1/posts/{postId}", savedPost.getId()) + mockMvc.perform(put(PostFixture.API_BASE_PATH + "/{postId}", savedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(updateRequest))) .andExpect(status().isUnauthorized()) diff --git a/back/src/test/java/com/back/domain/post/fixture/PostFixture.java b/back/src/test/java/com/back/domain/post/fixture/PostFixture.java new file mode 100644 index 0000000..17288c8 --- /dev/null +++ b/back/src/test/java/com/back/domain/post/fixture/PostFixture.java @@ -0,0 +1,107 @@ +package com.back.domain.post.fixture; + + +import com.back.domain.post.dto.PostRequest; +import com.back.domain.post.entity.Post; +import com.back.domain.post.enums.PostCategory; +import com.back.domain.post.repository.PostRepository; +import com.back.domain.user.entity.Gender; +import com.back.domain.user.entity.Mbti; +import com.back.domain.user.entity.Role; +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 테스트에 필요한 데이터 관리 클래스 + */ +public class PostFixture { + + public static final String API_BASE_PATH = "/api/v1/posts"; + public static final Long NON_EXISTENT_POST_ID = 9999L; + + private final UserRepository userRepository; + private final PostRepository postRepository; + + public PostFixture(UserRepository userRepository, PostRepository postRepository) { + this.userRepository = userRepository; + this.postRepository = postRepository; + } + + // User 생성 + public User createTestUser() { + return createUser("testLoginId", "test@example.com", "testPassword", "작성자1", Gender.M); + } + + public User createAnotherUser() { + return createUser("anotherLoginId", "another@example.com", "another", "작성자2", Gender.F); + } + + private User createUser(String loginId, String email, String password, String nickname, Gender gender) { + return userRepository.save(User.builder() + .loginId(loginId) + .email(email) + .password(password) + .nickname(nickname) + .beliefs("도전") + .gender(gender) + .role(Role.USER) + .mbti(Mbti.ISFJ) + .birthdayAt(LocalDateTime.of(2000, 1, 1, 0, 0)) + .build()); + } + + // Post 생성 + public Post createPost(User user, String title, String content, PostCategory category) { + return postRepository.save(Post.builder() + .title(title) + .content(content) + .category(category) + .user(user) + .build()); + } + + public List createPostsForPaging(User user, int count) { + List posts = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + posts.add(createPost(user, "목록 게시글 " + i, "목록 내용 " + i, PostCategory.CHAT)); + } + for (int i = 1; i <= count / 2; i++) { + posts.add(createPost(user, "시나리오 게시글 " + i, "시나리오 내용 " + i, PostCategory.SCENARIO)); + } + return posts; + } + + public Post createPostForDetail(User user) { + return createPost(user, "조회 테스트 게시글", "조회 테스트 내용입니다.", PostCategory.CHAT); + } + + public Post createPostForUpdate(User user) { + return createPost(user, "수정 전 제목", "수정 전 내용", PostCategory.CHAT); + } + + // PostRequest 생성 + public PostRequest createPostRequest(String title, String content, PostCategory category) { + return new PostRequest(title, content, category); + } + + public PostRequest createPostRequest() { + return createPostRequest("테스트 게시글", "테스트 내용입니다.", PostCategory.CHAT); + } + + public PostRequest createEmptyTitleRequest() { + return createPostRequest("", "테스트 내용입니다.", PostCategory.CHAT); + } + + public PostRequest createEmptyContentRequest() { + return createPostRequest("테스트 게시글", "", PostCategory.CHAT); + } + + public PostRequest createUpdateRequest() { + return createPostRequest("수정된 제목", "수정된 내용", PostCategory.CHAT); + } +} +