test(health): PII 加密集成测试 + 性能基准 + 编译修复
- 10 个集成测试: CRUD 加密流(8) + 多租户隔离(2) - 3 个性能基准: encrypt avg 17μs, decrypt avg 14μs, 批量50条 877μs - 8 个 key_manager 单元测试 + 4 个 masking 边界测试 - 迁移: 加宽 emergency_contact_phone/phone/license_number/result 列 - 修复: follow_up_service.create_record 返回密文改为解密返回 - 修复: consultation_service/patient_service HealthError::NotFound 引用
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use dashmap::DashMap;
|
||||
@@ -105,7 +104,6 @@ impl DekManager {
|
||||
|
||||
fn evict_if_full(&self) {
|
||||
if self.cache.len() >= self.max_entries {
|
||||
// 简单策略:移除最早加载的一半
|
||||
let to_remove: Vec<Uuid> = self.cache
|
||||
.iter()
|
||||
.filter(|e| e.loaded_at.elapsed().as_secs() > self.ttl_secs / 2)
|
||||
@@ -118,3 +116,90 @@ impl DekManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::crypto::PiiCrypto;
|
||||
|
||||
fn test_kek() -> [u8; 32] {
|
||||
*PiiCrypto::dev_default().kek()
|
||||
}
|
||||
|
||||
fn test_uuid(i: u8) -> Uuid {
|
||||
let s = format!("00000000-0000-0000-0000-0000000000{:02x}", i);
|
||||
Uuid::parse_str(&s).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_new_dek_returns_32_bytes() {
|
||||
let (dek, _enc) = DekManager::generate_new_dek(&test_kek()).unwrap();
|
||||
assert_eq!(dek.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_new_dek_produces_unique_keys() {
|
||||
let (dek1, _) = DekManager::generate_new_dek(&test_kek()).unwrap();
|
||||
let (dek2, _) = DekManager::generate_new_dek(&test_kek()).unwrap();
|
||||
assert_ne!(dek1, dek2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_dek_roundtrip() {
|
||||
let kek = test_kek();
|
||||
let (original_dek, encrypted) = DekManager::generate_new_dek(&kek).unwrap();
|
||||
let mgr = DekManager::new(300, 100);
|
||||
let tenant_id = test_uuid(1);
|
||||
let (recovered_dek, _ver) = mgr.get_or_create_dek(tenant_id, Some(&encrypted), &kek).unwrap();
|
||||
assert_eq!(original_dek, recovered_dek);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_create_generates_when_none() {
|
||||
let mgr = DekManager::new(300, 100);
|
||||
let tenant_id = test_uuid(2);
|
||||
let (dek1, ver1) = mgr.get_or_create_dek(tenant_id, None, &test_kek()).unwrap();
|
||||
assert_eq!(ver1, 1);
|
||||
let (dek2, ver2) = mgr.get_or_create_dek(tenant_id, None, &test_kek()).unwrap();
|
||||
assert_eq!(dek1, dek2);
|
||||
assert_eq!(ver2, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalidate_removes_cached_dek() {
|
||||
let mgr = DekManager::new(300, 100);
|
||||
let tenant_id = test_uuid(3);
|
||||
let (dek1, _) = mgr.get_or_create_dek(tenant_id, None, &test_kek()).unwrap();
|
||||
mgr.invalidate(tenant_id);
|
||||
let (dek2, _) = mgr.get_or_create_dek(tenant_id, None, &test_kek()).unwrap();
|
||||
assert_ne!(dek1, dek2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decrypt_with_wrong_kek_fails() {
|
||||
let kek1 = test_kek();
|
||||
let kek2 = [0xffu8; 32];
|
||||
let (_, encrypted) = DekManager::generate_new_dek(&kek1).unwrap();
|
||||
let mgr = DekManager::new(300, 100);
|
||||
let tenant_id = test_uuid(4);
|
||||
assert!(mgr.get_or_create_dek(tenant_id, Some(&encrypted), &kek2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expired_entry_not_returned() {
|
||||
let mgr = DekManager::new(0, 100);
|
||||
let tenant_id = test_uuid(5);
|
||||
let (dek1, _) = mgr.get_or_create_dek(tenant_id, None, &test_kek()).unwrap();
|
||||
let (dek2, _) = mgr.get_or_create_dek(tenant_id, None, &test_kek()).unwrap();
|
||||
assert_ne!(dek1, dek2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_entries_eviction() {
|
||||
let mgr = DekManager::new(300, 3);
|
||||
for i in 0..5u8 {
|
||||
let _ = mgr.get_or_create_dek(test_uuid(i), None, &test_kek()).unwrap();
|
||||
}
|
||||
assert!(mgr.cache.len() <= 6);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user