Skip to content

Commit 6b1ea3d

Browse files
committed
fix: resolve clippy warnings, broken doctest, and add unit tests
- Fix 5 mismatched_lifetime_syntaxes warnings in PyO3 bindings - Fix broken doctest in Wallet::create by converting Python-style docstring to idiomatic Rust doc comment format - Apply cargo fmt to resolve pre-existing formatting inconsistencies - Add 66 unit tests across config, keypair, keyfile, wallet, and utils modules (from 1 to 67 total tests) - All tests pass: cargo test --lib (67/67), cargo test --doc (0 failures)
1 parent 0c64aa4 commit 6b1ea3d

File tree

6 files changed

+603
-25
lines changed

6 files changed

+603
-25
lines changed

src/config.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,66 @@ impl Config {
7272
self.wallet.hotkey.clone()
7373
}
7474
}
75+
76+
#[cfg(test)]
77+
mod tests {
78+
use super::*;
79+
80+
#[test]
81+
fn test_wallet_config_defaults() {
82+
let config = WalletConfig::new(None, None, None);
83+
assert_eq!(config.name, BT_WALLET_NAME);
84+
assert_eq!(config.hotkey, BT_WALLET_HOTKEY);
85+
assert_eq!(config.path, BT_WALLET_PATH);
86+
}
87+
88+
#[test]
89+
fn test_wallet_config_custom_values() {
90+
let config = WalletConfig::new(
91+
Some("my_wallet".to_string()),
92+
Some("my_hotkey".to_string()),
93+
Some("/custom/path/".to_string()),
94+
);
95+
assert_eq!(config.name, "my_wallet");
96+
assert_eq!(config.hotkey, "my_hotkey");
97+
assert_eq!(config.path, "/custom/path/");
98+
}
99+
100+
#[test]
101+
fn test_wallet_config_partial_overrides() {
102+
let config = WalletConfig::new(Some("custom_name".to_string()), None, None);
103+
assert_eq!(config.name, "custom_name");
104+
assert_eq!(config.hotkey, BT_WALLET_HOTKEY);
105+
assert_eq!(config.path, BT_WALLET_PATH);
106+
}
107+
108+
#[test]
109+
fn test_config_delegates_to_wallet_config() {
110+
let config = Config::new(
111+
Some("test_wallet".to_string()),
112+
Some("test_hotkey".to_string()),
113+
Some("/test/path/".to_string()),
114+
);
115+
assert_eq!(config.name(), "test_wallet");
116+
assert_eq!(config.hotkey(), "test_hotkey");
117+
assert_eq!(config.path(), "/test/path/");
118+
}
119+
120+
#[test]
121+
fn test_config_display_format() {
122+
let config = Config::new(None, None, None);
123+
let display = format!("{}", config);
124+
assert!(display.contains(BT_WALLET_NAME));
125+
assert!(display.contains(BT_WALLET_PATH));
126+
assert!(display.contains(BT_WALLET_HOTKEY));
127+
}
128+
129+
#[test]
130+
fn test_config_clone() {
131+
let config = Config::new(Some("cloned".to_string()), Some("hotkey".to_string()), None);
132+
let cloned = config.clone();
133+
assert_eq!(config.name(), cloned.name());
134+
assert_eq!(config.hotkey(), cloned.hotkey());
135+
assert_eq!(config.path(), cloned.path());
136+
}
137+
}

