diff --git a/Cargo.lock b/Cargo.lock index d161762..9ffe78a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,6 +536,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -591,6 +597,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" + [[package]] name = "generic-array" version = "0.14.7" @@ -651,6 +663,7 @@ dependencies = [ "indoc", "log", "log4rs", + "mockall", "rpassword", "secrecy", "serde", @@ -903,6 +916,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.175" @@ -1024,6 +1043,33 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1181,6 +1227,32 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1641,6 +1713,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "2.0.16" diff --git a/Cargo.toml b/Cargo.toml index ca7c7e1..0b1f70e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,10 +40,21 @@ dialoguer = "0.12.0" chrono = "0.4.42" indoc = "2.0.6" +[dev-dependencies] +mockall = "0.12.1" + [[bin]] bench = false name = "gman" +[[test]] +name = "mod_tests" +path = "tests/providers/mod_tests.rs" + +[[test]] +name = "local_tests" +path = "tests/providers/local_tests.rs" + [profile.release] lto = true strip = true diff --git a/src/config.rs b/src/config.rs index 5d2f29d..7b5e80b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use std::path::PathBuf; use validator::{Validate, ValidationError}; -#[derive(Debug, Clone, Validate, Serialize, Deserialize)] +#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)] #[validate(schema(function = "flags_or_none", skip_on_field_errors = false))] pub struct RunConfig { #[validate(required)] @@ -51,7 +51,7 @@ fn flags_or_none(run_config: &RunConfig) -> Result<(), ValidationError> { } #[serde_as] -#[derive(Debug, Clone, Validate, Serialize, Deserialize)] +#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)] pub struct Config { #[serde_as(as = "DisplayFromStr")] pub provider: SupportedProvider, diff --git a/tests/config_tests.rs b/tests/config_tests.rs new file mode 100644 index 0000000..0121625 --- /dev/null +++ b/tests/config_tests.rs @@ -0,0 +1,166 @@ +#[cfg(test)] +mod tests { + use gman::config::{Config, RunConfig}; + use gman::providers::local::LocalProvider; + use gman::providers::SupportedProvider; + + use validator::Validate; + + #[test] + fn test_run_config_valid() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: Some(vec!["secret1".to_string()]), + flag: None, + flag_position: None, + arg_format: None, + }; + assert!(run_config.validate().is_ok()); + } + + #[test] + fn test_run_config_missing_name() { + let run_config = RunConfig { + name: None, + secrets: Some(vec!["secret1".to_string()]), + flag: None, + flag_position: None, + arg_format: None, + }; + assert!(run_config.validate().is_err()); + } + + #[test] + fn test_run_config_missing_secrets() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: None, + flag: None, + flag_position: None, + arg_format: None, + }; + assert!(run_config.validate().is_err()); + } + + #[test] + fn test_run_config_invalid_flag_position() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: Some(vec!["secret1".to_string()]), + flag: Some("--test-flag".to_string()), + flag_position: Some(0), + arg_format: Some("{key}={value}".to_string()), + }; + assert!(run_config.validate().is_err()); + } + + #[test] + fn test_run_config_flags_or_none_all_some() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: Some(vec!["secret1".to_string()]), + flag: Some("--test-flag".to_string()), + flag_position: Some(1), + arg_format: Some("{key}={value}".to_string()), + }; + assert!(run_config.validate().is_ok()); + } + + #[test] + fn test_run_config_flags_or_none_all_none() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: Some(vec!["secret1".to_string()]), + flag: None, + flag_position: None, + arg_format: None, + }; + assert!(run_config.validate().is_ok()); + } + + #[test] + fn test_run_config_flags_or_none_partial_some() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: Some(vec!["secret1".to_string()]), + flag: Some("--test-flag".to_string()), + flag_position: None, + arg_format: None, + }; + assert!(run_config.validate().is_err()); + } + + #[test] + fn test_run_config_flags_or_none_missing_placeholder() { + let run_config = RunConfig { + name: Some("test".to_string()), + secrets: Some(vec!["secret1".to_string()]), + flag: Some("--test-flag".to_string()), + flag_position: Some(1), + arg_format: Some("key=value".to_string()), + }; + assert!(run_config.validate().is_err()); + } + + #[test] + fn test_config_valid() { + let config = Config { + provider: SupportedProvider::Local(LocalProvider), + password_file: None, + git_branch: None, + git_remote_url: None, + git_user_name: None, + git_user_email: Some("test@example.com".to_string()), + git_executable: None, + run_configs: None, + }; + assert!(config.validate().is_ok()); + } + + #[test] + fn test_config_invalid_email() { + let config = Config { + provider: SupportedProvider::Local(LocalProvider), + password_file: None, + git_branch: None, + git_remote_url: None, + git_user_name: None, + git_user_email: Some("test".to_string()), + git_executable: None, + run_configs: None, + }; + assert!(config.validate().is_err()); + } + + #[test] + fn test_config_default() { + let config = Config::default(); + assert_eq!( + config.provider, + SupportedProvider::Local(LocalProvider) + ); + assert_eq!(config.git_branch, Some("main".to_string())); + } + + #[test] + fn test_config_extract_provider() { + let config = Config::default(); + let provider = config.extract_provider(); + assert_eq!(provider.name(), "LocalProvider"); + } + + #[test] + fn test_config_local_provider_password_file() { + let path = Config::local_provider_password_file(); + let expected_path = dirs::home_dir().map(|p| p.join(".gman_password")); + if let Some(p) = &expected_path { + if !p.exists() { + assert_eq!(path, None); + } else { + assert_eq!(path, expected_path); + } + } else { + assert_eq!(path, None); + } + } +} \ No newline at end of file diff --git a/tests/providers/local_tests.rs b/tests/providers/local_tests.rs new file mode 100644 index 0000000..9a89e2e --- /dev/null +++ b/tests/providers/local_tests.rs @@ -0,0 +1,12 @@ +use gman::providers::local::LocalProviderConfig; + + +#[test] +fn test_local_provider_config_default() { + let config = LocalProviderConfig::default(); + let expected_path = dirs::home_dir() + .map(|p| p.join(".gman_vault")) + .and_then(|p| p.to_str().map(|s| s.to_string())) + .unwrap_or_else(|| ".gman_vault".into()); + assert_eq!(config.vault_path, expected_path); +} diff --git a/tests/providers/mod_tests.rs b/tests/providers/mod_tests.rs new file mode 100644 index 0000000..18bb52e --- /dev/null +++ b/tests/providers/mod_tests.rs @@ -0,0 +1,36 @@ +use gman::providers::{ParseProviderError, SupportedProvider}; +use gman::providers::local::LocalProvider; +use std::str::FromStr; + +#[test] +fn test_supported_provider_from_str_valid() { + assert_eq!( + SupportedProvider::from_str("local").unwrap(), + SupportedProvider::Local(LocalProvider) + ); + assert_eq!( + SupportedProvider::from_str("LOCAL").unwrap(), + SupportedProvider::Local(LocalProvider) + ); +} + +#[test] +fn test_supported_provider_from_str_invalid() { + let err = SupportedProvider::from_str("invalid").unwrap_err(); + assert_eq!( + err.to_string(), + "unsupported provider 'invalid'" + ); +} + +#[test] +fn test_supported_provider_display() { + let provider = SupportedProvider::Local(LocalProvider); + assert_eq!(provider.to_string(), "local"); +} + +#[test] +fn test_parse_provider_error_display() { + let err = ParseProviderError::Unsupported("test".to_string()); + assert_eq!(err.to_string(), "unsupported provider 'test'"); +}