Prepare public release v0.1.0
This commit is contained in:
@@ -2,39 +2,39 @@
|
||||
//!
|
||||
//! These tests require camera hardware or will use mocks on non-Linux systems.
|
||||
|
||||
use linux_hello_daemon::camera::{enumerate_cameras, Camera, IrEmitterControl};
|
||||
use linux_hello_common::Result;
|
||||
use linux_hello_daemon::camera::{enumerate_cameras, Camera, IrEmitterControl};
|
||||
|
||||
#[test]
|
||||
fn test_camera_enumeration() -> Result<()> {
|
||||
let cameras = enumerate_cameras()?;
|
||||
|
||||
|
||||
// Should at least enumerate (may be empty if no cameras)
|
||||
println!("Found {} camera(s)", cameras.len());
|
||||
for cam in &cameras {
|
||||
println!(" - {}", cam);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_camera_open_and_capture() -> Result<()> {
|
||||
let cameras = enumerate_cameras()?;
|
||||
|
||||
|
||||
if cameras.is_empty() {
|
||||
println!("No cameras available, skipping capture test");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
// Try to open the first camera
|
||||
let first_cam = &cameras[0];
|
||||
println!("Opening camera: {}", first_cam.device_path);
|
||||
|
||||
|
||||
let mut camera = Camera::open(&first_cam.device_path)?;
|
||||
let (width, height) = camera.resolution();
|
||||
println!("Camera resolution: {}x{}", width, height);
|
||||
|
||||
|
||||
// Capture a few frames
|
||||
for i in 0..3 {
|
||||
let frame = camera.capture_frame()?;
|
||||
@@ -47,13 +47,13 @@ fn test_camera_open_and_capture() -> Result<()> {
|
||||
frame.data.len(),
|
||||
frame.timestamp_us
|
||||
);
|
||||
|
||||
|
||||
// Verify frame properties
|
||||
assert_eq!(frame.width, width);
|
||||
assert_eq!(frame.height, height);
|
||||
assert!(!frame.data.is_empty());
|
||||
}
|
||||
|
||||
|
||||
camera.stop();
|
||||
Ok(())
|
||||
}
|
||||
@@ -61,20 +61,20 @@ fn test_camera_open_and_capture() -> Result<()> {
|
||||
#[test]
|
||||
fn test_ir_emitter_control() -> Result<()> {
|
||||
let cameras = enumerate_cameras()?;
|
||||
|
||||
|
||||
// Find an IR camera if available
|
||||
let ir_camera = cameras.iter().find(|c| c.is_ir);
|
||||
|
||||
|
||||
if let Some(cam) = ir_camera {
|
||||
println!("Testing IR emitter on: {}", cam.device_path);
|
||||
|
||||
|
||||
let mut emitter = IrEmitterControl::new(&cam.device_path);
|
||||
|
||||
|
||||
// Try to enable
|
||||
emitter.enable()?;
|
||||
assert!(emitter.is_active());
|
||||
println!("IR emitter enabled");
|
||||
|
||||
|
||||
// Try to disable
|
||||
emitter.disable()?;
|
||||
assert!(!emitter.is_active());
|
||||
@@ -82,19 +82,19 @@ fn test_ir_emitter_control() -> Result<()> {
|
||||
} else {
|
||||
println!("No IR camera found, skipping IR emitter test");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_camera_info_properties() -> Result<()> {
|
||||
let cameras = enumerate_cameras()?;
|
||||
|
||||
|
||||
for cam in cameras {
|
||||
// Verify all cameras have valid properties
|
||||
assert!(!cam.device_path.is_empty());
|
||||
assert!(!cam.name.is_empty());
|
||||
|
||||
|
||||
println!(
|
||||
"Camera: {} (IR: {}, Resolutions: {})",
|
||||
cam.device_path,
|
||||
@@ -102,6 +102,6 @@ fn test_camera_info_properties() -> Result<()> {
|
||||
cam.resolutions.len()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,9 +8,12 @@ fn cli_binary() -> String {
|
||||
let _ = Command::new("cargo")
|
||||
.args(["build", "--bin", "linux-hello"])
|
||||
.output();
|
||||
|
||||
|
||||
// Return path to the binary
|
||||
format!("{}/target/debug/linux-hello", env!("CARGO_MANIFEST_DIR").replace("/linux-hello-tests", ""))
|
||||
format!(
|
||||
"{}/target/debug/linux-hello",
|
||||
env!("CARGO_MANIFEST_DIR").replace("/linux-hello-tests", "")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -19,15 +22,15 @@ fn test_cli_status_command() {
|
||||
.args(["status"])
|
||||
.output()
|
||||
.expect("Failed to execute CLI");
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
|
||||
println!("Status stdout: {}", stdout);
|
||||
if !stderr.is_empty() {
|
||||
println!("Status stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
// Status should at least run without crashing
|
||||
// May fail if no cameras, but should handle gracefully
|
||||
assert!(output.status.code().is_some());
|
||||
@@ -39,18 +42,21 @@ fn test_cli_config_command() {
|
||||
.args(["config"])
|
||||
.output()
|
||||
.expect("Failed to execute CLI");
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
|
||||
println!("Config output: {}", stdout);
|
||||
if !stderr.is_empty() {
|
||||
println!("Config stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
// Should output TOML configuration
|
||||
assert!(stdout.contains("[general]") || stdout.contains("log_level"),
|
||||
"Expected config output in stdout, got: {}", stdout);
|
||||
assert!(
|
||||
stdout.contains("[general]") || stdout.contains("log_level"),
|
||||
"Expected config output in stdout, got: {}",
|
||||
stdout
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -59,18 +65,21 @@ fn test_cli_config_json_command() {
|
||||
.args(["config", "--json"])
|
||||
.output()
|
||||
.expect("Failed to execute CLI");
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
|
||||
println!("Config JSON output: {}", stdout);
|
||||
if !stderr.is_empty() {
|
||||
println!("Config JSON stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
// Should output JSON configuration
|
||||
assert!(stdout.contains("\"general\"") || stdout.contains("log_level"),
|
||||
"Expected JSON config output in stdout, got: {}", stdout);
|
||||
assert!(
|
||||
stdout.contains("\"general\"") || stdout.contains("log_level"),
|
||||
"Expected JSON config output in stdout, got: {}",
|
||||
stdout
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -78,7 +87,7 @@ fn test_cli_capture_command() {
|
||||
// Create a temporary directory for output
|
||||
let temp_dir = std::env::temp_dir().join("linux-hello-test");
|
||||
std::fs::create_dir_all(&temp_dir).expect("Failed to create temp dir");
|
||||
|
||||
|
||||
let output = Command::new(cli_binary())
|
||||
.args([
|
||||
"capture",
|
||||
@@ -89,15 +98,15 @@ fn test_cli_capture_command() {
|
||||
])
|
||||
.output()
|
||||
.expect("Failed to execute CLI");
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
|
||||
println!("Capture stdout: {}", stdout);
|
||||
if !stderr.is_empty() {
|
||||
println!("Capture stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
// May fail if no camera, but should handle gracefully
|
||||
// If successful, check for output files
|
||||
if output.status.success() {
|
||||
@@ -107,7 +116,7 @@ fn test_cli_capture_command() {
|
||||
.collect();
|
||||
println!("Created {} file(s) in temp dir", files.len());
|
||||
}
|
||||
|
||||
|
||||
// Cleanup
|
||||
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
@@ -117,34 +126,33 @@ fn test_cli_detect_command() {
|
||||
// Create a simple test image
|
||||
let temp_dir = std::env::temp_dir().join("linux-hello-test-detect");
|
||||
std::fs::create_dir_all(&temp_dir).expect("Failed to create temp dir");
|
||||
|
||||
|
||||
// Create a simple grayscale PNG (100x100, mid-gray)
|
||||
use image::{GrayImage, Luma};
|
||||
let img = GrayImage::from_fn(100, 100, |_, _| Luma([128u8]));
|
||||
let img_path = temp_dir.join("test.png");
|
||||
img.save(&img_path).expect("Failed to save test image");
|
||||
|
||||
|
||||
let output = Command::new(cli_binary())
|
||||
.args([
|
||||
"detect",
|
||||
"--image",
|
||||
img_path.to_str().unwrap(),
|
||||
])
|
||||
.args(["detect", "--image", img_path.to_str().unwrap()])
|
||||
.output()
|
||||
.expect("Failed to execute CLI");
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
|
||||
println!("Detect stdout: {}", stdout);
|
||||
if !stderr.is_empty() {
|
||||
println!("Detect stderr: {}", stderr);
|
||||
}
|
||||
|
||||
|
||||
// Should at least run and report something
|
||||
assert!(stdout.contains("detected") || stdout.contains("Face") || stdout.contains("No face"),
|
||||
"Expected detection output, got: {}", stdout);
|
||||
|
||||
assert!(
|
||||
stdout.contains("detected") || stdout.contains("Face") || stdout.contains("No face"),
|
||||
"Expected detection output, got: {}",
|
||||
stdout
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Integration tests for face detection
|
||||
|
||||
use linux_hello_daemon::detection::{detect_face_simple, FaceDetection, FaceDetect, SimpleFaceDetector};
|
||||
use linux_hello_common::Result;
|
||||
use linux_hello_daemon::detection::{
|
||||
detect_face_simple, FaceDetect, FaceDetection, SimpleFaceDetector,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_simple_face_detection() {
|
||||
@@ -9,28 +11,31 @@ fn test_simple_face_detection() {
|
||||
let width = 640u32;
|
||||
let height = 480u32;
|
||||
let mut image = Vec::new();
|
||||
|
||||
|
||||
// Create a gradient pattern (simulates a face-like region)
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
// Center region with higher intensity (face-like)
|
||||
let center_x = width / 2;
|
||||
let center_y = height / 2;
|
||||
let dist = ((x as i32 - center_x as i32).pow(2) + (y as i32 - center_y as i32).pow(2)) as f32;
|
||||
let dist =
|
||||
((x as i32 - center_x as i32).pow(2) + (y as i32 - center_y as i32).pow(2)) as f32;
|
||||
let max_dist = ((width / 2).pow(2) + (height / 2).pow(2)) as f32;
|
||||
|
||||
|
||||
let intensity = (128.0 + (1.0 - dist / max_dist) * 100.0) as u8;
|
||||
image.push(intensity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let detection = detect_face_simple(&image, width, height);
|
||||
assert!(detection.is_some(), "Should detect face in test image");
|
||||
|
||||
|
||||
if let Some(det) = detection {
|
||||
println!("Face detected: x={:.2}, y={:.2}, w={:.2}, h={:.2}, conf={:.2}",
|
||||
det.x, det.y, det.width, det.height, det.confidence);
|
||||
|
||||
println!(
|
||||
"Face detected: x={:.2}, y={:.2}, w={:.2}, h={:.2}, conf={:.2}",
|
||||
det.x, det.y, det.width, det.height, det.confidence
|
||||
);
|
||||
|
||||
// Verify detection is within image bounds
|
||||
assert!(det.x >= 0.0 && det.x <= 1.0);
|
||||
assert!(det.y >= 0.0 && det.y <= 1.0);
|
||||
@@ -53,7 +58,7 @@ fn test_face_detection_low_contrast() {
|
||||
let width = 100u32;
|
||||
let height = 100u32;
|
||||
let image = vec![10u8; (width * height) as usize];
|
||||
|
||||
|
||||
let detection = detect_face_simple(&image, width, height);
|
||||
// May or may not detect, but shouldn't crash
|
||||
if let Some(det) = detection {
|
||||
@@ -67,7 +72,7 @@ fn test_face_detection_high_contrast() {
|
||||
let width = 100u32;
|
||||
let height = 100u32;
|
||||
let image = vec![255u8; (width * height) as usize];
|
||||
|
||||
|
||||
let detection = detect_face_simple(&image, width, height);
|
||||
// Should not detect (too bright)
|
||||
if let Some(det) = detection {
|
||||
@@ -78,22 +83,22 @@ fn test_face_detection_high_contrast() {
|
||||
#[test]
|
||||
fn test_simple_face_detector_trait() -> Result<()> {
|
||||
let detector = SimpleFaceDetector::new(0.3);
|
||||
|
||||
|
||||
// Test with reasonable image
|
||||
let width = 200u32;
|
||||
let height = 200u32;
|
||||
let image: Vec<u8> = (0..width * height)
|
||||
.map(|i| ((i % 200) + 50) as u8)
|
||||
.collect();
|
||||
|
||||
|
||||
let detections = detector.detect(&image, width, height)?;
|
||||
println!("Detector found {} face(s)", detections.len());
|
||||
|
||||
|
||||
// Test with threshold too high
|
||||
let strict_detector = SimpleFaceDetector::new(0.9);
|
||||
let strict_detections = strict_detector.detect(&image, width, height)?;
|
||||
println!("Strict detector found {} face(s)", strict_detections.len());
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -106,13 +111,13 @@ fn test_face_detection_pixel_conversion() {
|
||||
height: 0.8,
|
||||
confidence: 0.95,
|
||||
};
|
||||
|
||||
|
||||
let (x, y, w, h) = detection.to_pixels(640, 480);
|
||||
assert_eq!(x, 160);
|
||||
assert_eq!(y, 48);
|
||||
assert_eq!(w, 320);
|
||||
assert_eq!(h, 384);
|
||||
|
||||
|
||||
// Test edge cases
|
||||
let edge = FaceDetection {
|
||||
x: 0.0,
|
||||
|
||||
@@ -10,11 +10,11 @@ fn test_authenticate_request_serialization() {
|
||||
let request = IpcRequest::Authenticate {
|
||||
user: "testuser".to_string(),
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
assert!(json.contains("\"action\":\"authenticate\""));
|
||||
assert!(json.contains("\"user\":\"testuser\""));
|
||||
|
||||
|
||||
// Deserialize back
|
||||
let parsed: IpcRequest = serde_json::from_str(&json).unwrap();
|
||||
match parsed {
|
||||
@@ -33,16 +33,20 @@ fn test_enroll_request_serialization() {
|
||||
label: "default".to_string(),
|
||||
frame_count: 5,
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
assert!(json.contains("\"action\":\"enroll\""));
|
||||
assert!(json.contains("\"user\":\"testuser\""));
|
||||
assert!(json.contains("\"label\":\"default\""));
|
||||
|
||||
|
||||
// Deserialize back
|
||||
let parsed: IpcRequest = serde_json::from_str(&json).unwrap();
|
||||
match parsed {
|
||||
IpcRequest::Enroll { user, label, frame_count } => {
|
||||
IpcRequest::Enroll {
|
||||
user,
|
||||
label,
|
||||
frame_count,
|
||||
} => {
|
||||
assert_eq!(user, "testuser");
|
||||
assert_eq!(label, "default");
|
||||
assert_eq!(frame_count, 5);
|
||||
@@ -57,10 +61,10 @@ fn test_list_request_serialization() {
|
||||
let request = IpcRequest::List {
|
||||
user: "testuser".to_string(),
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
assert!(json.contains("\"action\":\"list\""));
|
||||
|
||||
|
||||
let parsed: IpcRequest = serde_json::from_str(&json).unwrap();
|
||||
match parsed {
|
||||
IpcRequest::List { user } => {
|
||||
@@ -79,7 +83,7 @@ fn test_remove_request_serialization() {
|
||||
label: Some("default".to_string()),
|
||||
all: false,
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
let parsed: IpcRequest = serde_json::from_str(&json).unwrap();
|
||||
match parsed {
|
||||
@@ -90,14 +94,14 @@ fn test_remove_request_serialization() {
|
||||
}
|
||||
_ => panic!("Expected Remove request"),
|
||||
}
|
||||
|
||||
|
||||
// Remove all
|
||||
let request = IpcRequest::Remove {
|
||||
user: "testuser".to_string(),
|
||||
label: None,
|
||||
all: true,
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
let parsed: IpcRequest = serde_json::from_str(&json).unwrap();
|
||||
match parsed {
|
||||
@@ -114,10 +118,10 @@ fn test_remove_request_serialization() {
|
||||
#[test]
|
||||
fn test_ping_request_serialization() {
|
||||
let request = IpcRequest::Ping;
|
||||
|
||||
|
||||
let json = serde_json::to_string(&request).unwrap();
|
||||
assert!(json.contains("\"action\":\"ping\""));
|
||||
|
||||
|
||||
let parsed: IpcRequest = serde_json::from_str(&json).unwrap();
|
||||
match parsed {
|
||||
IpcRequest::Ping => {}
|
||||
@@ -135,16 +139,16 @@ fn test_response_serialization() {
|
||||
confidence: Some(0.95),
|
||||
templates: None,
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&response).unwrap();
|
||||
assert!(json.contains("\"success\":true"));
|
||||
assert!(json.contains("\"confidence\":0.95"));
|
||||
|
||||
|
||||
// Deserialize
|
||||
let parsed: IpcResponse = serde_json::from_str(&json).unwrap();
|
||||
assert!(parsed.success);
|
||||
assert_eq!(parsed.confidence, Some(0.95));
|
||||
|
||||
|
||||
// Response with templates
|
||||
let response = IpcResponse {
|
||||
success: true,
|
||||
@@ -152,10 +156,13 @@ fn test_response_serialization() {
|
||||
confidence: None,
|
||||
templates: Some(vec!["default".to_string(), "glasses".to_string()]),
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&response).unwrap();
|
||||
let parsed: IpcResponse = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.templates, Some(vec!["default".to_string(), "glasses".to_string()]));
|
||||
assert_eq!(
|
||||
parsed.templates,
|
||||
Some(vec!["default".to_string(), "glasses".to_string()])
|
||||
);
|
||||
}
|
||||
|
||||
/// Test error response serialization
|
||||
@@ -167,11 +174,11 @@ fn test_error_response_serialization() {
|
||||
confidence: None,
|
||||
templates: None,
|
||||
};
|
||||
|
||||
|
||||
let json = serde_json::to_string(&response).unwrap();
|
||||
assert!(json.contains("\"success\":false"));
|
||||
assert!(json.contains("User not enrolled"));
|
||||
|
||||
|
||||
let parsed: IpcResponse = serde_json::from_str(&json).unwrap();
|
||||
assert!(!parsed.success);
|
||||
assert!(parsed.message.unwrap().contains("not enrolled"));
|
||||
@@ -196,9 +203,10 @@ fn test_pam_protocol_compatibility() {
|
||||
}
|
||||
_ => panic!("Failed to parse PAM-format request"),
|
||||
}
|
||||
|
||||
|
||||
// Test response format
|
||||
let success_response = r#"{"success":true,"message":"Authentication successful","confidence":1.0}"#;
|
||||
let success_response =
|
||||
r#"{"success":true,"message":"Authentication successful","confidence":1.0}"#;
|
||||
let parsed: IpcResponse = serde_json::from_str(success_response).unwrap();
|
||||
assert!(parsed.success);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
use linux_hello_common::{Config, FaceTemplate, TemplateStore};
|
||||
use linux_hello_daemon::auth::AuthService;
|
||||
use linux_hello_daemon::embedding::{PlaceholderEmbeddingExtractor, EmbeddingExtractor, cosine_similarity};
|
||||
use linux_hello_daemon::matching::{match_template, average_embeddings, MatchResult};
|
||||
use linux_hello_daemon::embedding::{
|
||||
cosine_similarity, EmbeddingExtractor, PlaceholderEmbeddingExtractor,
|
||||
};
|
||||
use linux_hello_daemon::matching::{average_embeddings, match_template, MatchResult};
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Test template storage operations
|
||||
@@ -14,10 +16,10 @@ use tempfile::TempDir;
|
||||
fn test_template_store_operations() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TemplateStore::new(temp_dir.path());
|
||||
|
||||
|
||||
// Initialize store
|
||||
store.initialize().unwrap();
|
||||
|
||||
|
||||
// Create test template
|
||||
let template = FaceTemplate {
|
||||
user: "testuser".to_string(),
|
||||
@@ -26,29 +28,29 @@ fn test_template_store_operations() {
|
||||
enrolled_at: 1234567890,
|
||||
frame_count: 5,
|
||||
};
|
||||
|
||||
|
||||
// Store template
|
||||
store.store(&template).unwrap();
|
||||
|
||||
|
||||
// Verify user is enrolled
|
||||
assert!(store.is_enrolled("testuser"));
|
||||
assert!(!store.is_enrolled("nonexistent"));
|
||||
|
||||
|
||||
// Load template back
|
||||
let loaded = store.load("testuser", "default").unwrap();
|
||||
assert_eq!(loaded.user, "testuser");
|
||||
assert_eq!(loaded.label, "default");
|
||||
assert_eq!(loaded.embedding, vec![0.1, 0.2, 0.3, 0.4, 0.5]);
|
||||
assert_eq!(loaded.frame_count, 5);
|
||||
|
||||
|
||||
// List users
|
||||
let users = store.list_users().unwrap();
|
||||
assert!(users.contains(&"testuser".to_string()));
|
||||
|
||||
|
||||
// List templates
|
||||
let templates = store.list_templates("testuser").unwrap();
|
||||
assert!(templates.contains(&"default".to_string()));
|
||||
|
||||
|
||||
// Remove template
|
||||
store.remove("testuser", "default").unwrap();
|
||||
assert!(!store.is_enrolled("testuser"));
|
||||
@@ -60,7 +62,7 @@ fn test_multiple_templates_per_user() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let store = TemplateStore::new(temp_dir.path());
|
||||
store.initialize().unwrap();
|
||||
|
||||
|
||||
// Add multiple templates
|
||||
for (i, label) in ["default", "glasses", "profile"].iter().enumerate() {
|
||||
let template = FaceTemplate {
|
||||
@@ -72,15 +74,15 @@ fn test_multiple_templates_per_user() {
|
||||
};
|
||||
store.store(&template).unwrap();
|
||||
}
|
||||
|
||||
|
||||
// Load all templates
|
||||
let templates = store.load_all("multiuser").unwrap();
|
||||
assert_eq!(templates.len(), 3);
|
||||
|
||||
|
||||
// List template labels
|
||||
let labels = store.list_templates("multiuser").unwrap();
|
||||
assert_eq!(labels.len(), 3);
|
||||
|
||||
|
||||
// Remove all
|
||||
store.remove_all("multiuser").unwrap();
|
||||
assert!(!store.is_enrolled("multiuser"));
|
||||
@@ -90,23 +92,23 @@ fn test_multiple_templates_per_user() {
|
||||
#[test]
|
||||
fn test_embedding_extraction() {
|
||||
let extractor = PlaceholderEmbeddingExtractor::new(128);
|
||||
|
||||
|
||||
// Create test grayscale image
|
||||
let mut img = image::GrayImage::new(100, 100);
|
||||
|
||||
|
||||
// Fill with gradient pattern
|
||||
for y in 0..100 {
|
||||
for x in 0..100 {
|
||||
img.put_pixel(x, y, image::Luma([(x + y) as u8 / 2]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extract embedding
|
||||
let embedding = extractor.extract(&img).unwrap();
|
||||
|
||||
|
||||
// Check dimension
|
||||
assert_eq!(embedding.len(), 128);
|
||||
|
||||
|
||||
// Check normalization (should be approximately unit length)
|
||||
let norm: f32 = embedding.iter().map(|&x| x * x).sum::<f32>().sqrt();
|
||||
assert!((norm - 1.0).abs() < 0.1 || norm < 0.01);
|
||||
@@ -116,11 +118,11 @@ fn test_embedding_extraction() {
|
||||
#[test]
|
||||
fn test_embedding_consistency() {
|
||||
let extractor = PlaceholderEmbeddingExtractor::new(128);
|
||||
|
||||
|
||||
// Create identical images
|
||||
let mut img1 = image::GrayImage::new(100, 100);
|
||||
let mut img2 = image::GrayImage::new(100, 100);
|
||||
|
||||
|
||||
for y in 0..100 {
|
||||
for x in 0..100 {
|
||||
let val = ((x * 2 + y * 3) % 256) as u8;
|
||||
@@ -128,14 +130,18 @@ fn test_embedding_consistency() {
|
||||
img2.put_pixel(x, y, image::Luma([val]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extract embeddings
|
||||
let emb1 = extractor.extract(&img1).unwrap();
|
||||
let emb2 = extractor.extract(&img2).unwrap();
|
||||
|
||||
|
||||
// Should be identical
|
||||
let similarity = cosine_similarity(&emb1, &emb2);
|
||||
assert!((similarity - 1.0).abs() < 0.001, "Identical images should have similarity ~1.0, got {}", similarity);
|
||||
assert!(
|
||||
(similarity - 1.0).abs() < 0.001,
|
||||
"Identical images should have similarity ~1.0, got {}",
|
||||
similarity
|
||||
);
|
||||
}
|
||||
|
||||
/// Test cosine similarity
|
||||
@@ -145,11 +151,11 @@ fn test_cosine_similarity() {
|
||||
let a = vec![1.0, 0.0, 0.0];
|
||||
let b = vec![1.0, 0.0, 0.0];
|
||||
assert!((cosine_similarity(&a, &b) - 1.0).abs() < 0.001);
|
||||
|
||||
|
||||
// Orthogonal vectors
|
||||
let c = vec![0.0, 1.0, 0.0];
|
||||
assert!((cosine_similarity(&a, &c) - 0.0).abs() < 0.001);
|
||||
|
||||
|
||||
// Similar vectors
|
||||
let d = vec![0.9, 0.1, 0.0];
|
||||
let similarity = cosine_similarity(&a, &d);
|
||||
@@ -175,17 +181,17 @@ fn test_template_matching() {
|
||||
frame_count: 1,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
// Exact match
|
||||
let result = match_template(&vec![1.0, 0.0, 0.0], &templates, 0.5);
|
||||
assert!(result.matched);
|
||||
assert_eq!(result.matched_label, Some("default".to_string()));
|
||||
assert!((result.best_similarity - 1.0).abs() < 0.001);
|
||||
|
||||
|
||||
// Close match (should match glasses template better due to similarity)
|
||||
let result = match_template(&vec![0.85, 0.15, 0.0], &templates, 0.5);
|
||||
assert!(result.matched);
|
||||
|
||||
|
||||
// No match (orthogonal)
|
||||
let result = match_template(&vec![0.0, 0.0, 1.0], &templates, 0.3);
|
||||
assert!(!result.matched);
|
||||
@@ -199,16 +205,16 @@ fn test_embedding_averaging() {
|
||||
vec![0.0, 1.0, 0.0],
|
||||
vec![0.0, 0.0, 1.0],
|
||||
];
|
||||
|
||||
|
||||
let averaged = average_embeddings(&embeddings).unwrap();
|
||||
|
||||
|
||||
// Should be 3 dimensional
|
||||
assert_eq!(averaged.len(), 3);
|
||||
|
||||
|
||||
// Should be normalized
|
||||
let norm: f32 = averaged.iter().map(|&x| x * x).sum::<f32>().sqrt();
|
||||
assert!((norm - 1.0).abs() < 0.01);
|
||||
|
||||
|
||||
// All components should be roughly equal (1/sqrt(3) normalized)
|
||||
let expected = 1.0 / 3.0_f32.sqrt();
|
||||
for val in &averaged {
|
||||
@@ -229,16 +235,19 @@ fn test_empty_embeddings_error() {
|
||||
fn test_auth_service_init() {
|
||||
let config = Config::default();
|
||||
let auth_service = AuthService::new(config);
|
||||
|
||||
|
||||
// This should succeed (creates template directory if needed)
|
||||
// Note: May need root permissions in production
|
||||
// For testing, we can verify it doesn't panic
|
||||
let result = auth_service.initialize();
|
||||
|
||||
|
||||
// On systems without /var/lib, this might fail
|
||||
// That's okay for unit testing
|
||||
if result.is_err() {
|
||||
println!("Auth service init failed (expected without root): {:?}", result);
|
||||
println!(
|
||||
"Auth service init failed (expected without root): {:?}",
|
||||
result
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +260,7 @@ fn test_match_result_structure() {
|
||||
distance_threshold: 0.5,
|
||||
matched_label: Some("default".to_string()),
|
||||
};
|
||||
|
||||
|
||||
assert!(result.matched);
|
||||
assert_eq!(result.best_similarity, 0.95);
|
||||
assert_eq!(result.distance_threshold, 0.5);
|
||||
@@ -262,18 +271,18 @@ fn test_match_result_structure() {
|
||||
#[test]
|
||||
fn test_embedding_diversity() {
|
||||
let extractor = PlaceholderEmbeddingExtractor::new(128);
|
||||
|
||||
|
||||
// Create different pattern images
|
||||
let patterns: Vec<Box<dyn Fn(u32, u32) -> u8>> = vec![
|
||||
Box::new(|x, _y| x as u8), // Horizontal gradient
|
||||
Box::new(|_x, y| y as u8), // Vertical gradient
|
||||
Box::new(|x, y| (x ^ y) as u8), // XOR pattern
|
||||
Box::new(|_x, _y| 128), // Solid gray
|
||||
Box::new(|x, _y| x as u8), // Horizontal gradient
|
||||
Box::new(|_x, y| y as u8), // Vertical gradient
|
||||
Box::new(|x, y| (x ^ y) as u8), // XOR pattern
|
||||
Box::new(|_x, _y| 128), // Solid gray
|
||||
Box::new(|x, y| ((x * y) % 256) as u8), // Multiplication pattern
|
||||
];
|
||||
|
||||
|
||||
let mut embeddings = Vec::new();
|
||||
|
||||
|
||||
for pattern in &patterns {
|
||||
let mut img = image::GrayImage::new(100, 100);
|
||||
for y in 0..100 {
|
||||
@@ -283,7 +292,7 @@ fn test_embedding_diversity() {
|
||||
}
|
||||
embeddings.push(extractor.extract(&img).unwrap());
|
||||
}
|
||||
|
||||
|
||||
// Check that different patterns produce somewhat different embeddings
|
||||
// (not all identical)
|
||||
for i in 0..embeddings.len() {
|
||||
@@ -306,13 +315,13 @@ fn test_template_serialization() {
|
||||
enrolled_at: 1704153600,
|
||||
frame_count: 10,
|
||||
};
|
||||
|
||||
|
||||
// Serialize
|
||||
let json = serde_json::to_string(&original).unwrap();
|
||||
|
||||
|
||||
// Deserialize
|
||||
let restored: FaceTemplate = serde_json::from_str(&json).unwrap();
|
||||
|
||||
|
||||
// Verify
|
||||
assert_eq!(original.user, restored.user);
|
||||
assert_eq!(original.label, restored.label);
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
//!
|
||||
//! Tests for TPM storage, secure memory, and anti-spoofing functionality.
|
||||
|
||||
use linux_hello_common::FaceTemplate;
|
||||
use linux_hello_daemon::anti_spoofing::{
|
||||
AntiSpoofingConfig, AntiSpoofingDetector, AntiSpoofingFrame,
|
||||
};
|
||||
use linux_hello_daemon::secure_memory::{SecureBytes, SecureEmbedding, memory_protection};
|
||||
use linux_hello_daemon::secure_memory::{memory_protection, SecureBytes, SecureEmbedding};
|
||||
use linux_hello_daemon::tpm::{EncryptedTemplate, SoftwareTpmFallback, TpmStorage};
|
||||
use linux_hello_daemon::SecureTemplateStore;
|
||||
use linux_hello_common::FaceTemplate;
|
||||
use tempfile::TempDir;
|
||||
|
||||
// =============================================================================
|
||||
@@ -19,7 +19,7 @@ use tempfile::TempDir;
|
||||
fn test_software_tpm_initialization() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut storage = SoftwareTpmFallback::new(temp.path());
|
||||
|
||||
|
||||
assert!(storage.is_available());
|
||||
storage.initialize().unwrap();
|
||||
}
|
||||
@@ -29,14 +29,14 @@ fn test_software_tpm_encrypt_decrypt_roundtrip() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut storage = SoftwareTpmFallback::new(temp.path());
|
||||
storage.initialize().unwrap();
|
||||
|
||||
|
||||
let plaintext = b"Sensitive face embedding data for security testing";
|
||||
let encrypted = storage.encrypt("testuser", plaintext).unwrap();
|
||||
|
||||
|
||||
assert!(!encrypted.ciphertext.is_empty());
|
||||
assert_ne!(encrypted.ciphertext.as_slice(), plaintext);
|
||||
assert!(!encrypted.tpm_encrypted); // Software fallback
|
||||
|
||||
|
||||
let decrypted = storage.decrypt("testuser", &encrypted).unwrap();
|
||||
assert_eq!(decrypted.as_slice(), plaintext);
|
||||
}
|
||||
@@ -46,13 +46,13 @@ fn test_software_tpm_user_key_management() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut storage = SoftwareTpmFallback::new(temp.path());
|
||||
storage.initialize().unwrap();
|
||||
|
||||
|
||||
storage.create_user_key("user1").unwrap();
|
||||
storage.create_user_key("user2").unwrap();
|
||||
|
||||
|
||||
storage.remove_user_key("user1").unwrap();
|
||||
// user2's key should still exist
|
||||
|
||||
|
||||
// Can still encrypt for both users (key derivation is deterministic)
|
||||
let encrypted = storage.encrypt("user1", b"test").unwrap();
|
||||
assert!(!encrypted.ciphertext.is_empty());
|
||||
@@ -62,7 +62,9 @@ fn test_software_tpm_user_key_management() {
|
||||
fn test_encrypted_template_structure() {
|
||||
let template = EncryptedTemplate {
|
||||
ciphertext: vec![1, 2, 3, 4, 5, 6, 7, 8],
|
||||
iv: vec![0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], // 12 bytes for AES-GCM
|
||||
iv: vec![
|
||||
0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
], // 12 bytes for AES-GCM
|
||||
salt: vec![0u8; 32], // 32 bytes for PBKDF2 salt
|
||||
key_handle: 0x81000001,
|
||||
tpm_encrypted: true,
|
||||
@@ -86,7 +88,7 @@ fn test_encrypted_template_structure() {
|
||||
fn test_secure_embedding_operations() {
|
||||
let data = vec![0.1, 0.2, 0.3, 0.4, 0.5];
|
||||
let embedding = SecureEmbedding::new(data.clone());
|
||||
|
||||
|
||||
assert_eq!(embedding.len(), 5);
|
||||
assert!(!embedding.is_empty());
|
||||
assert_eq!(embedding.as_slice(), data.as_slice());
|
||||
@@ -97,13 +99,13 @@ fn test_secure_embedding_similarity_metrics() {
|
||||
// Identical vectors
|
||||
let emb1 = SecureEmbedding::new(vec![1.0, 0.0, 0.0, 0.0]);
|
||||
let emb2 = SecureEmbedding::new(vec![1.0, 0.0, 0.0, 0.0]);
|
||||
|
||||
|
||||
let similarity = emb1.cosine_similarity(&emb2);
|
||||
assert!((similarity - 1.0).abs() < 0.001);
|
||||
|
||||
|
||||
let distance = emb1.euclidean_distance(&emb2);
|
||||
assert!(distance.abs() < 0.001);
|
||||
|
||||
|
||||
// Orthogonal vectors
|
||||
let emb3 = SecureEmbedding::new(vec![0.0, 1.0, 0.0, 0.0]);
|
||||
let similarity2 = emb1.cosine_similarity(&emb3);
|
||||
@@ -115,7 +117,7 @@ fn test_secure_embedding_serialization() {
|
||||
let original = SecureEmbedding::new(vec![1.5, -2.5, 3.14159, 0.0, f32::MAX]);
|
||||
let bytes = original.to_bytes();
|
||||
let restored = SecureEmbedding::from_bytes(&bytes).unwrap();
|
||||
|
||||
|
||||
assert_eq!(original.as_slice(), restored.as_slice());
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ fn test_secure_bytes_constant_time_comparison() {
|
||||
let secret1 = SecureBytes::new(vec![0x12, 0x34, 0x56, 0x78]);
|
||||
let secret2 = SecureBytes::new(vec![0x12, 0x34, 0x56, 0x78]);
|
||||
let secret3 = SecureBytes::new(vec![0x12, 0x34, 0x56, 0x79]);
|
||||
|
||||
|
||||
assert!(secret1.constant_time_eq(&secret2));
|
||||
assert!(!secret1.constant_time_eq(&secret3));
|
||||
}
|
||||
@@ -133,7 +135,7 @@ fn test_secure_bytes_constant_time_comparison() {
|
||||
fn test_secure_memory_zeroization() {
|
||||
let mut data = vec![0xAA_u8; 64];
|
||||
memory_protection::secure_zero(&mut data);
|
||||
|
||||
|
||||
assert!(data.iter().all(|&b| b == 0));
|
||||
}
|
||||
|
||||
@@ -141,7 +143,7 @@ fn test_secure_memory_zeroization() {
|
||||
fn test_secure_embedding_debug_hides_data() {
|
||||
let embedding = SecureEmbedding::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
|
||||
let debug_str = format!("{:?}", embedding);
|
||||
|
||||
|
||||
assert!(debug_str.contains("REDACTED"));
|
||||
assert!(!debug_str.contains("1.0"));
|
||||
assert!(!debug_str.contains("2.0"));
|
||||
@@ -166,12 +168,12 @@ fn test_secure_template_store_unencrypted() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut store = SecureTemplateStore::new(temp.path());
|
||||
store.initialize(false).unwrap();
|
||||
|
||||
|
||||
assert!(!store.is_encryption_enabled());
|
||||
|
||||
|
||||
let template = create_test_template("alice", "default");
|
||||
store.store(&template).unwrap();
|
||||
|
||||
|
||||
let loaded = store.load("alice", "default").unwrap();
|
||||
assert_eq!(loaded.user, "alice");
|
||||
assert_eq!(loaded.embedding, template.embedding);
|
||||
@@ -182,12 +184,14 @@ fn test_secure_template_store_enrollment_check() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut store = SecureTemplateStore::new(temp.path());
|
||||
store.initialize(false).unwrap();
|
||||
|
||||
|
||||
assert!(!store.is_enrolled("bob"));
|
||||
|
||||
store.store(&create_test_template("bob", "primary")).unwrap();
|
||||
|
||||
store
|
||||
.store(&create_test_template("bob", "primary"))
|
||||
.unwrap();
|
||||
assert!(store.is_enrolled("bob"));
|
||||
|
||||
|
||||
store.remove("bob", "primary").unwrap();
|
||||
assert!(!store.is_enrolled("bob"));
|
||||
}
|
||||
@@ -197,11 +201,17 @@ fn test_secure_template_store_load_all() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut store = SecureTemplateStore::new(temp.path());
|
||||
store.initialize(false).unwrap();
|
||||
|
||||
store.store(&create_test_template("charlie", "default")).unwrap();
|
||||
store.store(&create_test_template("charlie", "backup")).unwrap();
|
||||
store.store(&create_test_template("charlie", "outdoor")).unwrap();
|
||||
|
||||
|
||||
store
|
||||
.store(&create_test_template("charlie", "default"))
|
||||
.unwrap();
|
||||
store
|
||||
.store(&create_test_template("charlie", "backup"))
|
||||
.unwrap();
|
||||
store
|
||||
.store(&create_test_template("charlie", "outdoor"))
|
||||
.unwrap();
|
||||
|
||||
let templates = store.load_all("charlie").unwrap();
|
||||
assert_eq!(templates.len(), 3);
|
||||
}
|
||||
@@ -211,10 +221,10 @@ fn test_secure_template_store_load_secure_embedding() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut store = SecureTemplateStore::new(temp.path());
|
||||
store.initialize(false).unwrap();
|
||||
|
||||
|
||||
let template = create_test_template("dave", "default");
|
||||
store.store(&template).unwrap();
|
||||
|
||||
|
||||
let secure = store.load_secure("dave", "default").unwrap();
|
||||
assert_eq!(secure.len(), template.embedding.len());
|
||||
assert_eq!(secure.as_slice(), template.embedding.as_slice());
|
||||
@@ -227,18 +237,18 @@ fn test_secure_template_store_load_secure_embedding() {
|
||||
fn create_test_frame(brightness: u8, is_ir: bool, width: u32, height: u32) -> AntiSpoofingFrame {
|
||||
let size = (width * height) as usize;
|
||||
let mut pixels = vec![brightness; size];
|
||||
|
||||
|
||||
// Add realistic variation
|
||||
for (i, pixel) in pixels.iter_mut().enumerate() {
|
||||
let x = (i % width as usize) as u32;
|
||||
let y = (i / width as usize) as u32;
|
||||
|
||||
|
||||
// Add gradient and noise
|
||||
let gradient = ((x + y) % 20) as i16 - 10;
|
||||
let noise = ((i * 17 + 31) % 15) as i16 - 7;
|
||||
*pixel = (brightness as i16 + gradient + noise).clamp(0, 255) as u8;
|
||||
}
|
||||
|
||||
|
||||
AntiSpoofingFrame {
|
||||
pixels,
|
||||
width,
|
||||
@@ -253,10 +263,10 @@ fn create_test_frame(brightness: u8, is_ir: bool, width: u32, height: u32) -> An
|
||||
fn test_anti_spoofing_basic_check() {
|
||||
let config = AntiSpoofingConfig::default();
|
||||
let mut detector = AntiSpoofingDetector::new(config);
|
||||
|
||||
|
||||
let frame = create_test_frame(100, true, 200, 200);
|
||||
let result = detector.check_frame(&frame).unwrap();
|
||||
|
||||
|
||||
assert!(result.score >= 0.0 && result.score <= 1.0);
|
||||
assert!(result.checks.ir_check.is_some());
|
||||
assert!(result.checks.depth_check.is_some());
|
||||
@@ -267,19 +277,19 @@ fn test_anti_spoofing_basic_check() {
|
||||
fn test_anti_spoofing_ir_verification() {
|
||||
let config = AntiSpoofingConfig::default();
|
||||
let mut detector = AntiSpoofingDetector::new(config);
|
||||
|
||||
|
||||
// Normal IR frame (should pass)
|
||||
let normal_frame = create_test_frame(100, true, 200, 200);
|
||||
let result1 = detector.check_frame(&normal_frame).unwrap();
|
||||
let ir_score1 = result1.checks.ir_check.unwrap();
|
||||
|
||||
|
||||
detector.reset();
|
||||
|
||||
|
||||
// Very dark frame (suspicious)
|
||||
let dark_frame = create_test_frame(10, true, 200, 200);
|
||||
let result2 = detector.check_frame(&dark_frame).unwrap();
|
||||
let ir_score2 = result2.checks.ir_check.unwrap();
|
||||
|
||||
|
||||
// Normal frame should score higher than very dark frame
|
||||
assert!(ir_score1 > ir_score2);
|
||||
}
|
||||
@@ -289,9 +299,9 @@ fn test_anti_spoofing_temporal_analysis() {
|
||||
let mut config = AntiSpoofingConfig::default();
|
||||
config.enable_movement_check = true;
|
||||
config.temporal_frames = 5;
|
||||
|
||||
|
||||
let mut detector = AntiSpoofingDetector::new(config);
|
||||
|
||||
|
||||
// Simulate multiple frames with natural movement
|
||||
for i in 0..6 {
|
||||
let mut frame = create_test_frame(100, true, 200, 200);
|
||||
@@ -299,9 +309,9 @@ fn test_anti_spoofing_temporal_analysis() {
|
||||
// Add slight position variation
|
||||
let offset = (i % 3) as u32;
|
||||
frame.face_bbox = Some((50 + offset, 50, 100, 100));
|
||||
|
||||
|
||||
let result = detector.check_frame(&frame).unwrap();
|
||||
|
||||
|
||||
// After enough frames, movement check should be available
|
||||
if i >= 3 {
|
||||
assert!(result.checks.movement_check.is_some());
|
||||
@@ -313,17 +323,17 @@ fn test_anti_spoofing_temporal_analysis() {
|
||||
fn test_anti_spoofing_reset() {
|
||||
let mut config = AntiSpoofingConfig::default();
|
||||
config.enable_movement_check = true;
|
||||
|
||||
|
||||
let mut detector = AntiSpoofingDetector::new(config);
|
||||
|
||||
|
||||
// Process some frames
|
||||
for _ in 0..5 {
|
||||
let frame = create_test_frame(100, true, 200, 200);
|
||||
let _ = detector.check_frame(&frame);
|
||||
}
|
||||
|
||||
|
||||
detector.reset();
|
||||
|
||||
|
||||
// After reset, first frame should not have movement analysis
|
||||
let frame = create_test_frame(100, true, 200, 200);
|
||||
let result = detector.check_frame(&frame).unwrap();
|
||||
@@ -334,12 +344,12 @@ fn test_anti_spoofing_reset() {
|
||||
fn test_anti_spoofing_rejection_reasons() {
|
||||
let mut config = AntiSpoofingConfig::default();
|
||||
config.threshold = 0.95; // Very high threshold
|
||||
|
||||
|
||||
let mut detector = AntiSpoofingDetector::new(config);
|
||||
|
||||
|
||||
let frame = create_test_frame(100, true, 200, 200);
|
||||
let result = detector.check_frame(&frame).unwrap();
|
||||
|
||||
|
||||
if !result.is_live {
|
||||
assert!(result.rejection_reason.is_some());
|
||||
let reason = result.rejection_reason.unwrap();
|
||||
@@ -358,11 +368,11 @@ fn test_anti_spoofing_config_customization() {
|
||||
enable_movement_check: false,
|
||||
temporal_frames: 10,
|
||||
};
|
||||
|
||||
|
||||
let mut detector = AntiSpoofingDetector::new(config);
|
||||
let frame = create_test_frame(100, true, 200, 200);
|
||||
let result = detector.check_frame(&frame).unwrap();
|
||||
|
||||
|
||||
// Texture check should not be performed
|
||||
assert!(result.checks.texture_check.is_none());
|
||||
// IR and depth checks should be performed
|
||||
@@ -379,7 +389,7 @@ fn test_secure_workflow_enroll_and_verify() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut store = SecureTemplateStore::new(temp.path());
|
||||
store.initialize(false).unwrap();
|
||||
|
||||
|
||||
// Simulate enrollment embedding
|
||||
let enroll_embedding = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
|
||||
let template = FaceTemplate {
|
||||
@@ -392,17 +402,17 @@ fn test_secure_workflow_enroll_and_verify() {
|
||||
.as_secs(),
|
||||
frame_count: 5,
|
||||
};
|
||||
|
||||
|
||||
// Store securely
|
||||
store.store(&template).unwrap();
|
||||
assert!(store.is_enrolled("secure_user"));
|
||||
|
||||
|
||||
// Load as secure embedding for matching
|
||||
let stored = store.load_secure("secure_user", "default").unwrap();
|
||||
|
||||
|
||||
// Simulate authentication embedding (slightly different)
|
||||
let auth_embedding = SecureEmbedding::new(vec![0.11, 0.19, 0.31, 0.39, 0.51, 0.59, 0.71, 0.79]);
|
||||
|
||||
|
||||
// Compare securely
|
||||
let similarity = stored.cosine_similarity(&auth_embedding);
|
||||
assert!(similarity > 0.9); // Should be very similar
|
||||
@@ -414,6 +424,6 @@ fn test_memory_protection_basics() {
|
||||
let data = vec![0xABu8; 1024];
|
||||
let result = memory_protection::lock_memory(&data);
|
||||
assert!(result.is_ok()); // Should not error, even if lock fails
|
||||
|
||||
|
||||
let _ = memory_protection::unlock_memory(&data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user