#[path = "../src/models.rs"] mod models; #[path = "../src/validation.rs"] mod validation; use models::{ ComponentId, ProfileInput, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol, TargetInput, TargetKind, }; use validation::{normalize_profile, normalize_target}; #[test] fn normalizes_profile_source_items() { let profile = normalize_profile(ProfileInput { id: Some("Discord + Vesktop".to_string()), name: " Discord + Vesktop ".to_string(), enabled: true, target_id: " home-gateway ".to_string(), protocols: vec!["tcp".to_string(), "UDP".to_string(), "TCP".to_string()], items: vec![ ProfileItemInput { item_type: "process".to_string(), value: "Discord.exe".to_string(), recursive: None, }, ProfileItemInput { item_type: "folder".to_string(), value: "%LOCALAPPDATA%\\Vesktop".to_string(), recursive: Some(true), }, ProfileItemInput { item_type: "exe".to_string(), value: "C:\\Games\\Game\\game.exe".to_string(), recursive: Some(true), }, ], }) .expect("profile should normalize"); assert_eq!(profile.id, "discord-vesktop"); assert_eq!(profile.name, "Discord + Vesktop"); assert_eq!(profile.target_id, "home-gateway"); assert_eq!(profile.protocols, vec![Protocol::Tcp, Protocol::Udp]); assert_eq!(profile.items[0].item_type, ProfileItemType::Process); assert_eq!(profile.items[0].value, "Discord"); assert!(!profile.items[0].recursive); assert_eq!(profile.items[1].item_type, ProfileItemType::Folder); assert!(profile.items[1].recursive); assert_eq!(profile.items[2].item_type, ProfileItemType::Exe); assert!(!profile.items[2].recursive); } #[test] fn rejects_unsupported_profile_protocols() { let error = normalize_profile(ProfileInput { id: None, name: "Bad protocol".to_string(), enabled: true, target_id: "home-gateway".to_string(), protocols: vec!["icmp".to_string()], items: vec![ProfileItemInput { item_type: "process".to_string(), value: "Discord".to_string(), recursive: None, }], }) .expect_err("unsupported protocol should fail"); assert!(error.iter().any(|item| item.field == "protocols")); } #[test] fn normalizes_external_target_without_local_singbox() { let target = normalize_target(TargetInput { id: Some("Home Gateway".to_string()), name: " Home Gateway ".to_string(), kind: "external".to_string(), protocol: "socks5".to_string(), host: " 192.168.50.111 ".to_string(), port: 8080, requires_component: None, }) .expect("external target should normalize"); assert_eq!(target.id, "home-gateway"); assert_eq!(target.kind, TargetKind::External); assert_eq!(target.protocol, ProxyProtocol::Socks5); assert_eq!(target.host, "192.168.50.111"); assert_eq!(target.port, 8080); assert_eq!(target.requires_component, None); } #[test] fn local_singbox_target_can_exist_before_component_is_installed() { let target = normalize_target(TargetInput { id: Some("local-singbox".to_string()), name: "Local sing-box".to_string(), kind: "local".to_string(), protocol: "socks5".to_string(), host: "127.0.0.1".to_string(), port: 1080, requires_component: Some("singbox".to_string()), }) .expect("local target definition should not require installed component"); assert_eq!(target.kind, TargetKind::Local); assert_eq!(target.requires_component, Some(ComponentId::Singbox)); } #[test] fn rejects_malformed_target_fields() { let error = normalize_target(TargetInput { id: None, name: "".to_string(), kind: "external".to_string(), protocol: "ftp".to_string(), host: "".to_string(), port: 70_000, requires_component: Some("unknown".to_string()), }) .expect_err("invalid target should fail"); assert!(error.iter().any(|item| item.field == "name")); assert!(error.iter().any(|item| item.field == "host")); assert!(error.iter().any(|item| item.field == "port")); assert!(error.iter().any(|item| item.field == "protocol")); assert!(error .iter() .any(|item| item.field == "requires_component")); }