From b2eafb082a598f70a9d33f96e07c3c55c0795182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:03:33 +0900 Subject: [PATCH 1/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EA=B3=B5=EC=A7=80=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=9C?= =?UTF-8?q?=ED=96=89=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../keyword/service/KeywordServiceTest.java | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java diff --git a/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java new file mode 100644 index 0000000000..39080f9c40 --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java @@ -0,0 +1,171 @@ +package in.koreatech.koin.unit.domain.community.keyword.service; + +import static in.koreatech.koin.domain.community.keyword.enums.KeywordCategory.KOREATECH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +import in.koreatech.koin.common.event.KoreatechArticleKeywordEvent; +import in.koreatech.koin.domain.community.article.model.readmodel.ArticleSummary; +import in.koreatech.koin.domain.community.article.repository.ArticleRepository; +import in.koreatech.koin.domain.community.keyword.dto.KeywordNotificationRequest; +import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordRepository; +import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordSuggestRepository; +import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordUserMapRepository; +import in.koreatech.koin.domain.community.keyword.service.ArticleKeywordUserMatcher; +import in.koreatech.koin.domain.community.keyword.service.KeywordService; +import in.koreatech.koin.domain.community.util.KeywordExtractor; +import in.koreatech.koin.domain.user.repository.UserRepository; + +@ExtendWith(MockitoExtension.class) +public class KeywordServiceTest { + + @InjectMocks + private KeywordService keywordService; + + @Mock + private ApplicationEventPublisher eventPublisher; + + @Mock + private ArticleKeywordUserMapRepository articleKeywordUserMapRepository; + + @Mock + private ArticleKeywordRepository articleKeywordRepository; + + @Mock + private ArticleKeywordSuggestRepository articleKeywordSuggestRepository; + + @Mock + private ArticleRepository articleRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private KeywordExtractor keywordExtractor; + + @Mock + private ArticleKeywordUserMatcher articleKeywordUserMatcher; + + @Test + void 공지사항_ID_목록으로_게시글_요약을_조회한다() { + Set articleIds = Set.of(1, 2, 3); + KeywordNotificationRequest request = new KeywordNotificationRequest(articleIds); + when(articleRepository.findAllSummariesByIdIn(articleIds)).thenReturn(List.of()); + + keywordService.sendKeywordNotification(request); + + verify(articleRepository).findAllSummariesByIdIn(articleIds); + } + + @Test + void 게시글_제목에_매칭된_키워드가_없으면_이벤트를_발행하지_않는다() { + KeywordNotificationRequest request = new KeywordNotificationRequest(Set.of(1)); + ArticleSummary articleSummary = new ArticleSummary(1, 4, "일반 공지입니다"); + when(articleRepository.findAllSummariesByIdIn(request.updateNotification())) + .thenReturn(List.of(articleSummary)); + when(keywordExtractor.matchKeywords(articleSummary.title(), KOREATECH)) + .thenReturn(List.of()); + + keywordService.sendKeywordNotification(request); + + verifyNoInteractions(articleKeywordUserMatcher); + verify(eventPublisher, never()).publishEvent(any()); + } + + @Test + void 매칭된_키워드를_가진_사용자가_없으면_이벤트를_발행하지_않는다() { + KeywordNotificationRequest request = new KeywordNotificationRequest(Set.of(1)); + ArticleSummary articleSummary = new ArticleSummary(1, 4, "수강신청 안내"); + List matchedKeywords = List.of("수강신청"); + when(articleRepository.findAllSummariesByIdIn(request.updateNotification())) + .thenReturn(List.of(articleSummary)); + when(keywordExtractor.matchKeywords(articleSummary.title(), KOREATECH)) + .thenReturn(matchedKeywords); + when(articleKeywordUserMatcher.findKeywordsByUserId(KOREATECH, matchedKeywords)) + .thenReturn(Map.of()); + + keywordService.sendKeywordNotification(request); + + verify(eventPublisher, never()).publishEvent(any()); + } + + @Test + void 매칭된_키워드를_가진_사용자가_있으면_키워드_이벤트를_발행한다() { + KeywordNotificationRequest request = new KeywordNotificationRequest(Set.of(1)); + ArticleSummary articleSummary = new ArticleSummary(1, 4, "수강신청 안내"); + List matchedKeywords = List.of("수강신청"); + Map keywordByUserId = Map.of( + 10, "수강신청", + 20, "신청" + ); + when(articleRepository.findAllSummariesByIdIn(request.updateNotification())) + .thenReturn(List.of(articleSummary)); + when(keywordExtractor.matchKeywords(articleSummary.title(), KOREATECH)) + .thenReturn(matchedKeywords); + when(articleKeywordUserMatcher.findKeywordsByUserId(KOREATECH, matchedKeywords)) + .thenReturn(keywordByUserId); + + keywordService.sendKeywordNotification(request); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass( + KoreatechArticleKeywordEvent.class); + verify(eventPublisher).publishEvent(eventCaptor.capture()); + KoreatechArticleKeywordEvent event = eventCaptor.getValue(); + + assertThat(event.articleId()).isEqualTo(articleSummary.id()); + assertThat(event.boardId()).isEqualTo(articleSummary.boardId()); + assertThat(event.articleTitle()).isEqualTo(articleSummary.title()); + assertThat(event.matchedKeywordUsers().keywordByUserId()).isEqualTo(keywordByUserId); + } + + @Test + void 여러_게시글_중_매칭된_사용자가_있는_게시글만_이벤트를_발행한다() { + KeywordNotificationRequest request = new KeywordNotificationRequest(Set.of(1, 2, 3)); + ArticleSummary unmatchedArticle = new ArticleSummary(1, 4, "일반 공지입니다"); + ArticleSummary noUserArticle = new ArticleSummary(2, 4, "장학금 안내"); + ArticleSummary matchedArticle = new ArticleSummary(3, 4, "수강신청 안내"); + List scholarshipKeywords = List.of("장학금"); + List courseRegistrationKeywords = List.of("수강신청"); + Map keywordByUserId = Map.of(10, "수강신청"); + when(articleRepository.findAllSummariesByIdIn(request.updateNotification())) + .thenReturn(List.of(unmatchedArticle, noUserArticle, matchedArticle)); + when(keywordExtractor.matchKeywords(unmatchedArticle.title(), KOREATECH)) + .thenReturn(List.of()); + when(keywordExtractor.matchKeywords(noUserArticle.title(), KOREATECH)) + .thenReturn(scholarshipKeywords); + when(keywordExtractor.matchKeywords(matchedArticle.title(), KOREATECH)) + .thenReturn(courseRegistrationKeywords); + when(articleKeywordUserMatcher.findKeywordsByUserId(KOREATECH, scholarshipKeywords)) + .thenReturn(Map.of()); + when(articleKeywordUserMatcher.findKeywordsByUserId(KOREATECH, courseRegistrationKeywords)) + .thenReturn(keywordByUserId); + + keywordService.sendKeywordNotification(request); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass( + KoreatechArticleKeywordEvent.class); + verify(eventPublisher).publishEvent(eventCaptor.capture()); + KoreatechArticleKeywordEvent event = eventCaptor.getValue(); + + assertThat(event.articleId()).isEqualTo(matchedArticle.id()); + assertThat(event.boardId()).isEqualTo(matchedArticle.boardId()); + assertThat(event.articleTitle()).isEqualTo(matchedArticle.title()); + assertThat(event.matchedKeywordUsers().keywordByUserId()).isEqualTo(keywordByUserId); + } +} From 0b80f26e7f439bfe63c6dc679fae268e1f562d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:04:31 +0900 Subject: [PATCH 2/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=20=EB=A7=A4=EC=B9=AD=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArticleKeywordUserMatcherTest.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java diff --git a/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java new file mode 100644 index 0000000000..908332bc24 --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java @@ -0,0 +1,130 @@ +package in.koreatech.koin.unit.domain.community.keyword.service; + +import static in.koreatech.koin.domain.community.keyword.enums.KeywordCategory.KOREATECH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +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 in.koreatech.koin.domain.community.keyword.model.ArticleKeyword; +import in.koreatech.koin.domain.community.keyword.model.ArticleKeywordUserMap; +import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordUserMapRepository; +import in.koreatech.koin.domain.community.keyword.service.ArticleKeywordUserMatcher; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.unit.fixture.UserFixture; + +@ExtendWith(MockitoExtension.class) +public class ArticleKeywordUserMatcherTest { + + @InjectMocks + private ArticleKeywordUserMatcher articleKeywordUserMatcher; + + @Mock + private ArticleKeywordUserMapRepository articleKeywordUserMapRepository; + + @Test + void 매칭된_키워드로_사용자별_키워드를_조회한다() { + List matchedKeywords = List.of("수강신청", "장학금"); + when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( + KOREATECH, + matchedKeywords + )).thenReturn(List.of()); + + articleKeywordUserMatcher.findKeywordsByUserId(KOREATECH, matchedKeywords); + + verify(articleKeywordUserMapRepository) + .findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn(KOREATECH, matchedKeywords); + } + + @Test + void 키워드_사용자_매핑을_사용자별_키워드로_변환한다() { + User firstUser = UserFixture.id_설정_코인_유저(1); + User secondUser = UserFixture.id_설정_코인_유저(2); + ArticleKeyword firstKeyword = createArticleKeyword("수강신청"); + ArticleKeyword secondKeyword = createArticleKeyword("장학금"); + List matchedKeywords = List.of("수강신청", "장학금"); + when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( + KOREATECH, + matchedKeywords + )).thenReturn(List.of( + createKeywordUserMap(firstUser, firstKeyword), + createKeywordUserMap(secondUser, secondKeyword) + )); + + Map keywordByUserId = articleKeywordUserMatcher.findKeywordsByUserId( + KOREATECH, + matchedKeywords + ); + + assertThat(keywordByUserId).containsExactlyInAnyOrderEntriesOf(Map.of( + firstUser.getId(), firstKeyword.getKeyword(), + secondUser.getId(), secondKeyword.getKeyword() + )); + } + + @Test + void 한_사용자가_여러_키워드에_매칭되면_더_긴_키워드를_선택한다() { + User user = UserFixture.id_설정_코인_유저(1); + ArticleKeyword shortKeyword = createArticleKeyword("신청"); + ArticleKeyword longKeyword = createArticleKeyword("수강신청"); + List matchedKeywords = List.of("신청", "수강신청"); + when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( + KOREATECH, + matchedKeywords + )).thenReturn(List.of( + createKeywordUserMap(user, shortKeyword), + createKeywordUserMap(user, longKeyword) + )); + + Map keywordByUserId = articleKeywordUserMatcher.findKeywordsByUserId( + KOREATECH, + matchedKeywords + ); + + assertThat(keywordByUserId).containsExactly(Map.entry(user.getId(), longKeyword.getKeyword())); + } + + @Test + void 한_사용자가_같은_길이의_키워드에_매칭되면_먼저_조회된_키워드를_유지한다() { + User user = UserFixture.id_설정_코인_유저(1); + ArticleKeyword firstKeyword = createArticleKeyword("장학금"); + ArticleKeyword secondKeyword = createArticleKeyword("생활관"); + List matchedKeywords = List.of("장학금", "생활관"); + when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( + KOREATECH, + matchedKeywords + )).thenReturn(List.of( + createKeywordUserMap(user, firstKeyword), + createKeywordUserMap(user, secondKeyword) + )); + + Map keywordByUserId = articleKeywordUserMatcher.findKeywordsByUserId( + KOREATECH, + matchedKeywords + ); + + assertThat(keywordByUserId).containsExactly(Map.entry(user.getId(), firstKeyword.getKeyword())); + } + + private ArticleKeyword createArticleKeyword(String keyword) { + return ArticleKeyword.builder() + .keyword(keyword) + .category(KOREATECH) + .build(); + } + + private ArticleKeywordUserMap createKeywordUserMap(User user, ArticleKeyword articleKeyword) { + return ArticleKeywordUserMap.builder() + .user(user) + .articleKeyword(articleKeyword) + .build(); + } +} From e1b7cda7d2c9102f023b2d30a9545a1d9b30a4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:06:07 +0900 Subject: [PATCH 3/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=B2=98=EB=A6=AC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KeywordNotificationServiceTest.java | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java diff --git a/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java new file mode 100644 index 0000000000..eb12444476 --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java @@ -0,0 +1,200 @@ +package in.koreatech.koin.unit.domain.notification.service; + +import static in.koreatech.koin.common.model.MobileAppPath.KEYWORD; +import static in.koreatech.koin.domain.notification.model.NotificationSubscribeType.ARTICLE_KEYWORD; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import in.koreatech.koin.common.event.KoreatechArticleKeywordEvent; +import in.koreatech.koin.domain.notification.model.Notification; +import in.koreatech.koin.domain.notification.model.NotificationFactory; +import in.koreatech.koin.domain.notification.model.NotificationSubscribe; +import in.koreatech.koin.domain.notification.repository.NotificationSubscribeRepository; +import in.koreatech.koin.domain.notification.service.KeywordNotificationService; +import in.koreatech.koin.domain.notification.service.NotificationService; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.unit.fixture.UserFixture; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("unchecked") +public class KeywordNotificationServiceTest { + + @InjectMocks + private KeywordNotificationService keywordNotificationService; + + @Mock + private NotificationFactory notificationFactory; + + @Mock + private NotificationSubscribeRepository notificationSubscribeRepository; + + @Mock + private NotificationService notificationService; + + @Test + void 매칭된_사용자가_없으면_알림을_처리하지_않는다() { + KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + 1, + 4, + "수강신청 안내", + Map.of() + ); + + keywordNotificationService.notifyArticleKeyword(event); + + verifyNoInteractions(notificationSubscribeRepository, notificationFactory, notificationService); + } + + @Test + void 매칭된_사용자의_공지_키워드_구독을_조회한다() { + KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + 1, + 4, + "수강신청 안내", + Map.of( + 1, "수강신청", + 2, "장학금" + ) + ); + when(notificationSubscribeRepository.findArticleKeywordSubscribesByUserIdIn( + eq(ARTICLE_KEYWORD), + anyList() + )).thenReturn(List.of()); + + keywordNotificationService.notifyArticleKeyword(event); + + ArgumentCaptor> userIdsCaptor = ArgumentCaptor.forClass(List.class); + verify(notificationSubscribeRepository) + .findArticleKeywordSubscribesByUserIdIn(eq(ARTICLE_KEYWORD), userIdsCaptor.capture()); + assertThat(userIdsCaptor.getValue()).containsExactlyInAnyOrder(1, 2); + } + + @Test + void 구독자별로_키워드_알림을_생성하고_푸시한다() { + User firstUser = UserFixture.id_설정_코인_유저(1); + User secondUser = UserFixture.id_설정_코인_유저(2); + NotificationSubscribe firstSubscribe = createNotificationSubscribe(firstUser); + NotificationSubscribe secondSubscribe = createNotificationSubscribe(secondUser); + KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + 100, + 4, + "수강신청 안내", + Map.of( + firstUser.getId(), "수강신청", + secondUser.getId(), "신청" + ) + ); + Notification firstNotification = createNotification(firstUser); + Notification secondNotification = createNotification(secondUser); + when(notificationSubscribeRepository.findArticleKeywordSubscribesByUserIdIn( + eq(ARTICLE_KEYWORD), + anyList() + )).thenReturn(List.of(firstSubscribe, secondSubscribe)); + when(notificationFactory.generateKeywordNotification( + KEYWORD, + event.articleId(), + "수강신청", + event.articleTitle(), + event.boardId(), + firstUser + )).thenReturn(firstNotification); + when(notificationFactory.generateKeywordNotification( + KEYWORD, + event.articleId(), + "신청", + event.articleTitle(), + event.boardId(), + secondUser + )).thenReturn(secondNotification); + + keywordNotificationService.notifyArticleKeyword(event); + + ArgumentCaptor> notificationsCaptor = ArgumentCaptor.forClass(List.class); + verify(notificationService).pushNotifications(notificationsCaptor.capture()); + assertThat(notificationsCaptor.getValue()).containsExactly(firstNotification, secondNotification); + } + + @Test + void 매칭된_사용자라도_구독자가_아니면_알림을_생성하지_않는다() { + User subscribedUser = UserFixture.id_설정_코인_유저(1); + KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + 100, + 4, + "수강신청 안내", + Map.of( + subscribedUser.getId(), "수강신청", + 2, "장학금" + ) + ); + NotificationSubscribe subscribe = createNotificationSubscribe(subscribedUser); + Notification notification = createNotification(subscribedUser); + when(notificationSubscribeRepository.findArticleKeywordSubscribesByUserIdIn( + eq(ARTICLE_KEYWORD), + anyList() + )).thenReturn(List.of(subscribe)); + when(notificationFactory.generateKeywordNotification( + KEYWORD, + event.articleId(), + "수강신청", + event.articleTitle(), + event.boardId(), + subscribedUser + )).thenReturn(notification); + + keywordNotificationService.notifyArticleKeyword(event); + + verify(notificationFactory).generateKeywordNotification( + KEYWORD, + event.articleId(), + "수강신청", + event.articleTitle(), + event.boardId(), + subscribedUser + ); + verify(notificationFactory, never()).generateKeywordNotification( + eq(KEYWORD), + eq(event.articleId()), + eq("장학금"), + eq(event.articleTitle()), + eq(event.boardId()), + any(User.class) + ); + ArgumentCaptor> notificationsCaptor = ArgumentCaptor.forClass(List.class); + verify(notificationService).pushNotifications(notificationsCaptor.capture()); + assertThat(notificationsCaptor.getValue()).containsExactly(notification); + } + + private NotificationSubscribe createNotificationSubscribe(User user) { + return NotificationSubscribe.builder() + .subscribeType(ARTICLE_KEYWORD) + .user(user) + .build(); + } + + private Notification createNotification(User user) { + return Notification.of( + KEYWORD, + "koin://keyword", + "수강신청 안내", + "방금 등록된 수강신청 공지를 확인해보세요!", + null, + user + ); + } +} From c48a1371549d81b091c4c6b04a47d3ec9f608e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:11:23 +0900 Subject: [PATCH 4/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=94=BD=EC=8A=A4=EC=B2=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArticleKeywordUserMatcherTest.java | 39 ++++++----------- .../KeywordNotificationServiceTest.java | 39 ++++++----------- .../koin/unit/fixture/KeywordFixture.java | 42 +++++++++++++++++++ .../unit/fixture/NotificationFixture.java | 31 ++++++++++++++ 4 files changed, 98 insertions(+), 53 deletions(-) create mode 100644 src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java create mode 100644 src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java diff --git a/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java index 908332bc24..eadb3594a2 100644 --- a/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java +++ b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/ArticleKeywordUserMatcherTest.java @@ -15,10 +15,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword; -import in.koreatech.koin.domain.community.keyword.model.ArticleKeywordUserMap; import in.koreatech.koin.domain.community.keyword.repository.ArticleKeywordUserMapRepository; import in.koreatech.koin.domain.community.keyword.service.ArticleKeywordUserMatcher; import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.unit.fixture.KeywordFixture; import in.koreatech.koin.unit.fixture.UserFixture; @ExtendWith(MockitoExtension.class) @@ -48,15 +48,15 @@ public class ArticleKeywordUserMatcherTest { void 키워드_사용자_매핑을_사용자별_키워드로_변환한다() { User firstUser = UserFixture.id_설정_코인_유저(1); User secondUser = UserFixture.id_설정_코인_유저(2); - ArticleKeyword firstKeyword = createArticleKeyword("수강신청"); - ArticleKeyword secondKeyword = createArticleKeyword("장학금"); + ArticleKeyword firstKeyword = KeywordFixture.공지_키워드("수강신청"); + ArticleKeyword secondKeyword = KeywordFixture.공지_키워드("장학금"); List matchedKeywords = List.of("수강신청", "장학금"); when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( KOREATECH, matchedKeywords )).thenReturn(List.of( - createKeywordUserMap(firstUser, firstKeyword), - createKeywordUserMap(secondUser, secondKeyword) + KeywordFixture.키워드_사용자_매핑(firstUser, firstKeyword), + KeywordFixture.키워드_사용자_매핑(secondUser, secondKeyword) )); Map keywordByUserId = articleKeywordUserMatcher.findKeywordsByUserId( @@ -73,15 +73,15 @@ public class ArticleKeywordUserMatcherTest { @Test void 한_사용자가_여러_키워드에_매칭되면_더_긴_키워드를_선택한다() { User user = UserFixture.id_설정_코인_유저(1); - ArticleKeyword shortKeyword = createArticleKeyword("신청"); - ArticleKeyword longKeyword = createArticleKeyword("수강신청"); + ArticleKeyword shortKeyword = KeywordFixture.공지_키워드("신청"); + ArticleKeyword longKeyword = KeywordFixture.공지_키워드("수강신청"); List matchedKeywords = List.of("신청", "수강신청"); when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( KOREATECH, matchedKeywords )).thenReturn(List.of( - createKeywordUserMap(user, shortKeyword), - createKeywordUserMap(user, longKeyword) + KeywordFixture.키워드_사용자_매핑(user, shortKeyword), + KeywordFixture.키워드_사용자_매핑(user, longKeyword) )); Map keywordByUserId = articleKeywordUserMatcher.findKeywordsByUserId( @@ -95,15 +95,15 @@ public class ArticleKeywordUserMatcherTest { @Test void 한_사용자가_같은_길이의_키워드에_매칭되면_먼저_조회된_키워드를_유지한다() { User user = UserFixture.id_설정_코인_유저(1); - ArticleKeyword firstKeyword = createArticleKeyword("장학금"); - ArticleKeyword secondKeyword = createArticleKeyword("생활관"); + ArticleKeyword firstKeyword = KeywordFixture.공지_키워드("장학금"); + ArticleKeyword secondKeyword = KeywordFixture.공지_키워드("생활관"); List matchedKeywords = List.of("장학금", "생활관"); when(articleKeywordUserMapRepository.findAllByArticleKeywordCategoryAndArticleKeywordKeywordIn( KOREATECH, matchedKeywords )).thenReturn(List.of( - createKeywordUserMap(user, firstKeyword), - createKeywordUserMap(user, secondKeyword) + KeywordFixture.키워드_사용자_매핑(user, firstKeyword), + KeywordFixture.키워드_사용자_매핑(user, secondKeyword) )); Map keywordByUserId = articleKeywordUserMatcher.findKeywordsByUserId( @@ -114,17 +114,4 @@ public class ArticleKeywordUserMatcherTest { assertThat(keywordByUserId).containsExactly(Map.entry(user.getId(), firstKeyword.getKeyword())); } - private ArticleKeyword createArticleKeyword(String keyword) { - return ArticleKeyword.builder() - .keyword(keyword) - .category(KOREATECH) - .build(); - } - - private ArticleKeywordUserMap createKeywordUserMap(User user, ArticleKeyword articleKeyword) { - return ArticleKeywordUserMap.builder() - .user(user) - .articleKeyword(articleKeyword) - .build(); - } } diff --git a/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java index eb12444476..5df765b039 100644 --- a/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java +++ b/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java @@ -29,6 +29,8 @@ import in.koreatech.koin.domain.notification.service.KeywordNotificationService; import in.koreatech.koin.domain.notification.service.NotificationService; import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.unit.fixture.KeywordFixture; +import in.koreatech.koin.unit.fixture.NotificationFixture; import in.koreatech.koin.unit.fixture.UserFixture; @ExtendWith(MockitoExtension.class) @@ -49,7 +51,7 @@ public class KeywordNotificationServiceTest { @Test void 매칭된_사용자가_없으면_알림을_처리하지_않는다() { - KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + KoreatechArticleKeywordEvent event = KeywordFixture.공지_키워드_이벤트( 1, 4, "수강신청 안내", @@ -63,7 +65,7 @@ public class KeywordNotificationServiceTest { @Test void 매칭된_사용자의_공지_키워드_구독을_조회한다() { - KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + KoreatechArticleKeywordEvent event = KeywordFixture.공지_키워드_이벤트( 1, 4, "수강신청 안내", @@ -89,9 +91,9 @@ public class KeywordNotificationServiceTest { void 구독자별로_키워드_알림을_생성하고_푸시한다() { User firstUser = UserFixture.id_설정_코인_유저(1); User secondUser = UserFixture.id_설정_코인_유저(2); - NotificationSubscribe firstSubscribe = createNotificationSubscribe(firstUser); - NotificationSubscribe secondSubscribe = createNotificationSubscribe(secondUser); - KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + NotificationSubscribe firstSubscribe = NotificationFixture.공지_키워드_구독(firstUser); + NotificationSubscribe secondSubscribe = NotificationFixture.공지_키워드_구독(secondUser); + KoreatechArticleKeywordEvent event = KeywordFixture.공지_키워드_이벤트( 100, 4, "수강신청 안내", @@ -100,8 +102,8 @@ public class KeywordNotificationServiceTest { secondUser.getId(), "신청" ) ); - Notification firstNotification = createNotification(firstUser); - Notification secondNotification = createNotification(secondUser); + Notification firstNotification = NotificationFixture.키워드_알림(firstUser); + Notification secondNotification = NotificationFixture.키워드_알림(secondUser); when(notificationSubscribeRepository.findArticleKeywordSubscribesByUserIdIn( eq(ARTICLE_KEYWORD), anyList() @@ -133,7 +135,7 @@ public class KeywordNotificationServiceTest { @Test void 매칭된_사용자라도_구독자가_아니면_알림을_생성하지_않는다() { User subscribedUser = UserFixture.id_설정_코인_유저(1); - KoreatechArticleKeywordEvent event = KoreatechArticleKeywordEvent.of( + KoreatechArticleKeywordEvent event = KeywordFixture.공지_키워드_이벤트( 100, 4, "수강신청 안내", @@ -142,8 +144,8 @@ public class KeywordNotificationServiceTest { 2, "장학금" ) ); - NotificationSubscribe subscribe = createNotificationSubscribe(subscribedUser); - Notification notification = createNotification(subscribedUser); + NotificationSubscribe subscribe = NotificationFixture.공지_키워드_구독(subscribedUser); + Notification notification = NotificationFixture.키워드_알림(subscribedUser); when(notificationSubscribeRepository.findArticleKeywordSubscribesByUserIdIn( eq(ARTICLE_KEYWORD), anyList() @@ -180,21 +182,4 @@ public class KeywordNotificationServiceTest { assertThat(notificationsCaptor.getValue()).containsExactly(notification); } - private NotificationSubscribe createNotificationSubscribe(User user) { - return NotificationSubscribe.builder() - .subscribeType(ARTICLE_KEYWORD) - .user(user) - .build(); - } - - private Notification createNotification(User user) { - return Notification.of( - KEYWORD, - "koin://keyword", - "수강신청 안내", - "방금 등록된 수강신청 공지를 확인해보세요!", - null, - user - ); - } } diff --git a/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java b/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java new file mode 100644 index 0000000000..67c876d3be --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java @@ -0,0 +1,42 @@ +package in.koreatech.koin.unit.fixture; + +import java.util.Map; + +import in.koreatech.koin.common.event.KoreatechArticleKeywordEvent; +import in.koreatech.koin.domain.community.keyword.enums.KeywordCategory; +import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword; +import in.koreatech.koin.domain.community.keyword.model.ArticleKeywordUserMap; +import in.koreatech.koin.domain.user.model.User; + +public final class KeywordFixture { + + private KeywordFixture() {} + + public static ArticleKeyword 공지_키워드(String keyword) { + return ArticleKeyword.builder() + .keyword(keyword) + .category(KeywordCategory.KOREATECH) + .build(); + } + + public static ArticleKeywordUserMap 키워드_사용자_매핑(User user, ArticleKeyword articleKeyword) { + return ArticleKeywordUserMap.builder() + .user(user) + .articleKeyword(articleKeyword) + .build(); + } + + public static KoreatechArticleKeywordEvent 공지_키워드_이벤트( + Integer articleId, + Integer boardId, + String articleTitle, + Map keywordByUserId + ) { + return KoreatechArticleKeywordEvent.of( + articleId, + boardId, + articleTitle, + keywordByUserId + ); + } +} diff --git a/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java b/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java new file mode 100644 index 0000000000..d56dcfbbce --- /dev/null +++ b/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java @@ -0,0 +1,31 @@ +package in.koreatech.koin.unit.fixture; + +import static in.koreatech.koin.common.model.MobileAppPath.KEYWORD; +import static in.koreatech.koin.domain.notification.model.NotificationSubscribeType.ARTICLE_KEYWORD; + +import in.koreatech.koin.domain.notification.model.Notification; +import in.koreatech.koin.domain.notification.model.NotificationSubscribe; +import in.koreatech.koin.domain.user.model.User; + +public final class NotificationFixture { + + private NotificationFixture() {} + + public static NotificationSubscribe 공지_키워드_구독(User user) { + return NotificationSubscribe.builder() + .subscribeType(ARTICLE_KEYWORD) + .user(user) + .build(); + } + + public static Notification 키워드_알림(User user) { + return Notification.of( + KEYWORD, + "koin://keyword", + "수강신청 안내", + "방금 등록된 수강신청 공지를 확인해보세요!", + null, + user + ); + } +} From db6eb24692301a8a18073430ecbd0ef0d8eaabc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:13:00 +0900 Subject: [PATCH 5/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20import=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/KeywordNotificationServiceTest.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java index 5df765b039..116579e14e 100644 --- a/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java +++ b/src/test/java/in/koreatech/koin/unit/domain/notification/service/KeywordNotificationServiceTest.java @@ -3,13 +3,8 @@ import static in.koreatech.koin.common.model.MobileAppPath.KEYWORD; import static in.koreatech.koin.domain.notification.model.NotificationSubscribeType.ARTICLE_KEYWORD; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; import java.util.List; import java.util.Map; @@ -34,7 +29,6 @@ import in.koreatech.koin.unit.fixture.UserFixture; @ExtendWith(MockitoExtension.class) -@SuppressWarnings("unchecked") public class KeywordNotificationServiceTest { @InjectMocks From be098ffec457a15eea9a82fceeba8470a9c3aad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:14:01 +0900 Subject: [PATCH 6/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20imp?= =?UTF-8?q?ort=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/community/keyword/service/KeywordServiceTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java index 39080f9c40..fae68a91a6 100644 --- a/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java +++ b/src/test/java/in/koreatech/koin/unit/domain/community/keyword/service/KeywordServiceTest.java @@ -3,10 +3,7 @@ import static in.koreatech.koin.domain.community.keyword.enums.KeywordCategory.KOREATECH; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import java.util.List; import java.util.Map; From 32e0913474f721f877cd923464c628be2c769eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Sat, 6 Jun 2026 21:18:14 +0900 Subject: [PATCH 7/7] =?UTF-8?q?test:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=ED=94=BD=EC=8A=A4=EC=B2=98=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/unit/fixture/KeywordFixture.java | 7 ++++--- .../koreatech/koin/unit/fixture/NotificationFixture.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java b/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java index 67c876d3be..e817b492d5 100644 --- a/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java +++ b/src/test/java/in/koreatech/koin/unit/fixture/KeywordFixture.java @@ -1,21 +1,22 @@ package in.koreatech.koin.unit.fixture; +import static in.koreatech.koin.domain.community.keyword.enums.KeywordCategory.KOREATECH; + import java.util.Map; import in.koreatech.koin.common.event.KoreatechArticleKeywordEvent; -import in.koreatech.koin.domain.community.keyword.enums.KeywordCategory; import in.koreatech.koin.domain.community.keyword.model.ArticleKeyword; import in.koreatech.koin.domain.community.keyword.model.ArticleKeywordUserMap; import in.koreatech.koin.domain.user.model.User; -public final class KeywordFixture { +public class KeywordFixture { private KeywordFixture() {} public static ArticleKeyword 공지_키워드(String keyword) { return ArticleKeyword.builder() .keyword(keyword) - .category(KeywordCategory.KOREATECH) + .category(KOREATECH) .build(); } diff --git a/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java b/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java index d56dcfbbce..816577f2d7 100644 --- a/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java +++ b/src/test/java/in/koreatech/koin/unit/fixture/NotificationFixture.java @@ -7,7 +7,7 @@ import in.koreatech.koin.domain.notification.model.NotificationSubscribe; import in.koreatech.koin.domain.user.model.User; -public final class NotificationFixture { +public class NotificationFixture { private NotificationFixture() {}