diff --git a/src/main/java/com/devpick/domain/content/service/ContentService.java b/src/main/java/com/devpick/domain/content/service/ContentService.java index ac3dd2e..294671d 100644 --- a/src/main/java/com/devpick/domain/content/service/ContentService.java +++ b/src/main/java/com/devpick/domain/content/service/ContentService.java @@ -74,6 +74,7 @@ public class ContentService { private final AiSummaryService aiSummaryService; private final ContentViewLogService contentViewLogService; private final SimilarContentClient similarContentClient; + private final RecommendService recommendService; private final StringRedisTemplate redisTemplate; private final ObjectMapper objectMapper; @@ -110,7 +111,7 @@ public ContentListResponse getFeed(UUID userId, Pageable pageable) { } List tagIds = loggedIn - ? feedTagIds(userId, profileUser.map(User::getJob).orElse(null)) + ? resolveTagIds(userId, profileUser.map(User::getJob).orElse(null)) : List.of(); Page page; @@ -189,6 +190,14 @@ private String publicFeedCacheKey(UUID userId, Pageable pageable) { + ":sort:" + pageable.getSort(); } + private List resolveTagIds(UUID userId, Job job) { + List historyTagIds = recommendService.getOrCacheTagIds(userId); + if (!historyTagIds.isEmpty()) { + return historyTagIds; + } + return feedTagIds(userId, job); + } + private List feedTagIds(UUID userId, Job job) { List tagIds = new ArrayList<>(userTagRepository.findByUser_Id(userId).stream() .map(ut -> ut.getTag().getId()) diff --git a/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java b/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java index 4d15d5f..c4e58de 100644 --- a/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java +++ b/src/test/java/com/devpick/domain/content/service/ContentServiceTest.java @@ -80,6 +80,8 @@ class ContentServiceTest { private ContentViewLogService contentViewLogService; @Mock private com.devpick.domain.content.client.SimilarContentClient similarContentClient; + @Mock + private RecommendService recommendService; private UUID userId; private UUID contentId; @@ -111,6 +113,7 @@ void setUp() { .build(); lenient().when(userRepository.findByIdAndIsActiveTrue(userId)).thenReturn(Optional.of(user)); lenient().when(tagRepository.findByNameIgnoreCaseIn(any())).thenReturn(List.of()); + lenient().when(recommendService.getOrCacheTagIds(any())).thenReturn(List.of()); } @Test @@ -646,6 +649,48 @@ void getDetail_viewLogThrows_stillReturnsDetail() { assertThat(response.title()).isEqualTo("Spring Boot 가이드"); } + // ── getFeed 학습 이력 기반 개인화 ───────────────────────────────────────── + + @Test + @DisplayName("getFeed — 학습 이력 태그 있으면 이력 기반 태그로 랭킹, userTagRepository 미조회") + void getFeed_withHistoryTags_usesHistoryTagsForRanking() { + UUID historyTagId = UUID.randomUUID(); + given(recommendService.getOrCacheTagIds(userId)).willReturn(List.of(historyTagId)); + given(contentRepository.findAllRankedByTagIds(any(), any())) + .willReturn(new PageImpl<>(List.of(content))); + given(scrapRepository.findScrappedContentIds(any(), any())).willReturn(List.of()); + given(likeRepository.findLikedContentIds(any(), any())).willReturn(List.of()); + given(aiSummaryService.findCachedCoreSummaries(any(), any())).willReturn(Map.of()); + + ContentListResponse response = contentService.getFeed(userId, PageRequest.of(0, 20)); + + assertThat(response.contents()).hasSize(1); + verify(contentRepository).findAllRankedByTagIds(any(), any()); + verify(userTagRepository, never()).findByUser_Id(any()); + } + + @Test + @DisplayName("getFeed — 학습 이력 태그 없으면 회원가입 태그로 폴백하여 랭킹") + void getFeed_historyTagsEmpty_fallsBackToUserTags() { + given(recommendService.getOrCacheTagIds(userId)).willReturn(List.of()); + UserTag userTag = UserTag.builder() + .user(user) + .tag(Tag.builder().name("Java").build()) + .build(); + given(userTagRepository.findByUser_Id(userId)).willReturn(List.of(userTag)); + given(contentRepository.findAllRankedByTagIds(any(), any())) + .willReturn(new PageImpl<>(List.of(content))); + given(scrapRepository.findScrappedContentIds(any(), any())).willReturn(List.of()); + given(likeRepository.findLikedContentIds(any(), any())).willReturn(List.of()); + given(aiSummaryService.findCachedCoreSummaries(any(), any())).willReturn(Map.of()); + + ContentListResponse response = contentService.getFeed(userId, PageRequest.of(0, 20)); + + assertThat(response.contents()).hasSize(1); + verify(contentRepository).findAllRankedByTagIds(any(), any()); + verify(userTagRepository).findByUser_Id(userId); + } + // ── getFeed 플랜 제한 ──────────────────────────────────────────────────── @Test