Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,66 @@ impl Config {
self.wallet.hotkey.clone()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_wallet_config_defaults() {
let config = WalletConfig::new(None, None, None);
assert_eq!(config.name, BT_WALLET_NAME);
assert_eq!(config.hotkey, BT_WALLET_HOTKEY);
assert_eq!(config.path, BT_WALLET_PATH);
}

#[test]
fn test_wallet_config_custom_values() {
let config = WalletConfig::new(
Some("my_wallet".to_string()),
Some("my_hotkey".to_string()),
Some("/custom/path/".to_string()),
);
assert_eq!(config.name, "my_wallet");
assert_eq!(config.hotkey, "my_hotkey");
assert_eq!(config.path, "/custom/path/");
}

#[test]
fn test_wallet_config_partial_overrides() {
let config = WalletConfig::new(Some("custom_name".to_string()), None, None);
assert_eq!(config.name, "custom_name");
assert_eq!(config.hotkey, BT_WALLET_HOTKEY);
assert_eq!(config.path, BT_WALLET_PATH);
}

#[test]
fn test_config_delegates_to_wallet_config() {
let config = Config::new(
Some("test_wallet".to_string()),
Some("test_hotkey".to_string()),
Some("/test/path/".to_string()),
);
assert_eq!(config.name(), "test_wallet");
assert_eq!(config.hotkey(), "test_hotkey");
assert_eq!(config.path(), "/test/path/");
}

#[test]
fn test_config_display_format() {
let config = Config::new(None, None, None);
let display = format!("{}", config);
assert!(display.contains(BT_WALLET_NAME));
assert!(display.contains(BT_WALLET_PATH));
assert!(display.contains(BT_WALLET_HOTKEY));
}

#[test]
fn test_config_clone() {
let config = Config::new(Some("cloned".to_string()), Some("hotkey".to_string()), None);
let cloned = config.clone();
assert_eq!(config.name(), cloned.name());
assert_eq!(config.hotkey(), cloned.hotkey());
assert_eq!(config.path(), cloned.path());
}
}
148 changes: 148 additions & 0 deletions src/keyfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,3 +1038,151 @@ impl Keyfile {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::keypair::Keypair;

#[test]
fn test_serialize_keypair_with_mnemonic() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let data =
serialized_keypair_to_keyfile_data(&keypair).expect("Failed to serialize keypair");
assert!(!data.is_empty());
let json_str = std::str::from_utf8(&data).expect("Invalid UTF-8");
assert!(json_str.contains("ss58Address"));
assert!(json_str.contains("publicKey"));
assert!(json_str.contains("secretPhrase"));
}

#[test]
fn test_serialize_deserialize_roundtrip() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let original = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let data =
serialized_keypair_to_keyfile_data(&original).expect("Failed to serialize keypair");
let restored =
deserialize_keypair_from_keyfile_data(&data).expect("Failed to deserialize keypair");
assert_eq!(original.ss58_address(), restored.ss58_address());
}

#[test]
fn test_serialize_pubkey_only_keypair() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let full_keypair =
Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let ss58 = full_keypair.ss58_address().expect("No ss58 address");
let pubkey_only =
Keypair::new(Some(ss58), None, None, 42, None, 1).expect("Failed to create pubkey kp");
let data = serialized_keypair_to_keyfile_data(&pubkey_only)
.expect("Failed to serialize pubkey keypair");
let json_str = std::str::from_utf8(&data).expect("Invalid UTF-8");
assert!(json_str.contains("ss58Address"));
assert!(!json_str.contains("secretPhrase"));
}

#[test]
fn test_deserialize_invalid_json() {
let invalid_data = b"not valid json";
let result = deserialize_keypair_from_keyfile_data(invalid_data);
assert!(result.is_err());
}

#[test]
fn test_deserialize_empty_json() {
let empty_json = b"{}";
let result = deserialize_keypair_from_keyfile_data(empty_json);
assert!(result.is_err());
}

#[test]
fn test_keyfile_data_is_encrypted_nacl() {
assert!(keyfile_data_is_encrypted_nacl(b"$NACLsomedata"));
assert!(!keyfile_data_is_encrypted_nacl(b"plaintext"));
}

#[test]
fn test_keyfile_data_is_encrypted_ansible() {
assert!(keyfile_data_is_encrypted_ansible(b"$ANSIBLE_VAULTdata"));
assert!(!keyfile_data_is_encrypted_ansible(b"plaintext"));
}

#[test]
fn test_keyfile_data_is_encrypted_legacy() {
assert!(keyfile_data_is_encrypted_legacy(b"gAAAAAsomedata"));
assert!(!keyfile_data_is_encrypted_legacy(b"plaintext"));
}

