//! Tests for WireGuard Interface and Peer repository operations. use chrono::Utc; use ipnet::IpNet; use nx9_wg_core::crypto::{generate_keypair, generate_preshared_key}; use nx9_wg_core::types::wireguard::{ Interface, InterfaceRole, Peer, PeerProfile, PeerState, PeerType, WireGuardPublicKey, }; use nx9_wg_db::Store; use std::str::FromStr; use uuid::Uuid; #[tokio::test] async fn test_interface_and_peer_crud_and_cascade() { let store = Store::connect_in_memory().await.expect("connect"); store.migrate().await.expect("migrate"); let now = Utc::now().naive_utc(); let iface_id = Uuid::new_v4(); let (priv_k, pub_k) = generate_keypair(); let iface = Interface { id: iface_id, name: "wg0".to_string(), role: InterfaceRole::Overlay, private_key: priv_k.clone(), public_key: pub_k.clone(), listen_port: Some(51820), address_v4: IpNet::from_str("10.0.0.1/24").expect("valid cidr"), address_v6: Some(IpNet::from_str("fd00::1/64").expect("valid cidr")), mtu: Some(1420), dns: Some("1.1.1.1, 8.8.8.8".to_string()), enabled: true, pre_up: None, post_up: Some("iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE".to_string()), pre_down: None, post_down: None, created_at: now, updated_at: now, }; store .create_interface(&iface) .await .expect("create_interface"); // Lookup interface by ID and name let fetched = store .get_interface(iface_id) .await .expect("get_interface") .expect("iface found"); assert_eq!(fetched.name, "wg0"); assert_eq!(fetched.role, InterfaceRole::Overlay); assert_eq!(fetched.listen_port, Some(51820)); assert_eq!(fetched.address_v4.to_string(), "10.0.0.1/24"); assert_eq!(fetched.mtu, Some(1420)); let by_name = store .get_interface_by_name("wg0") .await .expect("get_by_name") .expect("found"); assert_eq!(by_name.id, iface_id); // Reject duplicate interface name let dup_iface = Interface { id: Uuid::new_v4(), name: "wg0".to_string(), role: InterfaceRole::Overlay, private_key: priv_k.clone(), public_key: pub_k.clone(), listen_port: Some(51821), address_v4: IpNet::from_str("10.0.1.1/24").unwrap(), address_v6: None, mtu: None, dns: None, enabled: true, pre_up: None, post_up: None, pre_down: None, post_down: None, created_at: now, updated_at: now, }; assert!( store.create_interface(&dup_iface).await.is_err(), "duplicate interface name must fail" ); // Create a peer let peer_id = Uuid::new_v4(); let (peer_priv, peer_pub) = generate_keypair(); let psk = generate_preshared_key(); let peer = Peer { id: peer_id, interface_id: iface_id, name: "phone-alice".to_string(), peer_type: PeerType::RoadWarrior, state: PeerState::Active, public_key: peer_pub.clone(), private_key: Some(peer_priv.clone()), preshared_key: Some(psk.clone()), endpoint: None, allowed_ips: "10.0.0.2/32".to_string(), server_allowed_ips: Some("10.0.0.2/32".to_string()), address_v4: Some(IpNet::from_str("10.0.0.2/32").unwrap()), address_v6: None, dns: Some("10.0.0.1".to_string()), mtu: Some(1420), persistent_keepalive: Some(25), profile: PeerProfile::FullTunnel, expires_at: None, last_handshake_at: None, created_at: now, updated_at: now, }; store.create_peer(&peer).await.expect("create_peer"); // Fetch peer let fetched_peer = store .get_peer(peer_id) .await .expect("get_peer") .expect("peer found"); assert_eq!(fetched_peer.name, "phone-alice"); assert_eq!(fetched_peer.peer_type, PeerType::RoadWarrior); assert_eq!(fetched_peer.state, PeerState::Active); assert_eq!(fetched_peer.profile, PeerProfile::FullTunnel); assert_eq!(fetched_peer.allowed_ips, "10.0.0.2/32"); assert_eq!(fetched_peer.persistent_keepalive, Some(25)); // Lookup peer by name and by public key let by_pname = store .get_peer_by_name(iface_id, "phone-alice") .await .expect("by name") .expect("found"); assert_eq!(by_pname.id, peer_id); let by_pubk = store .get_peer_by_public_key(iface_id, peer_pub.as_str()) .await .expect("by pubk") .expect("found"); assert_eq!(by_pubk.id, peer_id); // Reject duplicate peer name on same interface let dup_pname = Peer { id: Uuid::new_v4(), interface_id: iface_id, name: "phone-alice".to_string(), peer_type: PeerType::RoadWarrior, state: PeerState::Active, public_key: WireGuardPublicKey::new("different_key_123=".to_string()), private_key: None, preshared_key: None, endpoint: None, allowed_ips: "10.0.0.3/32".to_string(), server_allowed_ips: None, address_v4: None, address_v6: None, dns: None, mtu: None, persistent_keepalive: None, profile: PeerProfile::SplitTunnel, expires_at: None, last_handshake_at: None, created_at: now, updated_at: now, }; assert!( store.create_peer(&dup_pname).await.is_err(), "duplicate peer name on same interface must fail" ); // Reject peer for non-existent interface (foreign key violation) let non_existent_iface_peer = Peer { id: Uuid::new_v4(), interface_id: Uuid::new_v4(), name: "orphan-peer".to_string(), peer_type: PeerType::RoadWarrior, state: PeerState::Active, public_key: WireGuardPublicKey::new("orphan_key_123=".to_string()), private_key: None, preshared_key: None, endpoint: None, allowed_ips: "10.0.0.4/32".to_string(), server_allowed_ips: None, address_v4: None, address_v6: None, dns: None, mtu: None, persistent_keepalive: None, profile: PeerProfile::SplitTunnel, expires_at: None, last_handshake_at: None, created_at: now, updated_at: now, }; assert!( store.create_peer(&non_existent_iface_peer).await.is_err(), "peer for non-existent interface must fail foreign key constraint" ); // Test peer state transition: active -> disabled -> revoked store .set_peer_state(peer_id, PeerState::Disabled) .await .expect("set disabled"); let disabled = store.get_peer(peer_id).await.expect("get").expect("peer"); assert_eq!(disabled.state, PeerState::Disabled); store .set_peer_state(peer_id, PeerState::Revoked) .await .expect("set revoked"); let revoked = store.get_peer(peer_id).await.expect("get").expect("peer"); assert_eq!(revoked.state, PeerState::Revoked); // Test update_peer_handshake let handshake_time = Utc::now().naive_utc(); store .update_peer_handshake(peer_id, handshake_time) .await .expect("update handshake"); let after_hs = store.get_peer(peer_id).await.expect("get").expect("peer"); assert!(after_hs.last_handshake_at.is_some()); // Test list_peers_for_interface let peer_list = store .list_peers_for_interface(iface_id) .await .expect("list peers"); assert_eq!(peer_list.len(), 1); // Test upstream interface with listen_port = None let upstream_id = Uuid::new_v4(); let upstream_iface = Interface { id: upstream_id, name: "proton0".to_string(), role: InterfaceRole::Upstream, private_key: priv_k.clone(), public_key: pub_k.clone(), listen_port: None, address_v4: IpNet::from_str("10.2.0.2/32").unwrap(), address_v6: None, mtu: Some(1420), dns: Some("10.2.0.1".to_string()), enabled: true, pre_up: None, post_up: None, pre_down: None, post_down: None, created_at: now, updated_at: now, }; store .create_interface(&upstream_iface) .await .expect("create upstream interface"); let fetched_upstream = store .get_interface(upstream_id) .await .expect("get") .expect("upstream found"); assert_eq!(fetched_upstream.name, "proton0"); assert_eq!(fetched_upstream.role, InterfaceRole::Upstream); assert_eq!(fetched_upstream.listen_port, None); }