using File.Application.Common; using FluentAssertions; using Xunit; namespace BuildFlow.File.Tests; /// /// 输入清洗规则单测:这是文件服务的安全防线(路径遍历、桶名注入、目录穿越)。 /// 覆盖所有公开方法的合法/非法/边界场景。 /// public class SanitizeRulesTests { // ===== IsValidBucketName ===== [Theory] [InlineData("my-bucket")] // 标准合法 [InlineData("rag.files.2024")] // 含点和数字 [InlineData("abc")] // 最短 3 字符 [InlineData("a01234567890123456789012345678901234567890123456789012345678901")] // 最长 63 字符 public void IsValidBucketName_合法桶名_返回true(string name) { SanitizeRules.IsValidBucketName(name).Should().BeTrue(); } [Theory] [InlineData(null)] // null [InlineData("")] // 空串 [InlineData("ab")] // 太短 <3 [InlineData("A-Bucket")] // 含大写 [InlineData("-leading")] // 以 - 开头 [InlineData("trailing-")] // 以 - 结尾 [InlineData(".dot-first")] // 以 . 开头 [InlineData("under_score")] // 含下划线(非法) [InlineData("中文桶名")] // 含非 ASCII [InlineData("has space")] // 含空格 [InlineData("a01234567890123456789012345678901234567890123456789012345678901234")] // 太长 >63 public void IsValidBucketName_非法桶名_返回false(string? name) { SanitizeRules.IsValidBucketName(name).Should().BeFalse(); } // ===== IsValidObjectKey(路径遍历防护 - 安全关键)===== [Theory] [InlineData("documents/report.pdf")] [InlineData("uuid-550e8400-e29b-41d4-a716-446655440000")] [InlineData("a")] // 单字符 [InlineData("folder/sub/deep/file.txt")] // 多级 public void IsValidObjectKey_合法key_返回true(string key) { SanitizeRules.IsValidObjectKey(key).Should().BeTrue(); } [Theory] [InlineData("../etc/passwd")] // 路径遍历(最危险) [InlineData("folder/../escape")] // 中间遍历 [InlineData("a../../b")] // 多重遍历 [InlineData("/leading-slash")] // 首斜杠(绝对路径注入) [InlineData("double//slash")] // 双斜杠(目录穿越) [InlineData("back\\slash")] // 反斜杠 [InlineData("control\tchar")] // 制表符 [InlineData("control\nchar")] // 换行符 [InlineData(null)] [InlineData("")] public void IsValidObjectKey_非法key_返回false(string? key) { SanitizeRules.IsValidObjectKey(key).Should().BeFalse(); } [Fact] public void IsValidObjectKey_超长key_返回false() { // 1025 字符,超出上限 var tooLong = new string('a', 1025); SanitizeRules.IsValidObjectKey(tooLong).Should().BeFalse(); } [Fact] public void IsValidObjectKey_刚好1024字符_返回true() { // 边界值:1024 字符应合法 var maxLength = new string('a', 1024); SanitizeRules.IsValidObjectKey(maxLength).Should().BeTrue(); } // ===== IsValidFolderName ===== [Theory] [InlineData("我的文件夹")] // 中文 [InlineData("Project 2024")] [InlineData(" 需要trim的名称 ")] // 带空格(应 trim 后合法) [InlineData("a")] // 单字符 public void IsValidFolderName_合法名_返回true(string name) { SanitizeRules.IsValidFolderName(name).Should().BeTrue(); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] // 纯空白 [InlineData("has/slash")] [InlineData("has\\backslash")] [InlineData("tab\tinside")] public void IsValidFolderName_非法名_返回false(string? name) { SanitizeRules.IsValidFolderName(name).Should().BeFalse(); } // ===== IsValidResourceType ===== [Theory] [InlineData("folder")] [InlineData("file")] [InlineData("FOLDER")] // 大小写不敏感 [InlineData("File")] public void IsValidResourceType_合法_返回true(string t) { SanitizeRules.IsValidResourceType(t).Should().BeTrue(); } [Theory] [InlineData(null)] [InlineData("")] [InlineData("bucket")] // 非法类型 [InlineData("object")] public void IsValidResourceType_非法_返回false(string? t) { SanitizeRules.IsValidResourceType(t).Should().BeFalse(); } // ===== SanitizeFilename ===== [Fact] public void SanitizeFilename_转义双引号() { SanitizeRules.SanitizeFilename("report\"final.pdf") .Should().Be("report\\\"final.pdf"); } [Fact] public void SanitizeFilename_无双引号_原样返回() { SanitizeRules.SanitizeFilename("normal-name.txt") .Should().Be("normal-name.txt"); } }