age-crypto 0.2.0

A safe, ergonomic Rust wrapper around the age encryption library with strong typing, comprehensive error handling, and passphrase support.
Documentation
use age_crypto::{decrypt_with_passphrase, encrypt_with_passphrase};
use std::collections::HashSet;
fn test_passphrases() -> Vec<String> {
    vec![
        "simple".into(),
        "MyP@ssw0rd!2024".into(),
        "🔐emoji-passphrase🗝️".into(),
        "中文密码测试".into(),
        "a".repeat(100),
        "pass with spaces and symbols !@#$%^&*()".into(),
    ]
}
fn assert_roundtrip(plaintext: &[u8], passphrase: &str) {
    let encrypted = encrypt_with_passphrase(plaintext, passphrase).expect("Encryption failed");
    let decrypted =
        decrypt_with_passphrase(encrypted.as_bytes(), passphrase).expect("Decryption failed");
    assert_eq!(
        decrypted,
        plaintext,
        "Roundtrip mismatch for pass: {}",
        &passphrase[..passphrase.len().min(10)]
    );
}
#[test]
fn test_basic_encrypt_success() {
    let plaintext = b"Hello, secure world!";
    for passphrase in test_passphrases() {
        let encrypted = encrypt_with_passphrase(plaintext, &passphrase).unwrap();
        assert!(
            !encrypted.as_bytes().is_empty(),
            "Ciphertext must not be empty"
        );
        assert!(
            encrypted.len() > plaintext.len(),
            "Ciphertext must include headers/overhead"
        );
    }
}
#[test]
#[ignore]
fn test_roundtrip_all_passphrases() {
    let test_cases: Vec<&[u8]> = vec![
        b"",
        b"x",
        b"Hello World",
        "🎉 Unicode: 日本語, العربية, 🚀".as_bytes(),
        &[0u8; 100],
        &[255u8; 1000],
    ];
    for pt in test_cases {
        for pass in test_passphrases() {
            assert_roundtrip(pt, &pass);
        }
    }
}
#[test]
fn test_empty_plaintext() {
    assert_roundtrip(b"", "any-pass");
}
#[test]
fn test_different_passphrases_differ() {
    let pt = b"Same plaintext";
    let enc1 = encrypt_with_passphrase(pt, "pass-one").unwrap();
    let enc2 = encrypt_with_passphrase(pt, "pass-two").unwrap();
    assert_ne!(
        enc1.as_bytes(),
        enc2.as_bytes(),
        "Different keys must yield different ciphertexts"
    );
    assert!(decrypt_with_passphrase(enc1.as_bytes(), "pass-two").is_err());
    assert!(decrypt_with_passphrase(enc2.as_bytes(), "pass-one").is_err());
}
#[test]
fn test_randomized_output() {
    let pt = b"Test randomized output";
    let pass = "test-pass";
    let mut cts = HashSet::new();
    for _ in 0..5 {
        cts.insert(
            encrypt_with_passphrase(pt, pass)
                .unwrap()
                .as_bytes()
                .to_vec(),
        );
    }
    assert_eq!(
        cts.len(),
        5,
        "Encryption must produce unique ciphertexts per call"
    );
}
#[test]
fn test_wrong_passphrase_fails() {
    let encrypted = encrypt_with_passphrase(b"Secret", "correct").unwrap();
    let res = decrypt_with_passphrase(encrypted.as_bytes(), "wrong");
    assert!(res.is_err(), "Wrong passphrase must fail");
    assert!(
        matches!(
            res.unwrap_err(),
            age_crypto::Error::Decrypt(age_crypto::DecryptError::Failed(_))
        ),
        "Expected DecryptError::Failed"
    );
}
#[test]
fn test_passphrase_length_boundary() {
    assert_roundtrip(b"Boundary", "x");
    let long_pass = "a".repeat(1000);
    assert_roundtrip(b"Boundary", &long_pass);
}
#[test]
fn test_unicode_extreme() {
    let long_emoji = "🎉🎊🎈🎁🎀".repeat(100);
    let long_crab = "🦀".repeat(10000);
    let test_strings = [
        "🔐🗝️🔑🗝🔓",
        "Здравствуй мир",
        "مرحبا بالعالم",
        "こんにちは世界",
        &long_emoji,
        &long_crab,
    ];
    for s in test_strings {
        assert_roundtrip(s.as_bytes(), "unicode-pass");
    }
}
#[test]
fn test_binary_patterns() {
    let patterns: Vec<Vec<u8>> = vec![
        vec![0x00; 1000],
        vec![0xFF; 1000],
        (0..255).cycle().take(1000).collect(),
        (0..1000).map(|i| (i % 256) as u8).collect(),
    ];
    for pt in patterns {
        assert_roundtrip(&pt, "binary-test");
    }
}
#[test]
fn test_single_byte_representative() {
    let samples = [0, 1, 127, 128, 255, 42, 200, 254];
    for &byte in &samples {
        assert_roundtrip(&[byte], "test-pass");
    }
}
#[test]
#[ignore]
fn test_single_byte_exhaustive() {
    for byte in 0u8..=255 {
        assert_roundtrip(&[byte], "test-pass");
    }
}
#[test]
#[ignore]
fn test_large_data_10mb() {
    let plaintext = vec![0x5A; 10 * 1024 * 1024];
    assert_roundtrip(&plaintext, "large-test-10mb");
}
#[test]
fn test_large_data_1mb() {
    let plaintext = vec![0x5A; 1024 * 1024];
    assert_roundtrip(&plaintext, "large-test-1mb");
}
#[test]
fn test_concurrent_encryptions() {
    use std::thread;
    let handles: Vec<_> = (0..4)
        .map(|i| {
            let pt = vec![i as u8; 10_000];
            let pass = format!("thread-{}", i);
            thread::spawn(move || {
                let enc = encrypt_with_passphrase(&pt, &pass).unwrap();
                let dec = decrypt_with_passphrase(enc.as_bytes(), &pass).unwrap();
                assert_eq!(dec, pt);
            })
        })
        .collect();
    for h in handles {
        h.join().expect("Thread should not panic");
    }
}
#[test]
#[ignore]
fn test_performance_iterations() {
    let pt = vec![0x42; 100_000];
    let pass = "perf-test";
    let start = std::time::Instant::now();
    let rounds = 50;
    for _ in 0..rounds {
        let enc = encrypt_with_passphrase(&pt, pass).unwrap();
        let _dec = decrypt_with_passphrase(enc.as_bytes(), pass).unwrap();
    }
    let elapsed = start.elapsed();
    println!("{} roundtrips took {:?}", rounds, elapsed);
    assert!(elapsed.as_secs() < 60, "Performance regression detected");
}
#[test]
fn test_output_no_aliasing() {
    let pt = b"Aliasing test";
    let enc1 = encrypt_with_passphrase(pt, "pass").unwrap();
    let enc2 = encrypt_with_passphrase(pt, "pass").unwrap();
    assert_ne!(
        enc1.as_bytes().as_ptr(),
        enc2.as_bytes().as_ptr(),
        "Encrypted outputs must be independent heap allocations"
    );
}
#[test]
fn test_no_input_mutation() {
    let pt = vec![0xAB; 1000];
    let original = pt.clone();
    let _ = encrypt_with_passphrase(&pt, "test").unwrap();
    assert_eq!(pt, original, "Input buffer must remain immutable");
}