use std::fs;
use std::process::Command;
use tempfile::tempdir;
fn rumdl_bin() -> &'static str {
env!("CARGO_BIN_EXE_rumdl")
}
#[test]
fn test_flavor_display_is_consistent_between_config_and_config_get() {
let temp_dir = tempdir().unwrap();
let config_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "--no-config"])
.output()
.unwrap();
let config_stdout = String::from_utf8_lossy(&config_output.stdout);
let get_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "global.flavor", "--no-config"])
.output()
.unwrap();
let get_stdout = String::from_utf8_lossy(&get_output.stdout);
assert!(
config_stdout.contains("flavor = \"standard\""),
"`rumdl config` output should contain `flavor = \"standard\"`, got:\n{config_stdout}"
);
assert!(
get_stdout.contains("\"standard\""),
"`rumdl config get global.flavor` should contain `\"standard\"`, got:\n{get_stdout}"
);
}
#[test]
fn test_flavor_display_lowercase_in_no_defaults_mode() {
let temp_dir = tempdir().unwrap();
fs::write(temp_dir.path().join(".rumdl.toml"), "[global]\nflavor = \"mkdocs\"\n").unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "--no-defaults"])
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("flavor = \"mkdocs\""),
"`rumdl config --no-defaults` should show `flavor = \"mkdocs\"`, got:\n{stdout}"
);
}
#[test]
fn test_flavor_display_consistent_for_non_default_flavor() {
let temp_dir = tempdir().unwrap();
fs::write(temp_dir.path().join(".rumdl.toml"), "[global]\nflavor = \"mkdocs\"\n").unwrap();
let config_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config"])
.output()
.unwrap();
let get_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "global.flavor"])
.output()
.unwrap();
let config_stdout = String::from_utf8_lossy(&config_output.stdout);
let get_stdout = String::from_utf8_lossy(&get_output.stdout);
assert!(
config_stdout.contains("flavor = \"mkdocs\""),
"`rumdl config` should show `flavor = \"mkdocs\"`, got:\n{config_stdout}"
);
assert!(
get_stdout.contains("\"mkdocs\""),
"`rumdl config get global.flavor` should contain `\"mkdocs\"`, got:\n{get_stdout}"
);
assert!(
!config_stdout.contains("MkDocs"),
"`rumdl config` must not use Debug format (MkDocs), got:\n{config_stdout}"
);
assert!(
!get_stdout.contains("MkDocs"),
"`rumdl config get` must not use Debug format (MkDocs), got:\n{get_stdout}"
);
}
#[test]
fn test_flavor_display_lowercase_when_set_to_mkdocs() {
let temp_dir = tempdir().unwrap();
fs::write(temp_dir.path().join(".rumdl.toml"), "[global]\nflavor = \"mkdocs\"\n").unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config"])
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("flavor = \"mkdocs\""),
"`rumdl config` with mkdocs flavor should show `flavor = \"mkdocs\"`, got:\n{stdout}"
);
}
#[test]
fn test_config_get_bare_rule_name_returns_all_keys() {
let temp_dir = tempdir().unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD076", "--no-config"])
.output()
.unwrap();
assert!(
output.status.success(),
"`rumdl config get MD076` should succeed, stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("MD076.style"),
"Output should contain MD076.style, got:\n{stdout}"
);
assert!(
stdout.contains("MD076.allow-loose-continuation"),
"Output should contain MD076.allow-loose-continuation, got:\n{stdout}"
);
assert!(
stdout.contains("[from default]"),
"Output should contain [from default] annotation, got:\n{stdout}"
);
}
#[test]
fn test_config_get_bare_rule_name_shows_overridden_value() {
let temp_dir = tempdir().unwrap();
fs::write(temp_dir.path().join(".rumdl.toml"), "[MD076]\nstyle = \"sublist\"\n").unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD076"])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("MD076.style") && stdout.contains("\"sublist\""),
"Should show overridden style value, got:\n{stdout}"
);
assert!(
stdout.contains("[from project config]"),
"Overridden value should show project config provenance, got:\n{stdout}"
);
}
#[test]
fn test_config_get_unknown_bare_name_errors_gracefully() {
let temp_dir = tempdir().unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD999", "--no-config"])
.output()
.unwrap();
assert!(
!output.status.success(),
"`rumdl config get MD999` should fail for unknown rule"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("MD999"),
"Error message should include the unknown rule name, got:\n{stderr}"
);
}
#[test]
fn test_config_get_bare_rule_name_is_case_insensitive() {
let temp_dir = tempdir().unwrap();
let lower = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "md076", "--no-config"])
.output()
.unwrap();
let upper = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD076", "--no-config"])
.output()
.unwrap();
assert!(
lower.status.success(),
"`rumdl config get md076` (lowercase) should succeed, stderr:\n{}",
String::from_utf8_lossy(&lower.stderr)
);
assert_eq!(
String::from_utf8_lossy(&lower.stdout),
String::from_utf8_lossy(&upper.stdout),
"`rumdl config get md076` and `rumdl config get MD076` must produce identical output"
);
}
#[test]
fn test_config_get_bare_rule_name_output_is_sorted() {
let temp_dir = tempdir().unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD076", "--no-config"])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let pos_allow = stdout
.find("allow-loose-continuation")
.expect("Output should contain allow-loose-continuation");
let pos_style = stdout.find("MD076.style").expect("Output should contain MD076.style");
assert!(
pos_allow < pos_style,
"Fields must be sorted alphabetically: allow-loose-continuation ({pos_allow}) should precede style ({pos_style})"
);
}
#[test]
fn test_config_get_bare_alias_matches_canonical_rule_name() {
let temp_dir = tempdir().unwrap();
let alias_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "heading-increment", "--no-config"])
.output()
.unwrap();
let canonical_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD001", "--no-config"])
.output()
.unwrap();
assert!(
alias_output.status.success(),
"`rumdl config get heading-increment` should succeed, stderr:\n{}",
String::from_utf8_lossy(&alias_output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&alias_output.stdout),
String::from_utf8_lossy(&canonical_output.stdout),
"`rumdl config get heading-increment` and `rumdl config get MD001` must produce identical output"
);
}
#[test]
fn test_config_get_dotted_alias_field_resolves_rule() {
let temp_dir = tempdir().unwrap();
let alias_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "line-length.line-length", "--no-config"])
.output()
.unwrap();
let canonical_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "MD013.line-length", "--no-config"])
.output()
.unwrap();
assert!(
alias_output.status.success(),
"`rumdl config get line-length.line-length` should succeed, stderr:\n{}",
String::from_utf8_lossy(&alias_output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&alias_output.stdout),
String::from_utf8_lossy(&canonical_output.stdout),
"`rumdl config get line-length.line-length` and `rumdl config get MD013.line-length` must produce identical output"
);
}
#[test]
fn test_config_get_underscore_alias_is_normalized() {
let temp_dir = tempdir().unwrap();
let underscore_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "line_length", "--no-config"])
.output()
.unwrap();
let hyphen_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "line-length", "--no-config"])
.output()
.unwrap();
assert!(
underscore_output.status.success(),
"`rumdl config get line_length` should succeed, stderr:\n{}",
String::from_utf8_lossy(&underscore_output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&underscore_output.stdout),
String::from_utf8_lossy(&hyphen_output.stdout),
"`rumdl config get line_length` and `rumdl config get line-length` must produce identical output"
);
}
#[test]
fn test_config_get_alias_is_case_insensitive() {
let temp_dir = tempdir().unwrap();
let upper_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "HEADING-INCREMENT", "--no-config"])
.output()
.unwrap();
let lower_output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "heading-increment", "--no-config"])
.output()
.unwrap();
assert!(
upper_output.status.success(),
"`rumdl config get HEADING-INCREMENT` should succeed, stderr:\n{}",
String::from_utf8_lossy(&upper_output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&upper_output.stdout),
String::from_utf8_lossy(&lower_output.stdout),
"Alias lookup must be case-insensitive"
);
}
#[test]
fn test_config_get_alias_reflects_project_config_override() {
let temp_dir = tempdir().unwrap();
fs::write(temp_dir.path().join(".rumdl.toml"), "[MD013]\nline-length = 120\n").unwrap();
let output = Command::new(rumdl_bin())
.current_dir(temp_dir.path())
.args(["config", "get", "line-length.line-length"])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("120"),
"Alias query should show overridden line-length value, got:\n{stdout}"
);
assert!(
stdout.contains("[from project config]"),
"Alias query should show project config provenance, got:\n{stdout}"
);
}