test: Added tests for the new gopass provider
This commit is contained in:
+46
-46
@@ -1,7 +1,8 @@
|
|||||||
use crate::command::preview_command;
|
use crate::command::preview_command;
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use clap_complete::CompletionCandidate;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use gman::config::{load_config, Config, RunConfig};
|
use gman::config::{Config, RunConfig, load_config};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -9,7 +10,6 @@ use std::ffi::{OsStr, OsString};
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use clap_complete::CompletionCandidate;
|
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
|
|
||||||
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
||||||
@@ -256,48 +256,49 @@ pub fn parse_args(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_config_completer(current: &OsStr) -> Vec<CompletionCandidate> {
|
pub fn run_config_completer(current: &OsStr) -> Vec<CompletionCandidate> {
|
||||||
let cur = current.to_string_lossy();
|
let cur = current.to_string_lossy();
|
||||||
match load_config() {
|
match load_config() {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
if let Some(run_configs) = config.run_configs {
|
if let Some(run_configs) = config.run_configs {
|
||||||
run_configs
|
run_configs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|rc| {
|
.filter(|rc| {
|
||||||
rc.name
|
rc.name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("run config has no name")
|
.expect("run config has no name")
|
||||||
.starts_with(&*cur)
|
.starts_with(&*cur)
|
||||||
})
|
})
|
||||||
.map(|rc| {
|
.map(|rc| {
|
||||||
CompletionCandidate::new(rc.name.as_ref().expect("run config has no name"))
|
CompletionCandidate::new(rc.name.as_ref().expect("run config has no name"))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => vec![],
|
Err(_) => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secrets_completer(current: &OsStr) -> Vec<CompletionCandidate> {
|
pub fn secrets_completer(current: &OsStr) -> Vec<CompletionCandidate> {
|
||||||
let cur = current.to_string_lossy();
|
let cur = current.to_string_lossy();
|
||||||
match load_config() {
|
match load_config() {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
let mut provider_config = match config.extract_provider_config(None) {
|
let mut provider_config = match config.extract_provider_config(None) {
|
||||||
Ok(pc) => pc,
|
Ok(pc) => pc,
|
||||||
Err(_) => return vec![],
|
Err(_) => return vec![],
|
||||||
};
|
};
|
||||||
let secrets_provider = provider_config.extract_provider();
|
let secrets_provider = provider_config.extract_provider();
|
||||||
let h = Handle::current();
|
let h = Handle::current();
|
||||||
tokio::task::block_in_place(|| h.block_on(secrets_provider.list_secrets())).unwrap_or_default()
|
tokio::task::block_in_place(|| h.block_on(secrets_provider.list_secrets()))
|
||||||
.into_iter()
|
.unwrap_or_default()
|
||||||
.filter(|s| s.starts_with(&*cur))
|
.into_iter()
|
||||||
.map(CompletionCandidate::new)
|
.filter(|s| s.starts_with(&*cur))
|
||||||
.collect()
|
.map(CompletionCandidate::new)
|
||||||
}
|
.collect()
|
||||||
Err(_) => vec![],
|
}
|
||||||
}
|
Err(_) => vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -402,11 +403,10 @@ mod tests {
|
|||||||
..Config::default()
|
..Config::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Capture stderr for dry_run preview
|
|
||||||
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
|
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
|
||||||
// Best-effort: ensure function does not error under dry_run
|
let err = wrap_and_run_command(None, &cfg, tokens, None, true)
|
||||||
let res = wrap_and_run_command(None, &cfg, tokens, None, true).await;
|
.await
|
||||||
assert!(res.is_ok());
|
.expect_err("expected failed secret resolution in dry_run");
|
||||||
// Not asserting output text to keep test platform-agnostic
|
assert!(err.to_string().contains("Failed to fetch"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ use crate::cli::secrets_completer;
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
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 clap_complete::{ArgValueCompleter, CompleteEnv};
|
use clap_complete::{ArgValueCompleter, CompleteEnv};
|
||||||
use crossterm::execute;
|
use crossterm::execute;
|
||||||
use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen};
|
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
|
||||||
use gman::config::{get_config_file_path, load_config, Config};
|
use gman::config::{Config, get_config_file_path, load_config};
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::io::{self, IsTerminal, Read, Write};
|
use std::io::{self, IsTerminal, Read, Write};
|
||||||
use std::panic::PanicHookInfo;
|
use std::panic::PanicHookInfo;
|
||||||
@@ -87,7 +87,7 @@ enum Commands {
|
|||||||
#[clap(alias = "show")]
|
#[clap(alias = "show")]
|
||||||
Get {
|
Get {
|
||||||
/// Name of the secret to retrieve
|
/// Name of the secret to retrieve
|
||||||
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ enum Commands {
|
|||||||
/// If a provider does not support updating secrets, this command will return an error.
|
/// If a provider does not support updating secrets, this command will return an error.
|
||||||
Update {
|
Update {
|
||||||
/// Name of the secret to update
|
/// Name of the secret to update
|
||||||
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ enum Commands {
|
|||||||
#[clap(aliases = &["remove", "rm"])]
|
#[clap(aliases = &["remove", "rm"])]
|
||||||
Delete {
|
Delete {
|
||||||
/// Name of the secret to delete
|
/// Name of the secret to delete
|
||||||
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
//!
|
//!
|
||||||
//! let rc = RunConfig{
|
//! let rc = RunConfig{
|
||||||
//! name: Some("echo".into()),
|
//! name: Some("echo".into()),
|
||||||
|
//! provider: None,
|
||||||
//! secrets: Some(vec!["api_key".into()]),
|
//! secrets: Some(vec!["api_key".into()]),
|
||||||
//! files: None,
|
//! files: None,
|
||||||
//! flag: None,
|
//! flag: None,
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ use validator::Validate;
|
|||||||
///
|
///
|
||||||
/// Example
|
/// Example
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// use gman::providers::local::GopassProvider;
|
/// use gman::providers::gopass::GopassProvider;
|
||||||
/// use gman::providers::{SecretProvider, SupportedProvider};
|
/// use gman::providers::{SecretProvider, SupportedProvider};
|
||||||
/// use gman::config::Config;
|
/// use gman::config::Config;
|
||||||
///
|
///
|
||||||
/// let provider = GopassProvider::default();
|
/// let provider = GopassProvider::default();
|
||||||
/// let _ = provider.set_secret("MY_SECRET", "value");
|
/// 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)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct GopassProvider {
|
pub struct GopassProvider {
|
||||||
pub store: Option<String>,
|
pub store: Option<String>,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub mod aws_secrets_manager;
|
|||||||
pub mod azure_key_vault;
|
pub mod azure_key_vault;
|
||||||
pub mod gcp_secret_manager;
|
pub mod gcp_secret_manager;
|
||||||
mod git_sync;
|
mod git_sync;
|
||||||
mod gopass;
|
pub mod gopass;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
|
||||||
use crate::providers::gopass::GopassProvider;
|
use crate::providers::gopass::GopassProvider;
|
||||||
|
|||||||
+11
-11
@@ -9,7 +9,7 @@ mod tests {
|
|||||||
fn test_run_config_valid() {
|
fn test_run_config_valid() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -24,7 +24,7 @@ mod tests {
|
|||||||
fn test_run_config_missing_name() {
|
fn test_run_config_missing_name() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: None,
|
name: None,
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -39,7 +39,7 @@ mod tests {
|
|||||||
fn test_run_config_missing_secrets() {
|
fn test_run_config_missing_secrets() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: None,
|
secrets: None,
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -54,7 +54,7 @@ mod tests {
|
|||||||
fn test_run_config_invalid_flag_position() {
|
fn test_run_config_invalid_flag_position() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(0),
|
flag_position: Some(0),
|
||||||
@@ -69,7 +69,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_all_some() {
|
fn test_run_config_flags_or_none_all_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -84,7 +84,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_all_none() {
|
fn test_run_config_flags_or_none_all_none() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -99,7 +99,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_partial_some() {
|
fn test_run_config_flags_or_none_partial_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -114,7 +114,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_missing_placeholder() {
|
fn test_run_config_flags_or_none_missing_placeholder() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -129,7 +129,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_all_none() {
|
fn test_run_config_flags_or_files_all_none() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -144,7 +144,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_files_is_some() {
|
fn test_run_config_flags_or_files_files_is_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -159,7 +159,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_all_some() {
|
fn test_run_config_flags_or_files_all_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
provider: None,
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
|
|||||||
@@ -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,5 +1,6 @@
|
|||||||
mod aws_secrets_manager_tests;
|
mod aws_secrets_manager_tests;
|
||||||
mod azure_key_vault_tests;
|
mod azure_key_vault_tests;
|
||||||
mod gcp_secret_manager_tests;
|
mod gcp_secret_manager_tests;
|
||||||
|
mod gopass_tests;
|
||||||
mod local_tests;
|
mod local_tests;
|
||||||
mod provider_tests;
|
mod provider_tests;
|
||||||
|
|||||||
Reference in New Issue
Block a user