144 lines
5.1 KiB
C#
144 lines
5.1 KiB
C#
using FsCheck;
|
|
using FsCheck.Fluent;
|
|
using FsCheck.Xunit;
|
|
using System.Linq;
|
|
using XplorePlane.Helpers;
|
|
|
|
namespace XplorePlane.Tests.Helpers
|
|
{
|
|
/// <summary>
|
|
/// FsCheck property-based tests for FileNameSanitizer.
|
|
/// Property 1: 文件名非法字符清理
|
|
/// Validates: Requirements 1.2
|
|
/// </summary>
|
|
public class FileNameSanitizerPropertyTests
|
|
{
|
|
private static readonly char[] InvalidChars =
|
|
new[] { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
|
|
|
|
/// <summary>
|
|
/// 生成包含各种字符(含非法字符)的文件名字符串
|
|
/// </summary>
|
|
private static Arbitrary<string> 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<char>(charGen, len)
|
|
select new string(chars.ToArray());
|
|
|
|
return gen.ToArbitrary();
|
|
}
|
|
|
|
// ── Property 1: Output never contains any illegal characters ─────────
|
|
|
|
/// <summary>
|
|
/// **Validates: Requirements 1.2**
|
|
/// For any input string, the sanitized output must not contain any of the 9 illegal characters.
|
|
/// </summary>
|
|
[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 ──────
|
|
|
|
/// <summary>
|
|
/// **Validates: Requirements 1.2**
|
|
/// For any input string, characters that are NOT illegal must appear unchanged
|
|
/// at the same position in the output.
|
|
/// </summary>
|
|
[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 ─────────────
|
|
|
|
/// <summary>
|
|
/// **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 "_".
|
|
/// </summary>
|
|
[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 ────────────────────────
|
|
|
|
/// <summary>
|
|
/// **Validates: Requirements 1.2**
|
|
/// Sanitizing the output a second time produces the same result (idempotent).
|
|
/// </summary>
|
|
[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 ──────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// **Validates: Requirements 1.2**
|
|
/// Null or empty input always returns "_".
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public Property NullOrEmptyInput_ReturnsUnderscore()
|
|
{
|
|
var gen = Gen.Elements<string>(null, "", null, "");
|
|
|
|
return Prop.ForAll(
|
|
gen.ToArbitrary(),
|
|
input =>
|
|
{
|
|
var result = FileNameSanitizer.Sanitize(input);
|
|
return result == "_";
|
|
});
|
|
}
|
|
}
|
|
}
|