//! Integration tests for Phase 5 Network Engine, Route Management, and nftables ruleset builder. use chrono::Utc; use ipnet::IpNet; use nx9_wg_core::types::firewall::{ FirewallAction, FirewallDirection, FirewallProtocol, FirewallRule, }; use nx9_wg_core::types::network::Route; use nx9_wg_network::{NetworkEngine, NftablesRulesetBuilder, SimulatedNetworkEngine}; use std::net::IpAddr; use std::str::FromStr; use uuid::Uuid; #[tokio::test] async fn test_network_engine_routes_and_firewall_lifecycle() { let engine = SimulatedNetworkEngine::new(); let now = Utc::now().naive_utc(); // 1. Sync routes let r1 = Route { id: Uuid::new_v4(), network_id: None, interface_id: None, destination: IpNet::from_str("192.168.10.0/24").unwrap(), gateway: Some(IpAddr::from_str("10.0.0.1").unwrap()), interface_name: Some("wg0".to_string()), metric: Some(100), 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("192.168.20.0/24").unwrap(), gateway: None, interface_name: Some("wg0".to_string()), metric: None, enabled: false, // disabled route should not be synchronized description: None, created_at: now, updated_at: now, }; engine.sync_routes(&[r1, r2]).await.expect("sync routes"); // 2. Sync firewall rules & NAT let fw1 = FirewallRule { id: Uuid::new_v4(), name: "Allow WireGuard Port".to_string(), interface_id: None, peer_id: None, direction: FirewallDirection::In, action: FirewallAction::Accept, protocol: FirewallProtocol::Udp, source: None, destination: None, source_port: None, destination_port: Some(51820), port_range: None, priority: 1, enabled: true, description: None, created_at: now, updated_at: now, }; let subnets = vec![ IpNet::from_str("10.0.0.0/24").unwrap(), IpNet::from_str("fd00::/64").unwrap(), ]; engine .sync_firewall(&[fw1], true, &subnets) .await .expect("sync firewall"); let ruleset = engine .get_active_nftables_ruleset() .await .expect("get ruleset"); assert!(ruleset.contains("table inet nx9_wg")); assert!(ruleset.contains("udp dport 51820 accept")); assert!(ruleset.contains("ip saddr 10.0.0.0/24 oifname != \"wg*\" masquerade")); assert!(ruleset.contains("ip6 saddr fd00::/64 oifname != \"wg*\" masquerade")); // 3. IP Forwarding inspection let status = engine.get_forwarding_status().await.expect("forwarding"); assert!(status.ipv4_enabled); } #[test] fn test_nftables_builder_ordering_and_rules() { let now = Utc::now().naive_utc(); let r_prio10 = FirewallRule { id: Uuid::new_v4(), name: "Low Priority Accept".to_string(), interface_id: None, peer_id: None, direction: FirewallDirection::In, action: FirewallAction::Accept, protocol: FirewallProtocol::Tcp, source: None, destination: None, source_port: None, destination_port: Some(80), port_range: None, priority: 10, enabled: true, description: None, created_at: now, updated_at: now, }; let r_prio1 = FirewallRule { id: Uuid::new_v4(), name: "High Priority Drop".to_string(), interface_id: None, peer_id: None, direction: FirewallDirection::In, action: FirewallAction::Drop, protocol: FirewallProtocol::Tcp, source: Some("1.2.3.4".to_string()), destination: None, source_port: None, destination_port: Some(80), port_range: None, priority: 1, enabled: true, description: None, created_at: now, updated_at: now, }; let subnets = vec![IpNet::from_str("10.0.0.0/24").unwrap()]; let ruleset = NftablesRulesetBuilder::build(&[r_prio10, r_prio1], false, &subnets); // High priority drop (priority 1) must appear before low priority accept (priority 10) let drop_idx = ruleset.find("1.2.3.4").expect("drop rule"); let accept_idx = ruleset.find("dport 80 accept").expect("accept rule"); assert!( drop_idx < accept_idx, "Higher priority rule (1) must appear before lower priority rule (10)" ); }