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;
});
}
}