348 lines
12 KiB
C#
348 lines
12 KiB
C#
using FsCheck;
|
||
using FsCheck.Xunit;
|
||
using Xunit;
|
||
using XiangYi.Application.Services;
|
||
|
||
namespace XiangYi.Application.Tests.Services;
|
||
|
||
/// <summary>
|
||
/// SensitiveWordService属性测试 - 敏感词过滤
|
||
/// </summary>
|
||
public class SensitiveWordFilterPropertyTests
|
||
{
|
||
/// <summary>
|
||
/// **Feature: backend-api, Property 25: 敏感词过滤**
|
||
/// **Validates: Requirements 15.1, 15.2**
|
||
///
|
||
/// *For any* 包含敏感词的聊天消息, 发送后消息内容中的敏感词应被替换为"***"
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property SensitiveWord_ShouldBeReplacedWithAsterisks()
|
||
{
|
||
// 生成敏感词列表
|
||
var sensitiveWordsArb = Gen.Elements(
|
||
new List<string> { "敏感词", "违规", "禁止" },
|
||
new List<string> { "bad", "forbidden", "illegal" },
|
||
new List<string> { "测试敏感", "不良内容" }
|
||
);
|
||
|
||
// 生成包含敏感词的消息内容
|
||
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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 敏感词检测 - 应该能检测到所有敏感词
|
||
/// </summary>
|
||
[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<string> { sensitiveWord };
|
||
|
||
// Act - 检测敏感词
|
||
var matchedWords = SensitiveWordService.FindMatchedWords(content, sensitiveWords);
|
||
|
||
// Assert - 应该检测到敏感词
|
||
return matchedWords.Contains(sensitiveWord);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 敏感词替换 - 替换后内容不应包含原敏感词
|
||
/// </summary>
|
||
[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<string> { sensitiveWord });
|
||
|
||
// Assert - 验证敏感词是否被替换
|
||
return SensitiveWordService.IsSensitiveWordReplaced(
|
||
content, filteredContent, sensitiveWord);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 敏感词替换 - 应该替换为"***"
|
||
/// </summary>
|
||
[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<string> { sensitiveWord };
|
||
|
||
// Act - 替换敏感词
|
||
var filteredContent = SensitiveWordService.ReplaceWords(content, sensitiveWords);
|
||
|
||
// Assert - 替换后应该是"***"
|
||
return filteredContent == SensitiveWordService.ReplacementText;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 空内容 - 应该返回原内容
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property EmptyContent_ShouldReturnOriginal()
|
||
{
|
||
var emptyContentArb = Gen.Elements("", null, " ", "\t", "\n");
|
||
var sensitiveWordsArb = Gen.Elements(
|
||
new List<string> { "敏感词" },
|
||
new List<string> { "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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 空敏感词列表 - 应该返回原内容
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property EmptySensitiveWordList_ShouldReturnOriginal()
|
||
{
|
||
var contentArb = Gen.Elements("正常内容", "Normal content", "测试消息");
|
||
var emptySensitiveWordsArb = Gen.Elements(
|
||
new List<string>(),
|
||
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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 不区分大小写 - 应该能检测到不同大小写的敏感词
|
||
/// </summary>
|
||
[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<string> { lowerWord };
|
||
|
||
// Act - 检测敏感词
|
||
var matchedWords = SensitiveWordService.FindMatchedWords(content, sensitiveWords);
|
||
|
||
// Assert - 应该能检测到(不区分大小写)
|
||
return matchedWords.Count > 0;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 多个敏感词 - 应该全部被替换
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property MultipleSensitiveWords_ShouldAllBeReplaced()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.Default.PositiveInt(),
|
||
_ =>
|
||
{
|
||
// Arrange - 包含多个敏感词的内容
|
||
var content = "这条消息包含敏感词和违规内容";
|
||
var sensitiveWords = new List<string> { "敏感词", "违规" };
|
||
|
||
// 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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 不包含敏感词的内容 - 应该保持不变
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property ContentWithoutSensitiveWord_ShouldRemainUnchanged()
|
||
{
|
||
var cleanContentArb = Gen.Elements(
|
||
"这是正常的消息",
|
||
"Hello world",
|
||
"普通内容",
|
||
"Normal message"
|
||
);
|
||
|
||
var sensitiveWordsArb = Gen.Elements(
|
||
new List<string> { "敏感词", "违规" },
|
||
new List<string> { "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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证过滤正确性函数 - 应该正确判断过滤结果
|
||
/// </summary>
|
||
[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<string> { 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;
|
||
});
|
||
}
|
||
}
|