//! Kernel-independent unit and conversion tests for Phase 2 Native Linux Network Engine. use chrono::Utc; use ipnet::IpNet; use nx9_wg_core::types::network::Route; use nx9_wg_network::error::NetworkError; use nx9_wg_network::forwarding::IpForwardingStatus; use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; use uuid::Uuid; #[test] fn test_ipv4_route_destination_conversion() { let dest = IpNet::from_str("192.168.10.0/24").expect("valid cidr"); assert_eq!(dest.addr(), IpAddr::V4(Ipv4Addr::new(192, 168, 10, 0))); assert_eq!(dest.prefix_len(), 24); } #[test] fn test_ipv6_route_destination_conversion() { let dest = IpNet::from_str("fd00:abcd::/64").expect("valid ipv6 cidr"); assert_eq!(dest.prefix_len(), 64); assert!(dest.addr().is_ipv6()); } #[test] fn test_optional_gateway_resolution() { let now = Utc::now().naive_utc(); let r_with_gw = Route { id: Uuid::new_v4(), network_id: None, interface_id: None, destination: IpNet::from_str("10.100.0.0/16").unwrap(), gateway: Some(IpAddr::from_str("10.0.0.1").unwrap()), interface_name: Some("wg0".to_string()), metric: Some(50), enabled: true, description: None, created_at: now, updated_at: now, }; assert!(r_with_gw.gateway.is_some()); assert_eq!( r_with_gw.gateway.unwrap(), IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)) ); let r_no_gw = Route { id: Uuid::new_v4(), network_id: None, interface_id: None, destination: IpNet::from_str("10.200.0.0/16").unwrap(), gateway: None, interface_name: Some("wg0".to_string()), metric: None, enabled: true, description: None, created_at: now, updated_at: now, }; assert!(r_no_gw.gateway.is_none()); } #[test] fn test_default_route_safety_invariants() { let v4_default = IpNet::from_str("0.0.0.0/0").unwrap(); assert!(v4_default.addr().is_unspecified()); assert_eq!(v4_default.prefix_len(), 0); let v6_default = IpNet::from_str("::/0").unwrap(); assert!(v6_default.addr().is_unspecified()); assert_eq!(v6_default.prefix_len(), 0); let non_default = IpNet::from_str("10.0.0.0/8").unwrap(); assert!(!non_default.addr().is_unspecified() || non_default.prefix_len() != 0); } #[test] fn test_forwarding_status_serde() { let status = IpForwardingStatus { ipv4_enabled: true, ipv6_enabled: false, }; let json = serde_json::to_string(&status).expect("serialize"); assert!(json.contains("\"ipv4_enabled\":true")); assert!(json.contains("\"ipv6_enabled\":false")); let deserialized: IpForwardingStatus = serde_json::from_str(&json).expect("deserialize"); assert_eq!(status, deserialized); } #[test] fn test_error_variants_formatting() { let err_iface = NetworkError::InterfaceNotFound("wg-test".to_string()); assert_eq!(err_iface.to_string(), "interface 'wg-test' not found"); let err_addr = NetworkError::AddressNotFound("10.0.0.1/24".to_string()); assert_eq!(err_addr.to_string(), "address '10.0.0.1/24' not found"); let err_route = NetworkError::RouteNotFound("192.168.1.0/24".to_string()); assert_eq!(err_route.to_string(), "route '192.168.1.0/24' not found"); let err_netlink = NetworkError::Netlink("Netlink connection refused".to_string()); assert_eq!( err_netlink.to_string(), "netlink error: Netlink connection refused" ); let err_perm = NetworkError::PermissionDenied("Operation requires CAP_NET_ADMIN".to_string()); assert_eq!( err_perm.to_string(), "permission denied: Operation requires CAP_NET_ADMIN" ); } #[test] fn test_route_equality_and_filtering() { let now = Utc::now().naive_utc(); let r1 = Route { id: Uuid::new_v4(), network_id: None, interface_id: None, destination: IpNet::from_str("172.16.0.0/12").unwrap(), gateway: Some(IpAddr::from_str("10.0.0.254").unwrap()), interface_name: Some("wg0".to_string()), metric: Some(20), enabled: true, description: None, created_at: now, updated_at: now, }; let r2 = Route { id: Uuid::new_v4(), network_id: None, interface_id: None, destination: IpNet::from_str("172.16.0.0/12").unwrap(), gateway: Some(IpAddr::from_str("10.0.0.254").unwrap()), interface_name: Some("wg0".to_string()), metric: Some(20), enabled: false, description: None, created_at: now, updated_at: now, }; assert_eq!(r1.destination, r2.destination); assert_eq!(r1.gateway, r2.gateway); assert!(r1.enabled); assert!(!r2.enabled); }