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 == "_";
});
}
}
}