use anyhow::Result;
use ralph::commands::init::{
InitOptions, ReadmeVersionError, check_readme_current_from_root, extract_readme_version,
run_init,
};
use ralph::config::Resolved;
use ralph::contracts::Config;
use std::fs;
use tempfile::TempDir;
fn resolved_for(dir: &TempDir) -> Resolved {
let repo_root = dir.path().to_path_buf();
let queue_path = repo_root.join(".ralph/queue.jsonc");
let done_path = repo_root.join(".ralph/done.jsonc");
let project_config_path = Some(repo_root.join(".ralph/config.jsonc"));
Resolved {
config: Config::default(),
repo_root,
queue_path,
done_path,
id_prefix: "RQ".to_string(),
id_width: 4,
global_config_path: None,
project_config_path,
}
}
#[test]
fn extract_readme_version_fails_on_non_numeric_version() {
let content = "<!-- RALPH_README_VERSION: abc -->\n# Test";
let result = extract_readme_version(content);
assert!(matches!(result, Err(ReadmeVersionError::ParseError { value }) if value == "abc"));
}
#[test]
fn extract_readme_version_fails_on_missing_end_delimiter() {
let content = "<!-- RALPH_README_VERSION: 5\n# Test";
let result = extract_readme_version(content);
assert!(matches!(result, Err(ReadmeVersionError::InvalidFormat)));
}
#[test]
fn check_readme_current_propagates_malformed_marker_error() -> Result<()> {
let dir = TempDir::new()?;
let repo_root = dir.path();
fs::create_dir_all(repo_root.join(".ralph/prompts"))?;
let prompt_content = "This prompt references .ralph/README.md for context";
fs::write(repo_root.join(".ralph/prompts/worker.md"), prompt_content)?;
let malformed_readme = "<!-- RALPH_README_VERSION: not-a-number -->\n# Test README";
fs::write(repo_root.join(".ralph/README.md"), malformed_readme)?;
let result = check_readme_current_from_root(repo_root);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("malformed") || err_msg.contains("invalid"),
"Error message should indicate malformed marker: {}",
err_msg
);
Ok(())
}
#[test]
fn check_readme_current_handles_legacy_no_marker() -> Result<()> {
let dir = TempDir::new()?;
let repo_root = dir.path();
fs::create_dir_all(repo_root.join(".ralph/prompts"))?;
let prompt_content = "This prompt references .ralph/README.md for context";
fs::write(repo_root.join(".ralph/prompts/worker.md"), prompt_content)?;
let legacy_readme = "# Test README\nSome content";
fs::write(repo_root.join(".ralph/README.md"), legacy_readme)?;
let result = check_readme_current_from_root(repo_root)?;
match result {
ralph::commands::init::ReadmeCheckResult::Current(v) => {
assert_eq!(v, 1);
}
ralph::commands::init::ReadmeCheckResult::Outdated {
current_version, ..
} => {
assert_eq!(current_version, 1);
}
_ => panic!("Unexpected result for legacy README: {:?}", result),
}
Ok(())
}
#[test]
fn init_fails_on_malformed_readme_version() -> Result<()> {
let dir = TempDir::new()?;
let resolved = resolved_for(&dir);
fs::create_dir_all(resolved.repo_root.join(".ralph"))?;
fs::create_dir_all(resolved.repo_root.join(".ralph/prompts"))?;
fs::write(&resolved.queue_path, r#"{"version":1,"tasks":[]}"#)?;
fs::write(&resolved.done_path, r#"{"version":1,"tasks":[]}"#)?;
fs::write(
resolved.project_config_path.as_ref().unwrap(),
r#"{"version":2}"#,
)?;
let prompt_content = "This prompt references .ralph/README.md for context";
fs::write(
resolved.repo_root.join(".ralph/prompts/worker.md"),
prompt_content,
)?;
let malformed_readme = "<!-- RALPH_README_VERSION: invalid-version -->\n# Test";
fs::write(
resolved.repo_root.join(".ralph/README.md"),
malformed_readme,
)?;
let result = run_init(
&resolved,
InitOptions {
force: false,
force_lock: false,
interactive: false,
update_readme: true, },
);
assert!(
result.is_err(),
"Init should fail on malformed README version marker"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("malformed") || err_msg.contains("invalid") || err_msg.contains("version"),
"Error should mention malformed version: {}",
err_msg
);
Ok(())
}
#[test]
fn init_succeeds_on_legacy_readme_without_marker() -> Result<()> {
let dir = TempDir::new()?;
let resolved = resolved_for(&dir);
fs::create_dir_all(resolved.repo_root.join(".ralph"))?;
fs::create_dir_all(resolved.repo_root.join(".ralph/prompts"))?;
fs::write(&resolved.queue_path, r#"{"version":1,"tasks":[]}"#)?;
fs::write(&resolved.done_path, r#"{"version":1,"tasks":[]}"#)?;
fs::write(
resolved.project_config_path.as_ref().unwrap(),
r#"{"version":2}"#,
)?;
let prompt_content = "This prompt references .ralph/README.md for context";
fs::write(
resolved.repo_root.join(".ralph/prompts/worker.md"),
prompt_content,
)?;
let legacy_readme = "# Test README\nSome content";
fs::write(resolved.repo_root.join(".ralph/README.md"), legacy_readme)?;
let result = run_init(
&resolved,
InitOptions {
force: false,
force_lock: false,
interactive: false,
update_readme: false,
},
);
assert!(
result.is_ok(),
"Init should succeed on legacy README without marker"
);
Ok(())
}