debug

Change-Id: I5c4de18f786f8cc336d8ad66ae9b424d02ed3674
diff --git a/src/test/java/com/example/g8backend/service/PostServiceRecommendTest.java b/src/test/java/com/example/g8backend/service/PostServiceRecommendTest.java
index 23890ba..e2a4c7f 100644
--- a/src/test/java/com/example/g8backend/service/PostServiceRecommendTest.java
+++ b/src/test/java/com/example/g8backend/service/PostServiceRecommendTest.java
@@ -1,50 +1,43 @@
 package com.example.g8backend.service;
-
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.example.g8backend.entity.Post;
 import com.example.g8backend.entity.PostView;
-import com.example.g8backend.mapper.CommentMapper;
-import com.example.g8backend.mapper.PostMapper;
-import com.example.g8backend.mapper.PostViewMapper;
+import com.example.g8backend.entity.UserTagPreference;
+import com.example.g8backend.mapper.*;
 import com.example.g8backend.service.impl.PostServiceImpl;
 import org.junit.jupiter.api.BeforeEach;
 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.MockitoAnnotations;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.transaction.annotation.Transactional;
-
 import java.sql.Timestamp;
 import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
+import java.util.*;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
-
 @ExtendWith(MockitoExtension.class)
 @Transactional
 public class PostServiceRecommendTest {
-
     @Mock
     private PostMapper postMapper;
-
     @Mock
     private PostViewMapper postViewMapper;
-
     @Mock
     private CommentMapper commentMapper;
-
+    @Mock
+    private UserTagPreferenceMapper userTagPreferenceMapper;
+    @Mock
+    private PostTagMapper postTagMapper;
     @InjectMocks
     private PostServiceImpl postService;
-
     private Post mockPost;
     private PostView mockPostView;
-
     @BeforeEach
     void setUp() {
         // 初始化测试数据
@@ -55,56 +48,55 @@
                 .setViewCount(50)
                 .setCreatedAt(Timestamp.from(Instant.now().minusSeconds(7200)))
                 .setHotScore(5.0);
-
         mockPostView = new PostView()
                 .setViewId(1L)
                 .setUserId(200L)
                 .setPostId(1L)
                 .setViewTime(Timestamp.from(Instant.now()).toLocalDateTime());
     }
-
+    @Test
+    void testRecordViewHistory_NormalCase() {
+        // 调用方法
+        postService.recordViewHistory(100L, 1L);
+        // 验证 postTagMapper 被调用
+        verify(postTagMapper).findTagIdsByPostId(1L);
+        verify(postViewMapper).insert(any(PostView.class));
+        verify(postMapper).incrementViewCount(1L);
+    }
     @Test
     public void testGetRecommendedPosts_ExcludesViewedPosts() {
         // 模拟用户已浏览的帖子ID
         Long userId = 200L;
         when(postViewMapper.findViewedPostIds(userId))
                 .thenReturn(Arrays.asList(1L, 2L));
-
         // 模拟推荐结果(未浏览的帖子)
         Post recommendedPost = new Post().setPostId(3L).setHotScore(8.0);
         when(postMapper.selectPage(any(Page.class), any(QueryWrapper.class)))
                 .thenReturn(new Page<Post>().setRecords(Collections.singletonList(recommendedPost)));
-
         // 调用推荐接口
         Page<Post> result = postService.getRecommendedPosts(1, 10, userId);
-
         // 验证结果
         assertEquals(1, result.getRecords().size(), "应返回1条推荐结果");
         assertEquals(3L, result.getRecords().get(0).getPostId(), "推荐结果应为未浏览的帖子ID 3");
         assertFalse(result.getRecords().stream().anyMatch(p -> p.getPostId() == 1L), "结果中不应包含已浏览的帖子ID 1");
     }
-
     @Test
     public void testGetRecommendedPosts_NoViewedPosts() {
         // 模拟用户未浏览任何帖子
         Long userId = 300L;
         when(postViewMapper.findViewedPostIds(userId))
                 .thenReturn(Collections.emptyList());
-
         // 模拟推荐结果(所有帖子按热度排序)
         Post post1 = new Post().setPostId(1L).setHotScore(7.5);
         Post post2 = new Post().setPostId(2L).setHotScore(9.0);
         when(postMapper.selectPage(any(Page.class), any(QueryWrapper.class)))
                 .thenReturn(new Page<Post>().setRecords(Arrays.asList(post2, post1)));
-
         // 调用推荐接口
         Page<Post> result = postService.getRecommendedPosts(1, 10, userId);
-
         // 验证结果
         assertEquals(2, result.getRecords().size(), "应返回所有帖子");
         assertEquals(2L, result.getRecords().get(0).getPostId(), "热度更高的帖子应排在前面");
     }
-
     @Test
     public void testCalculateHotScores_UpdatesHotScoreCorrectly() {
         // 设置存根
@@ -113,20 +105,17 @@
         when(postMapper.selectLikeCount(anyLong())).thenReturn(30L);
         when(commentMapper.selectCountByPostId(anyLong())).thenReturn(20L);
         when(postMapper.batchUpdateHotScore(anyList())).thenReturn(1);
-
         // 执行并验证
         postService.calculateHotScores();
         double expectedScore = (Math.log(51) * 0.2 + 30 * 0.5 + 20 * 0.3) / Math.pow(4, 1.5);
         assertEquals(expectedScore, mockPost.getHotScore(), 0.01);
         verify(postMapper).batchUpdateHotScore(anyList());
     }
-
     //--------------------- 测试冷启动逻辑 ---------------------
     @Test
     public void testCreatePost_SetsInitialHotScore() {
         Post newPost = new Post().setPostId(4L).setPostTitle("New Post");
         postService.createPost(newPost);
-
         assertEquals(5.0, newPost.getHotScore(), "新帖子的初始热度应为5.0");
         assertNotNull(newPost.getCreatedAt(), "创建时间不应为空");
     }
@@ -134,11 +123,82 @@
     public void testConcurrentViewCountUpdate() {
         // 设置存根
         doNothing().when(postMapper).incrementViewCount(anyLong());
-
         postService.recordViewHistory(100L, 1L);
         postService.recordViewHistory(200L, 1L);
-
         verify(postMapper, times(2)).incrementViewCount(1L);
         verify(postViewMapper, times(2)).insert(any(PostView.class));
     }
+    @Test
+    public void testGetRecommendedByTags_WithPreferredTags() {
+        // 模拟用户偏好标签
+        Long userId = 200L;
+        UserTagPreference pref1 = new UserTagPreference().setTagId(100L).setWeight(2.0);
+        UserTagPreference pref2 = new UserTagPreference().setTagId(200L).setWeight(1.5);
+        when(userTagPreferenceMapper.selectByUserId(userId))
+                .thenReturn(Arrays.asList(pref1, pref2));
+        // 模拟标签关联的帖子ID
+        List<Long> expectedPostIds = Arrays.asList(3L, 4L, 5L);
+        when(postTagMapper.findPostIdsByTagIds(Arrays.asList(100L, 200L)))
+                .thenReturn(expectedPostIds);
+        // 使用 ArgumentCaptor 捕获 QueryWrapper 对象
+        ArgumentCaptor<QueryWrapper<Post>> wrapperCaptor = ArgumentCaptor.forClass(QueryWrapper.class);
+        Page<Post> mockPage = new Page<Post>().setRecords(Arrays.asList(
+                new Post().setPostId(5L).setHotScore(9.0),
+                new Post().setPostId(3L).setHotScore(8.0),
+                new Post().setPostId(4L).setHotScore(7.5)
+        ));
+        when(postMapper.selectPage(any(Page.class), wrapperCaptor.capture()))
+                .thenReturn(mockPage);
+        // 调用标签推荐接口
+        Page<Post> result = postService.getRecommendedByTags(1, 10, userId);
+        // ---------- 验证查询条件 ----------
+        QueryWrapper<Post> actualWrapper = wrapperCaptor.getValue();
+        String sqlSegment = actualWrapper.getSqlSegment();
+        // 验证 SQL 条件格式
+        assertTrue(
+                sqlSegment.matches(".*post_id\\s+IN\\s*\\(.*\\).*"),
+                "应包含 post_id IN 条件,实际条件:" + sqlSegment
+        );
+        // 验证参数值(忽略顺序)
+        Map<String, Object> params = actualWrapper.getParamNameValuePairs();
+        List<Object> actualPostIds = new ArrayList<>(params.values());
+        assertEquals(3, actualPostIds.size(), "IN 条件应包含3个参数");
+        assertTrue(
+                actualPostIds.containsAll(expectedPostIds),
+                "参数应包含所有预期帖子ID,实际参数:" + actualPostIds
+        );
+        // ---------- 验证结果排序和内容 ----------
+        assertEquals(3, result.getRecords().size(), "应返回3条结果");
+        assertEquals(5L, result.getRecords().get(0).getPostId(), "热度最高的帖子应为ID 5");
+        assertEquals(3L, result.getRecords().get(1).getPostId(), "热度次高的帖子应为ID 3");
+        assertEquals(4L, result.getRecords().get(2).getPostId(), "热度最低的帖子应为ID 4");
+    }
+    @Test
+    public void testGetRecommendedByTags_NoPreferredTags() {
+        // 模拟用户无偏好标签
+        Long userId = 300L;
+        when(userTagPreferenceMapper.selectByUserId(userId))
+                .thenReturn(Collections.emptyList());
+        // 调用标签推荐接口
+        Page<Post> result = postService.getRecommendedByTags(1, 10, userId);
+        // 验证结果为空或兜底逻辑
+        assertTrue(result.getRecords().isEmpty(), "无偏好标签时应返回空结果");
+    }
+    @Test
+    public void testGetRecommendedByTags_WithNonExistingTags() {
+        // 模拟用户偏好标签(但无关联帖子)
+        Long userId = 400L;
+        UserTagPreference pref = new UserTagPreference().setTagId(999L).setWeight(2.0);
+        when(userTagPreferenceMapper.selectByUserId(userId))
+                .thenReturn(Collections.singletonList(pref));
+        when(postTagMapper.findPostIdsByTagIds(Collections.singletonList(999L)))
+                .thenReturn(Collections.emptyList());
+        // 调用标签推荐接口
+        Page<Post> result = postService.getRecommendedByTags(1, 10, userId);
+        // 验证结果为空
+        assertNotNull(result, "分页结果不应为null");
+        assertTrue(result.getRecords().isEmpty(), "无关联帖子时应返回空结果");
+        // 验证postMapper.selectPage未被调用
+        verify(postMapper, never()).selectPage(any(Page.class), any(QueryWrapper.class));
+    }
 }
\ No newline at end of file