using FsCheck; using FsCheck.Xunit; using Xunit; using XiangYi.Application.Services; namespace XiangYi.Application.Tests.Services; /// /// SensitiveWordService属性测试 - 敏感词过滤 /// public class SensitiveWordFilterPropertyTests { /// /// **Feature: backend-api, Property 25: 敏感词过滤** /// **Validates: Requirements 15.1, 15.2** /// /// *For any* 包含敏感词的聊天消息, 发送后消息内容中的敏感词应被替换为"***" /// [Property(MaxTest = 100)] public Property SensitiveWord_ShouldBeReplacedWithAsterisks() { // 生成敏感词列表 var sensitiveWordsArb = Gen.Elements( new List { "敏感词", "违规", "禁止" }, new List { "bad", "forbidden", "illegal" }, new List { "测试敏感", "不良内容" } ); // 生成包含敏感词的消息内容 var contentWithSensitiveWordArb = Gen.Elements( "这是一条包含敏感词的消息", "This message contains bad words", "消息中有违规内容", "这里有forbidden内容", "测试敏感词过滤功能" ); return Prop.ForAll( sensitiveWordsArb.ToArbitrary(), contentWithSensitiveWordArb.ToArbitrary(), (sensitiveWords, content) => { // Act - 查找匹配的敏感词 var matchedWords = SensitiveWordService.FindMatchedWords(content, sensitiveWords); // 如果没有匹配到敏感词,跳过此测试用例 if (matchedWords.Count == 0) { return true; } // Act - 替换敏感词 var filteredContent = SensitiveWordService.ReplaceWords(content, matchedWords); // Assert - 验证敏感词是否被正确替换 var isCorrectlyFiltered = SensitiveWordService.IsContentCorrectlyFiltered( content, filteredContent, sensitiveWords); return isCorrectlyFiltered; }); } /// /// 敏感词检测 - 应该能检测到所有敏感词 /// [Property(MaxTest = 100)] public Property SensitiveWord_Detection_ShouldFindAllMatches() { // 生成敏感词 var sensitiveWordArb = Gen.Elements("敏感", "违规", "禁止", "bad", "test"); // 生成包含敏感词的内容模板 var templateArb = Gen.Elements( "这是{0}内容", "消息包含{0}词汇", "The content has {0} word", "{0}出现在开头", "结尾是{0}" ); return Prop.ForAll( sensitiveWordArb.ToArbitrary(), templateArb.ToArbitrary(), (sensitiveWord, template) => { // Arrange - 构造包含敏感词的内容 var content = string.Format(template, sensitiveWord); var sensitiveWords = new List { sensitiveWord }; // Act - 检测敏感词 var matchedWords = SensitiveWordService.FindMatchedWords(content, sensitiveWords); // Assert - 应该检测到敏感词 return matchedWords.Contains(sensitiveWord); }); } /// /// 敏感词替换 - 替换后内容不应包含原敏感词 /// [Property(MaxTest = 100)] public Property SensitiveWord_Replacement_ShouldRemoveOriginalWord() { // 生成敏感词(排除"***"本身) var sensitiveWordArb = Gen.Elements("敏感词", "违规", "禁止", "bad", "forbidden") .Where(w => w != SensitiveWordService.ReplacementText); // 生成包含敏感词的内容 var contentArb = Gen.Elements( "这是敏感词测试", "包含违规内容", "禁止发布", "This is bad", "Something forbidden here" ); return Prop.ForAll( sensitiveWordArb.ToArbitrary(), contentArb.ToArbitrary(), (sensitiveWord, content) => { // 检查内容是否包含敏感词 if (!content.ToLowerInvariant().Contains(sensitiveWord.ToLowerInvariant())) { return true; // 跳过不包含敏感词的情况 } // Act - 替换敏感词 var filteredContent = SensitiveWordService.ReplaceWords( content, new List { sensitiveWord }); // Assert - 验证敏感词是否被替换 return SensitiveWordService.IsSensitiveWordReplaced( content, filteredContent, sensitiveWord); }); } /// /// 敏感词替换 - 应该替换为"***" /// [Property(MaxTest = 100)] public Property SensitiveWord_Replacement_ShouldUseAsterisks() { var sensitiveWordArb = Gen.Elements("敏感", "违规", "bad"); return Prop.ForAll( sensitiveWordArb.ToArbitrary(), sensitiveWord => { // Arrange - 构造只包含敏感词的内容 var content = sensitiveWord; var sensitiveWords = new List { sensitiveWord }; // Act - 替换敏感词 var filteredContent = SensitiveWordService.ReplaceWords(content, sensitiveWords); // Assert - 替换后应该是"***" return filteredContent == SensitiveWordService.ReplacementText; }); } /// /// 空内容 - 应该返回原内容 /// [Property(MaxTest = 100)] public Property EmptyContent_ShouldReturnOriginal() { var emptyContentArb = Gen.Elements("", null, " ", "\t", "\n"); var sensitiveWordsArb = Gen.Elements( new List { "敏感词" }, new List { "bad", "test" } ); return Prop.ForAll( emptyContentArb.ToArbitrary(), sensitiveWordsArb.ToArbitrary(), (emptyContent, sensitiveWords) => { // Act var matchedWords = SensitiveWordService.FindMatchedWords(emptyContent!, sensitiveWords); var filteredContent = SensitiveWordService.ReplaceWords(emptyContent!, sensitiveWords); // Assert - 空内容不应匹配任何敏感词 return matchedWords.Count == 0 && filteredContent == emptyContent; }); } /// /// 空敏感词列表 - 应该返回原内容 /// [Property(MaxTest = 100)] public Property EmptySensitiveWordList_ShouldReturnOriginal() { var contentArb = Gen.Elements("正常内容", "Normal content", "测试消息"); var emptySensitiveWordsArb = Gen.Elements( new List(), null! ); return Prop.ForAll( contentArb.ToArbitrary(), emptySensitiveWordsArb.ToArbitrary(), (content, emptySensitiveWords) => { // Act var matchedWords = SensitiveWordService.FindMatchedWords(content, emptySensitiveWords!); var filteredContent = SensitiveWordService.ReplaceWords(content, emptySensitiveWords!); // Assert - 空敏感词列表不应匹配任何内容 return matchedWords.Count == 0 && filteredContent == content; }); } /// /// 不区分大小写 - 应该能检测到不同大小写的敏感词 /// [Property(MaxTest = 100)] public Property SensitiveWord_Detection_ShouldBeCaseInsensitive() { // 生成大小写变体 var wordVariantsArb = Gen.Elements( ("bad", "BAD"), ("bad", "Bad"), ("test", "TEST"), ("test", "Test"), ("forbidden", "FORBIDDEN") ); return Prop.ForAll( wordVariantsArb.ToArbitrary(), variants => { var (lowerWord, upperWord) = variants; // Arrange - 内容包含大写变体,敏感词列表包含小写 var content = $"This contains {upperWord} word"; var sensitiveWords = new List { lowerWord }; // Act - 检测敏感词 var matchedWords = SensitiveWordService.FindMatchedWords(content, sensitiveWords); // Assert - 应该能检测到(不区分大小写) return matchedWords.Count > 0; }); } /// /// 多个敏感词 - 应该全部被替换 /// [Property(MaxTest = 100)] public Property MultipleSensitiveWords_ShouldAllBeReplaced() { return Prop.ForAll( Arb.Default.PositiveInt(), _ => { // Arrange - 包含多个敏感词的内容 var content = "这条消息包含敏感词和违规内容"; var sensitiveWords = new List { "敏感词", "违规" }; // Act - 替换敏感词 var filteredContent = SensitiveWordService.ReplaceWords(content, sensitiveWords); // Assert - 所有敏感词都应该被替换 var isCorrectlyFiltered = SensitiveWordService.IsContentCorrectlyFiltered( content, filteredContent, sensitiveWords); // 验证每个敏感词都被替换 var allReplaced = sensitiveWords.All(word => SensitiveWordService.IsSensitiveWordReplaced(content, filteredContent, word)); return isCorrectlyFiltered && allReplaced; }); } /// /// 不包含敏感词的内容 - 应该保持不变 /// [Property(MaxTest = 100)] public Property ContentWithoutSensitiveWord_ShouldRemainUnchanged() { var cleanContentArb = Gen.Elements( "这是正常的消息", "Hello world", "普通内容", "Normal message" ); var sensitiveWordsArb = Gen.Elements( new List { "敏感词", "违规" }, new List { "bad", "forbidden" } ); return Prop.ForAll( cleanContentArb.ToArbitrary(), sensitiveWordsArb.ToArbitrary(), (cleanContent, sensitiveWords) => { // 确保内容不包含任何敏感词 var containsAny = sensitiveWords.Any(w => cleanContent.ToLowerInvariant().Contains(w.ToLowerInvariant())); if (containsAny) { return true; // 跳过包含敏感词的情况 } // Act var filteredContent = SensitiveWordService.ReplaceWords(cleanContent, sensitiveWords); // Assert - 内容应该保持不变 return filteredContent == cleanContent; }); } /// /// 验证过滤正确性函数 - 应该正确判断过滤结果 /// [Property(MaxTest = 100)] public Property FilterValidation_ShouldBeCorrect() { var sensitiveWordArb = Gen.Elements("敏感", "违规", "bad"); return Prop.ForAll( sensitiveWordArb.ToArbitrary(), sensitiveWord => { // Arrange var content = $"包含{sensitiveWord}的内容"; var sensitiveWords = new List { sensitiveWord }; // Act - 正确过滤 var correctlyFiltered = SensitiveWordService.ReplaceWords(content, sensitiveWords); // Act - 验证正确过滤 var isCorrect = SensitiveWordService.IsContentCorrectlyFiltered( content, correctlyFiltered, sensitiveWords); // Act - 验证错误过滤(原内容) var isIncorrect = SensitiveWordService.IsContentCorrectlyFiltered( content, content, sensitiveWords); // Assert - 正确过滤应该返回true,错误过滤应该返回false return isCorrect && !isIncorrect; }); } }