From b3498b8e1e75105d65a4ff29e9596f5f99c68b2e Mon Sep 17 00:00:00 2001 From: ghe0 Date: Thu, 22 Aug 2024 23:09:58 +0300 Subject: [PATCH] added tests that are currently failing --- .gitignore | 1 + src/datastore.rs | 124 ++++++++++++++++++++++++++++++++++++- src/persistence.rs | 149 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 268 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c3d77fe..0ce1b92 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build detee_challenge_nodes .cargo .idea +.tmp diff --git a/src/datastore.rs b/src/datastore.rs index 8fe6df0..672d3c6 100644 --- a/src/datastore.rs +++ b/src/datastore.rs @@ -49,7 +49,7 @@ impl std::fmt::Display for SigningError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let error_message = match self { SigningError::CorruptedKey => "The public key is corrupted", - SigningError::KeyNotFound => "Did not find the public key", + SigningError::KeyNotFound => "Did not find the private key", }; write!(f, "{}", error_message) } @@ -280,3 +280,125 @@ impl Store { .find(|k| !self.conns.contains(k)) } } + +#[cfg(test)] +mod tests { + use super::*; + use dashmap::DashMap; + use ed25519_dalek::SigningKey; + use rand::rngs::OsRng; + use std::time::{SystemTime, UNIX_EPOCH}; + + #[test] + fn test_node_info_creation() { + let keypair = SigningKey::generate(&mut OsRng); + let node_info = NodeInfo { + pubkey: keypair.verifying_key(), + updated_at: SystemTime::now(), + public: true, + }; + + assert_eq!(node_info.pubkey, keypair.verifying_key()); + assert!(node_info.updated_at >= UNIX_EPOCH); + assert!(node_info.public); + } + + #[test] + fn test_store_creation() { + let store = Store { + nodes: DashMap::new(), + conns: DashSet::new(), + keys: DashMap::new(), + }; + + assert!(store.nodes.is_empty()); + assert!(store.conns.is_empty()); + assert!(store.keys.is_empty()); + } + + #[test] + fn test_signing_error_from_hex_error() { + let hex_error: Result<(), hex::FromHexError> = + Err(hex::FromHexError::InvalidHexCharacter { c: 'a', index: 0 }); + let signing_error: SigningError = hex_error.unwrap_err().into(); + + match signing_error { + SigningError::CorruptedKey => assert!(true), + _ => assert!(false, "Expected SigningError::CorruptedKey"), + } + } + + #[tokio::test] + async fn test_sign_message_with_key() { + let keypair = SigningKey::generate(&mut OsRng); + let pubkey_hex = hex::encode(keypair.verifying_key().as_bytes()); + let store = Store { + nodes: DashMap::new(), + conns: DashSet::new(), + keys: DashMap::new(), + }; + store.keys.insert(keypair.verifying_key(), keypair); + + let message = "Test message"; + let result = store.sign_message_with_key(message, &pubkey_hex).await; + + assert!(result.is_ok()); + if let Ok(signature) = result { + assert!(!signature.is_empty()); + } + } + + #[tokio::test] + async fn test_process_node_update() { + let keypair = SigningKey::generate(&mut OsRng); + let node_update = NodeUpdate { + ip: "127.0.0.1".to_string(), + keypair: hex::encode(keypair.as_bytes()), + updated_at: Some(prost_types::Timestamp { + seconds: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64, + nanos: 0, + }), + public: true, + }; + + let store = Store { + nodes: DashMap::new(), + conns: DashSet::new(), + keys: DashMap::new(), + }; + + let result = store.process_node_update(node_update).await; + + assert!(result); + assert!(store.nodes.contains_key("127.0.0.1")); + } + + #[tokio::test] + async fn test_get_full_node_list() { + let keypair = SigningKey::generate(&mut OsRng); + let node_info = NodeInfo { + pubkey: keypair.verifying_key(), + updated_at: SystemTime::now(), + public: true, + }; + + let store = Store { + nodes: DashMap::new(), + conns: DashSet::new(), + keys: DashMap::new(), + }; + + store.nodes.insert("127.0.0.1".to_string(), node_info); + store.keys.insert(keypair.verifying_key(), keypair.clone()); + + let node_list = store.get_full_node_list().await; + + assert_eq!(node_list.len(), 1); + assert_eq!(node_list[0].ip, "127.0.0.1"); + assert_eq!(node_list[0].keypair, hex::encode(keypair.as_bytes())); + assert!(node_list[0].public); + } +} diff --git a/src/persistence.rs b/src/persistence.rs index 870ba39..cf3b439 100644 --- a/src/persistence.rs +++ b/src/persistence.rs @@ -8,7 +8,7 @@ use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom}; use tokio::sync::Mutex; -const DATA_SIZE: usize = 78; +const DATA_SIZE: usize = 76; enum Error { CorruptedIP, @@ -20,6 +20,7 @@ impl From for Error { } } +#[derive(Clone)] struct Node { ip: Ipv4Addr, keypair: SigningKey, @@ -50,7 +51,7 @@ impl Node { let mut result = [0; DATA_SIZE]; result[0..4].copy_from_slice(&self.ip.octets()); result[4..68].copy_from_slice(&self.keypair.to_keypair_bytes()); - result[69..DATA_SIZE].copy_from_slice( + result[68..DATA_SIZE].copy_from_slice( &self .joined_at .duration_since(UNIX_EPOCH) @@ -66,7 +67,7 @@ impl Node { let ip: Ipv4Addr = ip.into(); let keypair: [u8; KEYPAIR_LENGTH] = bytes[4..68].try_into().unwrap(); let keypair: SigningKey = SigningKey::from_keypair_bytes(&keypair).unwrap(); - let joined_at: [u8; 8] = bytes[69..DATA_SIZE].try_into().unwrap(); + let joined_at: [u8; 8] = bytes[68..DATA_SIZE].try_into().unwrap(); let joined_at: u64 = u64::from_le_bytes(joined_at); let joined_at = SystemTime::UNIX_EPOCH + Duration::from_secs(joined_at); Self { @@ -99,10 +100,148 @@ impl FileManager { async fn get_node_by_id(&self, id: u64) -> std::io::Result { let mut file = self.file.lock().await; - file.seek(SeekFrom::Start(id.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)))) - .await?; + file.seek(SeekFrom::Start( + id.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)), + )) + .await?; let mut node_bytes = [0; DATA_SIZE]; file.read_exact(&mut node_bytes).await?; Ok(Node::from_bytes(node_bytes)) } } + +#[cfg(test)] +mod tests { + + use super::*; + use ed25519_dalek::SigningKey; + use rand::rngs::OsRng; + use std::io::Result; + use tokio::fs::remove_file; + use tokio::io::AsyncWriteExt; + + const TEST_FILE_PREFIX: &str = ".tmp/test_"; + fn get_test_file_name(function: &str) -> String { + TEST_FILE_PREFIX.to_string() + function + } + async fn setup_test_file(function: &str) -> Result { + let path = get_test_file_name(function); + let mut file = File::create(path.clone()).await?; + file.flush().await?; + drop(file); + FileManager::init(&path).await + } + + #[test] + fn test_node_to_bytes_and_back() { + let keypair = SigningKey::generate(&mut OsRng); + + let original_node = Node { + ip: "192.168.1.1".parse().unwrap(), + keypair: keypair.clone(), + joined_at: SystemTime::now(), + }; + + let node_bytes = original_node.clone().to_bytes(); + let restored_node = Node::from_bytes(node_bytes); + + assert_eq!(original_node.ip_as_string(), restored_node.ip_as_string()); + assert_eq!( + original_node.keypair.to_keypair_bytes(), + restored_node.keypair.to_keypair_bytes() + ); + assert_eq!( + original_node + .joined_at + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + restored_node + .joined_at + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + ); + } + + #[tokio::test] + async fn setup_file_manager() { + match setup_test_file("setup_file_manager").await { + Err(e) => { + panic!("Could not init File Manager: {}", e); + } + _ => {} + } + } + + #[tokio::test] + async fn test_file_manager_append_and_retrieve_node() -> Result<()> { + let function_name = "test_file_manager_append_and_retrieve_node"; + let manager = setup_test_file(function_name).await?; + + let keypair = SigningKey::generate(&mut OsRng); + + let node = Node { + ip: "192.168.1.1".parse().unwrap(), + keypair: keypair.clone(), + joined_at: SystemTime::now(), + }; + + manager.append_node(node.clone()).await.unwrap(); + + let retrieved_node = manager.get_node_by_id(0).await.unwrap(); + + assert_eq!(node.ip_as_string(), retrieved_node.ip_as_string()); + assert_eq!( + node.keypair.to_keypair_bytes(), + retrieved_node.keypair.to_keypair_bytes() + ); + assert_eq!(node.joined_at, retrieved_node.joined_at); + + remove_file(get_test_file_name(function_name)).await?; + + Ok(()) + } + + #[tokio::test] + async fn test_file_manager_multiple_nodes() -> Result<()> { + let function_name = "test_file_manager_multiple_nodes"; + let manager = setup_test_file(function_name).await?; + + let keypair1 = SigningKey::generate(&mut OsRng); + let node1 = Node { + ip: "192.168.1.1".parse().unwrap(), + keypair: keypair1.clone(), + joined_at: SystemTime::now(), + }; + + let keypair2 = SigningKey::generate(&mut OsRng); + let node2 = Node { + ip: "10.0.0.1".parse().unwrap(), + keypair: keypair2.clone(), + joined_at: SystemTime::now(), + }; + + manager.append_node(node1.clone()).await.unwrap(); + manager.append_node(node2.clone()).await.unwrap(); + + let retrieved_node1 = manager.get_node_by_id(0).await?; + let retrieved_node2 = manager.get_node_by_id(1).await?; + + assert_eq!(node1.ip_as_string(), retrieved_node1.ip_as_string()); + assert_eq!( + node1.keypair.to_keypair_bytes(), + retrieved_node1.keypair.to_keypair_bytes() + ); + + assert_eq!(node2.ip_as_string(), retrieved_node2.ip_as_string()); + assert_eq!( + node2.keypair.to_keypair_bytes(), + retrieved_node2.keypair.to_keypair_bytes() + ); + + remove_file(get_test_file_name(function_name)).await?; + + Ok(()) + } +}