package com.yunhai.cms.support; import com.yunhai.cms.entity.CmsArticle; import com.yunhai.cms.entity.CmsBanner; import com.yunhai.cms.entity.CmsCategory; import com.yunhai.cms.entity.CmsLink; import com.yunhai.cms.entity.CmsSite; import com.yunhai.cms.enums.ArticleStatusEnum; import com.yunhai.cms.enums.CategoryTypeEnum; import com.yunhai.cms.repository.CmsArticleRepository; import com.yunhai.cms.repository.CmsAttachmentRepository; import com.yunhai.cms.repository.CmsBannerRepository; import com.yunhai.cms.repository.CmsCategoryRepository; import com.yunhai.cms.repository.CmsLinkRepository; import com.yunhai.cms.repository.CmsSiteRepository; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.CacheManager; import java.time.LocalDateTime; /** * 集成测试基类:真实 Spring 上下文 + 本地 MySQL(bsch_cms_test) + Caffeine 缓存。 * *

所有子类共享同一上下文配置(本类注解即完整配置),Spring TestContext 只启动一次, * schema-cms-test.sql 的 DROP/CREATE 也只执行一次;跨测试的库与缓存清洁由 * {@link #clearCaches()} / {@link #purgeTables()} 负责: *

*/ @SpringBootTest(properties = "cms.schedule.auto-enabled=false") @AutoConfigureMockMvc public abstract class CmsIntegrationTestBase { @Autowired protected CacheManager cacheManager; @Autowired protected CmsSiteRepository siteRepository; @Autowired protected CmsCategoryRepository categoryRepository; @Autowired protected CmsArticleRepository articleRepository; @Autowired protected CmsBannerRepository bannerRepository; @Autowired protected CmsLinkRepository linkRepository; @Autowired protected CmsAttachmentRepository attachmentRepository; @BeforeEach void clearCachesBeforeEachTest() { clearCaches(); } /** 清空全部 CMS 缓存(cms:site / cms:article / cms:list),防止用例间缓存串扰 */ protected void clearCaches() { for (String name : cacheManager.getCacheNames()) { cacheManager.getCache(name).clear(); } } /** 清空 6 张业务表(无外键约束,顺序无碍),非事务用例的隔离手段 */ protected void purgeTables() { attachmentRepository.deleteAllInBatch(); articleRepository.deleteAllInBatch(); categoryRepository.deleteAllInBatch(); bannerRepository.deleteAllInBatch(); linkRepository.deleteAllInBatch(); siteRepository.deleteAllInBatch(); } // ===== 落库种子(返回带自增 id 的实体)===== protected CmsSite persistSite(String code, Integer schId, String name) { CmsSite site = new CmsSite(); site.setCode(code); site.setPath(code); // W4:顶层 path=code(与 SiteService.save 口径一致) site.setSchId(schId); site.setSchName("学校-" + schId); site.setName(name); site.setTheme("default"); site.setStatus(1); return siteRepository.saveAndFlush(site); } /** 带上级站点的落库(院系子站,《院系子站方案》):parentSiteId 传 null 视为顶层 0 */ protected CmsSite persistSite(String code, Integer schId, String name, Integer parentSiteId) { CmsSite site = persistSite(code, schId, name); if (parentSiteId == null || parentSiteId == 0) { site.setParentSiteId(0); } else { CmsSite parent = siteRepository.findById(parentSiteId) .orElseThrow(IllegalStateException::new); site.setParentSiteId(parentSiteId); site.setPath(parent.getPath() + "/" + code); // W4:子站 path=父path/code } return siteRepository.saveAndFlush(site); } protected CmsCategory persistCategory(Integer siteId, Integer parentId, String name, CategoryTypeEnum type) { CmsCategory c = new CmsCategory(); c.setSiteId(siteId); c.setParentId(parentId == null ? 0 : parentId); c.setName(name); c.setType(type.getCode()); c.setStatus(1); return categoryRepository.saveAndFlush(c); } protected CmsArticle persistArticle(Integer siteId, Integer categoryId, String title, ArticleStatusEnum status, LocalDateTime publishTime) { CmsArticle a = new CmsArticle(); a.setSiteId(siteId); a.setCategoryId(categoryId); a.setTitle(title); a.setContent("

" + title + " 正文

"); a.setSummary(title + " 摘要"); a.setStatus(status.getCode()); a.setPublishTime(publishTime); return articleRepository.saveAndFlush(a); } protected CmsBanner persistBanner(Integer siteId, String position, String title, LocalDateTime start, LocalDateTime end) { CmsBanner b = new CmsBanner(); b.setSiteId(siteId); b.setPosition(position); b.setTitle(title); b.setImageUrl("/upload/cms/banner.png"); b.setLinkType(3); b.setLinkValue("https://www.example.com"); b.setStatus(1); b.setStartTime(start); b.setEndTime(end); return bannerRepository.saveAndFlush(b); } protected CmsLink persistLink(Integer siteId, String groupName, String name) { CmsLink l = new CmsLink(); l.setSiteId(siteId); l.setGroupName(groupName); l.setName(name); l.setLinkUrl("https://www.example.com"); l.setStatus(1); return linkRepository.saveAndFlush(l); } /** 简易轮询等待(无 awaitility 依赖):最长 waitMillis 内每 50ms 检查一次期望 */ protected static void awaitTrue(long waitMillis, java.util.function.BooleanSupplier condition) throws InterruptedException { long deadline = System.currentTimeMillis() + waitMillis; while (System.currentTimeMillis() < deadline) { if (condition.getAsBoolean()) { return; } Thread.sleep(50); } } protected static LocalDateTime nowPlusSeconds(long seconds) { return LocalDateTime.now().plusSeconds(seconds); } protected static LocalDateTime nowMinusSeconds(long seconds) { return LocalDateTime.now().minusSeconds(seconds); } }