diff --git a/Cargo.lock b/Cargo.lock index fa389cc..0f73a9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3404,6 +3404,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "serde_json", + "tempfile", "thiserror 2.0.18", "tokio", "tracing", diff --git a/crates/node/dkg/Cargo.toml b/crates/node/dkg/Cargo.toml index b337c27..a06377f 100644 --- a/crates/node/dkg/Cargo.toml +++ b/crates/node/dkg/Cargo.toml @@ -25,5 +25,8 @@ rand_core.workspace = true bytes.workspace = true hex.workspace = true +[dev-dependencies] +tempfile.workspace = true + [lints] workspace = true diff --git a/crates/node/dkg/src/output.rs b/crates/node/dkg/src/output.rs index b10c3ae..36dabf1 100644 --- a/crates/node/dkg/src/output.rs +++ b/crates/node/dkg/src/output.rs @@ -105,3 +105,208 @@ impl From for DkgError { Self::Serialization(e.to_string()) } } + +#[cfg(test)] +mod tests { + use tempfile::TempDir; + + use super::*; + + fn create_test_output() -> DkgOutput { + DkgOutput { + group_public_key: vec![0x01, 0x02, 0x03, 0x04], + public_polynomial: vec![0x05, 0x06, 0x07, 0x08], + threshold: 2, + participants: 3, + share_index: 1, + share_secret: vec![0x09, 0x0a, 0x0b, 0x0c], + participant_keys: vec![vec![0x0d, 0x0e], vec![0x0f, 0x10], vec![0x11, 0x12]], + } + } + + #[test] + fn test_dkg_output_save_and_load_roundtrip() { + let temp_dir = TempDir::new().expect("create temp dir"); + let output = create_test_output(); + + output.save(temp_dir.path()).expect("save output"); + + assert!(temp_dir.path().join("output.json").exists()); + assert!(temp_dir.path().join("share.key").exists()); + + let loaded = DkgOutput::load(temp_dir.path()).expect("load output"); + + assert_eq!(loaded.group_public_key, output.group_public_key); + assert_eq!(loaded.public_polynomial, output.public_polynomial); + assert_eq!(loaded.threshold, output.threshold); + assert_eq!(loaded.participants, output.participants); + assert_eq!(loaded.share_index, output.share_index); + assert_eq!(loaded.share_secret, output.share_secret); + assert_eq!(loaded.participant_keys, output.participant_keys); + } + + #[test] + fn test_dkg_output_exists_true() { + let temp_dir = TempDir::new().expect("create temp dir"); + let output = create_test_output(); + output.save(temp_dir.path()).expect("save output"); + + assert!(DkgOutput::exists(temp_dir.path())); + } + + #[test] + fn test_dkg_output_exists_false_empty_dir() { + let temp_dir = TempDir::new().expect("create temp dir"); + assert!(!DkgOutput::exists(temp_dir.path())); + } + + #[test] + fn test_dkg_output_exists_false_missing_share_key() { + let temp_dir = TempDir::new().expect("create temp dir"); + + std::fs::write( + temp_dir.path().join("output.json"), + r#"{"group_public_key":"01020304","public_polynomial":"05060708","threshold":2,"participants":3,"participant_keys":[]}"#, + ) + .expect("write output.json"); + + assert!(!DkgOutput::exists(temp_dir.path())); + } + + #[test] + fn test_dkg_output_exists_false_missing_output_json() { + let temp_dir = TempDir::new().expect("create temp dir"); + + std::fs::write(temp_dir.path().join("share.key"), r#"{"index":1,"secret":"090a0b0c"}"#) + .expect("write share.key"); + + assert!(!DkgOutput::exists(temp_dir.path())); + } + + #[test] + fn test_dkg_output_load_missing_output_json() { + let temp_dir = TempDir::new().expect("create temp dir"); + let result = DkgOutput::load(temp_dir.path()); + assert!(result.is_err()); + } + + #[test] + fn test_dkg_output_load_invalid_output_json() { + let temp_dir = TempDir::new().expect("create temp dir"); + + std::fs::write(temp_dir.path().join("output.json"), "not valid json") + .expect("write output.json"); + std::fs::write(temp_dir.path().join("share.key"), r#"{"index":1,"secret":"090a0b0c"}"#) + .expect("write share.key"); + + let result = DkgOutput::load(temp_dir.path()); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!(err, DkgError::Serialization(_))); + } + + #[test] + fn test_dkg_output_load_invalid_share_key() { + let temp_dir = TempDir::new().expect("create temp dir"); + + std::fs::write( + temp_dir.path().join("output.json"), + r#"{"group_public_key":"01020304","public_polynomial":"05060708","threshold":2,"participants":3,"participant_keys":[]}"#, + ) + .expect("write output.json"); + std::fs::write(temp_dir.path().join("share.key"), "not valid json") + .expect("write share.key"); + + let result = DkgOutput::load(temp_dir.path()); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!(err, DkgError::Serialization(_))); + } + + #[test] + fn test_dkg_output_load_invalid_hex_in_group_key() { + let temp_dir = TempDir::new().expect("create temp dir"); + + std::fs::write( + temp_dir.path().join("output.json"), + r#"{"group_public_key":"not_hex!","public_polynomial":"05060708","threshold":2,"participants":3,"participant_keys":[]}"#, + ) + .expect("write output.json"); + std::fs::write(temp_dir.path().join("share.key"), r#"{"index":1,"secret":"090a0b0c"}"#) + .expect("write share.key"); + + let result = DkgOutput::load(temp_dir.path()); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!(err, DkgError::Serialization(_))); + } + + #[test] + fn test_dkg_output_load_invalid_hex_in_share_secret() { + let temp_dir = TempDir::new().expect("create temp dir"); + + std::fs::write( + temp_dir.path().join("output.json"), + r#"{"group_public_key":"01020304","public_polynomial":"05060708","threshold":2,"participants":3,"participant_keys":[]}"#, + ) + .expect("write output.json"); + std::fs::write(temp_dir.path().join("share.key"), r#"{"index":1,"secret":"invalid!"}"#) + .expect("write share.key"); + + let result = DkgOutput::load(temp_dir.path()); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(matches!(err, DkgError::Serialization(_))); + } + + #[test] + fn test_dkg_output_with_empty_participant_keys() { + let temp_dir = TempDir::new().expect("create temp dir"); + let mut output = create_test_output(); + output.participant_keys = vec![]; + + output.save(temp_dir.path()).expect("save output"); + let loaded = DkgOutput::load(temp_dir.path()).expect("load output"); + + assert!(loaded.participant_keys.is_empty()); + } + + #[test] + fn test_dkg_output_clone() { + let output = create_test_output(); + let cloned = output.clone(); + assert_eq!(output.group_public_key, cloned.group_public_key); + assert_eq!(output.threshold, cloned.threshold); + } + + #[test] + fn test_dkg_output_debug() { + let output = create_test_output(); + let debug = format!("{:?}", output); + assert!(debug.contains("DkgOutput")); + assert!(debug.contains("threshold")); + } + + #[test] + fn test_serde_json_error_conversion() { + let result: Result = serde_json::from_str("{invalid}"); + let json_err = result.unwrap_err(); + let dkg_err: DkgError = json_err.into(); + assert!(matches!(dkg_err, DkgError::Serialization(_))); + } + + #[test] + fn test_dkg_output_save_creates_pretty_json() { + let temp_dir = TempDir::new().expect("create temp dir"); + let output = create_test_output(); + output.save(temp_dir.path()).expect("save output"); + + let output_content = + std::fs::read_to_string(temp_dir.path().join("output.json")).expect("read output.json"); + assert!(output_content.contains('\n')); + + let share_content = + std::fs::read_to_string(temp_dir.path().join("share.key")).expect("read share.key"); + assert!(share_content.contains('\n')); + } +}