diff --git a/src/cli.rs b/src/cli.rs index f93e57e..4f2885d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -41,8 +41,12 @@ impl Cli { pub enum Subcommands { /// Generate default configuration content for generator Defaultconfig, - /// Print configuration content to json - Config, + /// Print configuration content to json, or a single value if path is provided (e.g., "trunk.api") + Config { + /// Optional path to a specific config value (e.g., "trunk.api") + #[arg(value_name = "PATH")] + path: Option, + }, /// Clean out conflicting PRs and requeue failed PRs Housekeeping, /// Simulate a test with flake rate in consideration diff --git a/src/main.rs b/src/main.rs index ad45b79..788d5a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -604,10 +604,47 @@ fn run() -> anyhow::Result<()> { } Ok(()) } - Some(Subcommands::Config {}) => { - let config_json = - to_string_pretty(&config).expect("Failed to serialize config to JSON"); - println!("{}", config_json); + Some(Subcommands::Config { path }) => { + if let Some(path) = path { + let path = path.trim(); + if path.is_empty() { + // If path is empty, print full config + let config_json = + to_string_pretty(&config).expect("Failed to serialize config to JSON"); + println!("{}", config_json); + return Ok(()); + } + + // Extract a single value from the config + let config_json: Value = + serde_json::to_value(&config).expect("Failed to serialize config to JSON"); + + let parts: Vec<&str> = path.split('.').collect(); + let mut current = &config_json; + + for part in parts { + match current.get(part) { + Some(value) => { + current = value; + } + None => { + eprintln!("Config path '{}' not found", path); + std::process::exit(1); + } + } + } + + // Output the value - if it's a string, output without quotes, otherwise output as JSON + match current { + Value::String(s) => println!("{}", s), + _ => println!("{}", serde_json::to_string(current).unwrap()), + } + } else { + // Print full config as JSON + let config_json = + to_string_pretty(&config).expect("Failed to serialize config to JSON"); + println!("{}", config_json); + } Ok(()) } Some(Subcommands::Generate {}) => generate(&config, &cli), diff --git a/tests/config_tests.rs b/tests/config_tests.rs index 4fdd15a..72c4df5 100644 --- a/tests/config_tests.rs +++ b/tests/config_tests.rs @@ -1,5 +1,8 @@ use gen::config::{Conf, MergeConf, PullRequestConf, TestConf}; +mod test_utils; +use test_utils::run_mq_with_config_and_args; + /// Helper function to create a test config with valid defaults fn create_test_config(pullrequest: PullRequestConf) -> Conf { Conf { @@ -347,3 +350,139 @@ fn test_api_trigger_trunk_token_validation_in_config() { "API trigger should pass config validation - token check is at runtime" ); } + +#[test] +fn test_config_full_output() { + let config = r#" +[trunk] +api = "api.test.io" + +[git] +name = "Test User" +email = "test@example.com" + +[pullrequest] +max_deps = 5 + +[merge] +trigger = "api" +"#; + + let (exit_code, stdout, stderr) = run_mq_with_config_and_args(config, "config", &[]); + + assert_eq!(exit_code, 0, "Should succeed. stderr: {}", stderr); + // Should output full JSON config + assert!(stdout.contains("\"trunk\""), "Should contain trunk section"); + assert!(stdout.contains("\"git\""), "Should contain git section"); + assert!(stdout.contains("api.test.io"), "Should contain api value"); +} + +#[test] +fn test_config_single_value_string() { + let config = r#" +[trunk] +api = "api.test.io" + +[merge] +trigger = "api" +"#; + + let (exit_code, stdout, stderr) = run_mq_with_config_and_args(config, "config", &["trunk.api"]); + + assert_eq!(exit_code, 0, "Should succeed. stderr: {}", stderr); + // String values should be output without quotes + assert_eq!(stdout.trim(), "api.test.io"); +} + +#[test] +fn test_config_single_value_nested() { + let config = r#" +[git] +name = "Test User" +email = "test@example.com" + +[merge] +trigger = "api" +"#; + + let (exit_code, stdout, stderr) = run_mq_with_config_and_args(config, "config", &["git.name"]); + + assert_eq!(exit_code, 0, "Should succeed. stderr: {}", stderr); + assert_eq!(stdout.trim(), "Test User"); +} + +#[test] +fn test_config_single_value_number() { + let config = r#" +[pullrequest] +max_deps = 5 +max_impacted_deps = 3 + +[merge] +trigger = "api" +"#; + + let (exit_code, stdout, stderr) = + run_mq_with_config_and_args(config, "config", &["pullrequest.max_deps"]); + + assert_eq!(exit_code, 0, "Should succeed. stderr: {}", stderr); + // Numbers should be output as JSON + assert_eq!(stdout.trim(), "5"); +} + +#[test] +fn test_config_invalid_path() { + let config = r#" +[trunk] +api = "api.test.io" + +[merge] +trigger = "api" +"#; + + let (exit_code, _stdout, stderr) = + run_mq_with_config_and_args(config, "config", &["trunk.invalid"]); + + assert_eq!(exit_code, 1, "Should fail for invalid path"); + assert!( + stderr.contains("not found"), + "Should contain error message about path not found. stderr: {}", + stderr + ); +} + +#[test] +fn test_config_deeply_nested_path() { + let config = r#" +[pullrequest] +max_deps = 10 +max_impacted_deps = 5 + +[merge] +trigger = "api" +"#; + + let (exit_code, stdout, stderr) = + run_mq_with_config_and_args(config, "config", &["pullrequest.max_impacted_deps"]); + + assert_eq!(exit_code, 0, "Should succeed. stderr: {}", stderr); + assert_eq!(stdout.trim(), "5"); +} + +#[test] +fn test_config_empty_path_returns_full_config() { + let config = r#" +[trunk] +api = "api.test.io" + +[merge] +trigger = "api" +"#; + + // Empty path should return full config + let (exit_code, stdout, stderr) = run_mq_with_config_and_args(config, "config", &[""]); + + assert_eq!(exit_code, 0, "Should succeed. stderr: {}", stderr); + // Should output full JSON config + assert!(stdout.contains("\"trunk\""), "Should contain trunk section"); +} diff --git a/tests/test_utils.rs b/tests/test_utils.rs index 7786504..83a4bf8 100644 --- a/tests/test_utils.rs +++ b/tests/test_utils.rs @@ -25,6 +25,14 @@ pub fn get_binary_path() -> PathBuf { } pub fn run_mq_with_config(config_content: &str, subcommand: &str) -> (i32, String, String) { + run_mq_with_config_and_args(config_content, subcommand, &[]) +} + +pub fn run_mq_with_config_and_args( + config_content: &str, + subcommand: &str, + args: &[&str], +) -> (i32, String, String) { let binary = get_binary_path(); if !binary.exists() { @@ -51,11 +59,12 @@ pub fn run_mq_with_config(config_content: &str, subcommand: &str) -> (i32, Strin fs::write(&config_file, config_content).unwrap(); // Run the binary - let output = Command::new(&binary) - .arg(subcommand) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute binary"); + let mut cmd = Command::new(&binary); + cmd.arg(subcommand); + cmd.args(args); + cmd.current_dir(&temp_dir); + + let output = cmd.output().expect("Failed to execute binary"); // Clean up let _ = fs::remove_dir_all(&temp_dir);