use std::fs;
use std::process::Command;
use tempfile::tempdir;
#[test]
fn test_config_file_command_with_explicit_config() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("test.toml");
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[global]
disable = ["MD013"]
[MD004]
style = "asterisk"
"#;
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "file", "--config"])
.arg(&config_path)
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let absolute_path = fs::canonicalize(&config_path).unwrap();
assert_eq!(stdout.trim(), absolute_path.to_string_lossy());
}
#[test]
fn test_config_file_command_with_no_config() {
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "file", "--no-config"])
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(
stdout.trim(),
"No configuration file loaded (--no-config/--isolated specified)"
);
}
#[test]
fn test_config_file_command_with_isolated() {
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "file", "--isolated"])
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(
stdout.trim(),
"No configuration file loaded (--no-config/--isolated specified)"
);
}
#[test]
fn test_config_file_command_with_nonexistent_config() {
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "file", "--config", "nonexistent.toml"])
.output()
.expect("Failed to execute command");
assert_eq!(output.status.code(), Some(2), "Expected exit code 2 for file not found");
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.contains("config file not found") || stderr.contains("Failed to read config file"),
"stderr should mention the missing config file; got: {stderr}"
);
assert!(stderr.contains("nonexistent.toml"));
}
#[test]
fn test_config_file_command_auto_discovery() {
let temp_dir = tempdir().unwrap();
let config_content = r#"
[global]
disable = ["MD013"]
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "file"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let absolute_path = fs::canonicalize(&config_path).unwrap();
let found_configs: Vec<&str> = stdout.trim().split('\n').collect();
assert!(
found_configs
.iter()
.any(|&path| path == absolute_path.to_string_lossy()),
"Expected config file {} not found in output: {found_configs:?}",
absolute_path.display()
);
}
#[test]
fn test_config_file_command_multiple_files() {
let temp_dir = tempdir().unwrap();
let pyproject_content = r#"
[tool.rumdl]
line-length = 120
"#;
let pyproject_path = temp_dir.path().join("pyproject.toml");
fs::write(&pyproject_path, pyproject_content).unwrap();
let rumdl_content = r#"
[global]
disable = ["MD013"]
"#;
let rumdl_path = temp_dir.path().join(".rumdl.toml");
fs::write(&rumdl_path, rumdl_content).unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "file"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.trim().split('\n').collect();
let rumdl_absolute = fs::canonicalize(&rumdl_path).unwrap();
let pyproject_absolute = fs::canonicalize(&pyproject_path).unwrap();
assert!(
lines.iter().any(|&path| path == rumdl_absolute.to_string_lossy()),
"Expected .rumdl.toml in output: {lines:?}"
);
assert!(
!lines.iter().any(|&path| path == pyproject_absolute.to_string_lossy()),
"pyproject.toml should not be loaded when .rumdl.toml exists: {lines:?}"
);
}
#[test]
fn test_config_no_defaults_basic() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[global]
disable = ["MD013"]
line_length = 100
[MD004]
style = "asterisk"
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stdout.contains("disable = [\"MD013\"]"),
"Output should contain non-default disable value. stdout: {stdout}, stderr: {stderr}"
);
assert!(
stdout.contains("line_length = 100"),
"Output should contain non-default line_length value"
);
assert!(stdout.contains("[MD004]"), "Output should contain MD004 rule section");
assert!(
stdout.contains("style = \"asterisk\""),
"Output should contain non-default style value"
);
assert!(
stdout.contains("[from"),
"Output should contain provenance annotations for non-default values"
);
}
#[test]
fn test_config_no_defaults_all_defaults() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults", "--no-config"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults --no-config'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("All configurations are using default values"),
"Output should indicate all configs are defaults. stdout: {stdout}"
);
}
#[test]
fn test_config_defaults_and_no_defaults_mutually_exclusive() {
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "--defaults", "--no-defaults"])
.output()
.expect("Failed to execute command");
assert!(!output.status.success(), "Should fail when both flags are used");
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.contains("Cannot use both --defaults and --no-defaults"),
"Should show error about mutual exclusivity. stderr: {stderr}"
);
}
#[test]
fn test_config_no_defaults_toml_output() {
use toml::Value;
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[global]
disable = ["MD013"]
line_length = 100
[MD004]
style = "asterisk"
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults", "--output", "toml"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults --output toml'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("disable = [\"MD013\"]"),
"Output should contain non-default disable value"
);
assert!(
stdout.contains("line-length = 100") || stdout.contains("line_length = 100"),
"Output should contain non-default line_length value. Output: {stdout}"
);
assert!(stdout.contains("[MD004]"), "Output should contain MD004 rule section");
assert!(
stdout.contains("style = \"asterisk\""),
"Output should contain non-default style value"
);
assert!(
!stdout.contains("[from"),
"TOML output should not contain provenance annotations"
);
match toml::from_str::<Value>(&stdout) {
Ok(_) => {} Err(e) => panic!("Output should be valid TOML, but parsing failed: {e}\nOutput: {stdout}"),
}
}
#[test]
fn test_config_no_defaults_with_pyproject() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let pyproject_content = r#"
[tool.rumdl]
line-length = 120
"#;
let pyproject_path = temp_dir.path().join("pyproject.toml");
fs::write(&pyproject_path, pyproject_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("line_length = 120"),
"Output should contain non-default line_length from pyproject.toml"
);
assert!(
stdout.contains("pyproject.toml") || stdout.contains("[from pyproject.toml]"),
"Output should indicate source is pyproject.toml"
);
}
#[test]
fn test_config_no_defaults_json_output() {
use serde_json::Value;
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[global]
disable = ["MD013"]
line_length = 100
[MD004]
style = "asterisk"
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults", "--output", "json"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults --output json'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: Value = serde_json::from_str(&stdout).expect("Output should be valid JSON");
if let Some(global) = json.get("global").and_then(|g| g.as_object()) {
assert!(
global.contains_key("disable"),
"JSON should contain non-default disable value"
);
assert!(
global.contains_key("line-length") || global.contains_key("line_length"),
"JSON should contain non-default line_length value"
);
} else {
panic!("JSON should contain a 'global' object");
}
assert!(json.get("MD004").is_some(), "JSON should contain MD004 rule section");
if let Some(md004) = json.get("MD004").and_then(|r| r.as_object()) {
assert!(
md004.contains_key("style"),
"JSON should contain non-default style value"
);
}
}
#[test]
fn test_config_no_defaults_mixed_rule_config() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[MD013]
code_blocks = false
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("[MD013]"), "Output should contain MD013 rule section");
assert!(
stdout.contains("code_blocks = false") || stdout.contains("code-blocks = false"),
"Output should contain non-default code_blocks value"
);
assert!(
!stdout.contains("line_length = 80") && !stdout.contains("line-length = 80"),
"Output should NOT contain default line_length value"
);
}
#[test]
fn test_config_no_defaults_per_file_ignores() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[global]
disable = ["MD013"]
[per-file-ignores]
"README.md" = ["MD033", "MD041"]
"docs/**/*.md" = ["MD013"]
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("[per-file-ignores]") || stdout.contains("per-file-ignores"),
"Output should contain per-file-ignores section"
);
assert!(
stdout.contains("README.md") || stdout.contains("\"README.md\""),
"Output should contain README.md ignore pattern"
);
}
#[test]
fn test_config_no_defaults_multiple_sources() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let pyproject_content = r#"
[tool.rumdl]
line-length = 120
"#;
let pyproject_path = temp_dir.path().join("pyproject.toml");
fs::write(&pyproject_path, pyproject_content).unwrap();
let rumdl_content = r#"
[global]
disable = ["MD013"]
[MD004]
style = "asterisk"
"#;
let rumdl_path = temp_dir.path().join(".rumdl.toml");
fs::write(&rumdl_path, rumdl_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("disable = [\"MD013\"]"),
"Output should contain disable from .rumdl.toml"
);
assert!(
stdout.contains("style = \"asterisk\""),
"Output should contain style from .rumdl.toml"
);
}
#[test]
fn test_config_no_defaults_empty_arrays_explicit() {
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let config_content = r#"
[global]
enable = []
disable = []
"#;
let config_path = temp_dir.path().join(".rumdl.toml");
fs::write(&config_path, config_content).unwrap();
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("enable = []") || stdout.contains("disable = []"),
"Output should show explicitly set empty arrays if source is non-default"
);
}
#[test]
fn test_config_no_defaults_json_all_defaults() {
use serde_json::Value;
let temp_dir = tempdir().unwrap();
let rumdl_exe = env!("CARGO_BIN_EXE_rumdl");
let output = Command::new(rumdl_exe)
.args(["config", "--no-defaults", "--output", "json", "--no-config"])
.current_dir(&temp_dir)
.output()
.expect("Failed to execute 'rumdl config --no-defaults --output json --no-config'");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: Value = serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert!(
json.is_object() || json.is_null(),
"JSON should be an object or null when all configs are defaults"
);
}