测试用例补充并修复问题

This commit is contained in:
nojava
2026-05-18 21:54:13 +08:00
parent 2134ef4691
commit c6d1b54822
16 changed files with 1694 additions and 470 deletions
+5
View File
@@ -22,5 +22,10 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -35,7 +35,7 @@ public class PublicArticleController {
public ApiResult<ArticleVO> getArticle(
@PathVariable Long id,
@AuthenticationPrincipal Long userId) {
articleService.incrementViewCount(id);
articleService.incrementViewCount(id, userId);
return ApiResult.success(articleService.getArticleDetail(id, userId));
}
@@ -25,7 +25,7 @@ public interface ArticleService {
void restoreArticle(Long userId, Long articleId);
void incrementViewCount(Long articleId);
void incrementViewCount(Long articleId, Long viewerUserId);
boolean toggleLike(Long userId, Long articleId);
@@ -12,6 +12,8 @@ import fun.nojava.module.blog.entity.*;
import fun.nojava.module.blog.mapper.*;
import fun.nojava.module.blog.service.ArticleService;
import fun.nojava.module.system.entity.User;
import fun.nojava.module.system.entity.UserFollow;
import fun.nojava.module.system.mapper.UserFollowMapper;
import fun.nojava.module.system.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -25,16 +27,21 @@ import java.util.List;
@RequiredArgsConstructor
public class ArticleServiceImpl implements ArticleService {
private static final int MAX_TAG_COUNT = 5;
private final ArticleMapper articleMapper;
private final CategoryMapper categoryMapper;
private final TagMapper tagMapper;
private final ArticleTagMapper articleTagMapper;
private final ArticleLikeMapper articleLikeMapper;
private final UserMapper userMapper;
private final UserFollowMapper userFollowMapper;
@Override
@Transactional
public Long createArticle(Long userId, CreateArticleDTO dto) {
validateTagCount(dto.getTagIds());
Article article = new Article();
article.setUserId(userId);
article.setCategoryId(dto.getCategoryId());
@@ -71,6 +78,7 @@ public class ArticleServiceImpl implements ArticleService {
@Transactional
public void updateArticle(Long userId, Long articleId, UpdateArticleDTO dto) {
Article article = getArticleAndCheckOwner(userId, articleId);
validateTagCount(dto.getTagIds());
article.setTitle(dto.getTitle());
article.setSummary(dto.getSummary());
@@ -106,6 +114,7 @@ public class ArticleServiceImpl implements ArticleService {
if (article == null) {
throw new BusinessException(ErrorCode.ARTICLE_NOT_FOUND);
}
ensureReadable(article, currentUserId);
return buildArticleVO(article, currentUserId);
}
@@ -191,8 +200,7 @@ public class ArticleServiceImpl implements ArticleService {
@Override
@Transactional
public void adminDeleteArticle(Long articleId) {
Article article = articleMapper.selectById(articleId);
if (article == null) {
if (articleMapper.selectById(articleId) == null) {
throw new BusinessException(ErrorCode.ARTICLE_NOT_FOUND);
}
articleMapper.deleteById(articleId);
@@ -217,12 +225,16 @@ public class ArticleServiceImpl implements ArticleService {
}
@Override
public void incrementViewCount(Long articleId) {
public void incrementViewCount(Long articleId, Long viewerUserId) {
Article article = articleMapper.selectById(articleId);
if (article != null) {
article.setViewCount(article.getViewCount() + 1);
articleMapper.updateById(article);
if (article == null) {
return;
}
if (viewerUserId != null && viewerUserId.equals(article.getUserId())) {
return;
}
article.setViewCount(article.getViewCount() + 1);
articleMapper.updateById(article);
}
@Override
@@ -276,6 +288,47 @@ public class ArticleServiceImpl implements ArticleService {
return article;
}
private void validateTagCount(List<Long> tagIds) {
if (tagIds != null && tagIds.size() > MAX_TAG_COUNT) {
throw new BusinessException(ErrorCode.TAG_LIMIT_EXCEEDED);
}
}
private void ensureReadable(Article article, Long currentUserId) {
if (!Integer.valueOf(ArticleStatus.PUBLISHED.getCode()).equals(article.getStatus())) {
if (currentUserId == null || !currentUserId.equals(article.getUserId())) {
throw new BusinessException(ErrorCode.PERMISSION_DENIED);
}
return;
}
if (Integer.valueOf(ArticleVisibility.PUBLIC.getCode()).equals(article.getVisibility())) {
return;
}
if (currentUserId != null && currentUserId.equals(article.getUserId())) {
return;
}
if (Integer.valueOf(ArticleVisibility.PRIVATE.getCode()).equals(article.getVisibility())) {
throw new BusinessException(ErrorCode.PERMISSION_DENIED);
}
if (Integer.valueOf(ArticleVisibility.FOLLOWERS_ONLY.getCode()).equals(article.getVisibility())) {
if (currentUserId == null) {
throw new BusinessException(ErrorCode.PERMISSION_DENIED);
}
boolean following = userFollowMapper.selectCount(
new LambdaQueryWrapper<UserFollow>()
.eq(UserFollow::getFollowerId, currentUserId)
.eq(UserFollow::getFolloweeId, article.getUserId())
) > 0;
if (!following) {
throw new BusinessException(ErrorCode.PERMISSION_DENIED);
}
}
}
private ArticleVO buildArticleVO(Article article, Long currentUserId) {
User author = userMapper.selectById(article.getUserId());
Category category = article.getCategoryId() != null ? categoryMapper.selectById(article.getCategoryId()) : null;
@@ -0,0 +1,187 @@
package fun.nojava.module.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import fun.nojava.common.enums.ArticleStatus;
import fun.nojava.common.enums.ArticleVisibility;
import fun.nojava.common.exception.BusinessException;
import fun.nojava.common.exception.ErrorCode;
import fun.nojava.common.model.PageResult;
import fun.nojava.module.blog.dto.ArticleListVO;
import fun.nojava.module.blog.dto.CreateArticleDTO;
import fun.nojava.module.blog.entity.Article;
import fun.nojava.module.blog.mapper.*;
import fun.nojava.module.system.mapper.UserFollowMapper;
import fun.nojava.module.system.mapper.UserMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
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 java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class ArticleServiceImplP0P1Test {
@Mock
private ArticleMapper articleMapper;
@Mock
private CategoryMapper categoryMapper;
@Mock
private TagMapper tagMapper;
@Mock
private ArticleTagMapper articleTagMapper;
@Mock
private ArticleLikeMapper articleLikeMapper;
@Mock
private UserMapper userMapper;
@Mock
private UserFollowMapper userFollowMapper;
@InjectMocks
private ArticleServiceImpl articleService;
@Test
@Tag("p0")
@DisplayName("TC-AC-BLOG-LIST-01: 公开列表查询条件应限制已发布+公开可见")
void shouldConstrainPublicListToPublishedAndPublicArticles() {
Page<Article> mockPage = new Page<>(1, 10);
mockPage.setRecords(List.of());
mockPage.setTotal(0);
when(articleMapper.selectPage(org.mockito.ArgumentMatchers.<Page<Article>>any(),
org.mockito.ArgumentMatchers.<Wrapper<Article>>any()))
.thenReturn(mockPage);
PageResult<ArticleListVO> result = articleService.listPublicArticles(1, 10, null, null, null);
assertNotNull(result);
verify(articleMapper).selectPage(org.mockito.ArgumentMatchers.<Page<Article>>any(),
org.mockito.ArgumentMatchers.<Wrapper<Article>>any());
assertEquals(0L, result.getTotal());
assertEquals(1, result.getPage());
assertEquals(10, result.getPageSize());
}
@Test
@Tag("p0")
@DisplayName("TC-AC-BLOG-PUB-04: 查询不存在文章应返回 ARTICLE_NOT_FOUND")
void shouldThrowWhenArticleDoesNotExist() {
when(articleMapper.selectById(999L)).thenReturn(null);
BusinessException ex = assertThrows(BusinessException.class, () ->
articleService.getArticleDetail(999L, null));
assertEquals(ErrorCode.ARTICLE_NOT_FOUND.getCode(), ex.getCode());
}
@Test
@Tag("p0")
@DisplayName("TC-AC-BLOG-LIST-06: 访问文章详情应递增浏览量")
void shouldIncreaseViewCountWhenArticleExists() {
Article article = new Article();
article.setId(1L);
article.setUserId(2L);
article.setViewCount(12);
when(articleMapper.selectById(1L)).thenReturn(article);
articleService.incrementViewCount(1L, 3L);
assertEquals(13, article.getViewCount());
verify(articleMapper).updateById(article);
}
@Test
@Tag("p0")
@DisplayName("TC-AC-BLOG-PUB-04: 未授权用户访问私密文章应被拒绝")
void shouldRejectUnauthorizedUserForPrivateArticleDetail() {
Article article = new Article();
article.setId(2L);
article.setUserId(11L);
article.setStatus(ArticleStatus.PUBLISHED.getCode());
article.setVisibility(ArticleVisibility.PRIVATE.getCode());
when(articleMapper.selectById(2L)).thenReturn(article);
BusinessException ex = assertThrows(BusinessException.class, () ->
articleService.getArticleDetail(2L, 99L));
assertEquals(ErrorCode.PERMISSION_DENIED.getCode(), ex.getCode());
}
@Test
@Tag("p0")
@DisplayName("TC-AC-BLOG-LIST-06: 作者本人访问不应增加浏览量")
void shouldNotIncreaseViewCountWhenAuthorViewsOwnArticle() {
Article article = new Article();
article.setId(3L);
article.setUserId(21L);
article.setViewCount(33);
when(articleMapper.selectById(3L)).thenReturn(article);
articleService.incrementViewCount(3L, 21L);
assertEquals(33, article.getViewCount());
verify(articleMapper, never()).updateById(any(Article.class));
}
@Test
@Tag("p0")
@DisplayName("TC-AC-BLOG-PUB-06: 超过5个标签应被拒绝")
void shouldRejectWhenTagCountExceedsFive() {
CreateArticleDTO dto = new CreateArticleDTO();
dto.setTitle("tag-limit");
dto.setContent("content");
dto.setTagIds(List.of(1L, 2L, 3L, 4L, 5L, 6L));
BusinessException ex = assertThrows(BusinessException.class, () ->
articleService.createArticle(1L, dto));
assertEquals(ErrorCode.TAG_LIMIT_EXCEEDED.getCode(), ex.getCode());
verify(articleMapper, never()).insert(any(Article.class));
}
@Test
@Tag("p0")
@DisplayName("TC-FR-BLOG-LIST-06: 仅粉丝可见文章需关注关系")
void shouldRejectFollowerOnlyArticleWhenNotFollowing() {
Article article = new Article();
article.setId(4L);
article.setUserId(88L);
article.setStatus(ArticleStatus.PUBLISHED.getCode());
article.setVisibility(ArticleVisibility.FOLLOWERS_ONLY.getCode());
when(articleMapper.selectById(4L)).thenReturn(article);
when(userFollowMapper.selectCount(any())).thenReturn(0L);
BusinessException ex = assertThrows(BusinessException.class, () ->
articleService.getArticleDetail(4L, 99L));
assertEquals(ErrorCode.PERMISSION_DENIED.getCode(), ex.getCode());
}
@Test
@Tag("p0")
@DisplayName("TC-FR-BLOG-LIST-06: 已关注用户可访问仅粉丝可见文章")
void shouldAllowFollowerOnlyArticleWhenFollowing() {
Article article = new Article();
article.setId(5L);
article.setUserId(89L);
article.setStatus(ArticleStatus.PUBLISHED.getCode());
article.setVisibility(ArticleVisibility.FOLLOWERS_ONLY.getCode());
article.setCategoryId(null);
article.setViewCount(0);
article.setLikeCount(0);
article.setCommentCount(0);
when(articleMapper.selectById(5L)).thenReturn(article);
when(userFollowMapper.selectCount(any())).thenReturn(1L);
when(articleTagMapper.selectList(any())).thenReturn(List.of());
assertDoesNotThrow(() -> articleService.getArticleDetail(5L, 100L));
}
}