Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0633ef1
fix: 보드와 카테고리에 따른 포스트 조회 메서드 정렬 문제 해결
Hexeong Sep 30, 2025
89b0d95
test: 보드와 카테고리에 따른 포스트 조회 메서드 정렬 문제 해결 - 테스트 코드 수정
Hexeong Sep 30, 2025
6e164c6
fix: 코멘트 트리 조회 메서드 정렬 문제 해결
Hexeong Sep 30, 2025
a61e8ae
test: 코멘트 트리 조회 메서드 정렬 문제 해결 - 테스트 코드 수정
Hexeong Sep 30, 2025
8394ca0
style: PostRepository 메서드명 변경
Hexeong Sep 30, 2025
7120341
chore: todo 메세지 제거
Hexeong Sep 30, 2025
c1344f6
chore: 디버그 코드 제거
Hexeong Sep 30, 2025
ec76fad
test: 내림차순 정렬 검증이 비결정적일 수 있는 문제 수정
Hexeong Sep 30, 2025
563c71d
Revert "chore : debug code delete"
Hexeong Oct 1, 2025
920002e
refactor: 테스트 코드가 프로덕션 코드에게 영향 미치지 않게 reflaction 방식으로 createdAt 설정 메서…
Hexeong Oct 1, 2025
d6ba7be
refactor: 테스트 코드가 프로덕션 코드에게 영향 미치지 않게 TestTimeHelper 코드를 적용한 방식으로 Fix…
Hexeong Oct 1, 2025
a607431
test: Comment 조회 메서드에 대한 순서 정렬 테스트 코드 추가
Hexeong Oct 1, 2025
f45446a
test: 게시판 조회 메서드에 대한 순서 정렬 테스트 코드 추가
Hexeong Oct 1, 2025
6edde9b
test: 차단한 사용자 제외 댓글들 조회 메서드에 대한 순서 정렬 테스트 코드 추가
Hexeong Oct 1, 2025
58b8bdf
fix: rebase로 인한 테스트 코드 오류 수정
Hexeong Oct 1, 2025
244b292
test: import 구문 정리 & 디버깅 코드 제거
Hexeong Oct 2, 2025
6b561f7
refactor: 중복 동작 로직 제거
Hexeong Oct 2, 2025
e193241
test: 중복 검증 로직 제거
Hexeong Oct 2, 2025
dbc7955
test: 컨벤션에 맞춘 와일드 카드 정리
Hexeong Oct 2, 2025
eb7c63c
test: 차단된 경우에서 순서 유지 검증하는 테스트 추가
Hexeong Oct 2, 2025
774e3b2
chore: code reformatting 적용
Hexeong Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public ResponseEntity<?> findPostsByCodeAndCategory(
@PathVariable(value = "code") String code,
@RequestParam(value = "category", defaultValue = "전체") String category) {
List<PostListResponse> postsByCodeAndPostCategory = postQueryService
.findPostsByCodeAndPostCategory(code, category, siteUserId);
.findPostsByCodeAndPostCategoryOrderByCreatedAtDesc(code, category, siteUserId);
return ResponseEntity.ok().body(postsByCodeAndPostCategory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,30 @@
public interface CommentRepository extends JpaRepository<Comment, Long> {

@Query(value = """
WITH RECURSIVE CommentTree AS (
SELECT
id, parent_id, post_id, site_user_id, content,
created_at, updated_at, is_deleted,
0 AS level, CAST(id AS CHAR(255)) AS path
FROM comment
WHERE post_id = :postId AND parent_id IS NULL
AND site_user_id NOT IN (
SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
)
UNION ALL
SELECT
c.id, c.parent_id, c.post_id, c.site_user_id, c.content,
c.created_at, c.updated_at, c.is_deleted,
ct.level + 1, CONCAT(ct.path, '->', c.id)
FROM comment c
INNER JOIN CommentTree ct ON c.parent_id = ct.id
WHERE c.site_user_id NOT IN (
SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
)
)
SELECT * FROM CommentTree
ORDER BY path
""", nativeQuery = true)
WITH RECURSIVE CommentTree AS (
SELECT
id, parent_id, post_id, site_user_id, content,
created_at, updated_at, is_deleted,
0 AS level, CAST(id AS CHAR(255)) AS path
FROM comment
WHERE post_id = :postId AND parent_id IS NULL
AND site_user_id NOT IN (
SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
)
UNION ALL
SELECT
c.id, c.parent_id, c.post_id, c.site_user_id, c.content,
c.created_at, c.updated_at, c.is_deleted,
ct.level + 1, CONCAT(ct.path, '->', c.id)
FROM comment c
INNER JOIN CommentTree ct ON c.parent_id = ct.id
WHERE c.site_user_id NOT IN (
SELECT blocked_id FROM user_block WHERE blocker_id = :siteUserId
)
)
SELECT * FROM CommentTree
ORDER BY path, created_at
""", nativeQuery = true)
List<Comment> findCommentTreeByPostIdExcludingBlockedUsers(@Param("postId") Long postId, @Param("siteUserId") Long siteUserId);

default Comment getById(Long id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@

public interface PostRepository extends JpaRepository<Post, Long> {

List<Post> findByBoardCode(String boardCode);
List<Post> findByBoardCodeOrderByCreatedAtDesc(String boardCode);

@Query("""
SELECT p FROM Post p
WHERE p.boardCode = :boardCode
AND p.siteUserId NOT IN (
SELECT ub.blockedId FROM UserBlock ub WHERE ub.blockerId = :siteUserId
)
""")
List<Post> findByBoardCodeExcludingBlockedUsers(@Param("boardCode") String boardCode, @Param("siteUserId") Long siteUserId);
SELECT p FROM Post p
WHERE p.boardCode = :boardCode
AND p.siteUserId NOT IN (
SELECT ub.blockedId FROM UserBlock ub WHERE ub.blockerId = :siteUserId
)
ORDER BY p.createdAt DESC
""")
List<Post> findByBoardCodeExcludingBlockedUsersOrderByCreatedAtDesc(@Param("boardCode") String boardCode, @Param("siteUserId") Long siteUserId);

@EntityGraph(attributePaths = {"postImageList"})
Optional<Post> findPostById(Long id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ public class PostQueryService {
private final RedisUtils redisUtils;

@Transactional(readOnly = true)
public List<PostListResponse> findPostsByCodeAndPostCategory(String code, String category, Long siteUserId) {
public List<PostListResponse> findPostsByCodeAndPostCategoryOrderByCreatedAtDesc(String code, String category, Long siteUserId) {

String boardCode = validateCode(code);
PostCategory postCategory = validatePostCategory(category);
boardRepository.getByCode(boardCode);

List<Post> postList; // todo : 추후 개선 필요(현재 최신순으로 응답나가지 않고 있음)
List<Post> postList;
if (siteUserId != null) {
postList = postRepository.findByBoardCodeExcludingBlockedUsers(boardCode, siteUserId);
postList = postRepository.findByBoardCodeExcludingBlockedUsersOrderByCreatedAtDesc(boardCode, siteUserId);
} else {
postList = postRepository.findByBoardCode(boardCode);
postList = postRepository.findByBoardCodeOrderByCreatedAtDesc(boardCode);
}
return PostListResponse.from(getPostListByPostCategory(postList, postCategory));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.solidconnection.common.helper;

import com.example.solidconnection.common.BaseEntity;
import java.lang.reflect.Field;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class TestTimeHelper {

public static void setCreatedAt(BaseEntity entity, ZonedDateTime time) {
try {
Field field = BaseEntity.class.getDeclaredField("createdAt");
field.setAccessible(true);
field.set(entity, time.truncatedTo(ChronoUnit.MICROS));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,19 @@ public class CommentFixture {
.parentComment(parentComment)
.createChild();
}

public Comment 자식_댓글_지연저장(
String content,
Post post,
SiteUser siteUser,
Comment parentComment,
long secondsDelay
) {
return commentFixtureBuilder
.content(content)
.post(post)
.siteUser(siteUser)
.parentComment(parentComment)
.createChildWithDelaySeconds(secondsDelay);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.solidconnection.community.comment.fixture;

import com.example.solidconnection.common.helper.TestTimeHelper;
import com.example.solidconnection.community.comment.domain.Comment;
import com.example.solidconnection.community.comment.repository.CommentRepository;
import com.example.solidconnection.community.post.domain.Post;
Expand Down Expand Up @@ -46,8 +47,18 @@ public Comment createParent() {

public Comment createChild() {
Comment comment = new Comment(content);
comment.setPostAndSiteUserId(post, siteUser.getId());
comment.setParentCommentAndPostAndSiteUserId(parentComment, post, siteUser.getId());
return commentRepository.save(comment);
}

public Comment createChildWithDelaySeconds(long seconds) {
Comment comment = new Comment(content);
comment.setParentCommentAndPostAndSiteUserId(parentComment, post, siteUser.getId());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어라 이거도 레거시같은데 setParentCommentAndPostAndSiteUserId 동작이 setPostAndSiteUserId 동작을 포함하네요 ... 여기 PR에 반영하는 게 나을까요 따로 PR 파는 게 나을까요 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 같이 제거해도 될 거 같습니다!

7월에 반영된 거 같은데 놓쳤네요..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 반영하겠습니다!


Comment saved = commentRepository.save(comment);

TestTimeHelper.setCreatedAt(saved, saved.getCreatedAt().plusSeconds(seconds));

return commentRepository.save(saved);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,34 +82,55 @@ void setUp() {
class 댓글_조회_테스트 {

@Test
void 게시글의_모든_댓글을_조회한다() {
void 게시글의_모든_댓글과_대댓글을_생성시간_기준으로_정렬해_조회한다() {
// given
Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1);
Comment childComment = commentFixture.자식_댓글("자식 댓글 1", post, user2, parentComment);
List<Comment> comments = List.of(parentComment, childComment);
Comment childComment1 = commentFixture.자식_댓글("자식 댓글 1", post, user2, parentComment);
Comment childComment2 = commentFixture.자식_댓글_지연저장("자식 댓글 2", post, user1, parentComment, 3);
Comment childComment3 = commentFixture.자식_댓글_지연저장("자식 댓글 3", post, user2, parentComment, 5);
List<Comment> comments = List.of(parentComment, childComment1, childComment2, childComment3);

// when
List<PostFindCommentResponse> responses = commentService.findCommentsByPostId(user1.getId(), post.getId());

// then
assertAll(
() -> assertThat(responses).hasSize(comments.size()),
() -> assertThat(responses)
.filteredOn(response -> response.id().equals(parentComment.getId()))
.singleElement()
.satisfies(response -> assertAll(
() -> assertThat(response.id()).isEqualTo(parentComment.getId()),
() -> assertThat(response.parentId()).isNull(),
() -> assertThat(response.isOwner()).isTrue()
)),
() -> assertThat(responses)
.filteredOn(response -> response.id().equals(childComment.getId()))
.filteredOn(response -> response.id().equals(childComment1.getId()))
.singleElement()
.satisfies(response -> assertAll(
() -> assertThat(response.id()).isEqualTo(childComment.getId()),
() -> assertThat(response.parentId()).isEqualTo(parentComment.getId()),
() -> assertThat(response.isOwner()).isFalse()
))
)),
() -> assertThat(responses)
.filteredOn(response -> response.id().equals(childComment2.getId()))
.singleElement()
.satisfies(response -> assertAll(
() -> assertThat(response.parentId()).isEqualTo(parentComment.getId()),
() -> assertThat(response.isOwner()).isTrue()
)),
() -> assertThat(responses)
.filteredOn(response -> response.id().equals(childComment3.getId()))
.singleElement()
.satisfies(response -> assertAll(
() -> assertThat(response.parentId()).isEqualTo(parentComment.getId()),
() -> assertThat(response.isOwner()).isFalse()
)),
() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.containsExactly(
parentComment.getId(),
childComment1.getId(),
childComment2.getId(),
childComment3.getId()
)
);
}

Expand Down Expand Up @@ -198,24 +219,27 @@ class 댓글_조회_테스트 {
userBlockFixture.유저_차단(user1.getId(), user2.getId());
Comment parentComment1 = commentFixture.부모_댓글("부모 댓글1", post, user1);
Comment childComment1 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentComment1);
Comment childComment2 = commentFixture.자식_댓글("자식 댓글2", post, user2, parentComment1);
Comment parentCommen2 = commentFixture.부모_댓글("부모 댓글2", post, user2);
Comment childComment3 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentCommen2);
Comment childComment4 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentCommen2);

Comment childComment2 = commentFixture.자식_댓글_지연저장("자식 댓글2", post, user2, parentComment1, 2);
Comment childComment3 = commentFixture.자식_댓글_지연저장("자식 댓글3", post, user1, parentComment1, 3);
Comment parentComment2 = commentFixture.부모_댓글("부모 댓글2", post, user2);
Comment childComment4 = commentFixture.자식_댓글("자식 댓글1", post, user1, parentComment2);
Comment childComment5 = commentFixture.자식_댓글_지연저장("자식 댓글1", post, user1, parentComment2, 2);

// when
List<PostFindCommentResponse> responses = commentService.findCommentsByPostId(user1.getId(), post.getId());

// then
assertAll(
() -> assertThat(responses).hasSize(2),
() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.containsExactly(parentComment1.getId(), childComment1.getId()),
() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.doesNotContain(childComment2.getId(), parentCommen2.getId(), childComment3.getId(), childComment4.getId())
() -> assertThat(responses).hasSize(3),
() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.containsExactly(parentComment1.getId(), childComment1.getId(), childComment3.getId()),
() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.doesNotContain(childComment2.getId(), parentComment2.getId(), childComment4.getId(), childComment5.getId()),
() -> assertThat(responses)
.extracting(PostFindCommentResponse::id)
.containsSubsequence(parentComment1.getId(), childComment1.getId(), childComment3.getId())
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,25 @@ public class PostFixture {
.siteUser(siteUser)
.create();
}

public Post 게시글_지연저장(
String title,
String content,
Boolean isQuestion,
PostCategory postCategory,
Board board,
SiteUser siteUser,
long secondsDelay
) {
return postFixtureBuilder
.title(title)
.content(content)
.isQuestion(isQuestion)
.likeCount(0L)
.viewCount(0L)
.postCategory(postCategory)
.board(board)
.siteUser(siteUser)
.createWithDelaySeconds(secondsDelay);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.solidconnection.community.post.fixture;

import com.example.solidconnection.common.helper.TestTimeHelper;
import com.example.solidconnection.community.board.domain.Board;
import com.example.solidconnection.community.post.domain.Post;
import com.example.solidconnection.community.post.domain.PostCategory;
Expand Down Expand Up @@ -74,4 +75,20 @@ public Post create() {
post.setBoardAndSiteUserId(board.getCode(), siteUser.getId());
return postRepository.save(post);
}

public Post createWithDelaySeconds(long seconds) {
Post post = new Post(
title,
content,
isQuestion,
likeCount,
viewCount,
postCategory);
post.setBoardAndSiteUserId(board.getCode(), siteUser.getId());

Post saved = postRepository.save(post);

TestTimeHelper.setCreatedAt(saved, saved.getCreatedAt().plusSeconds(seconds));
return postRepository.save(post);
}
}
Loading
Loading