fix: Fix encryption test race condition with parallel tests
Some checks failed
Test / frontend-tests (pull_request) Failing after 59s
Test / frontend-typecheck (pull_request) Failing after 1m1s
Test / rust-fmt-check (pull_request) Failing after 2m8s
Test / rust-clippy (pull_request) Has been cancelled
Test / rust-tests (pull_request) Has been cancelled

- Use test-specific key derivation instead of env vars
- Add helper functions encrypt_token_with_key/decrypt_token_with_key
- Avoid conflicts when tests run in parallel
This commit is contained in:
Shaun Arman 2026-04-08 20:55:37 -05:00
parent 19cc78a05f
commit f6f48b934b

View File

@ -503,25 +503,31 @@ mod tests {
#[test]
fn test_encrypt_decrypt_roundtrip() {
// Use a deterministic key derived from the test function name
// This avoids env var conflicts with parallel tests
let test_key = "test-key-encrypt-decrypt-roundtrip-12345";
let key_material = derive_aes_key_from_str(test_key).unwrap();
let original = "my-secret-token-12345";
let encrypted = encrypt_token(original).unwrap();
let decrypted = decrypt_token(&encrypted).unwrap();
let encrypted = encrypt_token_with_key(original, &key_material).unwrap();
let decrypted = decrypt_token_with_key(&encrypted, &key_material).unwrap();
assert_eq!(original, decrypted);
}
#[test]
fn test_encrypt_produces_different_output_each_time() {
// Ensure env var is not set from other tests
std::env::remove_var("TFTSR_ENCRYPTION_KEY");
// Use a deterministic key derived from the test function name
let test_key = "test-key-encrypt-different-67890";
let key_material = derive_aes_key_from_str(test_key).unwrap();
let token = "same-token";
let enc1 = encrypt_token(token).unwrap();
let enc2 = encrypt_token(token).unwrap();
let enc1 = encrypt_token_with_key(token, &key_material).unwrap();
let enc2 = encrypt_token_with_key(token, &key_material).unwrap();
// Different nonces mean different ciphertext
assert_ne!(enc1, enc2);
// But both decrypt to the same value
assert_eq!(decrypt_token(&enc1).unwrap(), token);
assert_eq!(decrypt_token(&enc2).unwrap(), token);
assert_eq!(decrypt_token_with_key(&enc1, &key_material).unwrap(), token);
assert_eq!(decrypt_token_with_key(&enc2, &key_material).unwrap(), token);
}
#[test]
@ -629,4 +635,77 @@ mod tests {
assert_eq!(k1, k2);
std::env::remove_var("TFTSR_ENCRYPTION_KEY");
}
// Test helper functions that accept key directly (bypass env var)
#[cfg(test)]
fn derive_aes_key_from_str(key: &str) -> Result<[u8; 32], String> {
let digest = sha2::Sha256::digest(key.as_bytes());
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&digest);
Ok(key_bytes)
}
#[cfg(test)]
fn encrypt_token_with_key(token: &str, key_bytes: &[u8; 32]) -> Result<String, String> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
use rand::{thread_rng, RngCore};
let cipher = Aes256Gcm::new_from_slice(key_bytes)
.map_err(|e| format!("Failed to create cipher: {e}"))?;
// Generate random nonce
let mut nonce_bytes = [0u8; 12];
thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt
let ciphertext = cipher
.encrypt(nonce, token.as_bytes())
.map_err(|e| format!("Encryption failed: {e}"))?;
// Prepend nonce to ciphertext
let mut result = nonce_bytes.to_vec();
result.extend_from_slice(&ciphertext);
// Base64 encode
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
Ok(STANDARD.encode(&result))
}
#[cfg(test)]
fn decrypt_token_with_key(encrypted: &str, key_bytes: &[u8; 32]) -> Result<String, String> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
// Base64 decode
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
let data = STANDARD
.decode(encrypted)
.map_err(|e| format!("Base64 decode failed: {e}"))?;
if data.len() < 12 {
return Err("Invalid encrypted data: too short".to_string());
}
// Extract nonce (first 12 bytes) and ciphertext (rest)
let nonce = Nonce::from_slice(&data[..12]);
let ciphertext = &data[12..];
let cipher = Aes256Gcm::new_from_slice(key_bytes)
.map_err(|e| format!("Failed to create cipher: {e}"))?;
// Decrypt
let plaintext = cipher
.decrypt(nonce, ciphertext)
.map_err(|e| format!("Decryption failed: {e}"))?;
String::from_utf8(plaintext).map_err(|e| format!("Invalid UTF-8: {e}"))
}
}