use crate::errors::CameraError;
use crate::types::{CameraDeviceInfo, CameraFormat, CameraFrame, Platform};
use chrono::Utc;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
#[derive(Clone)]
pub struct MockCameraSystem {
devices: Arc<Mutex<Vec<CameraDeviceInfo>>>,
capture_mode: Arc<Mutex<MockCaptureMode>>,
error_mode: Arc<Mutex<Option<CameraError>>>,
}
#[derive(Debug, Clone)]
pub enum MockCaptureMode {
Success,
Failure,
SlowCapture,
}
impl MockCameraSystem {
pub fn new() -> Self {
Self::default()
}
pub fn add_mock_devices(&self, platform: Platform) {
let mut devices = self.devices.lock().expect("Devices mutex poisoned");
devices.clear();
let test_devices = match platform {
Platform::Windows => vec![
create_mock_device("win_cam_0", "Integrated Camera", platform),
create_mock_device("win_cam_1", "USB Webcam", platform),
],
Platform::MacOS => vec![
create_mock_device("mac_cam_0", "FaceTime HD Camera", platform),
create_mock_device("mac_cam_1", "External Camera", platform),
],
Platform::Linux => vec![
create_mock_device("v4l_0", "/dev/video0", platform),
create_mock_device("v4l_1", "/dev/video1", platform),
],
Platform::Unknown => vec![create_mock_device("unknown_0", "Generic Camera", platform)],
};
devices.extend(test_devices);
}
pub fn get_devices(&self) -> Vec<CameraDeviceInfo> {
self.devices.lock().expect("Devices mutex poisoned").clone()
}
pub fn set_capture_mode(&self, mode: MockCaptureMode) {
*self
.capture_mode
.lock()
.expect("Capture mode mutex poisoned") = mode;
}
pub fn set_error_mode(&self, error: Option<CameraError>) {
*self.error_mode.lock().expect("Error mode mutex poisoned") = error;
}
}
impl Default for MockCameraSystem {
fn default() -> Self {
Self {
devices: Arc::new(Mutex::new(Vec::new())),
capture_mode: Arc::new(Mutex::new(MockCaptureMode::Success)),
error_mode: Arc::new(Mutex::new(None)),
}
}
}
pub fn create_mock_device(id: &str, name: &str, platform: Platform) -> CameraDeviceInfo {
CameraDeviceInfo {
id: id.to_string(),
name: name.to_string(),
description: Some(format!(
"Mock camera device for {} on {}",
name,
platform.as_str()
)),
platform,
is_available: true,
supports_formats: get_test_formats(),
}
}
pub fn get_test_formats() -> Vec<CameraFormat> {
vec![
CameraFormat::low(),
CameraFormat::standard(),
CameraFormat::hd(),
]
}
pub fn create_mock_frame(device_id: &str) -> CameraFrame {
let width = 1280;
let height = 720;
let data = vec![128u8; (width * height * 3) as usize];
CameraFrame {
id: Uuid::new_v4().to_string(),
device_id: device_id.to_string(),
timestamp: Utc::now(),
width,
height,
format: "RGB8".to_string(),
data,
size_bytes: (width * height * 3) as usize,
metadata: crate::types::FrameMetadata::default(),
}
}
#[allow(clippy::unused_async)]
pub async fn setup_test_environment() -> MockCameraSystem {
let mock_system = MockCameraSystem::new();
mock_system.add_mock_devices(Platform::current());
mock_system
}
pub fn init_test_env() {
let _ = env_logger::builder().is_test(true).try_init();
}
use std::collections::HashMap;
use std::sync::LazyLock;
static MOCK_CAMERA_MODES: LazyLock<Arc<Mutex<HashMap<String, MockCaptureMode>>>> =
LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
pub fn set_mock_camera_mode(device_id: &str, mode: MockCaptureMode) {
let mut modes = MOCK_CAMERA_MODES
.lock()
.expect("MOCK_CAMERA_MODES mutex poisoned");
modes.insert(device_id.to_string(), mode);
}
#[allow(clippy::unwrap_used)] pub fn get_mock_camera_mode(device_id: &str) -> MockCaptureMode {
let modes = MOCK_CAMERA_MODES
.lock()
.expect("MOCK_CAMERA_MODES mutex poisoned");
modes
.get(device_id)
.cloned()
.unwrap_or(MockCaptureMode::Success)
}
#[cfg(test)]
mod mock_tests {
use super::*;
#[test]
fn test_mock_system_default_and_mode_setters() {
let system = MockCameraSystem::new();
system.set_capture_mode(MockCaptureMode::SlowCapture);
system.set_error_mode(Some(CameraError::CaptureError("x".to_string())));
assert!(system.get_devices().is_empty());
}
#[test]
fn test_add_mock_devices_per_platform() {
let system = MockCameraSystem::new();
system.add_mock_devices(Platform::Windows);
assert_eq!(system.get_devices().len(), 2);
system.add_mock_devices(Platform::MacOS);
assert_eq!(system.get_devices().len(), 2);
system.add_mock_devices(Platform::Linux);
assert_eq!(system.get_devices().len(), 2);
system.add_mock_devices(Platform::Unknown);
assert_eq!(system.get_devices().len(), 1);
}
#[test]
fn test_create_mock_device_and_formats() {
let device = create_mock_device("cam-1", "Mock Cam", Platform::Windows);
assert_eq!(device.id, "cam-1");
assert_eq!(device.name, "Mock Cam");
assert!(device.is_available);
assert_eq!(device.platform, Platform::Windows);
assert_eq!(device.supports_formats.len(), 3);
let formats = get_test_formats();
assert_eq!(formats.len(), 3);
}
#[test]
fn test_create_mock_frame_shape() {
let frame = create_mock_frame("device-a");
assert_eq!(frame.device_id, "device-a");
assert_eq!(frame.width, 1280);
assert_eq!(frame.height, 720);
assert_eq!(frame.format, "RGB8");
assert_eq!(frame.size_bytes, frame.data.len());
assert!(!frame.id.is_empty());
}
#[tokio::test]
async fn test_setup_test_environment_creates_devices() {
let env = setup_test_environment().await;
assert!(!env.get_devices().is_empty());
}
#[test]
fn test_mock_camera_mode_registry() {
let id = "mode-cam";
assert!(matches!(get_mock_camera_mode(id), MockCaptureMode::Success));
set_mock_camera_mode(id, MockCaptureMode::Failure);
assert!(matches!(get_mock_camera_mode(id), MockCaptureMode::Failure));
}
#[test]
fn test_init_test_env_is_idempotent() {
init_test_env();
init_test_env();
}
}