#[test]
fn test_keyfile_data_is_encrypted_combined() {
assert!(keyfile_data_is_encrypted(b"$NACLdata"));
assert!(keyfile_data_is_encrypted(b"$ANSIBLE_VAULTdata"));
assert!(keyfile_data_is_encrypted(b"gAAAAAsomedata"));
assert!(!keyfile_data_is_encrypted(b"plaintext data"));
}

#[test]
fn test_keyfile_data_encryption_method() {
assert_eq!(keyfile_data_encryption_method(b"$NACLdata"), "NaCl");
assert_eq!(
keyfile_data_encryption_method(b"$ANSIBLE_VAULTdata"),
"Ansible Vault"
);
assert_eq!(keyfile_data_encryption_method(b"gAAAAAsomedata"), "legacy");
assert_eq!(keyfile_data_encryption_method(b"plaintext"), "unknown");
}

#[test]
fn test_keyfile_new() {
let keyfile = Keyfile::new(
"/tmp/test/keyfile".to_string(),
Some("test".to_string()),
false,
)
.expect("Failed to create keyfile");
assert_eq!(keyfile.path, "/tmp/test/keyfile");
}

#[test]
fn test_keyfile_default_name() {
let keyfile = Keyfile::new("/tmp/test/keyfile".to_string(), None, false)
.expect("Failed to create keyfile");
let name = keyfile.get_name().expect("Failed to get name");
assert_eq!(name, "Keyfile");
}

#[test]
fn test_keyfile_get_path() {
let path = "/tmp/test/my_keyfile".to_string();
let keyfile = Keyfile::new(path.clone(), None, false).expect("Failed to create keyfile");
assert_eq!(keyfile.get_path().expect("Failed to get path"), path);
}

#[test]
fn test_keyfile_env_var_name() {
let keyfile = Keyfile::new("/tmp/test/keyfile".to_string(), None, false)
.expect("Failed to create keyfile");
let env_name = keyfile.env_var_name().expect("Failed to get env var name");
assert!(env_name.starts_with("BT_PW_"));
assert!(env_name.contains("TMP"));
}

#[test]
fn test_keyfile_nonexistent_file() {
let keyfile = Keyfile::new("/nonexistent/path/keyfile".to_string(), None, false)
.expect("Failed to create keyfile");
assert!(!keyfile
.exists_on_device()
.expect("Failed to check existence"));
}

#[test]
fn test_keyfile_display() {
let keyfile = Keyfile::new("/tmp/test_display".to_string(), None, false)
.expect("Failed to create keyfile");
let display = format!("{}", keyfile);
assert!(display.contains("/tmp/test_display"));
}
}
140 changes: 140 additions & 0 deletions src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,143 @@ impl fmt::Debug for Keypair {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_keypair_from_mnemonic() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
assert!(keypair.ss58_address().is_some());
assert!(keypair.public_key().unwrap().is_some());
assert!(keypair.mnemonic().is_some());
}

#[test]
fn test_keypair_generate_mnemonic_word_count() {
for n_words in [12, 15, 18, 21, 24] {
let mnemonic =
Keypair::generate_mnemonic(n_words).expect("Failed to generate mnemonic");
let word_count = mnemonic.split_whitespace().count();
assert_eq!(
word_count, n_words,
"Expected {} words, got {}",
n_words, word_count
);
}
}

#[test]
fn test_keypair_from_uri() {
let keypair = Keypair::create_from_uri("//Alice").expect("Failed to create from URI");
assert!(keypair.ss58_address().is_some());
assert!(keypair.public_key().unwrap().is_some());
}

#[test]
fn test_keypair_sign_and_verify() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let data = b"test message".to_vec();
let signature = keypair.sign(data.clone()).expect("Failed to sign");
assert!(!signature.is_empty());
let verified = keypair.verify(data, signature).expect("Failed to verify");
assert!(verified);
}

#[test]
fn test_keypair_verify_wrong_data() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let data = b"original message".to_vec();
let wrong_data = b"tampered message".to_vec();
let signature = keypair.sign(data).expect("Failed to sign");
let verified = keypair
.verify(wrong_data, signature)
.expect("Failed to verify");
assert!(!verified);
}

#[test]
fn test_keypair_from_ss58_address() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let original = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let ss58 = original.ss58_address().expect("Failed to get ss58");
let restored =
Keypair::new(Some(ss58.clone()), None, None, 42, None, 1).expect("Failed to restore");
assert_eq!(restored.ss58_address().unwrap(), ss58);
}

