Files
nx9-wg/crates/nx9-wg-db/tests/test_system_repositories.rs
T

396 lines
11 KiB
Rust

//! Tests for Settings, Audit Log, and Backup repositories.
use chrono::Utc;
use nx9_wg_core::types::audit::AuditEventType;
use nx9_wg_core::types::backup::BackupMeta;
use nx9_wg_db::{AuditFilter, Store};
use uuid::Uuid;
#[tokio::test]
async fn test_settings_repository() {
let store = Store::connect_in_memory().await.expect("connect");
store.migrate().await.expect("migrate");
// Initially missing key returns None
assert!(
store
.get_setting("non_existent")
.await
.expect("get")
.is_none()
);
assert!(
store
.get_setting_value("non_existent")
.await
.expect("get val")
.is_none()
);
// Set normal setting
store
.set_setting("server_endpoint", "vpn.example.com:51820", false)
.await
.expect("set");
let ep = store
.get_setting("server_endpoint")
.await
.expect("get")
.expect("setting found");
assert_eq!(ep.value, "vpn.example.com:51820");
assert!(!ep.is_secret);
// Set secret setting
store
.set_setting("session_secret", "SuperSecretKey999", true)
.await
.expect("set secret");
let sec = store
.get_setting("session_secret")
.await
.expect("get")
.expect("setting found");
assert_eq!(sec.value, "SuperSecretKey999");
assert!(sec.is_secret);
// Verify Debug formatting of secret setting redacts value
let sec_debug = format!("{:?}", sec);
assert!(sec_debug.contains("[REDACTED]"));
assert!(!sec_debug.contains("SuperSecretKey999"));
// Upsert existing setting
store
.set_setting("server_endpoint", "vpn2.example.com:51820", false)
.await
.expect("upsert");
let ep2 = store
.get_setting_value("server_endpoint")
.await
.expect("get")
.expect("value found");
assert_eq!(ep2, "vpn2.example.com:51820");
// List settings
let all = store.list_settings().await.expect("list");
assert_eq!(all.len(), 2);
// Delete setting
store
.delete_setting("server_endpoint")
.await
.expect("delete");
assert!(
store
.get_setting("server_endpoint")
.await
.expect("get")
.is_none()
);
}
#[tokio::test]
async fn test_audit_log_append_only_and_filtering() {
let store = Store::connect_in_memory().await.expect("connect");
store.migrate().await.expect("migrate");
// Record various events
store
.record_audit(
AuditEventType::Login,
"admin",
Some("session"),
Some("sess-1"),
Some("Admin login succeeded"),
None,
Some("192.168.1.50"),
)
.await
.expect("record login");
store
.record_audit(
AuditEventType::InterfaceCreate,
"admin",
Some("interface"),
Some("wg0"),
Some("Interface wg0 created"),
None,
Some("192.168.1.50"),
)
.await
.expect("record iface create");
store
.record_audit(
AuditEventType::PeerCreate,
"admin",
Some("peer"),
Some("peer-alice"),
Some("Peer alice created"),
None,
Some("192.168.1.50"),
)
.await
.expect("record peer create");
// Total count
let total = store
.count_audit_events(&AuditFilter::default())
.await
.expect("count");
assert_eq!(total, 3);
// Filter by event_type
let login_filter = AuditFilter {
event_type: Some(AuditEventType::Login),
..Default::default()
};
let login_events = store
.list_audit_events(&login_filter, 10, 0)
.await
.expect("list login");
assert_eq!(login_events.len(), 1);
assert_eq!(login_events[0].event_type, AuditEventType::Login);
// Filter by resource_type
let peer_filter = AuditFilter {
resource_type: Some("peer".to_string()),
..Default::default()
};
let peer_events = store
.list_audit_events(&peer_filter, 10, 0)
.await
.expect("list peer events");
assert_eq!(peer_events.len(), 1);
assert_eq!(peer_events[0].resource_id.as_deref(), Some("peer-alice"));
// Pagination test: limit 2, offset 0 -> 2 items; offset 2 -> 1 item
let page1 = store
.list_audit_events(&AuditFilter::default(), 2, 0)
.await
.expect("page1");
assert_eq!(page1.len(), 2);
let page2 = store
.list_audit_events(&AuditFilter::default(), 2, 2)
.await
.expect("page2");
assert_eq!(page2.len(), 1);
}
#[tokio::test]
async fn test_backup_metadata_crud() {
let store = Store::connect_in_memory().await.expect("connect");
store.migrate().await.expect("migrate");
let now = Utc::now().naive_utc();
let backup_id = Uuid::new_v4();
let meta = BackupMeta {
id: backup_id,
filename: "nx9-wg-backup-20260816.tar.gz".to_string(),
size_bytes: 1048576,
checksum: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
.to_string(),
schema_version: "1".to_string(),
encrypted: true,
description: Some("Automated nightly backup".to_string()),
created_at: now,
};
store
.create_backup_meta(&meta)
.await
.expect("create_backup_meta");
let fetched = store
.get_backup_meta(backup_id)
.await
.expect("get")
.expect("backup found");
assert_eq!(fetched.filename, "nx9-wg-backup-20260816.tar.gz");
assert_eq!(fetched.size_bytes, 1048576);
assert!(fetched.encrypted);
assert_eq!(fetched.schema_version, "1");
let list = store.list_backups().await.expect("list");
assert_eq!(list.len(), 1);
store.delete_backup_meta(backup_id).await.expect("delete");
assert!(
store
.get_backup_meta(backup_id)
.await
.expect("get")
.is_none()
);
}
#[tokio::test]
async fn test_server_endpoint_settings_crud_and_persistence() {
let store = Store::connect_in_memory().await.expect("connect");
store.migrate().await.expect("migrate");
// 1. Initial state defaults
let initial = store
.get_server_endpoint_settings()
.await
.expect("get initial");
assert_eq!(initial.host, "");
assert_eq!(initial.port, 51820);
assert!(initial.enabled);
// Initial resolution with no settings returns actionable error
let err = store.resolve_server_endpoint(None).await.unwrap_err();
assert!(
err.to_string()
.contains("No reachable WireGuard server endpoint is configured")
);
// 2. Set structured server endpoint settings
let new_settings = nx9_wg_core::types::settings::ServerEndpointSettings {
host: "vpn.thakares.com".to_string(),
port: 51820,
enabled: true,
};
store
.set_server_endpoint_settings(&new_settings)
.await
.expect("set server endpoint settings");
let loaded = store
.get_server_endpoint_settings()
.await
.expect("get loaded settings");
assert_eq!(loaded.host, "vpn.thakares.com");
assert_eq!(loaded.port, 51820);
assert!(loaded.enabled);
// 3. Resolve persistent setting
let resolved = store.resolve_server_endpoint(None).await.expect("resolve");
assert_eq!(resolved, "vpn.thakares.com:51820");
// 4. IPv6 persistence and formatting
let ipv6_settings = nx9_wg_core::types::settings::ServerEndpointSettings {
host: "2001:db8::10".to_string(),
port: 51821,
enabled: true,
};
store
.set_server_endpoint_settings(&ipv6_settings)
.await
.expect("set ipv6");
let resolved_v6 = store
.resolve_server_endpoint(None)
.await
.expect("resolve v6");
assert_eq!(resolved_v6, "[2001:db8::10]:51821");
// 5. Disable setting
let disabled = nx9_wg_core::types::settings::ServerEndpointSettings {
host: "vpn.thakares.com".to_string(),
port: 51820,
enabled: false,
};
store
.set_server_endpoint_settings(&disabled)
.await
.expect("set disabled");
let err = store.resolve_server_endpoint(None).await.unwrap_err();
assert!(
err.to_string()
.contains("No reachable WireGuard server endpoint is configured")
);
}
#[tokio::test]
async fn test_server_endpoint_resolution_precedence() {
let store = Store::connect_in_memory().await.expect("connect");
store.migrate().await.expect("migrate");
// 1. Explicit override with no settings configured
let ep = store
.resolve_server_endpoint(Some("custom.vpn.net:51820"))
.await
.expect("override");
assert_eq!(ep, "custom.vpn.net:51820");
// 2. Configure persistent settings
let settings = nx9_wg_core::types::settings::ServerEndpointSettings {
host: "persistent.vpn.io".to_string(),
port: 51820,
enabled: true,
};
store
.set_server_endpoint_settings(&settings)
.await
.expect("set");
// Precedence test: explicit override beats persistent setting
let overridden = store
.resolve_server_endpoint(Some("override.vpn.io:5555"))
.await
.expect("override beats persistent");
assert_eq!(overridden, "override.vpn.io:5555");
// Precedence test: None uses persistent setting
let default_resolved = store.resolve_server_endpoint(None).await.expect("default");
assert_eq!(default_resolved, "persistent.vpn.io:51820");
// 3. Legacy fallback test when wireguard.server_host is missing
store
.delete_setting(nx9_wg_core::types::settings::SETTING_SERVER_HOST)
.await
.expect("delete new host");
store
.set_setting("server_endpoint", "legacy.vpn.org:51820", false)
.await
.expect("set legacy");
let legacy_resolved = store
.resolve_server_endpoint(None)
.await
.expect("legacy resolved");
assert_eq!(legacy_resolved, "legacy.vpn.org:51820");
}
#[tokio::test]
async fn test_server_endpoint_survives_reopen() {
let temp_dir = tempfile::tempdir().expect("temp dir");
let db_path = temp_dir.path().join("persist_endpoint.db");
let db_url = format!("sqlite://{}?mode=rwc", db_path.display());
{
let store = Store::connect(&db_url).await.expect("connect 1");
store.migrate().await.expect("migrate 1");
let settings = nx9_wg_core::types::settings::ServerEndpointSettings {
host: "vpn.thakares.com".to_string(),
port: 51820,
enabled: true,
};
store
.set_server_endpoint_settings(&settings)
.await
.expect("set");
}
// Reconnect to existing store on disk
{
let store = Store::connect(&db_url).await.expect("connect 2");
let loaded = store
.get_server_endpoint_settings()
.await
.expect("get after reopen");
assert_eq!(loaded.host, "vpn.thakares.com");
assert_eq!(loaded.port, 51820);
assert!(loaded.enabled);
let resolved = store
.resolve_server_endpoint(None)
.await
.expect("resolve after reopen");
assert_eq!(resolved, "vpn.thakares.com:51820");
}
}