#[cfg(windows)] use proxywarden_lib::component_detection::SystemProxyfierDetectionHost; use proxywarden_lib::component_detection::{ detect_proxyfier_install_with_host, detect_singbox_install_with_host, has_additional_matching_legacy_proxifyre_service_with_host, inventory_proxyfier_with_host, inventory_singbox_with_host, matches_legacy_proxifyre_2_2_1_manifest, proxyfier_component_from_detection, proxyfier_component_from_inventory, service_executable_from_path_name, singbox_component_from_detection, LegacyPackageFileIdentity, ProxyfierDetectionHost, ProxyfierEngine, RegistryInstallEntry, LEGACY_PROXIFYRE_2_2_1_MANIFEST, }; use proxywarden_lib::component_inventory::{ BinaryIdentityEvidence, ComponentClassification, OWNERSHIP_MISMATCH, }; use proxywarden_lib::models::ComponentState; use std::{ collections::{HashMap, HashSet}, path::{Path, PathBuf}, }; #[test] fn detects_existing_proxifyre_from_registry_install_location() { let host = MockHost::new() .with_registry("ProxiFyre", r"C:\Tools\ProxiFyre") .with_path(r"C:\Tools\ProxiFyre") .with_path(r"C:\Tools\ProxiFyre\ProxiFyre.exe") .with_service_path( "ProxiFyreService", r#""C:\Tools\ProxiFyre\ProxiFyre.exe" --service"#, ) .with_known_binary(r"C:\Tools\ProxiFyre\ProxiFyre.exe") .with_version(r"C:\Tools\ProxiFyre\ProxiFyre.exe", "2.2.1.0"); let detected = detect_proxyfier_install_with_host(&host) .expect("existing ProxiFyre install should be detected"); assert_eq!(detected.engine, ProxyfierEngine::ProxiFyre); assert_eq!(detected.install_dir, PathBuf::from(r"C:\Tools\ProxiFyre")); assert_eq!( detected.config_path, Some(PathBuf::from(r"C:\Tools\ProxiFyre\app-config.json")) ); assert!(detected.running); assert_eq!(detected.service_name, Some("ProxiFyreService".to_string())); assert_eq!(detected.service_status, Some("running".to_string())); assert_eq!(detected.version, Some("2.2.1.0".to_string())); let component = proxyfier_component_from_detection(Some(&detected)); assert_eq!(component.state, ComponentState::Running); assert!(component.installed); assert!(component.running); assert_eq!(component.path, Some(r"C:\Tools\ProxiFyre".to_string())); assert_eq!(component.service_name, Some("ProxiFyreService".to_string())); assert_eq!(component.service_status, Some("running".to_string())); assert_eq!(component.version, Some("2.2.1.0".to_string())); assert!(component.problems.is_empty()); } #[test] fn detects_current_proxifyre_only_with_strong_marker_and_exact_service_path() { let root = r"C:\Program Files\ProxyWarden\components\ProxiFyre"; let executable = r"C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe"; let marker = serde_json::json!({ "manager": "ProxyWarden", "component": "proxifyre", "serviceName": "ProxiFyreService", "installRoot": root, "packetFilterInstalledByProxyWarden": false }) .to_string(); let host = MockHost::new() .with_path(root) .with_path(executable) .with_text( r"C:\Program Files\ProxyWarden\components\ProxiFyre\proxywarden-component.json", &marker, ) .with_stopped_service_path( "ProxiFyreService", r#""C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe" --service"#, ) .with_version(executable, "2.4.0.0"); let detected = detect_proxyfier_install_with_host(&host).expect("managed current ProxiFyre"); assert_eq!(detected.install_dir, PathBuf::from(root)); assert_eq!(detected.version, Some("2.4.0.0".to_string())); } #[test] fn current_proxifyre_with_foreign_same_name_service_is_ownership_mismatch() { let root = r"C:\Program Files\ProxyWarden\components\ProxiFyre"; let executable = r"C:\Program Files\ProxyWarden\components\ProxiFyre\ProxiFyre.exe"; let marker = serde_json::json!({ "manager": "ProxyWarden", "component": "proxifyre", "serviceName": "ProxiFyreService", "installRoot": root, "packetFilterInstalledByProxyWarden": false }) .to_string(); let host = MockHost::new() .with_path(root) .with_path(executable) .with_text( r"C:\Program Files\ProxyWarden\components\ProxiFyre\proxywarden-component.json", &marker, ) .with_service_path( "ProxiFyreService", r#""C:\Foreign\ProxiFyre.exe" --service"#, ); let inventory = inventory_proxyfier_with_host(&host); assert_eq!(inventory.classification(), ComponentClassification::Foreign); assert_eq!( inventory.selected_candidate().unwrap().issues[0].code, OWNERSHIP_MISMATCH ); } #[test] fn ignores_empty_common_proxifyre_folder_without_executable() { let host = MockHost::new().with_path(r"C:\Tools\ProxiFyre"); assert!(detect_proxyfier_install_with_host(&host).is_none()); let component = proxyfier_component_from_detection(None); assert_eq!(component.state, ComponentState::Missing); assert!(!component.installed); } #[test] fn ignores_plain_proxifier_install() { let host = MockHost::new() .with_registry("Proxifier", r"C:\Program Files\Proxifier") .with_path(r"C:\Program Files\Proxifier") .with_process("Proxifier.exe"); assert!(detect_proxyfier_install_with_host(&host).is_none()); } #[test] fn env_override_does_not_make_portable_proxifyre_managed() { let host = MockHost::new() .with_env("PROXYWARDEN_PROXIFYRE_ROOT", r"D:\Portable\ProxiFyre") .with_path(r"D:\Portable\ProxiFyre\ProxiFyre.exe"); assert!(detect_proxyfier_install_with_host(&host).is_none()); let inventory = inventory_proxyfier_with_host(&host); assert_eq!(inventory.classification(), ComponentClassification::Foreign); } #[test] fn reports_stopped_proxifyre_service_when_executable_exists() { let host = MockHost::new() .with_env("PROXYWARDEN_PROXIFYRE_ROOT", r"C:\Tools\ProxiFyre") .with_path(r"C:\Tools\ProxiFyre\ProxiFyre.exe") .with_stopped_service_path( "ProxiFyreService", r#""C:\Tools\ProxiFyre\ProxiFyre.exe" --service"#, ) .with_known_binary(r"C:\Tools\ProxiFyre\ProxiFyre.exe"); let detected = detect_proxyfier_install_with_host(&host).expect("proxifyre executable should be detected"); let component = proxyfier_component_from_detection(Some(&detected)); assert_eq!(component.state, ComponentState::Installed); assert!(component.installed); assert!(!component.running); assert_eq!(component.service_name, Some("ProxiFyreService".to_string())); assert_eq!(component.service_status, Some("stopped".to_string())); assert!(component.problems.is_empty()); } #[test] fn primary_and_alias_same_root_remain_discoverable_but_cutover_is_ambiguous() { let root = Path::new(r"C:\Tools\ProxiFyre"); let executable = root.join("ProxiFyre.exe"); let host = MockHost::new() .with_path(executable.to_str().expect("fixture path")) .with_service_path( "ProxiFyreService", r#""C:\Tools\ProxiFyre\ProxiFyre.exe" -displayname "ProxiFyre Service" -servicename ProxiFyreService"#, ) .with_service_path( "ProxiFyre", r#""C:\Tools\ProxiFyre\ProxiFyre.exe" --service"#, ) .with_known_binary(executable.to_str().expect("fixture path")); assert_eq!( inventory_proxyfier_with_host(&host).classification(), ComponentClassification::ManagedLegacy, "Task 5 discovery/Start/Stop classification stays unchanged" ); assert!(has_additional_matching_legacy_proxifyre_service_with_host( &host, root )); } #[test] fn any_present_alias_service_makes_the_strict_service_set_ambiguous() { let root = Path::new(r"C:\Tools\ProxiFyre"); for host in [ MockHost::new().with_service("ProxiFyre"), MockHost::new().with_service_path("ProxiFyre", r#""C:\Foreign\ProxiFyre.exe" --service"#), ] { assert!(has_additional_matching_legacy_proxifyre_service_with_host( &host, root )); } } #[test] fn missing_proxyfier_returns_install_action_status() { let component = proxyfier_component_from_detection(None); assert_eq!(component.state, ComponentState::Missing); assert!(!component.installed); assert_eq!(component.actions, vec!["Установить ProxiFyre"]); } #[test] fn ignores_known_service_name_when_path_points_to_foreign_binary() { let host = MockHost::new() .with_env("PROXYWARDEN_PROXIFYRE_ROOT", r"C:\Tools\ProxiFyre") .with_path(r"C:\Tools\ProxiFyre\ProxiFyre.exe") .with_service_path( "ProxiFyreService", r#""C:\Foreign\ProxiFyre.exe" --service"#, ); assert!(detect_proxyfier_install_with_host(&host).is_none()); let inventory = inventory_proxyfier_with_host(&host); let candidate = inventory.selected_candidate().expect("foreign collision"); assert_eq!(candidate.classification, ComponentClassification::Foreign); assert_eq!(candidate.issues[0].code, OWNERSHIP_MISMATCH); let component = proxyfier_component_from_inventory(&inventory); assert_eq!(component.state, ComponentState::Error); assert!(!component.running); assert!(component.actions.is_empty()); } #[test] fn ignores_known_service_name_without_path_metadata() { let host = MockHost::new() .with_env("PROXYWARDEN_PROXIFYRE_ROOT", r"C:\Tools\ProxiFyre") .with_path(r"C:\Tools\ProxiFyre\ProxiFyre.exe") .with_service("ProxiFyreService"); assert!(detect_proxyfier_install_with_host(&host).is_none()); let inventory = inventory_proxyfier_with_host(&host); assert_eq!(inventory.classification(), ComponentClassification::Foreign); assert_eq!( inventory.selected_candidate().unwrap().issues[0].code, OWNERSHIP_MISMATCH ); } #[test] fn detects_running_local_singbox_from_default_install_root_and_service() { let host = MockHost::new() .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe") .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe") .with_text( r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml", winsw_xml(), ) .with_service_path( "ProxyWardenSingBox", r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe""#, ) .with_version( r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe", "1.11.0.0", ); let detected = detect_singbox_install_with_host(&host).expect("existing sing-box should be detected"); assert_eq!( detected.executable_path, PathBuf::from(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe") ); assert_eq!(detected.service_name, "ProxyWardenSingBox"); assert!(detected.running); assert_eq!(detected.version, Some("1.11.0.0".to_string())); let component = singbox_component_from_detection(Some(&detected)); assert_eq!(component.state, ComponentState::Running); assert!(component.installed); assert!(component.running); assert_eq!( component.path, Some(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe".to_string()) ); assert!(component.problems.is_empty()); } #[test] fn current_singbox_with_foreign_same_name_service_is_ownership_mismatch() { let host = MockHost::new() .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe") .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe") .with_text( r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml", winsw_xml(), ) .with_service_path( "ProxyWardenSingBox", r#""C:\Foreign\ProxyWardenSingBox.exe""#, ); let inventory = inventory_singbox_with_host(&host); assert_eq!(inventory.classification(), ComponentClassification::Foreign); assert_eq!( inventory.selected_candidate().unwrap().issues[0].code, OWNERSHIP_MISMATCH ); } #[test] fn winsw_identity_cannot_be_spoofed_by_comments_or_unrelated_nodes() { let spoofed_xml = r#" run -c "%BASE%\config.json" ForeignService C:\Foreign\sing-box.exe run -c "C:\Foreign\config.json" "#; let host = MockHost::new() .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe") .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe") .with_text( r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml", spoofed_xml, ) .with_service_path( "ProxyWardenSingBox", r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe""#, ); let inventory = inventory_singbox_with_host(&host); assert_eq!( inventory.classification(), ComponentClassification::Incomplete ); assert!(detect_singbox_install_with_host(&host).is_none()); } #[test] fn winsw_identity_rejects_duplicate_dtd_cdata_second_root_and_oversized_xml() { let oversized = format!( "ProxyWardenSingBox%BASE%\\sing-box.exerun -c \"%BASE%\\config.json\"{}", "x".repeat(65 * 1024) ); let invalid_xml = vec![ r#"ProxyWardenSingBoxProxyWardenSingBox%BASE%\sing-box.exerun -c "%BASE%\config.json""#.to_string(), r#"]>&owned;%BASE%\sing-box.exerun -c "%BASE%\config.json""#.to_string(), r#"%BASE%\sing-box.exerun -c "%BASE%\config.json""#.to_string(), format!("{}", winsw_xml()), oversized, ]; for xml in invalid_xml { let host = current_singbox_host_with_xml(&xml); assert!( detect_singbox_install_with_host(&host).is_none(), "unsafe WinSW XML was accepted" ); } } #[test] fn winsw_identity_accepts_xml_declaration_bom_and_current_extra_nodes() { let xml = "\u{feff}ProxyWardenSingBoxProxyWarden Local sing-box%BASE%\\sing-box.exerun -c \"%BASE%\\config.json\"4".to_string(); let host = current_singbox_host_with_xml(&xml); assert!(detect_singbox_install_with_host(&host).is_some()); } #[test] fn portable_singbox_env_override_remains_foreign() { let host = MockHost::new() .with_env("PROXYWARDEN_SINGBOX_ROOT", r"D:\Portable\sing-box") .with_path(r"D:\Portable\sing-box\sing-box.exe"); assert!(detect_singbox_install_with_host(&host).is_none()); assert_eq!( inventory_singbox_with_host(&host).classification(), ComponentClassification::Foreign ); } #[test] fn missing_local_singbox_returns_optional_install_action_status() { let component = singbox_component_from_detection(None); assert_eq!(component.state, ComponentState::Missing); assert!(!component.installed); assert!(!component.running); assert_eq!(component.actions, vec!["Установить Local sing-box"]); assert!(component.problems.is_empty()); } #[test] fn parses_service_pathname_without_accepting_malformed_quotes() { assert_eq!( service_executable_from_path_name( r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe" install"#, ), Some(PathBuf::from( r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe" )) ); assert_eq!( service_executable_from_path_name(r"C:\Tools\ProxiFyre\ProxiFyre.exe --service"), Some(PathBuf::from(r"C:\Tools\ProxiFyre\ProxiFyre.exe")) ); assert!(service_executable_from_path_name(r#""C:\Broken\ProxiFyre.exe --service"#).is_none()); assert!(service_executable_from_path_name(" ").is_none()); } #[test] fn frozen_proxifyre_manifest_matches_all_ten_files_and_nothing_less() { let observed = LEGACY_PROXIFYRE_2_2_1_MANIFEST .iter() .rev() .map(|file| LegacyPackageFileIdentity { relative_path: PathBuf::from(file.relative_path.to_ascii_uppercase()), size: file.size, sha256: file.sha256.to_ascii_uppercase(), }) .collect::>(); assert!(matches_legacy_proxifyre_2_2_1_manifest(&observed)); let mut missing = observed.clone(); missing.pop(); assert!(!matches_legacy_proxifyre_2_2_1_manifest(&missing)); let mut wrong_size = observed.clone(); wrong_size[0].size += 1; assert!(!matches_legacy_proxifyre_2_2_1_manifest(&wrong_size)); let mut wrong_hash = observed.clone(); wrong_hash[0].sha256 = "0".repeat(64); assert!(!matches_legacy_proxifyre_2_2_1_manifest(&wrong_hash)); let mut extra = observed.clone(); extra.push(LegacyPackageFileIdentity { relative_path: PathBuf::from("unexpected.dll"), size: 1, sha256: "0".repeat(64), }); assert!(!matches_legacy_proxifyre_2_2_1_manifest(&extra)); let mut duplicate = observed; duplicate[0] = duplicate[1].clone(); assert!(!matches_legacy_proxifyre_2_2_1_manifest(&duplicate)); } #[test] #[cfg(windows)] fn reads_windows_pe_file_version_without_executing_binary() { let windows_dir = std::env::var("WINDIR").expect("WINDIR on Windows"); let notepad = PathBuf::from(windows_dir) .join("System32") .join("notepad.exe"); let version = SystemProxyfierDetectionHost .file_version(¬epad) .expect("notepad PE version"); assert_eq!(version.split('.').count(), 4); assert!(version .split('.') .all(|segment| segment.parse::().is_ok())); } #[test] fn production_component_detection_has_only_native_windows_owners() { let source = include_str!("../src/component_detection.rs"); let source_lower = source.to_ascii_lowercase(); for forbidden in [ "command_no_window(", "get-process", "get-service", "get-ciminstance", "\"powershell\"", "std::process::command", "extern \"system\"", "#[link(", ] { assert!( !source_lower.contains(forbidden), "production detection still contains shell boundary: {forbidden}" ); } for native_owner in [ "CreateToolhelp32Snapshot", "OpenSCManagerW", "QueryServiceStatusEx", "QueryServiceConfigW", "winreg::", ] { assert!( source.contains(native_owner), "native detection owner is missing: {native_owner}" ); } } #[test] #[cfg(windows)] fn native_process_inventory_finds_the_running_test_binary() { let executable_name = std::env::current_exe() .expect("current test executable") .file_name() .expect("current test executable file name") .to_string_lossy() .into_owned(); assert!(SystemProxyfierDetectionHost.process_running(&executable_name)); assert!(SystemProxyfierDetectionHost.process_running(&executable_name.to_ascii_uppercase())); assert!(SystemProxyfierDetectionHost.process_running(executable_name.trim_end_matches(".exe"))); } #[test] #[cfg(windows)] fn native_service_inventory_reads_status_and_path_from_scm() { let service = SystemProxyfierDetectionHost .service_info("EventLog") .expect("Windows EventLog service should be queryable without elevation"); assert_eq!(service.name, "EventLog"); assert!(matches!( service.status.as_str(), "stopped" | "start pending" | "stop pending" | "running" | "continue pending" | "pause pending" | "paused" | "unknown" )); assert!(service .path_name .as_deref() .is_some_and(|path| !path.trim().is_empty())); } #[derive(Default)] struct MockHost { env: HashMap, paths: HashSet, processes: HashSet, services: HashMap, service_paths: HashMap, texts: HashMap, known_binaries: HashSet, versions: HashMap, registry: Vec, } impl MockHost { fn new() -> Self { Self::default() } fn with_env(mut self, name: &str, value: &str) -> Self { self.env.insert(name.to_string(), value.to_string()); self } fn with_path(mut self, path: &str) -> Self { self.paths.insert(normalize_path(path)); self } fn with_process(mut self, process: &str) -> Self { self.processes.insert(process.to_ascii_lowercase()); self } fn with_service(mut self, service: &str) -> Self { self.services .insert(service.to_ascii_lowercase(), "running".to_string()); self } fn with_service_path(mut self, service: &str, path_name: &str) -> Self { self.services .insert(service.to_ascii_lowercase(), "running".to_string()); self.service_paths .insert(service.to_ascii_lowercase(), path_name.to_string()); self } fn with_stopped_service_path(mut self, service: &str, path_name: &str) -> Self { self.services .insert(service.to_ascii_lowercase(), "stopped".to_string()); self.service_paths .insert(service.to_ascii_lowercase(), path_name.to_string()); self } fn with_registry(mut self, display_name: &str, install_location: &str) -> Self { self.registry.push(RegistryInstallEntry { display_name: display_name.to_string(), install_location: Some(PathBuf::from(install_location)), display_icon: None, }); self } fn with_text(mut self, path: &str, contents: &str) -> Self { self.paths.insert(normalize_path(path)); self.texts .insert(normalize_path(path), contents.to_string()); self } fn with_known_binary(mut self, path: &str) -> Self { self.known_binaries.insert(normalize_path(path)); self } fn with_version(mut self, path: &str, version: &str) -> Self { self.versions .insert(normalize_path(path), version.to_string()); self } } impl ProxyfierDetectionHost for MockHost { fn env_var(&self, name: &str) -> Option { self.env.get(name).cloned() } fn path_exists(&self, path: &Path) -> bool { self.paths .contains(&normalize_path(&path.display().to_string())) } fn process_running(&self, process_name: &str) -> bool { self.processes.contains(&process_name.to_ascii_lowercase()) } fn service_status(&self, service_name: &str) -> Option { self.services .get(&service_name.to_ascii_lowercase()) .cloned() } fn service_info( &self, service_name: &str, ) -> Option { let key = service_name.to_ascii_lowercase(); self.services.get(&key).map(|status| { proxywarden_lib::component_detection::DetectedService { name: service_name.to_string(), status: status.clone(), path_name: self.service_paths.get(&key).cloned(), } }) } fn registry_install_entries(&self) -> Vec { self.registry.clone() } fn read_text(&self, path: &Path) -> Option { self.texts .get(&normalize_path(&path.display().to_string())) .cloned() } fn file_version(&self, path: &Path) -> Option { self.versions .get(&normalize_path(&path.display().to_string())) .cloned() } fn binary_identity( &self, _component_id: &proxywarden_lib::models::ComponentId, path: &Path, ) -> BinaryIdentityEvidence { if self .known_binaries .contains(&normalize_path(&path.display().to_string())) { BinaryIdentityEvidence::KnownPackage } else { BinaryIdentityEvidence::Unknown } } } fn normalize_path(path: &str) -> String { path.replace('/', "\\").to_ascii_lowercase() } fn winsw_xml() -> &'static str { r#" ProxyWardenSingBox %BASE%\sing-box.exe run -c "%BASE%\config.json" "# } #[test] fn current_singbox_recognizes_the_native_installer_xml() { let xml = proxywarden_lib::singbox_service::singbox_service_xml(); for xml in [ xml.to_owned(), xml.replace(""", """), xml.replace(""", """), ] { let inventory = inventory_singbox_with_host(¤t_singbox_host_with_xml(&xml)); assert_eq!( inventory.classification(), ComponentClassification::ManagedCurrent ); } for entity in ["&unknown;", """, "�", "�"] { let xml = xml.replace(""", entity); let inventory = inventory_singbox_with_host(¤t_singbox_host_with_xml(&xml)); assert_ne!( inventory.classification(), ComponentClassification::ManagedCurrent ); } } #[cfg(windows)] #[test] #[ignore = "read-only smoke test requiring an installed managed sing-box"] fn installed_singbox_inventory_is_current() { let inventory = proxywarden_lib::component_detection::inventory_singbox(); assert_eq!( inventory.classification(), ComponentClassification::ManagedCurrent, "{inventory:?}" ); } fn current_singbox_host_with_xml(xml: &str) -> MockHost { MockHost::new() .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\sing-box.exe") .with_path(r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe") .with_text( r"C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.xml", xml, ) .with_service_path( "ProxyWardenSingBox", r#""C:\Program Files\ProxyWarden\components\sing-box\ProxyWardenSingBox.exe""#, ) }