#[test]
fn test_keypair_from_public_key_hex() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let original = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let pub_key_bytes = original.public_key().unwrap().unwrap();
let pub_key_hex = hex::encode(&pub_key_bytes);
let restored = Keypair::new(None, Some(pub_key_hex), None, 42, None, 1)
.expect("Failed to restore from pubkey");
assert!(restored.ss58_address().is_some());
}

#[test]
fn test_keypair_unsupported_crypto_type() {
let result = Keypair::new(None, Some("a".repeat(64)), None, 42, None, 0);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unsupported crypto type"));
}

#[test]
fn test_keypair_no_address_or_key() {
let result = Keypair::new(None, None, None, 42, None, 1);
assert!(result.is_err());
}

#[test]
fn test_keypair_default() {
let kp = Keypair::default();
assert!(kp.ss58_address().is_none());
assert_eq!(kp.ss58_format(), 42);
assert_eq!(kp.crypto_type(), 1);
assert!(kp.mnemonic().is_none());
}

#[test]
fn test_keypair_display() {
let kp = Keypair::create_from_uri("//Bob").expect("Failed to create keypair");
let display = format!("{}", kp);
assert!(display.starts_with("<Keypair (address="));
assert!(display.ends_with(")>"));
}

#[test]
fn test_keypair_debug() {
let kp = Keypair::default();
let debug = format!("{:?}", kp);
assert!(debug.contains("Keypair"));
}

#[test]
fn test_keypair_clone_preserves_address() {
let kp = Keypair::create_from_uri("//Charlie").expect("Failed to create keypair");
let cloned = kp.clone();
assert_eq!(kp.ss58_address(), cloned.ss58_address());
}

#[test]
fn test_keypair_seed_hex() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let kp = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
assert!(kp.seed_hex().is_some());
}

#[test]
fn test_keypair_from_seed() {
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
let kp = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
let seed = kp.seed_hex().expect("No seed");
let restored = Keypair::create_from_seed(seed).expect("Failed to create from seed");
assert_eq!(kp.ss58_address(), restored.ss58_address());
}
}
27 changes: 14 additions & 13 deletions src/python_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,21 +925,22 @@ except argparse.ArgumentError:

/// Checks for existing coldkeypub and hotkeys, and creates them if non-existent.
///
/// Arguments:
/// coldkey_use_password (bool): Whether to use a password for coldkey. Defaults to ``True``.
/// hotkey_use_password (bool): Whether to use a password for hotkey. Defaults to ``False``.
/// save_coldkey_to_env (bool): Whether to save a coldkey password to local env. Defaults to ``False``.
/// save_hotkey_to_env (bool): Whether to save a hotkey password to local env. Defaults to ``False``.
/// coldkey_password (Optional[str]): Coldkey password for encryption. Defaults to ``None``. If `coldkey_password` is passed, then `coldkey_use_password` is automatically ``True``.
/// hotkey_password (Optional[str]): Hotkey password for encryption. Defaults to ``None``. If `hotkey_password` is passed, then `hotkey_use_password` is automatically ``True``.
/// overwrite (bool): Whether to overwrite an existing keys. Defaults to ``False``.
/// suppress (bool): If ``True``, suppresses the display of the keys mnemonic message. Defaults to ``False``.
/// # Arguments
///
/// Returns:
/// Wallet instance with created keys.
/// * `coldkey_use_password` - Whether to use a password for coldkey. Defaults to `true`.
/// * `hotkey_use_password` - Whether to use a password for hotkey. Defaults to `false`.
/// * `save_coldkey_to_env` - Whether to save a coldkey password to local env. Defaults to `false`.
/// * `save_hotkey_to_env` - Whether to save a hotkey password to local env. Defaults to `false`.
/// * `coldkey_password` - Coldkey password for encryption. Defaults to `None`.
/// If provided, `coldkey_use_password` is automatically `true`.
/// * `hotkey_password` - Hotkey password for encryption. Defaults to `None`.
/// If provided, `hotkey_use_password` is automatically `true`.
/// * `overwrite` - Whether to overwrite existing keys. Defaults to `false`.
/// * `suppress` - If `true`, suppresses the display of the keys mnemonic message. Defaults to `false`.
///
/// Raises:
/// WalletError: If key generation or file operations fail.
/// # Returns
///
/// Wallet instance with created keys, or error.
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (coldkey_use_password=true, hotkey_use_password=false, save_coldkey_to_env=false, save_hotkey_to_env=false, coldkey_password=None, hotkey_password=None, overwrite=false, suppress=false))]
pub fn create(
Expand Down
Loading
Loading