src/keyfile.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,3 +1038,151 @@ impl Keyfile {
10381038
}
10391039
}
10401040
}
1041+
1042+
#[cfg(test)]
1043+
mod tests {
1044+
use super::*;
1045+
use crate::keypair::Keypair;
1046+
1047+
#[test]
1048+
fn test_serialize_keypair_with_mnemonic() {
1049+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
1050+
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
1051+
let data =
1052+
serialized_keypair_to_keyfile_data(&keypair).expect("Failed to serialize keypair");
1053+
assert!(!data.is_empty());
1054+
let json_str = std::str::from_utf8(&data).expect("Invalid UTF-8");
1055+
assert!(json_str.contains("ss58Address"));
1056+
assert!(json_str.contains("publicKey"));
1057+
assert!(json_str.contains("secretPhrase"));
1058+
}
1059+
1060+
#[test]
1061+
fn test_serialize_deserialize_roundtrip() {
1062+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
1063+
let original = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
1064+
let data =
1065+
serialized_keypair_to_keyfile_data(&original).expect("Failed to serialize keypair");
1066+
let restored =
1067+
deserialize_keypair_from_keyfile_data(&data).expect("Failed to deserialize keypair");
1068+
assert_eq!(original.ss58_address(), restored.ss58_address());
1069+
}
1070+
1071+
#[test]
1072+
fn test_serialize_pubkey_only_keypair() {
1073+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
1074+
let full_keypair =
1075+
Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
1076+
let ss58 = full_keypair.ss58_address().expect("No ss58 address");
1077+
let pubkey_only =
1078+
Keypair::new(Some(ss58), None, None, 42, None, 1).expect("Failed to create pubkey kp");
1079+
let data = serialized_keypair_to_keyfile_data(&pubkey_only)
1080+
.expect("Failed to serialize pubkey keypair");
1081+
let json_str = std::str::from_utf8(&data).expect("Invalid UTF-8");
1082+
assert!(json_str.contains("ss58Address"));
1083+
assert!(!json_str.contains("secretPhrase"));
1084+
}
1085+
1086+
#[test]
1087+
fn test_deserialize_invalid_json() {
1088+
let invalid_data = b"not valid json";
1089+
let result = deserialize_keypair_from_keyfile_data(invalid_data);
1090+
assert!(result.is_err());
1091+
}
1092+
1093+
#[test]
1094+
fn test_deserialize_empty_json() {
1095+
let empty_json = b"{}";
1096+
let result = deserialize_keypair_from_keyfile_data(empty_json);
1097+
assert!(result.is_err());
1098+
}
1099+
1100+
#[test]
1101+
fn test_keyfile_data_is_encrypted_nacl() {
1102+
assert!(keyfile_data_is_encrypted_nacl(b"$NACLsomedata"));
1103+
assert!(!keyfile_data_is_encrypted_nacl(b"plaintext"));
1104+
}
1105+
1106+
#[test]
1107+
fn test_keyfile_data_is_encrypted_ansible() {
1108+
assert!(keyfile_data_is_encrypted_ansible(b"$ANSIBLE_VAULTdata"));
1109+
assert!(!keyfile_data_is_encrypted_ansible(b"plaintext"));
1110+
}
1111+
1112+
#[test]
1113+
fn test_keyfile_data_is_encrypted_legacy() {
1114+
assert!(keyfile_data_is_encrypted_legacy(b"gAAAAAsomedata"));
1115+
assert!(!keyfile_data_is_encrypted_legacy(b"plaintext"));
1116+
}
1117+
1118+
#[test]
1119+
fn test_keyfile_data_is_encrypted_combined() {
1120+
assert!(keyfile_data_is_encrypted(b"$NACLdata"));
1121+
assert!(keyfile_data_is_encrypted(b"$ANSIBLE_VAULTdata"));
1122+
assert!(keyfile_data_is_encrypted(b"gAAAAAsomedata"));
1123+
assert!(!keyfile_data_is_encrypted(b"plaintext data"));
1124+
}
1125+
1126+
#[test]
1127+
fn test_keyfile_data_encryption_method() {
1128+
assert_eq!(keyfile_data_encryption_method(b"$NACLdata"), "NaCl");
1129+
assert_eq!(
1130+
keyfile_data_encryption_method(b"$ANSIBLE_VAULTdata"),
1131+
"Ansible Vault"
1132+
);
1133+
assert_eq!(keyfile_data_encryption_method(b"gAAAAAsomedata"), "legacy");
1134+
assert_eq!(keyfile_data_encryption_method(b"plaintext"), "unknown");
1135+
}
1136+
1137+
#[test]
1138+
fn test_keyfile_new() {
1139+
let keyfile = Keyfile::new(
1140+
"/tmp/test/keyfile".to_string(),
1141+
Some("test".to_string()),
1142+
false,
1143+
)
1144+
.expect("Failed to create keyfile");
1145+
assert_eq!(keyfile.path, "/tmp/test/keyfile");
1146+
}
1147+
1148+
#[test]
1149+
fn test_keyfile_default_name() {
1150+
let keyfile = Keyfile::new("/tmp/test/keyfile".to_string(), None, false)
1151+
.expect("Failed to create keyfile");
1152+
let name = keyfile.get_name().expect("Failed to get name");
1153+
assert_eq!(name, "Keyfile");
1154+
}
1155+
1156+
#[test]
1157+
fn test_keyfile_get_path() {
1158+
let path = "/tmp/test/my_keyfile".to_string();
1159+
let keyfile = Keyfile::new(path.clone(), None, false).expect("Failed to create keyfile");
1160+
assert_eq!(keyfile.get_path().expect("Failed to get path"), path);
1161+
}
1162+
1163+
#[test]
1164+
fn test_keyfile_env_var_name() {
1165+
let keyfile = Keyfile::new("/tmp/test/keyfile".to_string(), None, false)
1166+
.expect("Failed to create keyfile");
1167+
let env_name = keyfile.env_var_name().expect("Failed to get env var name");
1168+
assert!(env_name.starts_with("BT_PW_"));
1169+
assert!(env_name.contains("TMP"));
1170+
}
1171+
1172+
#[test]
1173+
fn test_keyfile_nonexistent_file() {
1174+
let keyfile = Keyfile::new("/nonexistent/path/keyfile".to_string(), None, false)
1175+
.expect("Failed to create keyfile");
1176+
assert!(!keyfile
1177+
.exists_on_device()
1178+
.expect("Failed to check existence"));
1179+
}
1180+
1181+
#[test]
1182+
fn test_keyfile_display() {
1183+
let keyfile = Keyfile::new("/tmp/test_display".to_string(), None, false)
1184+
.expect("Failed to create keyfile");
1185+
let display = format!("{}", keyfile);
1186+
assert!(display.contains("/tmp/test_display"));
1187+
}
1188+
}

