xiangyixiangqin/server/tests/XiangYi.Application.Tests/Services/SensitiveWordServicePropertyTests.cs
2026-01-02 18:00:49 +08:00

348 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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