package com.emotion.service; import com.emotion.dto.request.EpicScriptCreateRequest; import com.emotion.entity.AiConfig; import com.emotion.service.impl.EpicScriptServiceImpl; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; /** * EpicScriptServiceImpl测试类 * 包含属性测试,验证输入组装完整性等 * 使用贴近真实业务场景的测试数据 * * Feature: coze-ai-integration * * @author system * @date 2025-12-23 */ @SpringBootTest @ActiveProfiles("local") public class EpicScriptServiceImplTest { @Autowired private EpicScriptService epicScriptService; @Autowired private AiConfigService aiConfigService; private Random random; /** * 爽文剧本生成的配置键 */ private static final String EPIC_SCRIPT_CONFIG_KEY = "coze.course.life.generate"; /** * 真实的剧本标题示例 */ private static final List SAMPLE_TITLES = Arrays.asList( "逆袭人生:从底层到巅峰", "命运转折点", "我的职场逆袭之路", "重生之商业帝国", "平凡人的不平凡故事", "从零开始的创业传奇", "人生赢家养成记", "绝地反击" ); /** * 真实的主题/渴望示例 */ private static final List SAMPLE_THEMES = Arrays.asList( "成为行业领袖,实现财务自由", "找到真爱,拥有幸福家庭", "突破自我,成就非凡人生", "获得认可,证明自己的价值", "改变命运,逆转人生轨迹", "实现梦想,活出精彩人生" ); /** * 真实的序幕(低谷回响)示例 */ private static final List SAMPLE_PLOT_INTROS = Arrays.asList( "我是一个普通的上班族,每天朝九晚五,工资勉强够生活。公司裁员名单上赫然出现了我的名字。", "大学毕业后,我满怀憧憬来到大城市,却发现现实远比想象残酷。租住在狭小的地下室,每天为生计发愁。", "创业失败后,我背负着巨额债务,朋友疏远,家人失望。站在人生的最低谷,我不知道该何去何从。", "三十岁了,事业无成,感情空白。看着同龄人都已成家立业,我感到前所未有的迷茫和焦虑。" ); /** * 真实的转折(契机出现)示例 */ private static final List SAMPLE_PLOT_TURNINGS = Arrays.asList( "一次偶然的机会,我遇到了一位行业前辈。他的一番话点醒了我,让我看到了新的可能。", "在最绝望的时候,我发现了一个被忽视的市场机会。这可能是改变命运的转折点。", "一封意外的邮件,一个久违的电话,让我重新燃起了希望。原来机会一直都在,只是我没有发现。", "参加了一场行业峰会,结识了志同道合的伙伴。我们决定一起做点不一样的事情。" ); /** * 真实的高潮(命运抉择)示例 */ private static final List SAMPLE_PLOT_CLIMAXES = Arrays.asList( "面对两个选择:稳定但平庸的工作,还是充满风险但可能改变人生的创业机会。我必须做出决定。", "关键时刻,曾经的对手提出了合作邀请。是放下过去携手共进,还是坚持己见独自前行?", "项目进入最关键的阶段,资金链即将断裂。是放弃还是孤注一掷?这个决定将决定一切。", "成功近在咫尺,但代价是牺牲与家人相处的时间。事业与家庭,我该如何抉择?" ); /** * 真实的结局(新的开始)示例 */ private static final List SAMPLE_PLOT_ENDINGS = Arrays.asList( "经过不懈努力,我终于实现了自己的目标。站在新的起点,我知道这只是开始,更精彩的人生还在前方。", "回首来时路,那些曾经的困难都成为了宝贵的财富。我不仅收获了成功,更收获了成长。", "梦想成真的那一刻,我流下了激动的泪水。感谢那个在低谷中没有放弃的自己。", "新的篇章已经开启,我带着过去的经验和教训,向着更高的目标前进。人生,永远充满可能。" ); @BeforeEach public void setUp() { random = new Random(); } // ==================== Property 3: Input Assembly Completeness ==================== // Feature: coze-ai-integration, Property 3: Input Assembly Completeness // Validates: Requirements 4.2 @Test @DisplayName("Property 3: 验证数据库中存在爽文剧本生成配置") public void testEpicScriptConfigExists() { // 从数据库获取配置 AiConfig config = aiConfigService.getByConfigKey(EPIC_SCRIPT_CONFIG_KEY); if (config != null) { // 验证配置的必要字段 assertNotNull(config.getWorkflowId(), "workflowId不应为null"); assertNotNull(config.getApiToken(), "apiToken不应为null"); assertNotNull(config.getApiBaseUrl(), "apiBaseUrl不应为null"); System.out.println("爽文剧本配置验证通过: " + EPIC_SCRIPT_CONFIG_KEY); System.out.println(" workflowId: " + config.getWorkflowId()); System.out.println(" apiBaseUrl: " + config.getApiBaseUrl()); } else { System.out.println("警告:未找到配置 " + EPIC_SCRIPT_CONFIG_KEY + ",请先在数据库中添加配置"); } } @Test @DisplayName("Property 3: 输入组装完整性 - 验证所有非空字段都被包含在输入中") public void testInputAssemblyCompleteness() throws Exception { // 使用反射获取私有方法 Method assembleMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "assembleScriptInput", EpicScriptCreateRequest.class); assembleMethod.setAccessible(true); // 使用真实业务数据进行多次测试 IntStream.range(0, 10).forEach(i -> { try { // 从样本数据中随机选择真实业务数据 String title = getRandomSample(SAMPLE_TITLES); String theme = getRandomSample(SAMPLE_THEMES); String style = getRandomStyle(); String length = getRandomLength(); String plotIntro = getRandomSample(SAMPLE_PLOT_INTROS); String plotTurning = getRandomSample(SAMPLE_PLOT_TURNINGS); String plotClimax = getRandomSample(SAMPLE_PLOT_CLIMAXES); String plotEnding = getRandomSample(SAMPLE_PLOT_ENDINGS); // 创建请求对象 EpicScriptCreateRequest request = new EpicScriptCreateRequest(); request.setTitle(title); request.setTheme(theme); request.setStyle(style); request.setLength(length); request.setPlotIntro(plotIntro); request.setPlotTurning(plotTurning); request.setPlotClimax(plotClimax); request.setPlotEnding(plotEnding); String result = (String) assembleMethod.invoke(epicScriptService, request); // 验证所有字段都被包含 assertTrue(result.contains(title), "输入应包含标题: " + title); assertTrue(result.contains(theme), "输入应包含主题: " + theme); assertTrue(result.contains(plotIntro), "输入应包含序幕"); assertTrue(result.contains(plotTurning), "输入应包含转折"); assertTrue(result.contains(plotClimax), "输入应包含高潮"); assertTrue(result.contains(plotEnding), "输入应包含结局"); // 验证风格和篇幅描述 assertTrue(result.contains("【剧本风格】"), "输入应包含风格标签"); assertTrue(result.contains("【篇幅长度】"), "输入应包含篇幅标签"); } catch (Exception e) { fail("测试执行失败: " + e.getMessage()); } }); } @Test @DisplayName("Property 3: 输入组装 - 验证风格描述转换") public void testStyleDescriptionConversion() throws Exception { Method getStyleDescMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "getStyleDescription", String.class); getStyleDescMethod.setAccessible(true); // 验证各种风格的描述转换 assertEquals("职场逆袭", getStyleDescMethod.invoke(epicScriptService, "career")); assertEquals("情感圆满", getStyleDescMethod.invoke(epicScriptService, "love")); assertEquals("玄幻觉醒", getStyleDescMethod.invoke(epicScriptService, "fantasy")); assertEquals("unknown", getStyleDescMethod.invoke(epicScriptService, "unknown")); } @Test @DisplayName("Property 3: 输入组装 - 验证篇幅描述转换") public void testLengthDescriptionConversion() throws Exception { Method getLengthDescMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "getLengthDescription", String.class); getLengthDescMethod.setAccessible(true); // 验证各种篇幅的描述转换 assertEquals("标准篇", getLengthDescMethod.invoke(epicScriptService, "medium")); assertEquals("长篇", getLengthDescMethod.invoke(epicScriptService, "long")); assertEquals("short", getLengthDescMethod.invoke(epicScriptService, "short")); } @Test @DisplayName("Property 3: 输入组装 - 验证空字段不被包含") public void testInputAssemblyWithEmptyFields() throws Exception { // 创建只有部分字段的请求 EpicScriptCreateRequest request = new EpicScriptCreateRequest(); request.setTitle("测试标题"); request.setTheme("测试主题"); // 其他字段为空 Method assembleMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "assembleScriptInput", EpicScriptCreateRequest.class); assembleMethod.setAccessible(true); String result = (String) assembleMethod.invoke(epicScriptService, request); // 验证包含非空字段 assertTrue(result.contains("测试标题"), "输入应包含标题"); assertTrue(result.contains("测试主题"), "输入应包含主题"); // 验证不包含空字段的标签 assertFalse(result.contains("【序幕-低谷回响】"), "空字段不应被包含"); assertFalse(result.contains("【转折-契机出现】"), "空字段不应被包含"); assertFalse(result.contains("【高潮-命运抉择】"), "空字段不应被包含"); assertFalse(result.contains("【结局-新的开始】"), "空字段不应被包含"); } @Test @DisplayName("Property 3: 输入组装 - 验证所有字段为空时返回空字符串") public void testInputAssemblyWithAllEmptyFields() throws Exception { EpicScriptCreateRequest request = new EpicScriptCreateRequest(); Method assembleMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "assembleScriptInput", EpicScriptCreateRequest.class); assembleMethod.setAccessible(true); String result = (String) assembleMethod.invoke(epicScriptService, request); // 验证返回空字符串 assertTrue(result.isEmpty(), "所有字段为空时应返回空字符串"); } @Test @DisplayName("Property 3: 输入组装 - 随机部分字段填充测试") public void testInputAssemblyWithRandomPartialFields() throws Exception { Method assembleMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "assembleScriptInput", EpicScriptCreateRequest.class); assembleMethod.setAccessible(true); // 使用真实业务数据进行多次测试 IntStream.range(0, 10).forEach(i -> { try { EpicScriptCreateRequest request = new EpicScriptCreateRequest(); // 随机决定哪些字段有值,使用真实业务数据 String title = random.nextBoolean() ? getRandomSample(SAMPLE_TITLES) : null; String theme = random.nextBoolean() ? getRandomSample(SAMPLE_THEMES) : null; String style = random.nextBoolean() ? getRandomStyle() : null; String length = random.nextBoolean() ? getRandomLength() : null; String plotIntro = random.nextBoolean() ? getRandomSample(SAMPLE_PLOT_INTROS) : null; String plotTurning = random.nextBoolean() ? getRandomSample(SAMPLE_PLOT_TURNINGS) : null; String plotClimax = random.nextBoolean() ? getRandomSample(SAMPLE_PLOT_CLIMAXES) : null; String plotEnding = random.nextBoolean() ? getRandomSample(SAMPLE_PLOT_ENDINGS) : null; request.setTitle(title); request.setTheme(theme); request.setStyle(style); request.setLength(length); request.setPlotIntro(plotIntro); request.setPlotTurning(plotTurning); request.setPlotClimax(plotClimax); request.setPlotEnding(plotEnding); String result = (String) assembleMethod.invoke(epicScriptService, request); // 验证非空字段被包含,空字段不被包含 if (title != null && !title.isEmpty()) { assertTrue(result.contains(title), "非空标题应被包含"); } if (theme != null && !theme.isEmpty()) { assertTrue(result.contains(theme), "非空主题应被包含"); } if (plotIntro != null && !plotIntro.isEmpty()) { assertTrue(result.contains(plotIntro), "非空序幕应被包含"); } if (plotTurning != null && !plotTurning.isEmpty()) { assertTrue(result.contains(plotTurning), "非空转折应被包含"); } if (plotClimax != null && !plotClimax.isEmpty()) { assertTrue(result.contains(plotClimax), "非空高潮应被包含"); } if (plotEnding != null && !plotEnding.isEmpty()) { assertTrue(result.contains(plotEnding), "非空结局应被包含"); } } catch (Exception e) { fail("测试执行失败: " + e.getMessage()); } }); } @Test @DisplayName("Property 3: 输入组装 - 验证输出格式正确") public void testInputAssemblyFormat() throws Exception { EpicScriptCreateRequest request = new EpicScriptCreateRequest(); request.setTitle("我的逆袭人生"); request.setTheme("成为行业领袖"); request.setStyle("career"); request.setLength("medium"); request.setPlotIntro("从一个普通员工开始"); request.setPlotTurning("遇到贵人指点"); request.setPlotClimax("面临重大抉择"); request.setPlotEnding("成功逆袭"); Method assembleMethod = EpicScriptServiceImpl.class.getDeclaredMethod( "assembleScriptInput", EpicScriptCreateRequest.class); assembleMethod.setAccessible(true); String result = (String) assembleMethod.invoke(epicScriptService, request); // 验证格式标签 assertTrue(result.contains("【剧本标题】我的逆袭人生"), "应包含正确格式的标题"); assertTrue(result.contains("【主题渴望】成为行业领袖"), "应包含正确格式的主题"); assertTrue(result.contains("【剧本风格】职场逆袭"), "应包含正确格式的风格"); assertTrue(result.contains("【篇幅长度】标准篇"), "应包含正确格式的篇幅"); assertTrue(result.contains("【序幕-低谷回响】从一个普通员工开始"), "应包含正确格式的序幕"); assertTrue(result.contains("【转折-契机出现】遇到贵人指点"), "应包含正确格式的转折"); assertTrue(result.contains("【高潮-命运抉择】面临重大抉择"), "应包含正确格式的高潮"); assertTrue(result.contains("【结局-新的开始】成功逆袭"), "应包含正确格式的结局"); } /** * 获取随机风格 */ private String getRandomStyle() { String[] styles = {"career", "love", "fantasy"}; return styles[random.nextInt(styles.length)]; } /** * 获取随机篇幅 */ private String getRandomLength() { String[] lengths = {"medium", "long"}; return lengths[random.nextInt(lengths.length)]; } /** * 从样本列表中随机获取一个元素 * @param samples 样本列表 * @return 随机选择的样本 */ private String getRandomSample(List samples) { return samples.get(random.nextInt(samples.size())); } }