#[path = "../src/models.rs"] mod models; #[path = "../src/adapters/proxifyre.rs"] mod proxifyre; #[path = "../src/adapters/proxy_router.rs"] mod proxy_router; use models::{ ComponentId, ComponentState, ComponentStatus, Profile, ProfileItem, ProfileItemType, Protocol, ProxyProtocol, Target, TargetKind, }; use proxifyre::{ProxiFyreAdapter, ProxiFyreConfig, PROXIFYRE_OUTPUT_FILE}; use proxy_router::{ProxyRouterAdapter, ProxyRouterErrorKind, ProxyRouterRequest}; #[test] fn generates_proxifyre_config_for_discord_external_socks5_target() { let adapter = ProxiFyreAdapter::default(); let profiles = vec![discord_profile("home-gateway")]; let targets = vec![external_socks5_target()]; let components = vec![missing_singbox_component()]; let generated = adapter .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) .expect("external socks5 target should not require sing-box"); let config: ProxiFyreConfig = serde_json::from_str(&generated.contents).expect("generated config json"); assert_eq!(generated.adapter_id, "proxifyre"); assert_eq!(generated.output_file_name, PROXIFYRE_OUTPUT_FILE); assert_eq!(generated.enabled_profiles, 1); assert_eq!(generated.routed_apps, 1); assert_eq!(config.log_level, "Info"); assert!(config.bypass_lan); assert_eq!(config.proxies.len(), 1); assert_eq!(config.proxies[0].app_names, vec!["Discord"]); assert_eq!( config.proxies[0].socks5_proxy_endpoint, "192.168.50.111:8080" ); assert_eq!(config.proxies[0].supported_protocols, vec!["TCP", "UDP"]); } #[test] fn skips_disabled_profiles_when_generating_proxifyre_config() { let adapter = ProxiFyreAdapter::default(); let mut disabled = discord_profile("home-gateway"); disabled.enabled = false; let profiles = vec![disabled]; let targets = vec![external_socks5_target()]; let components = Vec::new(); let generated = adapter .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) .expect("disabled profiles should produce empty config"); let config: ProxiFyreConfig = serde_json::from_str(&generated.contents).expect("generated config json"); assert_eq!(generated.enabled_profiles, 0); assert_eq!(generated.routed_apps, 0); assert!(config.proxies.is_empty()); } #[test] fn includes_folder_paths_when_generating_proxifyre_config() { let adapter = ProxiFyreAdapter::default(); let mut profile = discord_profile("home-gateway"); profile.items.push(ProfileItem { item_type: ProfileItemType::Folder, value: r"C:\Games\MyGame".to_string(), recursive: true, }); let profiles = vec![profile]; let targets = vec![external_socks5_target()]; let components = Vec::new(); let generated = adapter .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) .expect("folder paths should be accepted by ProxiFyre config generation"); let config: ProxiFyreConfig = serde_json::from_str(&generated.contents).expect("generated config json"); assert_eq!(generated.routed_apps, 2); assert_eq!( config.proxies[0].app_names, vec!["Discord", r"C:\Games\MyGame"] ); } #[test] fn blocks_local_singbox_target_when_required_component_is_missing() { let adapter = ProxiFyreAdapter::default(); let profiles = vec![discord_profile("local-singbox")]; let targets = vec![local_singbox_target()]; let components = Vec::new(); let error = adapter .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) .expect_err("local sing-box target should require installed running sing-box"); assert_eq!(error.kind, ProxyRouterErrorKind::MissingRequiredComponent); } #[test] fn local_singbox_target_generates_when_required_component_is_running() { let adapter = ProxiFyreAdapter::default(); let profiles = vec![discord_profile("local-singbox")]; let targets = vec![local_singbox_target()]; let components = vec![running_singbox_component()]; let generated = adapter .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) .expect("running sing-box should satisfy local target dependency"); let config: ProxiFyreConfig = serde_json::from_str(&generated.contents).expect("generated config json"); assert_eq!(config.proxies.len(), 1); assert_eq!(config.proxies[0].socks5_proxy_endpoint, "127.0.0.1:1080"); } #[test] fn rejects_http_target_because_proxifyre_adapter_is_socks5_only() { let adapter = ProxiFyreAdapter::default(); let profiles = vec![discord_profile("office-http")]; let targets = vec![Target { id: "office-http".to_string(), name: "Office HTTP".to_string(), kind: TargetKind::External, protocol: ProxyProtocol::Http, host: "192.168.50.111".to_string(), port: 3128, requires_component: None, }]; let components = Vec::new(); let error = adapter .generate_config(ProxyRouterRequest::new(&profiles, &targets, &components)) .expect_err("ProxiFyre should reject HTTP targets"); assert_eq!(error.kind, ProxyRouterErrorKind::UnsupportedTargetProtocol); } fn discord_profile(target_id: &str) -> Profile { Profile { id: "discord".to_string(), name: "Discord".to_string(), enabled: true, target_id: target_id.to_string(), protocols: vec![Protocol::Tcp, Protocol::Udp], items: vec![ProfileItem { item_type: ProfileItemType::Process, value: "Discord".to_string(), recursive: false, }], } } fn external_socks5_target() -> Target { Target { id: "home-gateway".to_string(), name: "Home Gateway".to_string(), kind: TargetKind::External, protocol: ProxyProtocol::Socks5, host: "192.168.50.111".to_string(), port: 8080, requires_component: None, } } fn local_singbox_target() -> Target { Target { id: "local-singbox".to_string(), name: "Local sing-box".to_string(), kind: TargetKind::Local, protocol: ProxyProtocol::Socks5, host: "127.0.0.1".to_string(), port: 1080, requires_component: Some(ComponentId::Singbox), } } fn missing_singbox_component() -> ComponentStatus { ComponentStatus { id: ComponentId::Singbox, name: "Local sing-box".to_string(), state: ComponentState::Missing, installed: false, running: false, version: None, path: None, problems: vec!["Local sing-box is not installed".to_string()], actions: vec!["Install Local sing-box".to_string()], } } fn running_singbox_component() -> ComponentStatus { ComponentStatus { id: ComponentId::Singbox, name: "Local sing-box".to_string(), state: ComponentState::Running, installed: true, running: true, version: Some("1.11.0".to_string()), path: Some(r"C:\Tools\ProxyWarden\sing-box\sing-box.exe".to_string()), problems: Vec::new(), actions: vec!["Restart".to_string(), "Stop".to_string()], } }