src/keypair.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,143 @@ impl fmt::Debug for Keypair {
524524
}
525525
}
526526
}
527+
528+
#[cfg(test)]
529+
mod tests {
530+
use super::*;
531+
532+
#[test]
533+
fn test_keypair_from_mnemonic() {
534+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
535+
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
536+
assert!(keypair.ss58_address().is_some());
537+
assert!(keypair.public_key().unwrap().is_some());
538+
assert!(keypair.mnemonic().is_some());
539+
}
540+
541+
#[test]
542+
fn test_keypair_generate_mnemonic_word_count() {
543+
for n_words in [12, 15, 18, 21, 24] {
544+
let mnemonic =
545+
Keypair::generate_mnemonic(n_words).expect("Failed to generate mnemonic");
546+
let word_count = mnemonic.split_whitespace().count();
547+
assert_eq!(
548+
word_count, n_words,
549+
"Expected {} words, got {}",
550+
n_words, word_count
551+
);
552+
}
553+
}
554+
555+
#[test]
556+
fn test_keypair_from_uri() {
557+
let keypair = Keypair::create_from_uri("//Alice").expect("Failed to create from URI");
558+
assert!(keypair.ss58_address().is_some());
559+
assert!(keypair.public_key().unwrap().is_some());
560+
}
561+
562+
#[test]
563+
fn test_keypair_sign_and_verify() {
564+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
565+
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
566+
let data = b"test message".to_vec();
567+
let signature = keypair.sign(data.clone()).expect("Failed to sign");
568+
assert!(!signature.is_empty());
569+
let verified = keypair.verify(data, signature).expect("Failed to verify");
570+
assert!(verified);
571+
}
572+
573+
#[test]
574+
fn test_keypair_verify_wrong_data() {
575+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
576+
let keypair = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
577+
let data = b"original message".to_vec();
578+
let wrong_data = b"tampered message".to_vec();
579+
let signature = keypair.sign(data).expect("Failed to sign");
580+
let verified = keypair
581+
.verify(wrong_data, signature)
582+
.expect("Failed to verify");
583+
assert!(!verified);
584+
}
585+
586+
#[test]
587+
fn test_keypair_from_ss58_address() {
588+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
589+
let original = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
590+
let ss58 = original.ss58_address().expect("Failed to get ss58");
591+
let restored =
592+
Keypair::new(Some(ss58.clone()), None, None, 42, None, 1).expect("Failed to restore");
593+
assert_eq!(restored.ss58_address().unwrap(), ss58);
594+
}
595+
596+
#[test]
597+
fn test_keypair_from_public_key_hex() {
598+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
599+
let original = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
600+
let pub_key_bytes = original.public_key().unwrap().unwrap();
601+
let pub_key_hex = hex::encode(&pub_key_bytes);
602+
let restored = Keypair::new(None, Some(pub_key_hex), None, 42, None, 1)
603+
.expect("Failed to restore from pubkey");
604+
assert!(restored.ss58_address().is_some());
605+
}
606+
607+
#[test]
608+
fn test_keypair_unsupported_crypto_type() {
609+
let result = Keypair::new(None, Some("a".repeat(64)), None, 42, None, 0);
610+
assert!(result.is_err());
611+
assert!(result.unwrap_err().contains("Unsupported crypto type"));
612+
}
613+
614+
#[test]
615+
fn test_keypair_no_address_or_key() {
616+
let result = Keypair::new(None, None, None, 42, None, 1);
617+
assert!(result.is_err());
618+
}
619+
620+
#[test]
621+
fn test_keypair_default() {
622+
let kp = Keypair::default();
623+
assert!(kp.ss58_address().is_none());
624+
assert_eq!(kp.ss58_format(), 42);
625+
assert_eq!(kp.crypto_type(), 1);
626+
assert!(kp.mnemonic().is_none());
627+
}
628+
629+
#[test]
630+
fn test_keypair_display() {
631+
let kp = Keypair::create_from_uri("//Bob").expect("Failed to create keypair");
632+
let display = format!("{}", kp);
633+
assert!(display.starts_with("<Keypair (address="));
634+
assert!(display.ends_with(")>"));
635+
}
636+
637+
#[test]
638+
fn test_keypair_debug() {
639+
let kp = Keypair::default();
640+
let debug = format!("{:?}", kp);
641+
assert!(debug.contains("Keypair"));
642+
}
643+
644+
#[test]
645+
fn test_keypair_clone_preserves_address() {
646+
let kp = Keypair::create_from_uri("//Charlie").expect("Failed to create keypair");
647+
let cloned = kp.clone();
648+
assert_eq!(kp.ss58_address(), cloned.ss58_address());
649+
}
650+
651+
#[test]
652+
fn test_keypair_seed_hex() {
653+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
654+
let kp = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
655+
assert!(kp.seed_hex().is_some());
656+
}
657+
658+
#[test]
659+
fn test_keypair_from_seed() {
660+
let mnemonic = Keypair::generate_mnemonic(12).expect("Failed to generate mnemonic");
661+
let kp = Keypair::create_from_mnemonic(&mnemonic).expect("Failed to create keypair");
662+
let seed = kp.seed_hex().expect("No seed");
663+
let restored = Keypair::create_from_seed(seed).expect("Failed to create from seed");
664+
assert_eq!(kp.ss58_address(), restored.ss58_address());
665+
}
666+
}

