|
@@ -0,0 +1,128 @@
|
|
|
|
|
+package com.etotem.cfc.unit;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.etotem.cfc.entity.Article;
|
|
|
|
|
+import com.etotem.cfc.mapper.ArticleMapper;
|
|
|
|
|
+import com.etotem.cfc.service.ArticlePermissionService;
|
|
|
|
|
+import com.etotem.cfc.service.ArticleService;
|
|
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 相关文章查询单元测试(纯逻辑,mock Mapper + PermissionService,无需 DB)
|
|
|
|
|
+ */
|
|
|
|
|
+class ArticleRelatedServiceTest {
|
|
|
|
|
+
|
|
|
|
|
+ private ArticleService service;
|
|
|
|
|
+ private ArticleMapper articleMapper;
|
|
|
|
|
+ private ArticlePermissionService permissionService;
|
|
|
|
|
+
|
|
|
|
|
+ @BeforeEach
|
|
|
|
|
+ void setUp() throws Exception {
|
|
|
|
|
+ service = new ArticleService();
|
|
|
|
|
+ articleMapper = mock(ArticleMapper.class);
|
|
|
|
|
+ permissionService = mock(ArticlePermissionService.class);
|
|
|
|
|
+ inject("articleMapper", articleMapper);
|
|
|
|
|
+ inject("articlePermissionService", permissionService);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void inject(String field, Object value) throws Exception {
|
|
|
|
|
+ Field f = ArticleService.class.getDeclaredField(field);
|
|
|
|
|
+ f.setAccessible(true);
|
|
|
|
|
+ f.set(service, value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Article a(Long id, String tags, Date publishedAt) {
|
|
|
|
|
+ Article art = new Article();
|
|
|
|
|
+ art.setId(id);
|
|
|
|
|
+ art.setTags(tags);
|
|
|
|
|
+ art.setPublishedAt(publishedAt);
|
|
|
|
|
+ art.setStatus("published");
|
|
|
|
|
+ return art;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void getRelatedArticles_currentNull_returnsEmpty() {
|
|
|
|
|
+ when(articleMapper.selectById(1L)).thenReturn(null);
|
|
|
|
|
+ List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
|
|
|
|
|
+ assertTrue(r.isEmpty());
|
|
|
|
|
+ verify(articleMapper, never()).selectList(any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void getRelatedArticles_emptyTags_returnsEmpty() {
|
|
|
|
|
+ when(articleMapper.selectById(1L)).thenReturn(a(1L, null, new Date()));
|
|
|
|
|
+ List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
|
|
|
|
|
+ assertTrue(r.isEmpty());
|
|
|
|
|
+ verify(articleMapper, never()).selectList(any());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void getRelatedArticles_noMatch_returnsEmpty() {
|
|
|
|
|
+ when(articleMapper.selectById(1L)).thenReturn(a(1L, "健康", new Date()));
|
|
|
|
|
+ when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(
|
|
|
|
|
+ Collections.singletonList(a(2L, "阅读,亲子", new Date())));
|
|
|
|
|
+ when(permissionService.filterVisible(any(), any(), any(), any())).thenReturn(Collections.emptyList());
|
|
|
|
|
+
|
|
|
|
|
+ List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
|
|
|
|
|
+ assertTrue(r.isEmpty());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void getRelatedArticles_sharedTag_sortedByScoreThenPublishedAt() {
|
|
|
|
|
+ Article current = a(1L, "健康,饮水", new Date(3000L));
|
|
|
|
|
+ Article moreShared = a(2L, "健康,饮水,亲子", new Date(2000L)); // shares 2
|
|
|
|
|
+ Article lessShared = a(3L, "健康", new Date(4000L)); // shares 1, newer
|
|
|
|
|
+ List<Article> candidates = Arrays.asList(moreShared, lessShared, a(4L, "阅读", new Date())); // last no match
|
|
|
|
|
+
|
|
|
|
|
+ when(articleMapper.selectById(1L)).thenReturn(current);
|
|
|
|
|
+ when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(candidates);
|
|
|
|
|
+ when(permissionService.filterVisible(any(), any(), any(), any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
+
|
|
|
|
|
+ List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ // order: id=2 (2 shared) > id=3 (1 shared) > id=4 filtered out by count=0 but still queried
|
|
|
|
|
+ assertEquals(2, r.size()); // size=3 but only 2 match
|
|
|
|
|
+ assertEquals(2L, r.get(0).getId());
|
|
|
|
|
+ assertEquals(3L, r.get(1).getId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void getRelatedArticles_excludesSelf() {
|
|
|
|
|
+ Article current = a(1L, "健康", new Date());
|
|
|
|
|
+ Article dup = a(1L, "健康,其他", new Date()); // same id
|
|
|
|
|
+ when(articleMapper.selectById(1L)).thenReturn(current);
|
|
|
|
|
+ when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.singletonList(dup));
|
|
|
|
|
+ when(permissionService.filterVisible(any(), any(), any(), any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
+
|
|
|
|
|
+ List<Article> r = service.getRelatedArticles(1L, 3, null, null, null);
|
|
|
|
|
+ assertTrue(r.isEmpty());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void getRelatedArticles_sizeLimit_applied() {
|
|
|
|
|
+ Article current = a(1L, "健康", new Date());
|
|
|
|
|
+ List<Article> cands = Arrays.asList(a(2L, "健康", new Date(2000L)),
|
|
|
|
|
+ a(3L, "健康", new Date(3000L)),
|
|
|
|
|
+ a(4L, "健康", new Date(4000L)));
|
|
|
|
|
+ when(articleMapper.selectById(1L)).thenReturn(current);
|
|
|
|
|
+ when(articleMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(cands);
|
|
|
|
|
+ when(permissionService.filterVisible(any(), any(), any(), any())).thenAnswer(inv -> inv.getArgument(0));
|
|
|
|
|
+
|
|
|
|
|
+ List<Article> r = service.getRelatedArticles(1L, 2, null, null, null);
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals(2, r.size());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|