using FsCheck; using FsCheck.Fluent; using FsCheck.Xunit; using System.Linq; using XplorePlane.Helpers; namespace XplorePlane.Tests.Helpers { /// /// FsCheck property-based tests for FileNameSanitizer. /// Property 1: 文件名非法字符清理 /// Validates: Requirements 1.2 /// public class FileNameSanitizerPropertyTests { private static readonly char[] InvalidChars = new[] { '\\', '/', ':', '*', '?', '"', '<', '>', '|' }; /// /// 生成包含各种字符(含非法字符)的文件名字符串 /// private static Arbitrary FileNameArb() { // Mix of normal chars, unicode chars, and illegal file system chars var charGen = Gen.Frequency( (5, Gen.Choose(0x20, 0x7E).Select(i => (char)i)), // printable ASCII (2, Gen.Elements(InvalidChars)), // illegal chars (2, Gen.Choose(0x4E00, 0x9FFF).Select(i => (char)i)), // CJK characters (1, Gen.Elements('_', '-', '.', ' ')) // common filename chars ); var gen = from len in Gen.Choose(1, 50) from chars in Gen.ListOf(charGen, len) select new string(chars.ToArray()); return gen.ToArbitrary(); } // ── Property 1: Output never contains any illegal characters ───────── /// /// **Validates: Requirements 1.2** /// For any input string, the sanitized output must not contain any of the 9 illegal characters. /// [Property(MaxTest = 100)] public Property SanitizedOutput_NeverContainsIllegalCharacters() { return Prop.ForAll( FileNameArb(), input => { var result = FileNameSanitizer.Sanitize(input); return !result.Any(c => InvalidChars.Contains(c)); }); } // ── Property 1 (cont): Legal characters are preserved unchanged ────── /// /// **Validates: Requirements 1.2** /// For any input string, characters that are NOT illegal must appear unchanged /// at the same position in the output. /// [Property(MaxTest = 100)] public Property SanitizedOutput_PreservesLegalCharacters() { return Prop.ForAll( FileNameArb(), input => { var result = FileNameSanitizer.Sanitize(input); for (int i = 0; i < input.Length; i++) { if (!InvalidChars.Contains(input[i])) { if (result[i] != input[i]) return false; } } return true; }); } // ── Property 1 (cont): Output length equals input length ───────────── /// /// **Validates: Requirements 1.2** /// For any non-null, non-empty input, the output length equals the input length. /// For null or empty input, the output is "_". /// [Property(MaxTest = 100)] public Property SanitizedOutput_LengthEqualsInputLength() { return Prop.ForAll( FileNameArb(), input => { var result = FileNameSanitizer.Sanitize(input); return result.Length == input.Length; }); } // ── Property 1 (cont): Sanitize is idempotent ──────────────────────── /// /// **Validates: Requirements 1.2** /// Sanitizing the output a second time produces the same result (idempotent). /// [Property(MaxTest = 100)] public Property SanitizedOutput_IsIdempotent() { return Prop.ForAll( FileNameArb(), input => { var once = FileNameSanitizer.Sanitize(input); var twice = FileNameSanitizer.Sanitize(once); return once == twice; }); } // ── Edge case: null and empty input ────────────────────────────────── /// /// **Validates: Requirements 1.2** /// Null or empty input always returns "_". /// [Property(MaxTest = 100)] public Property NullOrEmptyInput_ReturnsUnderscore() { var gen = Gen.Elements(null, "", null, ""); return Prop.ForAll( gen.ToArbitrary(), input => { var result = FileNameSanitizer.Sanitize(input); return result == "_"; }); } } }