src/python_bindings.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -925,21 +925,22 @@ except argparse.ArgumentError:
925925

926926
/// Checks for existing coldkeypub and hotkeys, and creates them if non-existent.
927927
///
928-
/// Arguments:
929-
/// coldkey_use_password (bool): Whether to use a password for coldkey. Defaults to ``True``.
930-
/// hotkey_use_password (bool): Whether to use a password for hotkey. Defaults to ``False``.
931-
/// save_coldkey_to_env (bool): Whether to save a coldkey password to local env. Defaults to ``False``.
932-
/// save_hotkey_to_env (bool): Whether to save a hotkey password to local env. Defaults to ``False``.
933-
/// coldkey_password (Optional[str]): Coldkey password for encryption. Defaults to ``None``. If `coldkey_password` is passed, then `coldkey_use_password` is automatically ``True``.
934-
/// hotkey_password (Optional[str]): Hotkey password for encryption. Defaults to ``None``. If `hotkey_password` is passed, then `hotkey_use_password` is automatically ``True``.
935-
/// overwrite (bool): Whether to overwrite an existing keys. Defaults to ``False``.
936-
/// suppress (bool): If ``True``, suppresses the display of the keys mnemonic message. Defaults to ``False``.
928+
/// # Arguments
937929
///
938-
/// Returns:
939-
/// Wallet instance with created keys.
930+
/// * `coldkey_use_password` - Whether to use a password for coldkey. Defaults to `true`.
931+
/// * `hotkey_use_password` - Whether to use a password for hotkey. Defaults to `false`.
932+
/// * `save_coldkey_to_env` - Whether to save a coldkey password to local env. Defaults to `false`.
933+
/// * `save_hotkey_to_env` - Whether to save a hotkey password to local env. Defaults to `false`.
934+
/// * `coldkey_password` - Coldkey password for encryption. Defaults to `None`.
935+
/// If provided, `coldkey_use_password` is automatically `true`.
936+
/// * `hotkey_password` - Hotkey password for encryption. Defaults to `None`.
937+
/// If provided, `hotkey_use_password` is automatically `true`.
938+
/// * `overwrite` - Whether to overwrite existing keys. Defaults to `false`.
939+
/// * `suppress` - If `true`, suppresses the display of the keys mnemonic message. Defaults to `false`.
940940
///
941-
/// Raises:
942-
/// WalletError: If key generation or file operations fail.
941+
/// # Returns
942+
///
943+
/// Wallet instance with created keys, or error.
943944
#[allow(clippy::too_many_arguments)]
944945
#[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))]
945946
pub fn create(

0 commit comments

Comments
 (0)