test: Added tests for the new gopass provider

This commit is contained in:
2025-09-29 17:28:04 -06:00
parent 1b83d9b199
commit a64f4dbf79
8 changed files with 121 additions and 66 deletions
+46 -46
View File
@@ -1,7 +1,8 @@
use crate::command::preview_command;
use anyhow::{Context, Result, anyhow};
use clap_complete::CompletionCandidate;
use futures::future::join_all;
use gman::config::{load_config, Config, RunConfig};
use gman::config::{Config, RunConfig, load_config};
use log::{debug, error};
use regex::Regex;
use std::collections::HashMap;
@@ -9,7 +10,6 @@ use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use clap_complete::CompletionCandidate;
use tokio::runtime::Handle;
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
@@ -256,48 +256,49 @@ pub fn parse_args(
}
pub fn run_config_completer(current: &OsStr) -> Vec<CompletionCandidate> {
let cur = current.to_string_lossy();
match load_config() {
Ok(config) => {
if let Some(run_configs) = config.run_configs {
run_configs
.iter()
.filter(|rc| {
rc.name
.as_ref()
.expect("run config has no name")
.starts_with(&*cur)
})
.map(|rc| {
CompletionCandidate::new(rc.name.as_ref().expect("run config has no name"))
})
.collect()
} else {
vec![]
}
}
Err(_) => vec![],
}
let cur = current.to_string_lossy();
match load_config() {
Ok(config) => {
if let Some(run_configs) = config.run_configs {
run_configs
.iter()
.filter(|rc| {
rc.name
.as_ref()
.expect("run config has no name")
.starts_with(&*cur)
})
.map(|rc| {
CompletionCandidate::new(rc.name.as_ref().expect("run config has no name"))
})
.collect()
} else {
vec![]
}
}
Err(_) => vec![],
}
}
pub fn secrets_completer(current: &OsStr) -> Vec<CompletionCandidate> {
let cur = current.to_string_lossy();
match load_config() {
Ok(config) => {
let mut provider_config = match config.extract_provider_config(None) {
Ok(pc) => pc,
Err(_) => return vec![],
};
let secrets_provider = provider_config.extract_provider();
let h = Handle::current();
tokio::task::block_in_place(|| h.block_on(secrets_provider.list_secrets())).unwrap_or_default()
.into_iter()
.filter(|s| s.starts_with(&*cur))
.map(CompletionCandidate::new)
.collect()
}
Err(_) => vec![],
}
let cur = current.to_string_lossy();
match load_config() {
Ok(config) => {
let mut provider_config = match config.extract_provider_config(None) {
Ok(pc) => pc,
Err(_) => return vec![],
};
let secrets_provider = provider_config.extract_provider();
let h = Handle::current();
tokio::task::block_in_place(|| h.block_on(secrets_provider.list_secrets()))
.unwrap_or_default()
.into_iter()
.filter(|s| s.starts_with(&*cur))
.map(CompletionCandidate::new)
.collect()
}
Err(_) => vec![],
}
}
#[cfg(test)]
@@ -402,11 +403,10 @@ mod tests {
..Config::default()
};
// Capture stderr for dry_run preview
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
// Best-effort: ensure function does not error under dry_run
let res = wrap_and_run_command(None, &cfg, tokens, None, true).await;
assert!(res.is_ok());
// Not asserting output text to keep test platform-agnostic
let err = wrap_and_run_command(None, &cfg, tokens, None, true)
.await
.expect_err("expected failed secret resolution in dry_run");
assert!(err.to_string().contains("Failed to fetch"));
}
}
+6 -6
View File
@@ -3,12 +3,12 @@ use crate::cli::secrets_completer;
use anyhow::{Context, Result};
use clap::Subcommand;
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 clap_complete::{ArgValueCompleter, CompleteEnv};
use crossterm::execute;
use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen};
use gman::config::{get_config_file_path, load_config, Config};
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
use gman::config::{Config, get_config_file_path, load_config};
use std::ffi::OsString;
use std::io::{self, IsTerminal, Read, Write};
use std::panic::PanicHookInfo;
@@ -87,7 +87,7 @@ enum Commands {
#[clap(alias = "show")]
Get {
/// Name of the secret to retrieve
#[arg(add = ArgValueCompleter::new(secrets_completer))]
#[arg(add = ArgValueCompleter::new(secrets_completer))]
name: String,
},
@@ -95,7 +95,7 @@ enum Commands {
/// If a provider does not support updating secrets, this command will return an error.
Update {
/// Name of the secret to update
#[arg(add = ArgValueCompleter::new(secrets_completer))]
#[arg(add = ArgValueCompleter::new(secrets_completer))]
name: String,
},
@@ -103,7 +103,7 @@ enum Commands {
#[clap(aliases = &["remove", "rm"])]
Delete {
/// Name of the secret to delete
#[arg(add = ArgValueCompleter::new(secrets_completer))]
#[arg(add = ArgValueCompleter::new(secrets_completer))]
name: String,
},
+1
View File
@@ -11,6 +11,7 @@
//!
//! let rc = RunConfig{
//! name: Some("echo".into()),
//! provider: None,
//! secrets: Some(vec!["api_key".into()]),
//! files: None,
//! flag: None,
+2 -2
View File
@@ -18,14 +18,14 @@ use validator::Validate;
///
/// Example
/// ```no_run
/// use gman::providers::local::GopassProvider;
/// use gman::providers::gopass::GopassProvider;
/// use gman::providers::{SecretProvider, SupportedProvider};
/// use gman::config::Config;
///
/// let provider = GopassProvider::default();
/// let _ = provider.set_secret("MY_SECRET", "value");
/// ```
#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Default, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct GopassProvider {
pub store: Option<String>,
+1 -1
View File
@@ -6,7 +6,7 @@ pub mod aws_secrets_manager;
pub mod azure_key_vault;
pub mod gcp_secret_manager;
mod git_sync;
mod gopass;
pub mod gopass;
pub mod local;
use crate::providers::gopass::GopassProvider;
+11 -11
View File
@@ -9,7 +9,7 @@ mod tests {
fn test_run_config_valid() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: None,
flag_position: None,
@@ -24,7 +24,7 @@ mod tests {
fn test_run_config_missing_name() {
let run_config = RunConfig {
name: None,
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: None,
flag_position: None,
@@ -39,7 +39,7 @@ mod tests {
fn test_run_config_missing_secrets() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: None,
flag: None,
flag_position: None,
@@ -54,7 +54,7 @@ mod tests {
fn test_run_config_invalid_flag_position() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: Some("--test-flag".to_string()),
flag_position: Some(0),
@@ -69,7 +69,7 @@ mod tests {
fn test_run_config_flags_or_none_all_some() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: Some("--test-flag".to_string()),
flag_position: Some(1),
@@ -84,7 +84,7 @@ mod tests {
fn test_run_config_flags_or_none_all_none() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: None,
flag_position: None,
@@ -99,7 +99,7 @@ mod tests {
fn test_run_config_flags_or_none_partial_some() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: Some("--test-flag".to_string()),
flag_position: None,
@@ -114,7 +114,7 @@ mod tests {
fn test_run_config_flags_or_none_missing_placeholder() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: Some("--test-flag".to_string()),
flag_position: Some(1),
@@ -129,7 +129,7 @@ mod tests {
fn test_run_config_flags_or_files_all_none() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: None,
flag_position: None,
@@ -144,7 +144,7 @@ mod tests {
fn test_run_config_flags_or_files_files_is_some() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: None,
flag_position: None,
@@ -159,7 +159,7 @@ mod tests {
fn test_run_config_flags_or_files_all_some() {
let run_config = RunConfig {
name: Some("test".to_string()),
provider: None,
provider: None,
secrets: Some(vec!["secret1".to_string()]),
flag: Some("--test-flag".to_string()),
flag_position: Some(1),
+53
View File
@@ -0,0 +1,53 @@
use gman::config::{Config, ProviderConfig};
use gman::providers::{SecretProvider, SupportedProvider};
use pretty_assertions::{assert_eq, assert_str_eq};
use validator::Validate;
#[test]
fn test_gopass_supported_provider_display_and_validate_from_yaml() {
// Build a SupportedProvider via YAML to avoid direct type import
let yaml = r#"---
type: gopass
store: personal
"#;
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
// Validate delegates to inner provider (no required fields)
assert!(sp.validate().is_ok());
// Display formatting for the enum variant
assert_eq!(sp.to_string(), "gopass");
}
#[test]
fn test_provider_config_with_gopass_deserialize_and_extract() {
// Minimal ProviderConfig YAML using the gopass variant
let yaml = r#"---
name: gopass
type: gopass
"#;
let pc: ProviderConfig = serde_yaml::from_str(yaml).expect("valid provider config yaml");
// Gopass has no required fields, so validation should pass
assert!(pc.validate().is_ok());
// Extract the provider and inspect its name via the trait
let mut pc_owned = pc.clone();
let provider: &mut dyn SecretProvider = pc_owned.extract_provider();
assert_str_eq!(provider.name(), "GopassProvider");
// Round-trip through Config with default_provider
let cfg_yaml = r#"---
default_provider: gopass
providers:
- name: gopass
type: gopass
store: personal
"#;
let cfg: Config = serde_yaml::from_str(cfg_yaml).expect("valid config yaml");
assert!(cfg.validate().is_ok());
let extracted = cfg
.extract_provider_config(None)
.expect("should find default provider");
assert_eq!(extracted.name.as_deref(), Some("gopass"));
}
+1
View File
@@ -1,5 +1,6 @@
mod aws_secrets_manager_tests;
mod azure_key_vault_tests;
mod gcp_secret_manager_tests;
mod gopass_tests;
mod local_tests;
mod provider_tests;