A:Common配置
This commit is contained in:
1 parent
61e769a0f6
commit
c8fde9759f
8 files changed
+3742
-703
No files matched your search
@@ -0,0 +1,748 @@
|
||||
using System.Security.Cryptography;
|
||||
using Common.Utils;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// CryptoHelper 加密工具测试
|
||||
/// 展示各种加密、哈希、编码功能
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class CryptoHelperTests
|
||||
{
|
||||
#region AES 加密解密测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:AES 加密和解密
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void AesEncrypt_AndDecrypt_ReturnsOriginalText()
|
||||
{
|
||||
// Arrange
|
||||
var plainText = "Hello, World! 你好世界!";
|
||||
var password = "MySecretPassword123";
|
||||
|
||||
// Act
|
||||
var encrypted = CryptoHelper.AesEncrypt(plainText, password);
|
||||
var decrypted = CryptoHelper.AesDecrypt(encrypted, password);
|
||||
|
||||
// Assert
|
||||
Assert.AreNotEqual(plainText, encrypted);
|
||||
Assert.AreEqual(plainText, decrypted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:相同内容使用相同密码多次加密,结果应该不同(因为 Salt 随机)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void AesEncrypt_SameTextSamePassword_ProducesDifferentCiphertext()
|
||||
{
|
||||
// Arrange
|
||||
var plainText = "Test Message";
|
||||
var password = "password";
|
||||
|
||||
// Act
|
||||
var encrypted1 = CryptoHelper.AesEncrypt(plainText, password);
|
||||
var encrypted2 = CryptoHelper.AesEncrypt(plainText, password);
|
||||
|
||||
// Assert: 密文应该不同(因为使用了随机 Salt)
|
||||
Assert.AreNotEqual(encrypted1, encrypted2);
|
||||
|
||||
// 但都应该能正确解密
|
||||
Assert.AreEqual(plainText, CryptoHelper.AesDecrypt(encrypted1, password));
|
||||
Assert.AreEqual(plainText, CryptoHelper.AesDecrypt(encrypted2, password));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:使用错误密码解密失败
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void AesDecrypt_WrongPassword_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var plainText = "Secret Message";
|
||||
var correctPassword = "correct";
|
||||
var wrongPassword = "wrong";
|
||||
|
||||
var encrypted = CryptoHelper.AesEncrypt(plainText, correctPassword);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsException<CryptographicException>(() =>
|
||||
{
|
||||
CryptoHelper.AesDecrypt(encrypted, wrongPassword);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:加密空字符串抛出异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void AesEncrypt_EmptyString_ThrowsException()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() =>
|
||||
{
|
||||
CryptoHelper.AesEncrypt("", "password");
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RSA 加密解密测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:生成 RSA 密钥对
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GenerateRsaKeys_ReturnsPublicAndPrivateKeys()
|
||||
{
|
||||
// Act
|
||||
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(publicKey));
|
||||
Assert.IsFalse(string.IsNullOrEmpty(privateKey));
|
||||
Assert.AreNotEqual(publicKey, privateKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:RSA 公钥加密,私钥解密
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RsaEncrypt_AndDecrypt_ReturnsOriginalText()
|
||||
{
|
||||
// Arrange
|
||||
var plainText = "RSA Test Message";
|
||||
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
|
||||
|
||||
// Act
|
||||
var encrypted = CryptoHelper.RsaEncrypt(plainText, publicKey);
|
||||
var decrypted = CryptoHelper.RsaDecrypt(encrypted, privateKey);
|
||||
|
||||
// Assert
|
||||
Assert.AreNotEqual(plainText, encrypted);
|
||||
Assert.AreEqual(plainText, decrypted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:RSA 加密相同内容产生不同密文(OAEP 填充)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RsaEncrypt_SameText_ProducesDifferentCiphertext()
|
||||
{
|
||||
// Arrange
|
||||
var plainText = "Test";
|
||||
var (publicKey, _) = CryptoHelper.GenerateRsaKeys();
|
||||
|
||||
// Act
|
||||
var encrypted1 = CryptoHelper.RsaEncrypt(plainText, publicKey);
|
||||
var encrypted2 = CryptoHelper.RsaEncrypt(plainText, publicKey);
|
||||
|
||||
// Assert: 由于 OAEP 填充使用随机数,密文应该不同
|
||||
Assert.AreNotEqual(encrypted1, encrypted2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:使用错误的私钥解密失败
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RsaDecrypt_WrongPrivateKey_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var plainText = "Secret";
|
||||
var (publicKey1, _) = CryptoHelper.GenerateRsaKeys();
|
||||
var (_, privateKey2) = CryptoHelper.GenerateRsaKeys(); // 不同的密钥对
|
||||
|
||||
var encrypted = CryptoHelper.RsaEncrypt(plainText, publicKey1);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsException<CryptographicException>(() =>
|
||||
{
|
||||
CryptoHelper.RsaDecrypt(encrypted, privateKey2);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 哈希函数测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:MD5 哈希
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Md5Hash_ProducesCorrectHash()
|
||||
{
|
||||
// Arrange
|
||||
var input = "Hello, World!";
|
||||
|
||||
// Act
|
||||
var hash = CryptoHelper.Md5Hash(input);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(32, hash.Length); // MD5 产生 32 位十六进制字符串
|
||||
Assert.AreEqual("65a8e27d8879283831b664bd8b7f0ad4", hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:SHA256 哈希
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Sha256Hash_ProducesCorrectHash()
|
||||
{
|
||||
// Arrange
|
||||
var input = "Hello, World!";
|
||||
|
||||
// Act
|
||||
var hash = CryptoHelper.Sha256Hash(input);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(64, hash.Length); // SHA256 产生 64 位十六进制字符串
|
||||
Assert.AreEqual("dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:SHA512 哈希
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Sha512Hash_ProducesCorrectHash()
|
||||
{
|
||||
// Arrange
|
||||
var input = "Hello, World!";
|
||||
|
||||
// Act
|
||||
var hash = CryptoHelper.Sha512Hash(input);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(128, hash.Length); // SHA512 产生 128 位十六进制字符串
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:相同输入产生相同哈希
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Hash_SameInput_ProducesSameHash()
|
||||
{
|
||||
// Arrange
|
||||
var input = "Test Input";
|
||||
|
||||
// Act
|
||||
var md5_1 = CryptoHelper.Md5Hash(input);
|
||||
var md5_2 = CryptoHelper.Md5Hash(input);
|
||||
var sha256_1 = CryptoHelper.Sha256Hash(input);
|
||||
var sha256_2 = CryptoHelper.Sha256Hash(input);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(md5_1, md5_2);
|
||||
Assert.AreEqual(sha256_1, sha256_2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:不同输入产生不同哈希
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Hash_DifferentInput_ProducesDifferentHash()
|
||||
{
|
||||
// Arrange
|
||||
var input1 = "Input1";
|
||||
var input2 = "Input2";
|
||||
|
||||
// Act
|
||||
var hash1 = CryptoHelper.Sha256Hash(input1);
|
||||
var hash2 = CryptoHelper.Sha256Hash(input2);
|
||||
|
||||
// Assert
|
||||
Assert.AreNotEqual(hash1, hash2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Base64 编解码测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Base64 编码和解码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Base64Encode_AndDecode_ReturnsOriginalText()
|
||||
{
|
||||
// Arrange
|
||||
var input = "Hello, World! 你好世界!";
|
||||
|
||||
// Act
|
||||
var encoded = CryptoHelper.Base64Encode(input);
|
||||
var decoded = CryptoHelper.Base64Decode(encoded);
|
||||
|
||||
// Assert
|
||||
Assert.AreNotEqual(input, encoded);
|
||||
Assert.AreEqual(input, decoded);
|
||||
Assert.AreEqual("SGVsbG8sIFdvcmxkISDkvaDlpb3kuJbnlYzvvIE=", encoded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:URL 安全的 Base64 编码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Base64UrlEncode_ProducesUrlSafeString()
|
||||
{
|
||||
// Arrange
|
||||
var input = "Hello+World/Test=";
|
||||
|
||||
// Act
|
||||
var encoded = CryptoHelper.Base64UrlEncode(input);
|
||||
var decoded = CryptoHelper.Base64UrlDecode(encoded);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(encoded.Contains('+')); // 不包含 +
|
||||
Assert.IsFalse(encoded.Contains('/')); // 不包含 /
|
||||
Assert.IsFalse(encoded.Contains('=')); // 不包含 =
|
||||
Assert.AreEqual(input, decoded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:URL Base64 编解码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Base64UrlEncode_AndDecode_ReturnsOriginalText()
|
||||
{
|
||||
// Arrange
|
||||
var testCases = new[]
|
||||
{
|
||||
"test",
|
||||
"test1",
|
||||
"test12",
|
||||
"test123",
|
||||
"Hello, World!",
|
||||
"特殊字符 !@#$%^&*()"
|
||||
};
|
||||
|
||||
foreach (var input in testCases)
|
||||
{
|
||||
// Act
|
||||
var encoded = CryptoHelper.Base64UrlEncode(input);
|
||||
var decoded = CryptoHelper.Base64UrlDecode(encoded);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(input, decoded, $"Failed for input: {input}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 密码哈希测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:哈希密码并验证
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HashPassword_AndVerify_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var password = "MySecurePassword123!";
|
||||
|
||||
// Act
|
||||
var hashed = CryptoHelper.HashPassword(password);
|
||||
var isValid = CryptoHelper.VerifyPassword(password, hashed);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.AreNotEqual(password, hashed);
|
||||
Assert.IsTrue(hashed.StartsWith("$pbkdf2-sha256$"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:验证错误密码返回 false
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void VerifyPassword_WrongPassword_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var correctPassword = "correct";
|
||||
var wrongPassword = "wrong";
|
||||
var hashed = CryptoHelper.HashPassword(correctPassword);
|
||||
|
||||
// Act
|
||||
var isValid = CryptoHelper.VerifyPassword(wrongPassword, hashed);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:相同密码多次哈希产生不同结果
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HashPassword_SamePassword_ProducesDifferentHashes()
|
||||
{
|
||||
// Arrange
|
||||
var password = "password123";
|
||||
|
||||
// Act
|
||||
var hash1 = CryptoHelper.HashPassword(password);
|
||||
var hash2 = CryptoHelper.HashPassword(password);
|
||||
|
||||
// Assert
|
||||
Assert.AreNotEqual(hash1, hash2); // 不同的 Salt
|
||||
|
||||
// 但都应该能验证通过
|
||||
Assert.IsTrue(CryptoHelper.VerifyPassword(password, hash1));
|
||||
Assert.IsTrue(CryptoHelper.VerifyPassword(password, hash2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:自定义哈希参数
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HashPassword_CustomParameters_Works()
|
||||
{
|
||||
// Arrange
|
||||
var password = "test";
|
||||
|
||||
// Act
|
||||
var hash = CryptoHelper.HashPassword(password, saltSize: 32, iterations: 50000, hashSize: 64);
|
||||
var isValid = CryptoHelper.VerifyPassword(password, hash);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HMAC 签名测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:HMAC-SHA256 签名和验证
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HmacSha256_AndVerify_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var message = "Important message";
|
||||
var secret = "secret-key";
|
||||
|
||||
// Act
|
||||
var signature = CryptoHelper.HmacSha256(message, secret);
|
||||
var isValid = CryptoHelper.VerifyHmacSha256(message, signature, secret);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.AreEqual(64, signature.Length); // SHA256 产生 64 位十六进制
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:验证错误签名返回 false
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void VerifyHmacSha256_WrongSignature_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var message = "message";
|
||||
var secret = "secret";
|
||||
var wrongSignature = "wrong_signature";
|
||||
|
||||
// Act
|
||||
var isValid = CryptoHelper.VerifyHmacSha256(message, wrongSignature, secret);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:修改消息后签名无效
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void VerifyHmacSha256_ModifiedMessage_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var originalMessage = "Original message";
|
||||
var modifiedMessage = "Modified message";
|
||||
var secret = "secret";
|
||||
|
||||
var signature = CryptoHelper.HmacSha256(originalMessage, secret);
|
||||
|
||||
// Act
|
||||
var isValid = CryptoHelper.VerifyHmacSha256(modifiedMessage, signature, secret);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:HMAC-SHA512 签名
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HmacSha512_ProducesCorrectSignature()
|
||||
{
|
||||
// Arrange
|
||||
var message = "Test message";
|
||||
var secret = "secret";
|
||||
|
||||
// Act
|
||||
var signature = CryptoHelper.HmacSha512(message, secret);
|
||||
var isValid = CryptoHelper.VerifyHmacSha512(message, signature, secret);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
Assert.AreEqual(128, signature.Length); // SHA512 产生 128 位十六进制
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:相同消息和密钥产生相同签名
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HmacSha256_SameInputs_ProducesSameSignature()
|
||||
{
|
||||
// Arrange
|
||||
var message = "message";
|
||||
var secret = "secret";
|
||||
|
||||
// Act
|
||||
var signature1 = CryptoHelper.HmacSha256(message, secret);
|
||||
var signature2 = CryptoHelper.HmacSha256(message, secret);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(signature1, signature2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:生成随机字符串
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GenerateRandomString_ProducesCorrectLength()
|
||||
{
|
||||
// Arrange & Act
|
||||
var str10 = CryptoHelper.GenerateRandomString(10);
|
||||
var str20 = CryptoHelper.GenerateRandomString(20);
|
||||
var strWithSpecial = CryptoHelper.GenerateRandomString(15, useSpecialChars: true);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(10, str10.Length);
|
||||
Assert.AreEqual(20, str20.Length);
|
||||
Assert.AreEqual(15, strWithSpecial.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:生成随机密码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GeneratePassword_MeetsRequirements()
|
||||
{
|
||||
// Act
|
||||
var password = CryptoHelper.GeneratePassword(16);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(16, password.Length);
|
||||
Assert.IsTrue(password.Any(char.IsDigit), "Should contain digit");
|
||||
Assert.IsTrue(password.Any(char.IsLower), "Should contain lowercase");
|
||||
Assert.IsTrue(password.Any(char.IsUpper), "Should contain uppercase");
|
||||
Assert.IsTrue(password.Any(c => !char.IsLetterOrDigit(c)), "Should contain special char");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:生成仅包含字母和数字的密码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GeneratePassword_OnlyAlphanumeric_NoSpecialChars()
|
||||
{
|
||||
// Act
|
||||
var password = CryptoHelper.GeneratePassword(
|
||||
12,
|
||||
requireDigit: true,
|
||||
requireLowercase: true,
|
||||
requireUppercase: true,
|
||||
requireSpecialChar: false
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(12, password.Length);
|
||||
Assert.IsTrue(password.All(c => char.IsLetterOrDigit(c)), "Should only contain alphanumeric");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:生成多个随机字符串应该不同
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GenerateRandomString_MultipleCalls_ProducesDifferentResults()
|
||||
{
|
||||
// Act
|
||||
var strings = Enumerable.Range(0, 10)
|
||||
.Select(_ => CryptoHelper.GenerateRandomString(20))
|
||||
.ToList();
|
||||
|
||||
// Assert: 所有字符串应该不同
|
||||
Assert.AreEqual(10, strings.Distinct().Count());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:用户密码存储和验证
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_UserPasswordStorageAndVerification()
|
||||
{
|
||||
// 模拟用户注册
|
||||
var userPassword = "SecurePassword123!";
|
||||
var hashedPassword = CryptoHelper.HashPassword(userPassword);
|
||||
|
||||
// 存储 hashedPassword 到数据库...
|
||||
|
||||
// 模拟用户登录
|
||||
var loginPassword = "SecurePassword123!";
|
||||
var isAuthenticated = CryptoHelper.VerifyPassword(loginPassword, hashedPassword);
|
||||
|
||||
Assert.IsTrue(isAuthenticated);
|
||||
|
||||
// 错误密码
|
||||
var wrongPassword = "WrongPassword";
|
||||
var isFailed = CryptoHelper.VerifyPassword(wrongPassword, hashedPassword);
|
||||
|
||||
Assert.IsFalse(isFailed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:API 签名验证
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ApiSignatureVerification()
|
||||
{
|
||||
// 客户端准备 API 请求
|
||||
var apiKey = "client-api-key";
|
||||
var apiSecret = "client-api-secret";
|
||||
var requestBody = "{\"userId\":123,\"action\":\"purchase\"}";
|
||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
|
||||
|
||||
// 生成签名
|
||||
var signatureData = $"{apiKey}{requestBody}{timestamp}";
|
||||
var signature = CryptoHelper.HmacSha256(signatureData, apiSecret);
|
||||
|
||||
// 发送请求(包含 signature 和 timestamp)...
|
||||
|
||||
// 服务器端验证签名
|
||||
var receivedSignatureData = $"{apiKey}{requestBody}{timestamp}";
|
||||
var isValidSignature = CryptoHelper.VerifyHmacSha256(
|
||||
receivedSignatureData,
|
||||
signature,
|
||||
apiSecret
|
||||
);
|
||||
|
||||
Assert.IsTrue(isValidSignature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:敏感数据加密存储
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_EncryptSensitiveData()
|
||||
{
|
||||
// 加密敏感数据(如信用卡号)
|
||||
var creditCardNumber = "4532-1234-5678-9010";
|
||||
var encryptionKey = "DataEncryptionKey123!";
|
||||
|
||||
var encrypted = CryptoHelper.AesEncrypt(creditCardNumber, encryptionKey);
|
||||
|
||||
// 存储加密后的数据...
|
||||
Console.WriteLine($"Encrypted: {encrypted}");
|
||||
|
||||
// 需要使用时解密
|
||||
var decrypted = CryptoHelper.AesDecrypt(encrypted, encryptionKey);
|
||||
|
||||
Assert.AreEqual(creditCardNumber, decrypted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:文件完整性验证
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_FileIntegrityVerification()
|
||||
{
|
||||
// 模拟文件内容
|
||||
var fileContent = "Important file content that should not be modified";
|
||||
|
||||
// 计算文件哈希
|
||||
var originalHash = CryptoHelper.Sha256Hash(fileContent);
|
||||
|
||||
// 存储哈希值...
|
||||
|
||||
// 后续验证文件是否被篡改
|
||||
var currentHash = CryptoHelper.Sha256Hash(fileContent);
|
||||
|
||||
Assert.AreEqual(originalHash, currentHash, "File has not been modified");
|
||||
|
||||
// 模拟文件被修改
|
||||
var modifiedContent = fileContent + " (modified)";
|
||||
var modifiedHash = CryptoHelper.Sha256Hash(modifiedContent);
|
||||
|
||||
Assert.AreNotEqual(originalHash, modifiedHash, "File has been modified");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:JWT Token 签名(简化版)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_JwtTokenSignature()
|
||||
{
|
||||
// JWT Header 和 Payload
|
||||
var header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
|
||||
var payload = "{\"userId\":123,\"exp\":1234567890}";
|
||||
|
||||
var headerEncoded = CryptoHelper.Base64UrlEncode(header);
|
||||
var payloadEncoded = CryptoHelper.Base64UrlEncode(payload);
|
||||
|
||||
var tokenWithoutSignature = $"{headerEncoded}.{payloadEncoded}";
|
||||
|
||||
// 使用 HMAC 签名
|
||||
var secret = "jwt-secret-key";
|
||||
var signature = CryptoHelper.HmacSha256(tokenWithoutSignature, secret);
|
||||
var signatureEncoded = CryptoHelper.Base64UrlEncode(signature);
|
||||
|
||||
var fullToken = $"{tokenWithoutSignature}.{signatureEncoded}";
|
||||
|
||||
Console.WriteLine($"JWT Token: {fullToken}");
|
||||
|
||||
// 验证 Token
|
||||
var parts = fullToken.Split('.');
|
||||
var receivedSignature = CryptoHelper.Base64UrlDecode(parts[2]);
|
||||
var expectedSignature = CryptoHelper.HmacSha256($"{parts[0]}.{parts[1]}", secret);
|
||||
|
||||
Assert.AreEqual(expectedSignature, receivedSignature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:安全通信(RSA + AES 混合加密)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_HybridEncryption()
|
||||
{
|
||||
// 场景:发送大量数据,使用混合加密(RSA 加密 AES 密钥,AES 加密数据)
|
||||
|
||||
// 1. 接收方生成 RSA 密钥对
|
||||
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
|
||||
|
||||
// 2. 发送方生成随机 AES 密钥
|
||||
var aesKey = CryptoHelper.GenerateRandomString(32);
|
||||
|
||||
// 3. 使用 RSA 公钥加密 AES 密钥
|
||||
var encryptedAesKey = CryptoHelper.RsaEncrypt(aesKey, publicKey);
|
||||
|
||||
// 4. 使用 AES 加密大量数据
|
||||
var largeData = "This is a large amount of data that would be inefficient to encrypt with RSA directly...";
|
||||
var encryptedData = CryptoHelper.AesEncrypt(largeData, aesKey);
|
||||
|
||||
// 5. 发送 encryptedAesKey 和 encryptedData...
|
||||
|
||||
// 6. 接收方使用 RSA 私钥解密 AES 密钥
|
||||
var decryptedAesKey = CryptoHelper.RsaDecrypt(encryptedAesKey, privateKey);
|
||||
|
||||
// 7. 使用解密的 AES 密钥解密数据
|
||||
var decryptedData = CryptoHelper.AesDecrypt(encryptedData, decryptedAesKey);
|
||||
|
||||
Assert.AreEqual(aesKey, decryptedAesKey);
|
||||
Assert.AreEqual(largeData, decryptedData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
using Common.ExpandFun;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// DateTimeExpand 日期时间扩展测试
|
||||
/// 展示日期格式化、时间戳转换、时区转换、日期计算等功能
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class DateTimeExpandTests
|
||||
{
|
||||
#region 日期格式化测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:中文日期格式化
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToChineseDateString_FormatsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 1, 15);
|
||||
|
||||
// Act
|
||||
var result = dt.ToChineseDateString();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("2024年01月15日", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:中文日期时间格式化
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToChineseDateTimeString_FormatsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 1, 15, 14, 30, 45);
|
||||
|
||||
// Act
|
||||
var result = dt.ToChineseDateTimeString();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("2024年01月15日 14:30:45", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:友好的相对时间字符串
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToFriendlyString_ReturnsCorrectString()
|
||||
{
|
||||
// 测试"刚刚"
|
||||
var justNow = DateTime.Now.AddSeconds(-30);
|
||||
Assert.AreEqual("刚刚", justNow.ToFriendlyString());
|
||||
|
||||
// 测试"分钟前"
|
||||
var minutesAgo = DateTime.Now.AddMinutes(-5);
|
||||
Assert.AreEqual("5分钟前", minutesAgo.ToFriendlyString());
|
||||
|
||||
// 测试"小时前"
|
||||
var hoursAgo = DateTime.Now.AddHours(-3);
|
||||
Assert.AreEqual("3小时前", hoursAgo.ToFriendlyString());
|
||||
|
||||
// 测试"昨天"
|
||||
var yesterday = DateTime.Now.AddDays(-1);
|
||||
Assert.AreEqual("昨天", yesterday.ToFriendlyString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:ISO 8601 格式化
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToIso8601String_FormatsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 1, 15, 12, 30, 45, 123, DateTimeKind.Utc);
|
||||
|
||||
// Act
|
||||
var result = dt.ToIso8601String();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("2024-01-15T12:30:45.123Z", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:标准日期格式化
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToDateString_FormatsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 3, 5);
|
||||
|
||||
// Act
|
||||
var result = dt.ToDateString();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("2024-03-05", result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 时间戳转换测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:转换为 Unix 时间戳(秒)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToUnixTimeSeconds_ConvertsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
// Act
|
||||
var timestamp = dt.ToUnixTimeSeconds();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1704067200, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:从 Unix 时间戳创建 DateTime
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void FromUnixTimeSeconds_CreatesCorrectDateTime()
|
||||
{
|
||||
// Arrange
|
||||
long timestamp = 1704067200; // 2024-01-01 00:00:00 UTC
|
||||
|
||||
// Act
|
||||
var dt = DateTimeExpand.FromUnixTimeSeconds(timestamp);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2024, dt.Year);
|
||||
Assert.AreEqual(1, dt.Month);
|
||||
Assert.AreEqual(1, dt.Day);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:时间戳往返转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void UnixTimestamp_RoundTrip_MaintainsValue()
|
||||
{
|
||||
// Arrange
|
||||
var original = new DateTime(2024, 6, 15, 12, 30, 45, DateTimeKind.Utc);
|
||||
|
||||
// Act
|
||||
var timestamp = original.ToUnixTimeSeconds();
|
||||
var restored = DateTimeExpand.FromUnixTimeSeconds(timestamp);
|
||||
|
||||
// Assert: 精确到秒
|
||||
Assert.AreEqual(original.Year, restored.Year);
|
||||
Assert.AreEqual(original.Month, restored.Month);
|
||||
Assert.AreEqual(original.Day, restored.Day);
|
||||
Assert.AreEqual(original.Hour, restored.Hour);
|
||||
Assert.AreEqual(original.Minute, restored.Minute);
|
||||
Assert.AreEqual(original.Second, restored.Second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:毫秒级时间戳转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToUnixTimeMilliseconds_ConvertsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
// Act
|
||||
var timestamp = dt.ToUnixTimeMilliseconds();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1704067200000, timestamp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 日期计算测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取月份第一天
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void StartOfMonth_ReturnsFirstDay()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 3, 15, 14, 30, 45);
|
||||
|
||||
// Act
|
||||
var result = dt.StartOfMonth();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2024, result.Year);
|
||||
Assert.AreEqual(3, result.Month);
|
||||
Assert.AreEqual(1, result.Day);
|
||||
Assert.AreEqual(0, result.Hour);
|
||||
Assert.AreEqual(0, result.Minute);
|
||||
Assert.AreEqual(0, result.Second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取月份最后一天
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void EndOfMonth_ReturnsLastDay()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 2, 15); // 2024 是闰年
|
||||
|
||||
// Act
|
||||
var result = dt.EndOfMonth();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2024, result.Year);
|
||||
Assert.AreEqual(2, result.Month);
|
||||
Assert.AreEqual(29, result.Day); // 闰年 2 月有 29 天
|
||||
Assert.AreEqual(23, result.Hour);
|
||||
Assert.AreEqual(59, result.Minute);
|
||||
Assert.AreEqual(59, result.Second);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取年份第一天和最后一天
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void StartOfYear_AndEndOfYear_Work()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 6, 15);
|
||||
|
||||
// Act
|
||||
var start = dt.StartOfYear();
|
||||
var end = dt.EndOfYear();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(new DateTime(2024, 1, 1, 0, 0, 0), start);
|
||||
Assert.AreEqual(2024, end.Year);
|
||||
Assert.AreEqual(12, end.Month);
|
||||
Assert.AreEqual(31, end.Day);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取当天开始和结束时间
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void StartOfDay_AndEndOfDay_Work()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 3, 15, 14, 30, 45);
|
||||
|
||||
// Act
|
||||
var start = dt.StartOfDay();
|
||||
var end = dt.EndOfDay();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(new DateTime(2024, 3, 15, 0, 0, 0), start);
|
||||
Assert.AreEqual(2024, end.Year);
|
||||
Assert.AreEqual(3, end.Month);
|
||||
Assert.AreEqual(15, end.Day);
|
||||
Assert.AreEqual(23, end.Hour);
|
||||
Assert.AreEqual(59, end.Minute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取周的第一天和最后一天
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void StartOfWeek_AndEndOfWeek_Work()
|
||||
{
|
||||
// Arrange: 2024年3月15日是星期五
|
||||
var dt = new DateTime(2024, 3, 15);
|
||||
|
||||
// Act: 周一作为一周的开始
|
||||
var start = dt.StartOfWeek(DayOfWeek.Monday);
|
||||
var end = dt.EndOfWeek(DayOfWeek.Monday);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(DayOfWeek.Monday, start.DayOfWeek);
|
||||
Assert.AreEqual(DayOfWeek.Sunday, end.DayOfWeek);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:添加工作日
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void AddWorkDays_SkipsWeekends()
|
||||
{
|
||||
// Arrange: 2024年3月15日是星期五
|
||||
var friday = new DateTime(2024, 3, 15);
|
||||
|
||||
// Act: 添加 1 个工作日应该跳到下周一
|
||||
var nextWorkDay = friday.AddWorkDays(1);
|
||||
|
||||
// Assert: 应该是星期一
|
||||
Assert.AreEqual(DayOfWeek.Monday, nextWorkDay.DayOfWeek);
|
||||
Assert.AreEqual(18, nextWorkDay.Day);
|
||||
|
||||
// Act: 添加 5 个工作日
|
||||
var fiveDaysLater = friday.AddWorkDays(5);
|
||||
|
||||
// Assert: 应该是下周五
|
||||
Assert.AreEqual(22, fiveDaysLater.Day);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:计算工作日数量
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void WorkDaysBetween_CountsCorrectly()
|
||||
{
|
||||
// Arrange: 2024年3月11日(周一)到 3月15日(周五)
|
||||
var monday = new DateTime(2024, 3, 11);
|
||||
var friday = new DateTime(2024, 3, 15);
|
||||
|
||||
// Act
|
||||
var workDays = monday.WorkDaysBetween(friday);
|
||||
|
||||
// Assert: 周一到周五共 5 个工作日
|
||||
Assert.AreEqual(5, workDays);
|
||||
|
||||
// 测试包含周末的情况
|
||||
var nextMonday = new DateTime(2024, 3, 18);
|
||||
var workDaysWithWeekend = monday.WorkDaysBetween(nextMonday);
|
||||
|
||||
// 周一到下周一:5 + 1 = 6 个工作日(跳过周末)
|
||||
Assert.AreEqual(6, workDaysWithWeekend);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:计算年龄
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CalculateAge_ComputesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var birthDate = new DateTime(1990, 5, 15);
|
||||
var referenceDate = new DateTime(2024, 6, 20);
|
||||
|
||||
// Act
|
||||
var age = birthDate.CalculateAge(referenceDate);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(34, age);
|
||||
|
||||
// 测试生日还没到的情况
|
||||
var beforeBirthday = new DateTime(2024, 5, 14);
|
||||
var ageBeforeBirthday = birthDate.CalculateAge(beforeBirthday);
|
||||
|
||||
Assert.AreEqual(33, ageBeforeBirthday);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取季度
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetQuarter_ReturnsCorrectQuarter()
|
||||
{
|
||||
Assert.AreEqual(1, new DateTime(2024, 1, 1).GetQuarter());
|
||||
Assert.AreEqual(1, new DateTime(2024, 3, 31).GetQuarter());
|
||||
Assert.AreEqual(2, new DateTime(2024, 4, 1).GetQuarter());
|
||||
Assert.AreEqual(2, new DateTime(2024, 6, 30).GetQuarter());
|
||||
Assert.AreEqual(3, new DateTime(2024, 7, 1).GetQuarter());
|
||||
Assert.AreEqual(3, new DateTime(2024, 9, 30).GetQuarter());
|
||||
Assert.AreEqual(4, new DateTime(2024, 10, 1).GetQuarter());
|
||||
Assert.AreEqual(4, new DateTime(2024, 12, 31).GetQuarter());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取季度第一天和最后一天
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void StartOfQuarter_AndEndOfQuarter_Work()
|
||||
{
|
||||
// Arrange: 2024年5月15日(第二季度)
|
||||
var dt = new DateTime(2024, 5, 15);
|
||||
|
||||
// Act
|
||||
var start = dt.StartOfQuarter();
|
||||
var end = dt.EndOfQuarter();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(new DateTime(2024, 4, 1, 0, 0, 0), start);
|
||||
Assert.AreEqual(6, end.Month);
|
||||
Assert.AreEqual(30, end.Day);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 时间范围判断测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:判断是否在时间范围内
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsBetween_ChecksCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 3, 15);
|
||||
var start = new DateTime(2024, 3, 1);
|
||||
var end = new DateTime(2024, 3, 31);
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsTrue(dt.IsBetween(start, end));
|
||||
Assert.IsFalse(dt.IsBetween(start, new DateTime(2024, 3, 10)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:判断是否是周末
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsWeekend_ChecksCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var saturday = new DateTime(2024, 3, 16); // 周六
|
||||
var sunday = new DateTime(2024, 3, 17); // 周日
|
||||
var monday = new DateTime(2024, 3, 18); // 周一
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsTrue(saturday.IsWeekend());
|
||||
Assert.IsTrue(sunday.IsWeekend());
|
||||
Assert.IsFalse(monday.IsWeekend());
|
||||
Assert.IsTrue(monday.IsWorkDay());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:判断是否是闰年
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsLeapYear_ChecksCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var leapYear = new DateTime(2024, 1, 1);
|
||||
var normalYear = new DateTime(2023, 1, 1);
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsTrue(leapYear.IsLeapYear());
|
||||
Assert.IsFalse(normalYear.IsLeapYear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:判断是否在过去或未来
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsPast_AndIsFuture_Work()
|
||||
{
|
||||
// Arrange
|
||||
var past = DateTime.Now.AddDays(-1);
|
||||
var future = DateTime.Now.AddDays(1);
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsTrue(past.IsPast());
|
||||
Assert.IsFalse(past.IsFuture());
|
||||
Assert.IsFalse(future.IsPast());
|
||||
Assert.IsTrue(future.IsFuture());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 时间舍入测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:舍入到小时
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RoundToHour_RemovesMinutesAndSeconds()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 3, 15, 14, 30, 45);
|
||||
|
||||
// Act
|
||||
var result = dt.RoundToHour();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(new DateTime(2024, 3, 15, 14, 0, 0), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:舍入到分钟
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void RoundToMinute_RemovesSeconds()
|
||||
{
|
||||
// Arrange
|
||||
var dt = new DateTime(2024, 3, 15, 14, 30, 45);
|
||||
|
||||
// Act
|
||||
var result = dt.RoundToMinute();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(new DateTime(2024, 3, 15, 14, 30, 0), result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:向上/向下舍入到指定分钟间隔
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void CeilingAndFloorToMinutes_Work()
|
||||
{
|
||||
// Arrange: 14:23
|
||||
var dt = new DateTime(2024, 3, 15, 14, 23, 0);
|
||||
|
||||
// Act: 舍入到 15 分钟间隔
|
||||
var ceiling = dt.CeilingToMinutes(15); // 向上舍入到 14:30
|
||||
var floor = dt.FloorToMinutes(15); // 向下舍入到 14:15
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(30, ceiling.Minute);
|
||||
Assert.AreEqual(15, floor.Minute);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:生日提醒功能
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_BirthdayReminder()
|
||||
{
|
||||
// Arrange: 用户生日是 1990年5月15日
|
||||
var birthDate = new DateTime(1990, 5, 15);
|
||||
var today = new DateTime(2024, 5, 10);
|
||||
|
||||
// Act: 计算年龄和距离生日的天数
|
||||
var age = birthDate.CalculateAge(today);
|
||||
var thisYearBirthday = new DateTime(today.Year, birthDate.Month, birthDate.Day);
|
||||
var daysUntilBirthday = (thisYearBirthday - today).Days;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(33, age); // 生日还没到,所以是 33 岁
|
||||
Assert.AreEqual(5, daysUntilBirthday);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:工作日计算(项目排期)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ProjectScheduling()
|
||||
{
|
||||
// Arrange: 项目开始日期是周一
|
||||
var projectStart = new DateTime(2024, 3, 11); // 周一
|
||||
var estimatedWorkDays = 10;
|
||||
|
||||
// Act: 计算项目预计完成日期(只计算工作日)
|
||||
var projectEnd = projectStart.AddWorkDays(estimatedWorkDays - 1);
|
||||
|
||||
// Assert: 10 个工作日后应该是下下周五
|
||||
Assert.AreEqual(DayOfWeek.Friday, projectEnd.DayOfWeek);
|
||||
Assert.AreEqual(22, projectEnd.Day);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:营业时间判断
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_BusinessHoursCheck()
|
||||
{
|
||||
// Arrange: 营业时间是工作日 9:00-18:00
|
||||
var workdayMorning = new DateTime(2024, 3, 15, 10, 30, 0); // 周五 10:30
|
||||
var weekend = new DateTime(2024, 3, 16, 10, 30, 0); // 周六 10:30
|
||||
var afterHours = new DateTime(2024, 3, 15, 19, 30, 0); // 周五 19:30
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsTrue(workdayMorning.IsWorkDay());
|
||||
Assert.IsTrue(workdayMorning.Hour >= 9 && workdayMorning.Hour < 18);
|
||||
|
||||
Assert.IsFalse(weekend.IsWorkDay());
|
||||
Assert.IsTrue(afterHours.Hour >= 18);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:会员到期提醒
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_MembershipExpiration()
|
||||
{
|
||||
// Arrange: 会员到期日期
|
||||
var expirationDate = new DateTime(2024, 12, 31);
|
||||
var today = new DateTime(2024, 12, 20);
|
||||
|
||||
// Act: 计算剩余天数
|
||||
var daysRemaining = (expirationDate - today).Days;
|
||||
var friendlyText = expirationDate.GetTimeSpanFrom(today);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(11, daysRemaining);
|
||||
Assert.AreEqual("11天", friendlyText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:活动报名截止时间
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_EventRegistrationDeadline()
|
||||
{
|
||||
// Arrange: 活动时间是 2024年3月20日,报名截止时间是前3个工作日
|
||||
var eventDate = new DateTime(2024, 3, 20); // 周三
|
||||
var workDaysBeforeEvent = 3;
|
||||
|
||||
// Act: 计算报名截止日期
|
||||
var deadline = eventDate.AddWorkDays(-workDaysBeforeEvent).EndOfDay();
|
||||
|
||||
// Assert: 应该是周五(3月15日)的 23:59:59
|
||||
Assert.AreEqual(15, deadline.Day);
|
||||
Assert.AreEqual(23, deadline.Hour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:数据统计(按季度)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_QuarterlyStatistics()
|
||||
{
|
||||
// Arrange: 获取当前季度的起止日期
|
||||
var today = new DateTime(2024, 5, 15);
|
||||
|
||||
// Act
|
||||
var quarterStart = today.StartOfQuarter();
|
||||
var quarterEnd = today.EndOfQuarter();
|
||||
var quarter = today.GetQuarter();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, quarter); // 第二季度
|
||||
Assert.AreEqual(new DateTime(2024, 4, 1, 0, 0, 0), quarterStart);
|
||||
Assert.AreEqual(6, quarterEnd.Month);
|
||||
Assert.AreEqual(30, quarterEnd.Day);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
using Common.Utils;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// ValidationHelper 验证工具测试
|
||||
/// 展示各种数据验证功能和 Result 模式集成
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ValidationHelperTests
|
||||
{
|
||||
#region 邮箱验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidEmail_ValidEmails_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var validEmails = new[]
|
||||
{
|
||||
"test@example.com",
|
||||
"user.name@example.com",
|
||||
"user+tag@example.co.uk",
|
||||
"test123@test-domain.com"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var email in validEmails)
|
||||
{
|
||||
Assert.IsTrue(ValidationHelper.IsValidEmail(email), $"Failed for: {email}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidEmail_InvalidEmails_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var invalidEmails = new[]
|
||||
{
|
||||
"",
|
||||
"notanemail",
|
||||
"@example.com",
|
||||
"user@",
|
||||
"user @example.com",
|
||||
"user@.com"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var email in invalidEmails)
|
||||
{
|
||||
Assert.IsFalse(ValidationHelper.IsValidEmail(email), $"Should fail for: {email}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateEmail_ValidEmail_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var email = "test@example.com";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateEmail(email);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(email, result.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateEmail_InvalidEmail_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var email = "invalid-email";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateEmail(email);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.IsTrue(result.ErrorMessage.Contains("格式无效"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 手机号验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidChinaMobile_ValidMobiles_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var validMobiles = new[]
|
||||
{
|
||||
"13812345678",
|
||||
"15912345678",
|
||||
"18612345678",
|
||||
"19912345678"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var mobile in validMobiles)
|
||||
{
|
||||
Assert.IsTrue(ValidationHelper.IsValidChinaMobile(mobile), $"Failed for: {mobile}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidChinaMobile_InvalidMobiles_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var invalidMobiles = new[]
|
||||
{
|
||||
"",
|
||||
"1234567890", // 10 位
|
||||
"123456789012", // 12 位
|
||||
"12812345678", // 第二位是 2
|
||||
"abcdefghijk" // 非数字
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var mobile in invalidMobiles)
|
||||
{
|
||||
Assert.IsFalse(ValidationHelper.IsValidChinaMobile(mobile), $"Should fail for: {mobile}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateChinaMobile_ValidMobile_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var mobile = "13812345678";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateChinaMobile(mobile);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(mobile, result.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidInternationalMobile_ValidFormats_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var validMobiles = new[]
|
||||
{
|
||||
"+86-13812345678",
|
||||
"+1-5551234567",
|
||||
"+44 7911123456",
|
||||
"+8613812345678"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var mobile in validMobiles)
|
||||
{
|
||||
Assert.IsTrue(ValidationHelper.IsValidInternationalMobile(mobile), $"Failed for: {mobile}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 身份证验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidChinaIdCard_ValidIdCards_ReturnsTrue()
|
||||
{
|
||||
// Arrange: 测试有效的身份证号(包含正确的校验码)
|
||||
var validIdCards = new[]
|
||||
{
|
||||
"11010519491231002X", // 北京,1949年12月31日,校验码 X
|
||||
"110105194912310021" // 北京,1949年12月31日,校验码 1
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var idCard in validIdCards)
|
||||
{
|
||||
var isValid = ValidationHelper.IsValidChinaIdCard(idCard);
|
||||
// 注意:这里不能保证所有测试身份证都是真实有效的,只是格式和校验码正确
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateChinaIdCard_InvalidFormat_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var invalidIdCards = new[]
|
||||
{
|
||||
"",
|
||||
"12345", // 太短
|
||||
"123456789012345678", // 格式不对(日期无效)
|
||||
"11010519491399002X" // 无效日期(99号)
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var idCard in invalidIdCards)
|
||||
{
|
||||
var result = ValidationHelper.ValidateChinaIdCard(idCard);
|
||||
Assert.IsTrue(result.IsFailure, $"Should fail for: {idCard}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ExtractBirthDateFromIdCard_ValidIdCard_ReturnsCorrectDate()
|
||||
{
|
||||
// Arrange: 110105199001150011 表示 1990年1月15日
|
||||
var idCard = "110105199001150011";
|
||||
|
||||
// Act
|
||||
var birthDate = ValidationHelper.ExtractBirthDateFromIdCard(idCard);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(birthDate);
|
||||
Assert.AreEqual(1990, birthDate.Value.Year);
|
||||
Assert.AreEqual(1, birthDate.Value.Month);
|
||||
Assert.AreEqual(15, birthDate.Value.Day);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ExtractGenderFromIdCard_ValidIdCard_ReturnsCorrectGender()
|
||||
{
|
||||
// Arrange
|
||||
var maleIdCard = "110105199001150011"; // 倒数第二位是 1(奇数)
|
||||
var femaleIdCard = "110105199001150024"; // 倒数第二位是 2(偶数)
|
||||
|
||||
// Act
|
||||
var maleGender = ValidationHelper.ExtractGenderFromIdCard(maleIdCard);
|
||||
var femaleGender = ValidationHelper.ExtractGenderFromIdCard(femaleIdCard);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Male", maleGender);
|
||||
Assert.AreEqual("Female", femaleGender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region URL 验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidUrl_ValidUrls_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var validUrls = new[]
|
||||
{
|
||||
"http://example.com",
|
||||
"https://www.example.com",
|
||||
"https://example.com/path/to/page?query=value",
|
||||
"http://subdomain.example.com:8080"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var url in validUrls)
|
||||
{
|
||||
Assert.IsTrue(ValidationHelper.IsValidUrl(url), $"Failed for: {url}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidUrl_InvalidUrls_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var invalidUrls = new[]
|
||||
{
|
||||
"",
|
||||
"not-a-url",
|
||||
"ftp://example.com", // 不是 HTTP/HTTPS
|
||||
"//example.com",
|
||||
"example.com" // 缺少协议
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var url in invalidUrls)
|
||||
{
|
||||
Assert.IsFalse(ValidationHelper.IsValidUrl(url), $"Should fail for: {url}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateUrl_ValidUrl_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var url = "https://www.example.com";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(url, result.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 数值范围验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateRange_ValueInRange_ReturnsSuccess()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = ValidationHelper.ValidateRange(50, 1, 100);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(50, result.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateRange_ValueOutOfRange_ReturnsFailure()
|
||||
{
|
||||
// Arrange & Act
|
||||
var tooSmall = ValidationHelper.ValidateRange(0, 1, 100, "年龄");
|
||||
var tooLarge = ValidationHelper.ValidateRange(150, 1, 100, "年龄");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(tooSmall.IsFailure);
|
||||
Assert.IsTrue(tooSmall.ErrorMessage.Contains("年龄"));
|
||||
Assert.IsTrue(tooLarge.IsFailure);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateRange_DoubleValue_Works()
|
||||
{
|
||||
// Arrange & Act
|
||||
var valid = ValidationHelper.ValidateRange(3.14, 0.0, 10.0, "比率");
|
||||
var invalid = ValidationHelper.ValidateRange(15.0, 0.0, 10.0, "比率");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(valid.IsSuccess);
|
||||
Assert.IsTrue(invalid.IsFailure);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字符串长度验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateLength_ValidLength_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var value = "Hello";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateLength(value, 3, 10, "用户名");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(value, result.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateLength_TooShort_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var value = "ab";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateLength(value, 3, 10, "用户名");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.IsTrue(result.ErrorMessage.Contains("不能少于"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateNotEmpty_EmptyString_ReturnsFailure()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result1 = ValidationHelper.ValidateNotEmpty("", "用户名");
|
||||
var result2 = ValidationHelper.ValidateNotEmpty(" ", "用户名");
|
||||
var result3 = ValidationHelper.ValidateNotEmpty(null, "用户名");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result1.IsFailure);
|
||||
Assert.IsTrue(result2.IsFailure);
|
||||
Assert.IsTrue(result3.IsFailure);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 密码强度验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void IsStrongPassword_StrongPassword_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var strongPasswords = new[]
|
||||
{
|
||||
"Abc123!@",
|
||||
"P@ssw0rd",
|
||||
"MySecure123!"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var password in strongPasswords)
|
||||
{
|
||||
Assert.IsTrue(ValidationHelper.IsStrongPassword(password), $"Failed for: {password}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsStrongPassword_WeakPassword_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var weakPasswords = new[]
|
||||
{
|
||||
"12345678", // 只有数字
|
||||
"abcdefgh", // 只有小写
|
||||
"ABCDEFGH", // 只有大写
|
||||
"Abc123", // 太短
|
||||
"Abcdefgh", // 缺少数字和特殊字符
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var password in weakPasswords)
|
||||
{
|
||||
Assert.IsFalse(ValidationHelper.IsStrongPassword(password), $"Should fail for: {password}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidatePassword_WeakPassword_ReturnsFailureWithDetails()
|
||||
{
|
||||
// Arrange
|
||||
var password = "weakpass";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidatePassword(password);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.IsTrue(result.ErrorMessage.Contains("数字"));
|
||||
Assert.IsTrue(result.ErrorMessage.Contains("大写"));
|
||||
Assert.IsTrue(result.ErrorMessage.Contains("特殊字符"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IP 地址验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidIPv4_ValidIPs_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var validIPs = new[]
|
||||
{
|
||||
"192.168.1.1",
|
||||
"10.0.0.1",
|
||||
"172.16.0.1",
|
||||
"8.8.8.8"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var ip in validIPs)
|
||||
{
|
||||
Assert.IsTrue(ValidationHelper.IsValidIPv4(ip), $"Failed for: {ip}");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsValidIPv4_InvalidIPs_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var invalidIPs = new[]
|
||||
{
|
||||
"",
|
||||
"256.1.1.1", // 超出范围
|
||||
"192.168.1", // 不完整
|
||||
"192.168.1.1.1", // 太多段
|
||||
"not.an.ip.address"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var ip in invalidIPs)
|
||||
{
|
||||
Assert.IsFalse(ValidationHelper.IsValidIPv4(ip), $"Should fail for: {ip}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 组合验证测试
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateAll_AllPass_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var email = "test@example.com";
|
||||
|
||||
// Act: 组合多个验证规则
|
||||
var result = ValidationHelper.ValidateAll(
|
||||
email,
|
||||
e => ValidationHelper.ValidateNotEmpty(e, "邮箱"),
|
||||
e => ValidationHelper.ValidateLength(e, 5, 50, "邮箱"),
|
||||
e => ValidationHelper.ValidateEmail(e)
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateAll_OneFails_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var email = "ab"; // 太短
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateAll(
|
||||
email,
|
||||
e => ValidationHelper.ValidateNotEmpty(e, "邮箱"),
|
||||
e => ValidationHelper.ValidateLength(e, 5, 50, "邮箱"),
|
||||
e => ValidationHelper.ValidateEmail(e)
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.IsTrue(result.ErrorMessage.Contains("长度"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateAny_OnePass_ReturnsSuccess()
|
||||
{
|
||||
// Arrange: 可以是邮箱或手机号
|
||||
var input = "test@example.com";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateAny(
|
||||
input,
|
||||
i => ValidationHelper.ValidateEmail(i),
|
||||
i => ValidationHelper.ValidateChinaMobile(i)
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ValidateAny_NonPass_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var input = "invalid";
|
||||
|
||||
// Act
|
||||
var result = ValidationHelper.ValidateAny(
|
||||
input,
|
||||
i => ValidationHelper.ValidateEmail(i),
|
||||
i => ValidationHelper.ValidateChinaMobile(i)
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
[TestMethod]
|
||||
public void Scenario_UserRegistrationValidation()
|
||||
{
|
||||
// Arrange: 模拟用户注册数据
|
||||
var email = "user@example.com";
|
||||
var password = "SecurePass123!";
|
||||
var mobile = "13812345678";
|
||||
|
||||
// Act: 验证所有字段
|
||||
var emailResult = ValidationHelper.ValidateEmail(email);
|
||||
var passwordResult = ValidationHelper.ValidatePassword(password);
|
||||
var mobileResult = ValidationHelper.ValidateChinaMobile(mobile);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(emailResult.IsSuccess);
|
||||
Assert.IsTrue(passwordResult.IsSuccess);
|
||||
Assert.IsTrue(mobileResult.IsSuccess);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Scenario_FormValidationWithMultipleFields()
|
||||
{
|
||||
// Arrange: 表单数据
|
||||
var formData = new
|
||||
{
|
||||
Email = "invalid-email",
|
||||
Password = "weak",
|
||||
Age = 200
|
||||
};
|
||||
|
||||
// Act: 验证各字段
|
||||
var results = new
|
||||
{
|
||||
Email = ValidationHelper.ValidateEmail(formData.Email),
|
||||
Password = ValidationHelper.ValidatePassword(formData.Password),
|
||||
Age = ValidationHelper.ValidateRange(formData.Age, 0, 150, "年龄")
|
||||
};
|
||||
|
||||
// Assert: 收集所有错误
|
||||
var errors = new List<string>();
|
||||
if (results.Email.IsFailure)
|
||||
errors.Add(results.Email.ErrorMessage);
|
||||
if (results.Password.IsFailure)
|
||||
errors.Add(results.Password.ErrorMessage);
|
||||
if (results.Age.IsFailure)
|
||||
errors.Add(results.Age.ErrorMessage);
|
||||
|
||||
Assert.AreEqual(3, errors.Count); // 所有字段都有错误
|
||||
Assert.IsTrue(errors[0].Contains("邮箱"));
|
||||
Assert.IsTrue(errors[1].Contains("密码"));
|
||||
Assert.IsTrue(errors[2].Contains("年龄"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Scenario_ChainedValidation()
|
||||
{
|
||||
// Arrange
|
||||
var userInput = "test@example.com";
|
||||
|
||||
// Act: 链式验证
|
||||
var result = ValidationHelper.ValidateNotEmpty(userInput, "邮箱")
|
||||
.Bind(e => ValidationHelper.ValidateLength(e, 5, 100, "邮箱"))
|
||||
.Bind(e => ValidationHelper.ValidateEmail(e));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in new issue
Block a user