Files
nx9-wg/crates/nx9-wg-db/tests/test_wireguard_repositories.rs
T
thakaresandCopilot 2ac6c81dfe cli: avoid data-dir initialization for version; create db parent dirs; redact generated passwords in CLI output
- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization.
- Create parent directories when an explicit --database path is provided.
- Redact printed generated administrator passwords; announce file path or redact instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-16 16:26:24 +05:30

247 lines
7.7 KiB
Rust

//! 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, 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(),
private_key: priv_k.clone(),
public_key: pub_k.clone(),
listen_port: 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.listen_port, 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(),
private_key: priv_k.clone(),
public_key: pub_k.clone(),
listen_port: 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 cascade delete: deleting interface must cascade and delete its peers
store
.delete_interface(iface_id)
.await
.expect("delete interface");
assert!(store.get_interface(iface_id).await.expect("get").is_none());
assert!(
store.get_peer(peer_id).await.expect("get").is_none(),
"peer must be cascade-deleted with interface"
);
}