test(health): PII 加密集成测试 + 性能基准 + 编译修复
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 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:
iven
2026-04-26 13:10:53 +08:00
parent 17b423b9b8
commit ebc0f20e33
10 changed files with 1211 additions and 4 deletions

View File

@@ -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);
}
}