feat: enhance crypto module test coverage with comprehensive test cases

This commit is contained in:
overtrue
2025-05-27 22:15:57 +08:00
parent f03b945e4f
commit ce16ad868b
2 changed files with 614 additions and 11 deletions

View File

@@ -1,14 +1,299 @@
use crate::{decrypt_data, encrypt_data};
const PASSWORD: &[u8] = "test_password".as_bytes();
const LONG_PASSWORD: &[u8] = "very_long_password_with_many_characters_for_testing_purposes_123456789".as_bytes();
const EMPTY_PASSWORD: &[u8] = b"";
#[test_case::test_case("hello world".as_bytes())]
#[test_case::test_case(&[])]
#[test_case::test_case(&[1, 2, 3])]
#[test_case::test_case(&[3, 2, 1])]
fn test(input: &[u8]) -> Result<(), crate::Error> {
fn test_basic_encrypt_decrypt_roundtrip(input: &[u8]) -> Result<(), crate::Error> {
let encrypted = encrypt_data(PASSWORD, input)?;
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
assert_eq!(input, decrypted, "input is not equal output");
Ok(())
}
#[test]
fn test_encrypt_decrypt_with_different_passwords() -> Result<(), crate::Error> {
let data = b"sensitive data";
let password1 = b"password1";
let password2 = b"password2";
let encrypted = encrypt_data(password1, data)?;
// Decrypting with correct password should work
let decrypted = decrypt_data(password1, &encrypted)?;
assert_eq!(data, decrypted.as_slice());
// Decrypting with wrong password should fail
let result = decrypt_data(password2, &encrypted);
assert!(result.is_err(), "Decryption with wrong password should fail");
Ok(())
}
#[test]
fn test_encrypt_decrypt_empty_data() -> Result<(), crate::Error> {
let empty_data = b"";
let encrypted = encrypt_data(PASSWORD, empty_data)?;
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
assert_eq!(empty_data, decrypted.as_slice());
Ok(())
}
#[test]
fn test_encrypt_decrypt_large_data() -> Result<(), crate::Error> {
// Test with 1MB of data
let large_data = vec![0xAB; 1024 * 1024];
let encrypted = encrypt_data(PASSWORD, &large_data)?;
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
assert_eq!(large_data, decrypted);
Ok(())
}
#[test]
fn test_encrypt_decrypt_with_empty_password() -> Result<(), crate::Error> {
let data = b"test data";
let encrypted = encrypt_data(EMPTY_PASSWORD, data)?;
let decrypted = decrypt_data(EMPTY_PASSWORD, &encrypted)?;
assert_eq!(data, decrypted.as_slice());
Ok(())
}
#[test]
fn test_encrypt_decrypt_with_long_password() -> Result<(), crate::Error> {
let data = b"test data with long password";
let encrypted = encrypt_data(LONG_PASSWORD, data)?;
let decrypted = decrypt_data(LONG_PASSWORD, &encrypted)?;
assert_eq!(data, decrypted.as_slice());
Ok(())
}
#[test]
fn test_encrypt_decrypt_binary_data() -> Result<(), crate::Error> {
// Test with various binary patterns
let binary_patterns = [
vec![0x00; 100], // All zeros
vec![0xFF; 100], // All ones
(0..=255u8).cycle().take(1000).collect::<Vec<u8>>(), // Sequential pattern
vec![0xAA, 0x55].repeat(500), // Alternating pattern
];
for pattern in &binary_patterns {
let encrypted = encrypt_data(PASSWORD, pattern)?;
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
assert_eq!(pattern, &decrypted, "Binary pattern mismatch");
}
Ok(())
}
#[test]
fn test_encrypt_decrypt_unicode_data() -> Result<(), crate::Error> {
let unicode_strings = [
"Hello, 世界! 🌍",
"Тест на русском языке",
"العربية اختبار",
"🚀🔐💻🌟⭐",
"Mixed: ASCII + 中文 + العربية + 🎉",
];
for text in &unicode_strings {
let data = text.as_bytes();
let encrypted = encrypt_data(PASSWORD, data)?;
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
assert_eq!(data, decrypted.as_slice(), "Unicode data mismatch for: {}", text);
}
Ok(())
}
#[test]
fn test_decrypt_with_corrupted_data() {
let data = b"test data";
let encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
// Test various corruption scenarios
let corruption_tests = [
(0, "Corrupt first byte"),
(encrypted.len() - 1, "Corrupt last byte"),
(encrypted.len() / 2, "Corrupt middle byte"),
];
for (corrupt_index, description) in &corruption_tests {
let mut corrupted = encrypted.clone();
corrupted[*corrupt_index] ^= 0xFF; // Flip all bits
let result = decrypt_data(PASSWORD, &corrupted);
assert!(result.is_err(), "{} should cause decryption to fail", description);
}
}
#[test]
fn test_decrypt_with_truncated_data() {
let data = b"test data for truncation";
let encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
// Test truncation at various lengths
let truncation_lengths = [
0, // Empty data
10, // Very short
32, // Salt length
44, // Just before nonce
encrypted.len() - 1, // Missing last byte
];
for &length in &truncation_lengths {
let truncated = &encrypted[..length.min(encrypted.len())];
let result = decrypt_data(PASSWORD, truncated);
assert!(result.is_err(), "Truncated data (length {}) should cause decryption to fail", length);
}
}
#[test]
fn test_decrypt_with_invalid_header() {
let data = b"test data";
let mut encrypted = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
// Corrupt the algorithm ID (byte 32)
if encrypted.len() > 32 {
encrypted[32] = 0xFF; // Invalid algorithm ID
let result = decrypt_data(PASSWORD, &encrypted);
assert!(result.is_err(), "Invalid algorithm ID should cause decryption to fail");
}
}
#[test]
fn test_encryption_produces_different_outputs() -> Result<(), crate::Error> {
let data = b"same data";
// Encrypt the same data multiple times
let encrypted1 = encrypt_data(PASSWORD, data)?;
let encrypted2 = encrypt_data(PASSWORD, data)?;
// Encrypted outputs should be different due to random salt and nonce
assert_ne!(encrypted1, encrypted2, "Encryption should produce different outputs for same input");
// But both should decrypt to the same original data
let decrypted1 = decrypt_data(PASSWORD, &encrypted1)?;
let decrypted2 = decrypt_data(PASSWORD, &encrypted2)?;
assert_eq!(decrypted1, decrypted2);
assert_eq!(data, decrypted1.as_slice());
Ok(())
}
#[test]
fn test_encrypted_data_structure() -> Result<(), crate::Error> {
let data = b"test data";
let encrypted = encrypt_data(PASSWORD, data)?;
// Encrypted data should be longer than original (due to salt, nonce, tag)
assert!(encrypted.len() > data.len(), "Encrypted data should be longer than original");
// Should have at least: 32 bytes salt + 1 byte ID + 12 bytes nonce + data + 16 bytes tag
let min_expected_length = 32 + 1 + 12 + data.len() + 16;
assert!(encrypted.len() >= min_expected_length,
"Encrypted data length {} should be at least {}", encrypted.len(), min_expected_length);
Ok(())
}
#[test]
fn test_password_variations() -> Result<(), crate::Error> {
let data = b"test data";
let password_variations = [
b"a".as_slice(), // Single character
b"12345".as_slice(), // Numeric
b"!@#$%^&*()".as_slice(), // Special characters
b"\x00\x01\x02\x03".as_slice(), // Binary password
"密码测试".as_bytes(), // Unicode password
&[0xFF; 64], // Long binary password
];
for password in &password_variations {
let encrypted = encrypt_data(password, data)?;
let decrypted = decrypt_data(password, &encrypted)?;
assert_eq!(data, decrypted.as_slice(), "Failed with password: {:?}", password);
}
Ok(())
}
#[test]
fn test_deterministic_with_same_salt_and_nonce() {
// Note: This test is more for understanding the behavior
// In real implementation, salt and nonce should be random
let data = b"test data";
let encrypted1 = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
let encrypted2 = encrypt_data(PASSWORD, data).expect("Encryption should succeed");
// Due to random salt and nonce, outputs should be different
assert_ne!(encrypted1, encrypted2, "Encryption should use random salt/nonce");
}
#[test]
fn test_cross_platform_compatibility() -> Result<(), crate::Error> {
// Test data that might behave differently on different platforms
let test_cases = [
vec![0x00, 0x01, 0x02, 0x03], // Low values
vec![0xFC, 0xFD, 0xFE, 0xFF], // High values
(0..256u16).map(|x| (x % 256) as u8).collect::<Vec<u8>>(), // Full byte range
];
for test_data in &test_cases {
let encrypted = encrypt_data(PASSWORD, test_data)?;
let decrypted = decrypt_data(PASSWORD, &encrypted)?;
assert_eq!(test_data, &decrypted, "Cross-platform compatibility failed");
}
Ok(())
}
#[test]
fn test_memory_safety_with_large_passwords() -> Result<(), crate::Error> {
let data = b"test data";
// Test with very large passwords
let large_passwords = [
vec![b'a'; 1024], // 1KB password
vec![b'x'; 10 * 1024], // 10KB password
(0..=255u8).cycle().take(5000).collect::<Vec<u8>>(), // 5KB varied password
];
for password in &large_passwords {
let encrypted = encrypt_data(password, data)?;
let decrypted = decrypt_data(password, &encrypted)?;
assert_eq!(data, decrypted.as_slice(), "Failed with large password of size {}", password.len());
}
Ok(())
}
#[test]
fn test_concurrent_encryption_safety() -> Result<(), crate::Error> {
use std::sync::Arc;
use std::thread;
let data = Arc::new(b"concurrent test data".to_vec());
let password = Arc::new(b"concurrent_password".to_vec());
let handles: Vec<_> = (0..10).map(|i| {
let data = Arc::clone(&data);
let password = Arc::clone(&password);
thread::spawn(move || {
let encrypted = encrypt_data(&password, &data).expect("Encryption should succeed");
let decrypted = decrypt_data(&password, &encrypted).expect("Decryption should succeed");
assert_eq!(**data, decrypted, "Thread {} failed", i);
})
}).collect();
for handle in handles {
handle.join().expect("Thread should complete successfully");
}
Ok(())
}

View File

@@ -1,19 +1,337 @@
use time::OffsetDateTime;
use serde_json::json;
use super::{decode::decode, encode::encode};
#[test]
fn test() {
let claims = serde_json::json!({
fn test_jwt_encode_decode_basic() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"aaa": 1,
"bbb": "bbb"
"sub": "user123",
"iat": OffsetDateTime::now_utc().unix_timestamp(),
"role": "admin"
});
let jwt_token = encode(b"aaaa", &claims).unwrap_or_default();
let new_claims = match decode(&jwt_token, b"aaaa") {
Ok(res) => Some(res.claims),
Err(_errr) => None,
};
assert_eq!(new_claims, Some(claims));
let secret = b"test_secret_key";
let jwt_token = encode(secret, &claims).expect("Failed to encode JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode JWT");
assert_eq!(decoded.claims, claims);
}
#[test]
fn test_jwt_encode_decode_with_complex_claims() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 3600,
"sub": "user456",
"iat": OffsetDateTime::now_utc().unix_timestamp(),
"permissions": ["read", "write", "delete"],
"metadata": {
"department": "engineering",
"level": 5,
"active": true
},
"custom_field": null
});
let secret = b"complex_secret_key_123";
let jwt_token = encode(secret, &claims).expect("Failed to encode complex JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode complex JWT");
assert_eq!(decoded.claims, claims);
}
#[test]
fn test_jwt_decode_with_wrong_secret() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123"
});
let correct_secret = b"correct_secret";
let wrong_secret = b"wrong_secret";
let jwt_token = encode(correct_secret, &claims).expect("Failed to encode JWT");
// Decoding with wrong secret should fail
let result = decode(&jwt_token, wrong_secret);
assert!(result.is_err(), "Decoding with wrong secret should fail");
}
#[test]
fn test_jwt_decode_invalid_token_format() {
let secret = b"test_secret";
// Test various invalid token formats
let invalid_tokens = [
"", // Empty token
"invalid", // Not a JWT format
"header.payload", // Missing signature
"header.payload.signature.extra", // Too many parts
"invalid.header.signature", // Invalid base64
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.invalid.signature", // Invalid payload
];
for invalid_token in &invalid_tokens {
let result = decode(invalid_token, secret);
assert!(result.is_err(), "Invalid token '{}' should fail to decode", invalid_token);
}
}
#[test]
fn test_jwt_with_expired_token() {
let expired_claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() - 1000, // Expired 1000 seconds ago
"sub": "user123",
"iat": OffsetDateTime::now_utc().unix_timestamp() - 2000
});
let secret = b"test_secret";
let jwt_token = encode(secret, &expired_claims).expect("Failed to encode expired JWT");
// Decoding expired token should fail
let result = decode(&jwt_token, secret);
assert!(result.is_err(), "Expired token should fail to decode");
}
#[test]
fn test_jwt_with_future_issued_at() {
let future_claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 3600,
"sub": "user123",
"iat": OffsetDateTime::now_utc().unix_timestamp() + 1000 // Issued in future
});
let secret = b"test_secret";
let jwt_token = encode(secret, &future_claims).expect("Failed to encode future JWT");
// Note: The current JWT implementation may not validate iat by default
// This test documents the current behavior - future iat tokens may still decode successfully
let result = decode(&jwt_token, secret);
// For now, we just verify the token can be decoded, but in a production system
// you might want to add custom validation for iat claims
assert!(result.is_ok(), "Token decoding should succeed, but iat validation should be handled separately");
}
#[test]
fn test_jwt_with_empty_claims() {
let empty_claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000, // Add required exp claim
});
let secret = b"test_secret";
let jwt_token = encode(secret, &empty_claims).expect("Failed to encode empty claims JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode empty claims JWT");
assert_eq!(decoded.claims, empty_claims);
}
#[test]
fn test_jwt_with_different_secret_lengths() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123"
});
// Test with various secret lengths
let secrets = [
b"a".as_slice(), // Very short
b"short_key".as_slice(), // Short
b"medium_length_secret_key".as_slice(), // Medium
b"very_long_secret_key_with_many_characters_for_testing_purposes".as_slice(), // Long
];
for secret in &secrets {
let jwt_token = encode(secret, &claims)
.expect(&format!("Failed to encode JWT with secret length {}", secret.len()));
let decoded = decode(&jwt_token, secret)
.expect(&format!("Failed to decode JWT with secret length {}", secret.len()));
assert_eq!(decoded.claims, claims);
}
}
#[test]
fn test_jwt_with_special_characters_in_claims() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user@example.com",
"name": "John Doe",
"description": "User with special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?",
"unicode": "测试用户 🚀 émojis",
"newlines": "line1\nline2\r\nline3",
"quotes": "He said \"Hello\" and she replied 'Hi'"
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode JWT with special characters");
let decoded = decode(&jwt_token, secret).expect("Failed to decode JWT with special characters");
assert_eq!(decoded.claims, claims);
}
#[test]
fn test_jwt_with_large_payload() {
// Create a large payload to test size limits
let large_data = "x".repeat(10000); // 10KB of data
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123",
"large_field": large_data
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode large JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode large JWT");
assert_eq!(decoded.claims, claims);
}
#[test]
fn test_jwt_token_structure() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123"
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode JWT");
// JWT should have exactly 3 parts separated by dots
let parts: Vec<&str> = jwt_token.split('.').collect();
assert_eq!(parts.len(), 3, "JWT should have exactly 3 parts");
// Each part should be non-empty
for (i, part) in parts.iter().enumerate() {
assert!(!part.is_empty(), "JWT part {} should not be empty", i);
}
}
#[test]
fn test_jwt_deterministic_encoding() {
let claims = json!({
"exp": 1234567890, // Fixed timestamp for deterministic test
"sub": "user123",
"iat": 1234567800
});
let secret = b"test_secret";
// Encode the same claims multiple times
let token1 = encode(secret, &claims).expect("Failed to encode JWT 1");
let token2 = encode(secret, &claims).expect("Failed to encode JWT 2");
// Tokens should be identical for same input
assert_eq!(token1, token2, "JWT encoding should be deterministic");
}
#[test]
fn test_jwt_cross_compatibility() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123"
});
let secret1 = b"secret1";
let secret2 = b"secret2";
// Encode with secret1
let token1 = encode(secret1, &claims).expect("Failed to encode with secret1");
// Decode with secret1 should work
let decoded1 = decode(&token1, secret1).expect("Failed to decode with correct secret");
assert_eq!(decoded1.claims, claims);
// Decode with secret2 should fail
let result2 = decode(&token1, secret2);
assert!(result2.is_err(), "Decoding with different secret should fail");
}
#[test]
fn test_jwt_header_algorithm() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123"
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode JWT");
// Verify the algorithm in header is HS512
assert_eq!(decoded.header.alg, jsonwebtoken::Algorithm::HS512);
assert_eq!(decoded.header.typ, Some("JWT".to_string()));
}
#[test]
fn test_jwt_claims_validation() {
let now = OffsetDateTime::now_utc().unix_timestamp();
let valid_claims = json!({
"exp": now + 3600, // Expires in 1 hour
"iat": now - 60, // Issued 1 minute ago
"nbf": now - 30, // Not before 30 seconds ago
"sub": "user123"
});
let secret = b"test_secret";
let jwt_token = encode(secret, &valid_claims).expect("Failed to encode valid JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode valid JWT");
assert_eq!(decoded.claims, valid_claims);
}
#[test]
fn test_jwt_with_numeric_claims() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123",
"age": 25,
"score": 95.5,
"count": 0,
"negative": -10
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode numeric JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode numeric JWT");
assert_eq!(decoded.claims, claims);
}
#[test]
fn test_jwt_with_boolean_claims() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123",
"is_admin": true,
"is_active": false,
"email_verified": true
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode boolean JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode boolean JWT");
assert_eq!(decoded.claims, claims);
}
#[test]
fn test_jwt_with_array_claims() {
let claims = json!({
"exp": OffsetDateTime::now_utc().unix_timestamp() + 1000,
"sub": "user123",
"roles": ["admin", "user", "moderator"],
"permissions": [1, 2, 3, 4, 5],
"tags": [],
"mixed_array": ["string", 123, true, null]
});
let secret = b"test_secret";
let jwt_token = encode(secret, &claims).expect("Failed to encode array JWT");
let decoded = decode(&jwt_token, secret).expect("Failed to decode array JWT");
assert_eq!(decoded.claims, claims);
}