Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
},
/// Clean out conflicting PRs and requeue failed PRs
Housekeeping,
/// Simulate a test with flake rate in consideration
Expand Down
45 changes: 41 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
139 changes: 139 additions & 0 deletions tests/config_tests.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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");
}
19 changes: 14 additions & 5 deletions tests/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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);
Expand Down