204 lines
6.5 KiB
Rust
204 lines
6.5 KiB
Rust
//! Integration tests for Phase 4 WireGuard Engine, Client Config Generator, and QR Code system.
|
|
|
|
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,
|
|
};
|
|
use nx9_wireguard::{
|
|
ClientConfigBuilder, SimulatedWireGuardEngine, WireGuardEngine, generate_qr_ascii,
|
|
generate_qr_data_url, generate_qr_png_bytes, generate_qr_svg,
|
|
};
|
|
use std::str::FromStr;
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::test]
|
|
async fn test_wireguard_engine_lifecycle_and_telemetry() {
|
|
let engine = SimulatedWireGuardEngine::new();
|
|
|
|
let (srv_priv, srv_pub) = generate_keypair();
|
|
let (peer1_priv, peer1_pub) = generate_keypair();
|
|
let (peer2_priv, peer2_pub) = generate_keypair();
|
|
let now = Utc::now().naive_utc();
|
|
|
|
let iface = Interface {
|
|
id: Uuid::new_v4(),
|
|
name: "wg0".to_string(),
|
|
role: InterfaceRole::Overlay,
|
|
private_key: srv_priv,
|
|
public_key: srv_pub.clone(),
|
|
listen_port: Some(51820),
|
|
address_v4: IpNet::from_str("10.0.0.1/24").unwrap(),
|
|
address_v6: None,
|
|
mtu: Some(1420),
|
|
dns: Some("1.1.1.1".to_string()),
|
|
enabled: true,
|
|
pre_up: None,
|
|
post_up: None,
|
|
pre_down: None,
|
|
post_down: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let p1 = Peer {
|
|
id: Uuid::new_v4(),
|
|
interface_id: iface.id,
|
|
name: "laptop-user".to_string(),
|
|
peer_type: PeerType::RoadWarrior,
|
|
state: PeerState::Active,
|
|
public_key: peer1_pub.clone(),
|
|
private_key: Some(peer1_priv),
|
|
preshared_key: Some(generate_preshared_key()),
|
|
endpoint: Some("198.51.100.2:45000".to_string()),
|
|
allowed_ips: "10.0.0.2/32".to_string(),
|
|
server_allowed_ips: None,
|
|
address_v4: Some(IpNet::from_str("10.0.0.2/32").unwrap()),
|
|
address_v6: None,
|
|
dns: None,
|
|
mtu: None,
|
|
persistent_keepalive: Some(25),
|
|
profile: PeerProfile::FullTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let p2 = Peer {
|
|
id: Uuid::new_v4(),
|
|
interface_id: iface.id,
|
|
name: "phone-user".to_string(),
|
|
peer_type: PeerType::RoadWarrior,
|
|
state: PeerState::Disabled, // disabled peer should NOT be in active kernel set
|
|
public_key: peer2_pub.clone(),
|
|
private_key: Some(peer2_priv),
|
|
preshared_key: None,
|
|
endpoint: None,
|
|
allowed_ips: "10.0.0.3/32".to_string(),
|
|
server_allowed_ips: None,
|
|
address_v4: Some(IpNet::from_str("10.0.0.3/32").unwrap()),
|
|
address_v6: None,
|
|
dns: None,
|
|
mtu: None,
|
|
persistent_keepalive: None,
|
|
profile: PeerProfile::FullTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
// 1. Sync interface with engine
|
|
engine
|
|
.sync_interface(&iface, &[p1.clone(), p2.clone()])
|
|
.await
|
|
.expect("sync interface");
|
|
|
|
// 2. Read live interface stats
|
|
let stats = engine
|
|
.get_interface_stats("wg0")
|
|
.await
|
|
.expect("get stats")
|
|
.expect("interface exists");
|
|
assert_eq!(stats.name, "wg0");
|
|
assert_eq!(stats.public_key, srv_pub.as_str());
|
|
assert_eq!(stats.addresses, vec!["10.0.0.1/24".to_string()]);
|
|
assert_eq!(stats.mtu, Some(1420));
|
|
assert!(stats.is_up);
|
|
assert_eq!(
|
|
stats.peers.len(),
|
|
1,
|
|
"only active peers should be synchronized"
|
|
);
|
|
assert_eq!(stats.peers[0].public_key, peer1_pub.as_str());
|
|
|
|
// 3. Simulate peer traffic activity
|
|
engine
|
|
.simulate_peer_activity("wg0", peer1_pub.as_str(), 1024, 2048)
|
|
.await
|
|
.expect("simulate activity");
|
|
|
|
let updated_stats = engine.get_interface_stats("wg0").await.unwrap().unwrap();
|
|
assert_eq!(updated_stats.peers[0].rx_bytes, 1024);
|
|
assert_eq!(updated_stats.peers[0].tx_bytes, 2048);
|
|
assert!(updated_stats.peers[0].last_handshake_at.is_some());
|
|
|
|
// 4. Delete interface
|
|
engine.delete_interface("wg0").await.expect("delete");
|
|
let after_del = engine.get_interface_stats("wg0").await.expect("query");
|
|
assert!(after_del.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_client_config_and_qr_codes() {
|
|
let (srv_priv, srv_pub) = generate_keypair();
|
|
let (peer_priv, peer_pub) = generate_keypair();
|
|
let psk = generate_preshared_key();
|
|
let now = Utc::now().naive_utc();
|
|
|
|
let iface = Interface {
|
|
id: Uuid::new_v4(),
|
|
name: "wg0".to_string(),
|
|
role: InterfaceRole::Overlay,
|
|
private_key: srv_priv,
|
|
public_key: srv_pub,
|
|
listen_port: Some(51820),
|
|
address_v4: IpNet::from_str("10.0.0.1/24").unwrap(),
|
|
address_v6: None,
|
|
mtu: Some(1420),
|
|
dns: Some("1.1.1.1".to_string()),
|
|
enabled: true,
|
|
pre_up: None,
|
|
post_up: None,
|
|
pre_down: None,
|
|
post_down: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let peer = Peer {
|
|
id: Uuid::new_v4(),
|
|
interface_id: iface.id,
|
|
name: "ipad-charlie".to_string(),
|
|
peer_type: PeerType::RoadWarrior,
|
|
state: PeerState::Active,
|
|
public_key: peer_pub,
|
|
private_key: Some(peer_priv),
|
|
preshared_key: Some(psk),
|
|
endpoint: None,
|
|
allowed_ips: "10.0.0.5/32".to_string(),
|
|
server_allowed_ips: None,
|
|
address_v4: Some(IpNet::from_str("10.0.0.5/32").unwrap()),
|
|
address_v6: None,
|
|
dns: None,
|
|
mtu: None,
|
|
persistent_keepalive: Some(25),
|
|
profile: PeerProfile::FullTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let config_str = ClientConfigBuilder::build(&peer, &iface, "203.0.113.10").unwrap();
|
|
assert!(config_str.contains("[Interface]"));
|
|
assert!(config_str.contains("[Peer]"));
|
|
assert!(config_str.contains("Endpoint = 203.0.113.10:51820"));
|
|
|
|
// Verify all QR code output formats
|
|
let svg = generate_qr_svg(&config_str).unwrap();
|
|
assert!(svg.contains("<svg"));
|
|
|
|
let png_bytes = generate_qr_png_bytes(&config_str).unwrap();
|
|
assert!(!png_bytes.is_empty());
|
|
assert_eq!(&png_bytes[1..4], b"PNG");
|
|
|
|
let data_url = generate_qr_data_url(&config_str).unwrap();
|
|
assert!(data_url.starts_with("data:image/png;base64,"));
|
|
|
|
let ascii = generate_qr_ascii(&config_str).unwrap();
|
|
assert!(!ascii.is_empty());
|
|
}
|