use qudag_protocol::{
persistence::PeerStats, MemoryBackend, Node, NodeConfig, NodeStateProvider, PersistedDagState,
PersistedPeer, PersistedState, PersistenceManager, SqliteBackend, StatePersistence,
StateProvider, CURRENT_STATE_VERSION,
};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use tempfile::TempDir;
use tokio::sync::RwLock;
#[tokio::test]
async fn test_memory_backend_persistence() {
let backend = Arc::new(MemoryBackend::default());
let state = create_test_state();
backend.save_state(&state).await.unwrap();
let loaded = backend.load_state().await.unwrap();
assert!(loaded.is_some());
let loaded_state = loaded.unwrap();
assert_eq!(loaded_state.version, state.version);
assert_eq!(loaded_state.node_id, state.node_id);
assert_eq!(loaded_state.peers.len(), state.peers.len());
}
#[tokio::test]
async fn test_sqlite_backend_persistence() {
let temp_dir = TempDir::new().unwrap();
std::fs::create_dir_all(temp_dir.path()).unwrap();
let db_path = temp_dir.path().join("test.db");
let backend = Arc::new(SqliteBackend::new(db_path.clone()).await.unwrap());
let state = create_test_state();
backend.save_state(&state).await.unwrap();
let loaded = backend.load_state().await.unwrap();
assert!(loaded.is_some());
let loaded_state = loaded.unwrap();
assert_eq!(loaded_state.version, state.version);
assert_eq!(loaded_state.node_id, state.node_id);
drop(backend);
let backend2 = Arc::new(SqliteBackend::new(db_path).await.unwrap());
let loaded2 = backend2.load_state().await.unwrap();
assert!(loaded2.is_some());
assert_eq!(loaded2.unwrap().node_id, state.node_id);
}
#[tokio::test]
async fn test_peer_persistence() {
let backend = Arc::new(MemoryBackend::default());
let peers = vec![
PersistedPeer {
id: vec![1, 2, 3, 4],
address: "127.0.0.1:8000".to_string(),
reputation: 80,
last_seen: 1234567890,
stats: PeerStats {
total_connections: 10,
successful_connections: 8,
failed_connections: 2,
bytes_sent: 1024,
bytes_received: 2048,
avg_response_time: 50,
},
blacklisted: false,
whitelisted: true,
metadata: HashMap::new(),
},
PersistedPeer {
id: vec![5, 6, 7, 8],
address: "192.168.1.100:9000".to_string(),
reputation: 60,
last_seen: 1234567900,
stats: PeerStats::default(),
blacklisted: true,
whitelisted: false,
metadata: HashMap::new(),
},
];
backend.save_peers(&peers).await.unwrap();
let loaded = backend.load_peers().await.unwrap();
assert_eq!(loaded.len(), 2);
assert_eq!(loaded[0].id, vec![1, 2, 3, 4]);
assert_eq!(loaded[0].reputation, 80);
assert!(loaded[0].whitelisted);
assert!(!loaded[0].blacklisted);
assert_eq!(loaded[1].id, vec![5, 6, 7, 8]);
assert!(loaded[1].blacklisted);
}
#[tokio::test]
async fn test_dag_state_persistence() {
let backend = Arc::new(MemoryBackend::default());
let dag_state = PersistedDagState {
vertices: HashMap::new(),
tips: HashSet::new(),
voting_records: HashMap::new(),
last_checkpoint: None,
};
backend.save_dag_state(&dag_state).await.unwrap();
let loaded = backend.load_dag_state().await.unwrap();
assert!(loaded.is_some());
}
#[tokio::test]
async fn test_state_validation() {
let backend = Arc::new(MemoryBackend::default());
assert!(backend.validate_state().await.unwrap());
let state = create_test_state();
backend.save_state(&state).await.unwrap();
assert!(backend.validate_state().await.unwrap());
let mut invalid_state = state.clone();
invalid_state.node_id = vec![];
backend.save_state(&invalid_state).await.unwrap();
assert!(!backend.validate_state().await.unwrap());
}
#[tokio::test]
async fn test_backup_and_restore() {
let temp_dir = TempDir::new().unwrap();
let backend = Arc::new(MemoryBackend::default());
let state = create_test_state();
backend.save_state(&state).await.unwrap();
let backup_path = temp_dir.path();
backend.create_backup(backup_path).await.unwrap();
backend
.save_state(&PersistedState {
version: CURRENT_STATE_VERSION,
node_id: vec![99, 99, 99],
protocol_state: qudag_protocol::state::ProtocolState::Initial,
sessions: HashMap::new(),
peers: vec![],
dag_state: PersistedDagState {
vertices: HashMap::new(),
tips: HashSet::new(),
voting_records: HashMap::new(),
last_checkpoint: None,
},
metrics: Default::default(),
last_saved: 0,
})
.await
.unwrap();
let modified = backend.load_state().await.unwrap().unwrap();
assert_eq!(modified.node_id, vec![99, 99, 99]);
backend.restore_backup(backup_path).await.unwrap();
let restored = backend.load_state().await.unwrap().unwrap();
assert_eq!(restored.node_id, state.node_id);
}
#[tokio::test]
async fn test_persistence_manager() {
let backend = Arc::new(MemoryBackend::default());
let manager = PersistenceManager::new(backend.clone());
let recovered = manager.recover_state().await.unwrap();
assert!(recovered.is_none());
let state = create_test_state();
backend.save_state(&state).await.unwrap();
let recovered = manager.recover_state().await.unwrap();
assert!(recovered.is_some());
assert_eq!(recovered.unwrap().node_id, state.node_id);
}
#[tokio::test]
async fn test_state_export_import() {
let temp_dir = TempDir::new().unwrap();
let backend = Arc::new(MemoryBackend::default());
let manager = PersistenceManager::new(backend.clone());
let state = create_test_state();
backend.save_state(&state).await.unwrap();
let export_path = temp_dir.path().join("export.json");
manager.export_state(&export_path).await.unwrap();
backend
.save_state(&PersistedState {
version: CURRENT_STATE_VERSION,
node_id: vec![88, 88, 88],
protocol_state: qudag_protocol::state::ProtocolState::Initial,
sessions: HashMap::new(),
peers: vec![],
dag_state: PersistedDagState {
vertices: HashMap::new(),
tips: HashSet::new(),
voting_records: HashMap::new(),
last_checkpoint: None,
},
metrics: Default::default(),
last_saved: 0,
})
.await
.unwrap();
manager.import_state(&export_path).await.unwrap();
let imported = backend.load_state().await.unwrap().unwrap();
assert_eq!(imported.node_id, state.node_id);
assert_eq!(imported.peers.len(), state.peers.len());
}
#[tokio::test]
async fn test_node_with_persistence() {
let temp_dir = TempDir::new().unwrap();
let config = NodeConfig {
data_dir: temp_dir.path().to_path_buf(),
..Default::default()
};
std::fs::create_dir_all(&config.data_dir).unwrap();
let mut node = Node::with_persistence(config.clone()).await.unwrap();
node.start().await.unwrap();
node.save_state().await.unwrap();
let backup_path = temp_dir.path().join("backup");
std::fs::create_dir_all(&backup_path).unwrap();
node.create_backup(backup_path.clone()).await.unwrap();
node.stop().await.unwrap();
let node2 = Node::with_persistence(config).await.unwrap();
assert!(node2.has_persistence());
}
#[tokio::test]
async fn test_node_state_provider() {
let config = NodeConfig::default();
let node = Arc::new(RwLock::new(Node::new(config).await.unwrap()));
let provider = NodeStateProvider::new(node.clone());
let state = provider.get_current_state().await.unwrap();
assert_eq!(state.version, CURRENT_STATE_VERSION);
assert!(!state.node_id.is_empty());
}
fn create_test_state() -> PersistedState {
PersistedState {
version: CURRENT_STATE_VERSION,
node_id: vec![1, 2, 3, 4, 5, 6, 7, 8],
protocol_state: qudag_protocol::state::ProtocolState::Active(
qudag_protocol::state::ActiveState::Normal,
),
sessions: HashMap::new(),
peers: vec![PersistedPeer {
id: vec![10, 11, 12, 13],
address: "127.0.0.1:8000".to_string(),
reputation: 75,
last_seen: 1234567890,
stats: PeerStats::default(),
blacklisted: false,
whitelisted: false,
metadata: HashMap::new(),
}],
dag_state: PersistedDagState {
vertices: HashMap::new(),
tips: HashSet::new(),
voting_records: HashMap::new(),
last_checkpoint: None,
},
metrics: qudag_protocol::state::StateMachineMetrics {
current_state: qudag_protocol::state::ProtocolState::Active(
qudag_protocol::state::ActiveState::Normal,
),
uptime: std::time::Duration::from_secs(100),
active_sessions: 5,
total_state_transitions: 10,
total_messages_sent: 1000,
total_messages_received: 1200,
total_bytes_sent: 100000,
total_bytes_received: 120000,
total_errors: 2,
},
last_saved: 1234567890,
}
}