From 5d735098680bb888028d98f8e91240956b264f2a Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Fri, 8 May 2026 15:03:36 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20Post=20aggregate=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=ED=9A=8C=EA=B7=80=20=EC=9C=84?= =?UTF-8?q?=ED=97=98=EC=9D=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #378은 summary pipeline/embedding projection 정리 전에 Post aggregate의 직접 노출 규칙을 테스트로 잠가 이후 리팩토링에서 의도치 않은 동작 변경을 빠르게 감지하려는 안전망 작업이다. Constraint: viewCount 증가는 기존 repository atomic update 경계를 유지해야 함 Rejected: #379/#380 책임 정리를 이번 이슈에 포함 | 테스트 안전망 이슈 성격이 흐려짐 Confidence: high Scope-risk: narrow Directive: summary pipeline과 embedding projection 책임 이동은 후속 이슈에서 다뤄야 함 Tested: ./gradlew test --tests 'com.techfork.domain.post.entity.PostTest' Not-tested: summary/embedding batch 리팩토링 경로에 대한 추가 회귀 검증 --- .../com/techfork/domain/post/entity/Post.java | 3 - .../techfork/domain/post/entity/PostTest.java | 129 ++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/techfork/domain/post/entity/PostTest.java diff --git a/src/main/java/com/techfork/domain/post/entity/Post.java b/src/main/java/com/techfork/domain/post/entity/Post.java index 2a4c99d..0a3ecdc 100644 --- a/src/main/java/com/techfork/domain/post/entity/Post.java +++ b/src/main/java/com/techfork/domain/post/entity/Post.java @@ -106,9 +106,6 @@ public static Post create(RssFeedItem item, TechBlog techBlog) { .build(); } - public void updateSummary(String summary) { - this.summary = summary; - } public void updateSummaries(String summary, String shortSummary) { this.summary = summary; diff --git a/src/test/java/com/techfork/domain/post/entity/PostTest.java b/src/test/java/com/techfork/domain/post/entity/PostTest.java new file mode 100644 index 0000000..d17b5ce --- /dev/null +++ b/src/test/java/com/techfork/domain/post/entity/PostTest.java @@ -0,0 +1,129 @@ +package com.techfork.domain.post.entity; + +import com.techfork.domain.source.dto.RssFeedItem; +import com.techfork.domain.source.entity.TechBlog; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import static org.assertj.core.api.Assertions.assertThat; + +class PostTest { + + @Nested + @DisplayName("create") + class Create { + + @Test + @DisplayName("RssFeedItem과 TechBlog로 게시글 aggregate를 생성한다") + void createsPostAggregateFromRssFeedItem() { + TechBlog techBlog = createTechBlog(); + LocalDateTime publishedAt = LocalDateTime.of(2026, 5, 7, 10, 30); + RssFeedItem rssFeedItem = RssFeedItem.builder() + .title("Post aggregate 설계") + .url("https://posts.example.com/post-aggregate") + .logoUrl("https://cdn.example.com/logo.png") + .thumbnailUrl("https://cdn.example.com/thumb.png") + .content("원문 본문") + .plainContent("평문 본문") + .publishedAt(publishedAt) + .company("TechFork") + .techBlogId(1L) + .build(); + LocalDateTime beforeCreate = LocalDateTime.now(); + + Post post = Post.create(rssFeedItem, techBlog); + + LocalDateTime afterCreate = LocalDateTime.now(); + assertThat(post.getTitle()).isEqualTo("Post aggregate 설계"); + assertThat(post.getFullContent()).isEqualTo("원문 본문"); + assertThat(post.getPlainContent()).isEqualTo("평문 본문"); + assertThat(post.getCompany()).isEqualTo("TechFork"); + assertThat(post.getLogoUrl()).isEqualTo("https://cdn.example.com/logo.png"); + assertThat(post.getThumbnailUrl()).isEqualTo("https://cdn.example.com/thumb.png"); + assertThat(post.getUrl()).isEqualTo("https://posts.example.com/post-aggregate"); + assertThat(post.getPublishedAt()).isEqualTo(publishedAt); + assertThat(post.getCrawledAt()).isBetween(beforeCreate, afterCreate); + assertThat(post.getTechBlog()).isSameAs(techBlog); + assertThat(post.getViewCount()).isZero(); + assertThat(post.getKeywords()).isEmpty(); + } + } + + @Nested + @DisplayName("updateSummaries") + class UpdateSummaries { + + @Test + @DisplayName("summary와 shortSummary를 함께 갱신한다") + void updatesSummaryAndShortSummaryTogether() { + Post post = createPost(); + + post.updateSummaries("새 요약", "새 짧은 요약"); + + assertThat(post.getSummary()).isEqualTo("새 요약"); + assertThat(post.getShortSummary()).isEqualTo("새 짧은 요약"); + } + } + + @Nested + @DisplayName("addKeyword") + class AddKeyword { + + @Test + @DisplayName("keyword를 추가하고 동일한 post 연관관계를 유지한다") + void addsKeywordWithSamePostReference() { + Post post = createPost(); + PostKeyword keyword = PostKeyword.create("AI", post); + + post.addKeyword(keyword); + + assertThat(post.getKeywords()).containsExactly(keyword); + assertThat(keyword.getPost()).isSameAs(post); + } + } + + @Nested + @DisplayName("clearKeywords") + class ClearKeywords { + + @Test + @DisplayName("기존 keyword를 모두 제거한다") + void clearsExistingKeywords() { + Post post = createPost(); + post.addKeyword(PostKeyword.create("AI", post)); + post.addKeyword(PostKeyword.create("Batch", post)); + + post.clearKeywords(); + + assertThat(post.getKeywords()).isEmpty(); + } + } + + private Post createPost() { + return Post.create( + RssFeedItem.builder() + .title("Post aggregate 설계") + .url("https://posts.example.com/post-aggregate") + .logoUrl("https://cdn.example.com/logo.png") + .thumbnailUrl("https://cdn.example.com/thumb.png") + .content("원문 본문") + .plainContent("평문 본문") + .publishedAt(LocalDateTime.of(2026, 5, 7, 10, 30)) + .company("TechFork") + .techBlogId(1L) + .build(), + createTechBlog() + ); + } + + private TechBlog createTechBlog() { + return TechBlog.create( + "TechFork", + "https://techfork.example.com", + "https://techfork.example.com/rss", + "https://cdn.example.com/logo.png" + ); + } +}