Added unit and integration tests

This commit is contained in:
2025-09-10 22:34:36 -06:00
parent 083245b447
commit 0f5c28a040
17 changed files with 244 additions and 47 deletions
Generated
+24
View File
@@ -475,6 +475,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -651,12 +657,14 @@ dependencies = [
"indoc", "indoc",
"log", "log",
"log4rs", "log4rs",
"pretty_assertions",
"regex", "regex",
"rpassword", "rpassword",
"secrecy", "secrecy",
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"tempfile",
"thiserror", "thiserror",
"validator", "validator",
"zeroize", "zeroize",
@@ -1182,6 +1190,16 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "proc-macro-error-attr2" name = "proc-macro-error-attr2"
version = "2.0.0" version = "2.0.0"
@@ -2210,6 +2228,12 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.0" version = "0.8.0"
+9 -9
View File
@@ -3,7 +3,7 @@ name = "gman"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "Universal secret management and command injection tool" description = "Universal secret management and injection tool"
keywords = ["cli", "secrets", "credentials", "command-line", "encryption"] keywords = ["cli", "secrets", "credentials", "command-line", "encryption"]
documentation = "https://github.com/Dark-Alex-17/gman" documentation = "https://github.com/Dark-Alex-17/gman"
repository = "https://github.com/Dark-Alex-17/gman" repository = "https://github.com/Dark-Alex-17/gman"
@@ -41,19 +41,19 @@ chrono = "0.4.42"
indoc = "2.0.6" indoc = "2.0.6"
regex = "1.11.2" regex = "1.11.2"
[dev-dependencies]
pretty_assertions = "1.4.1"
tempfile = "3.10.1"
[[bin]] [[bin]]
bench = false bench = false
name = "gman" name = "gman"
[[test]]
name = "mod_tests"
path = "tests/providers/mod_tests.rs"
[[test]]
name = "local_tests"
path = "tests/providers/local_tests.rs"
[profile.release] [profile.release]
lto = true lto = true
strip = true strip = true
opt-level = "z" opt-level = "z"
[[test]]
name = "integration"
path = "tests/tests.rs"
+27 -26
View File
@@ -1,5 +1,7 @@
use crate::command::preview_command; use crate::command::preview_command;
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use gman::config::{Config, RunConfig};
use gman::providers::SecretProvider;
use heck::ToSnakeCase; use heck::ToSnakeCase;
use log::{debug, error}; use log::{debug, error};
use regex::Regex; use regex::Regex;
@@ -8,8 +10,6 @@ use std::ffi::OsString;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use gman::config::{Config, RunConfig};
use gman::providers::SecretProvider;
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}"; const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}"; const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
@@ -246,32 +246,33 @@ pub fn parse_args(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashMap; use crate::cli::generate_files_secret_injections;
use gman::config::RunConfig; use gman::config::RunConfig;
use crate::cli::generate_files_secret_injections; use pretty_assertions::{assert_eq, assert_str_eq};
use std::collections::HashMap;
#[test] #[test]
fn test_generate_files_secret_injections() { fn test_generate_files_secret_injections() {
let mut secrets = HashMap::new(); let mut secrets = HashMap::new();
secrets.insert("SECRET1".to_string(), "value1".to_string()); secrets.insert("SECRET1".to_string(), "value1".to_string());
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();
let file_path = temp_dir.path().join("test.txt"); let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "{{secret1}}").unwrap(); std::fs::write(&file_path, "{{secret1}}").unwrap();
let run_config = RunConfig { let run_config = RunConfig {
name: Some("test".to_string()), name: Some("test".to_string()),
secrets: Some(vec!["secret1".to_string()]), secrets: Some(vec!["secret1".to_string()]),
files: Some(vec![file_path.clone()]), files: Some(vec![file_path.clone()]),
flag: None, flag: None,
flag_position: None, flag_position: None,
arg_format: None, arg_format: None,
}; };
let result = generate_files_secret_injections(secrets, &run_config).unwrap(); let result = generate_files_secret_injections(secrets, &run_config).unwrap();
assert_eq!(result.len(), 1); assert_eq!(result.len(), 1);
assert_eq!(result[0].0, &file_path); assert_eq!(result[0].0, &file_path);
assert_eq!(result[0].1, "{{secret1}}"); assert_str_eq!(result[0].1, "{{secret1}}");
assert_eq!(result[0].2, "value1"); assert_str_eq!(result[0].2, "value1");
} }
} }
+20
View File
@@ -102,3 +102,23 @@ fn ps_quote(s: &OsStr) -> String {
s.into_owned() s.into_owned()
} }
} }
#[cfg(test)]
mod tests {
use crate::command::preview_command;
use pretty_assertions::assert_str_eq;
use std::process::Command;
#[test]
fn test_preview_command() {
let mut cmd = Command::new("echo");
cmd.arg("hello world");
cmd.env("MY_VAR", "my_value");
let preview = preview_command(&cmd);
if cfg!(unix) {
assert_str_eq!(preview, "MY_VAR=my_value echo 'hello world'");
} else if cfg!(windows) {
assert_str_eq!(preview, "set MY_VAR=my_value && \"echo\" \"hello world\"");
}
}
}
+3 -3
View File
@@ -1,15 +1,15 @@
use clap::{ use clap::{
crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser, ValueEnum, CommandFactory, Parser, ValueEnum, crate_authors, crate_description, crate_name, crate_version,
}; };
use std::ffi::OsString; use std::ffi::OsString;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::Subcommand; use clap::Subcommand;
use crossterm::execute; use crossterm::execute;
use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen}; use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
use gman::config::Config; use gman::config::Config;
use gman::providers::local::LocalProvider;
use gman::providers::SupportedProvider; use gman::providers::SupportedProvider;
use gman::providers::local::LocalProvider;
use heck::ToSnakeCase; use heck::ToSnakeCase;
use std::io::{self, IsTerminal, Read, Write}; use std::io::{self, IsTerminal, Read, Write};
use std::panic::PanicHookInfo; use std::panic::PanicHookInfo;
+17
View File
@@ -41,3 +41,20 @@ pub fn get_log_path() -> PathBuf {
log_path.push("gman.log"); log_path.push("gman.log");
log_path log_path
} }
#[cfg(test)]
mod tests {
use crate::utils::get_log_path;
#[test]
fn test_get_log_path() {
let log_path = get_log_path();
if cfg!(target_os = "linux") {
assert!(log_path.ends_with(".cache/gman/gman.log"));
} else if cfg!(target_os = "macos") {
assert!(log_path.ends_with("Library/Logs/gman/gman.log"));
} else if cfg!(target_os = "windows") {
assert!(log_path.ends_with("Logs\\gman\\gman.log"));
}
}
}
+1
View File
@@ -157,6 +157,7 @@ pub fn decrypt_string(password: impl Into<SecretString>, envelope: &str) -> Resu
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use pretty_assertions::assert_eq;
#[test] #[test]
fn round_trip() { fn round_trip() {
+24
View File
@@ -330,3 +330,27 @@ fn get_password(config: &Config) -> Result<SecretString> {
Ok(SecretString::new(password.into())) Ok(SecretString::new(password.into()))
} }
} }
#[cfg(test)]
mod tests {
use crate::derive_key;
use crate::providers::local::derive_key_with_params;
use pretty_assertions::assert_eq;
use secrecy::SecretString;
#[test]
fn test_derive_key() {
let password = SecretString::new("test_password".to_string().into());
let salt = [0u8; 16];
let key = derive_key(&password, &salt).unwrap();
assert_eq!(key.as_slice().len(), 32);
}
#[test]
fn test_derive_key_with_params() {
let password = SecretString::new("test_password".to_string().into());
let salt = [0u8; 16];
let key = derive_key_with_params(&password, &salt, 10, 1, 1).unwrap();
assert_eq!(key.as_slice().len(), 32);
}
}
+22
View File
@@ -0,0 +1,22 @@
use gman::providers::SupportedProvider;
use gman::providers::local::LocalProvider;
use pretty_assertions::assert_eq;
#[test]
fn test_provider_kind_from() {
enum ProviderKind {
Local,
}
impl From<ProviderKind> for SupportedProvider {
fn from(k: ProviderKind) -> Self {
match k {
ProviderKind::Local => SupportedProvider::Local(LocalProvider),
}
}
}
let provider_kind = ProviderKind::Local;
let supported_provider: SupportedProvider = provider_kind.into();
assert_eq!(supported_provider, SupportedProvider::Local(LocalProvider));
}
+1
View File
@@ -0,0 +1 @@
mod main_tests;
+1
View File
@@ -0,0 +1 @@
mod gman;
+2 -1
View File
@@ -3,6 +3,7 @@ mod tests {
use gman::config::{Config, RunConfig}; use gman::config::{Config, RunConfig};
use gman::providers::SupportedProvider; use gman::providers::SupportedProvider;
use gman::providers::local::LocalProvider; use gman::providers::local::LocalProvider;
use pretty_assertions::{assert_eq, assert_str_eq};
use validator::Validate; use validator::Validate;
@@ -190,7 +191,7 @@ mod tests {
fn test_config_extract_provider() { fn test_config_extract_provider() {
let config = Config::default(); let config = Config::default();
let provider = config.extract_provider(); let provider = config.extract_provider();
assert_eq!(provider.name(), "LocalProvider"); assert_str_eq!(provider.name(), "LocalProvider");
} }
#[test] #[test]
+53
View File
@@ -0,0 +1,53 @@
use anyhow::Result;
use validator::Validate;
// Redefining the struct here for testing purposes
pub struct SyncOpts<'a> {
pub remote_url: &'a Option<String>,
pub branch: &'a Option<String>,
}
impl<'a> Validate for SyncOpts<'a> {
fn validate(&self) -> Result<(), validator::ValidationErrors> {
if self.remote_url.is_none() {
return Err(validator::ValidationErrors::new());
}
if self.branch.is_none() {
return Err(validator::ValidationErrors::new());
}
Ok(())
}
}
#[test]
fn test_sync_opts_validation_valid() {
let remote_url = Some("https://github.com/user/repo.git".to_string());
let branch = Some("main".to_string());
let opts = SyncOpts {
remote_url: &remote_url,
branch: &branch,
};
assert!(opts.validate().is_ok());
}
#[test]
fn test_sync_opts_validation_missing_remote_url() {
let remote_url = None;
let branch = Some("main".to_string());
let opts = SyncOpts {
remote_url: &remote_url,
branch: &branch,
};
assert!(opts.validate().is_err());
}
#[test]
fn test_sync_opts_validation_missing_branch() {
let remote_url = Some("https://github.com/user/repo.git".to_string());
let branch = None;
let opts = SyncOpts {
remote_url: &remote_url,
branch: &branch,
};
assert!(opts.validate().is_err());
}
+11 -1
View File
@@ -1,4 +1,5 @@
use gman::providers::local::LocalProviderConfig; use gman::providers::local::LocalProviderConfig;
use pretty_assertions::assert_str_eq;
#[test] #[test]
fn test_local_provider_config_default() { fn test_local_provider_config_default() {
@@ -7,5 +8,14 @@ fn test_local_provider_config_default() {
.map(|p| p.join(".gman_vault")) .map(|p| p.join(".gman_vault"))
.and_then(|p| p.to_str().map(|s| s.to_string())) .and_then(|p| p.to_str().map(|s| s.to_string()))
.unwrap_or_else(|| ".gman_vault".into()); .unwrap_or_else(|| ".gman_vault".into());
assert_eq!(config.vault_path, expected_path); assert_str_eq!(config.vault_path, expected_path);
}
#[test]
fn test_local_provider_name() {
use gman::providers::SecretProvider;
use gman::providers::local::LocalProvider;
let provider = LocalProvider;
assert_str_eq!(provider.name(), "LocalProvider");
} }
+3
View File
@@ -0,0 +1,3 @@
mod git_sync_tests;
mod local_tests;
mod provider_tests;
@@ -1,7 +1,29 @@
use gman::providers::local::LocalProvider; use gman::providers::local::LocalProvider;
use gman::providers::{ParseProviderError, SupportedProvider}; use gman::providers::{ParseProviderError, SupportedProvider};
use pretty_assertions::{assert_eq, assert_str_eq};
use std::str::FromStr; use std::str::FromStr;
#[test]
fn test_supported_provider_from_str() {
assert_eq!(
SupportedProvider::from_str("local").unwrap(),
SupportedProvider::Local(LocalProvider)
);
assert_eq!(
SupportedProvider::from_str(" Local ").unwrap(),
SupportedProvider::Local(LocalProvider)
);
assert!(matches!(
SupportedProvider::from_str("invalid"),
Err(ParseProviderError::Unsupported(_))
));
}
#[test]
fn test_supported_provider_display() {
assert_str_eq!(SupportedProvider::Local(LocalProvider).to_string(), "local");
}
#[test] #[test]
fn test_supported_provider_from_str_valid() { fn test_supported_provider_from_str_valid() {
assert_eq!( assert_eq!(
@@ -17,13 +39,7 @@ fn test_supported_provider_from_str_valid() {
#[test] #[test]
fn test_supported_provider_from_str_invalid() { fn test_supported_provider_from_str_invalid() {
let err = SupportedProvider::from_str("invalid").unwrap_err(); let err = SupportedProvider::from_str("invalid").unwrap_err();
assert_eq!(err.to_string(), "unsupported provider 'invalid'"); assert_str_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] #[test]
+3
View File
@@ -0,0 +1,3 @@
mod bin;
mod config_tests;
mod providers;