测试用例补充并修复问题
This commit is contained in:
@@ -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>
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
|
||||
+59
-6
@@ -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;
|
||||
|
||||
+187
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user