init local code
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package com.yunhai.cms.service.impl;
|
||||
|
||||
import com.yunhai.cms.entity.CmsArticle;
|
||||
import com.yunhai.cms.entity.CmsBanner;
|
||||
import com.yunhai.cms.enums.ArticleStatusEnum;
|
||||
import com.yunhai.cms.enums.CategoryTypeEnum;
|
||||
import com.yunhai.cms.model.dto.ArticleSaveRequest;
|
||||
import com.yunhai.cms.model.dto.BannerSaveRequest;
|
||||
import com.yunhai.cms.model.dto.CategorySaveRequest;
|
||||
import com.yunhai.cms.model.dto.DeleteRequest;
|
||||
import com.yunhai.cms.support.CmsIntegrationTestBase;
|
||||
import com.yunhai.cms.support.TestUsers;
|
||||
import com.yunhai.platform.apistd.BizException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* 脏数据健壮性回归(第 2 步 bug 清单 D-3/D-4/D-5/D-6):
|
||||
* 非法枚举码显式拒绝、定时时间窗校验、删除时轮播引用拦截(统一"被引用即拦"规则)。
|
||||
*/
|
||||
@DisplayName("脏数据校验 - 枚举/时间窗/删除引用拦截")
|
||||
@Transactional
|
||||
class DirtyValidationIT extends CmsIntegrationTestBase {
|
||||
|
||||
@Autowired
|
||||
private ArticleService articleService;
|
||||
@Autowired
|
||||
private CategoryService categoryService;
|
||||
@Autowired
|
||||
private BannerService bannerService;
|
||||
|
||||
private Integer siteId;
|
||||
private Integer categoryId;
|
||||
|
||||
@BeforeEach
|
||||
void seed() {
|
||||
siteId = persistSite("dirty1", 1, "脏数据演示站").getId();
|
||||
categoryId = persistCategory(siteId, 0, "新闻", CategoryTypeEnum.LIST).getId();
|
||||
}
|
||||
|
||||
private CategorySaveRequest catReq(Integer type) {
|
||||
CategorySaveRequest r = new CategorySaveRequest();
|
||||
r.setSiteId(siteId);
|
||||
r.setParentId(0);
|
||||
r.setName("新栏目");
|
||||
r.setType(type);
|
||||
return r;
|
||||
}
|
||||
|
||||
// ===== D-4 非法枚举码 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("栏目 type=9 显式拒绝(不再静默降级为列表栏目)")
|
||||
void categorySave_illegalType_rejected() {
|
||||
CategorySaveRequest r = catReq(9);
|
||||
assertThatThrownBy(() -> categoryService.save(TestUsers.superAdmin(), r))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("栏目类型非法");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("轮播 linkType=9 显式拒绝(不再误报\"跳转目标必须是数字 ID\")")
|
||||
void bannerSave_illegalLinkType_rejected() {
|
||||
BannerSaveRequest r = new BannerSaveRequest();
|
||||
r.setSiteId(siteId);
|
||||
r.setPosition("home");
|
||||
r.setTitle("t");
|
||||
r.setImageUrl("/upload/x.png");
|
||||
r.setLinkType(9);
|
||||
r.setLinkValue("https://example.cn");
|
||||
assertThatThrownBy(() -> bannerService.save(TestUsers.superAdmin(), r))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("跳转类型非法");
|
||||
}
|
||||
|
||||
// ===== D-5 定时时间窗 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("保存:offlineTime 早于 publishTime 显式拒绝(状态机自洽)")
|
||||
void articleSave_offlineBeforePublish_rejected() {
|
||||
ArticleSaveRequest r = new ArticleSaveRequest();
|
||||
r.setSiteId(siteId);
|
||||
r.setCategoryId(categoryId);
|
||||
r.setTitle("时间矛盾");
|
||||
r.setPublishTime(LocalDateTime.of(2026, 12, 1, 0, 0));
|
||||
r.setOfflineTime(LocalDateTime.of(2026, 11, 1, 0, 0));
|
||||
assertThatThrownBy(() -> articleService.save(TestUsers.superAdmin(), r))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("定时下架时间必须晚于发布时间");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("发布:过期 offlineTime 自愈清空(重发=新生命周期,不再被调度器立即下架)")
|
||||
void publish_staleOfflineTime_cleared() {
|
||||
Integer id = articleService.save(TestUsers.superAdmin(), draftReq());
|
||||
// 直接落库一个矛盾历史数据(模拟脏数据):offlineTime 已过
|
||||
articleRepository.findById(id).ifPresent(a -> {
|
||||
a.setOfflineTime(LocalDateTime.now().minusDays(1));
|
||||
articleRepository.saveAndFlush(a);
|
||||
});
|
||||
articleService.publish(TestUsers.superAdmin(), siteId, id);
|
||||
CmsArticle after = articleRepository.findById(id).orElseThrow(IllegalStateException::new);
|
||||
assertThat(after.getStatus()).isEqualTo(ArticleStatusEnum.PUBLISHED.getCode());
|
||||
assertThat(after.getOfflineTime()).isNull();
|
||||
}
|
||||
|
||||
private ArticleSaveRequest draftReq() {
|
||||
ArticleSaveRequest r = new ArticleSaveRequest();
|
||||
r.setSiteId(siteId);
|
||||
r.setCategoryId(categoryId);
|
||||
r.setTitle("普通草稿");
|
||||
r.setContent("<p>正文</p>");
|
||||
return r;
|
||||
}
|
||||
|
||||
// ===== D-6 删除时轮播引用拦截 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("删除文章:被轮播引用时拦截并提示")
|
||||
void articleDelete_blockedByBannerReference() {
|
||||
Integer articleId = articleService.save(TestUsers.superAdmin(), draftReq());
|
||||
persistBannerRef(1, String.valueOf(articleId));
|
||||
DeleteRequest req = new DeleteRequest();
|
||||
req.setSiteId(siteId);
|
||||
req.setId(articleId);
|
||||
assertThatThrownBy(() -> articleService.delete(TestUsers.superAdmin(), req))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("轮播");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除栏目:被轮播引用时拦截并提示(与子栏目/文章拦截同一规则)")
|
||||
void categoryDelete_blockedByBannerReference() {
|
||||
persistBannerRef(2, String.valueOf(categoryId));
|
||||
DeleteRequest req = new DeleteRequest();
|
||||
req.setSiteId(siteId);
|
||||
req.setId(categoryId);
|
||||
assertThatThrownBy(() -> categoryService.delete(TestUsers.superAdmin(), req))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("轮播");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("删除文章:无轮播引用时正常删除(不误拦)")
|
||||
void articleDelete_withoutReference_succeeds() {
|
||||
Integer articleId = articleService.save(TestUsers.superAdmin(), draftReq());
|
||||
DeleteRequest req = new DeleteRequest();
|
||||
req.setSiteId(siteId);
|
||||
req.setId(articleId);
|
||||
articleService.delete(TestUsers.superAdmin(), req);
|
||||
assertThat(articleRepository.findById(articleId)).isEmpty();
|
||||
}
|
||||
|
||||
private void persistBannerRef(int linkType, String linkValue) {
|
||||
CmsBanner b = new CmsBanner();
|
||||
b.setSiteId(siteId);
|
||||
b.setPosition("home");
|
||||
b.setTitle("引用型轮播");
|
||||
b.setImageUrl("/upload/banner.png");
|
||||
b.setLinkType(linkType);
|
||||
b.setLinkValue(linkValue);
|
||||
b.setSortNo(1);
|
||||
b.setStatus(1);
|
||||
bannerRepository.saveAndFlush(b);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user