use crate::brain::tools::plan_tool::{
RalphLoopConfig, SpecCheckPolicy, ralph_config_path, ralph_loop_config,
};
use crate::brain::tools::toml_hot_reload::safety_path;
use std::fs;
fn scratch(name: &str) -> tempfile::TempDir {
tempfile::Builder::new()
.prefix(&format!("ralph947_{name}_"))
.tempdir()
.expect("tempdir")
}
#[test]
fn ralph_config_path_prefers_project_file() {
let dir = scratch("prefer_project");
let project_file = dir.path().join("ralph_loop.toml");
fs::write(&project_file, "[forward]\nmax_iterations = 7\n").unwrap();
assert_eq!(ralph_config_path(dir.path()), Some(project_file));
}
#[test]
fn ralph_config_path_falls_back_to_safety_copy() {
let dir = scratch("fallback");
assert_eq!(
ralph_config_path(dir.path()),
safety_path("ralph_loop.toml")
);
}
#[test]
fn project_ralph_file_overrides_machine_default() {
let dir = scratch("override");
fs::write(
dir.path().join("ralph_loop.toml"),
"[forward]\nmax_iterations = 7\n",
)
.unwrap();
let config = ralph_loop_config(dir.path()).expect("project file present");
assert_eq!(config.forward.max_iterations, 7);
}
#[test]
fn project_ralph_file_hot_reloads_on_change() {
let dir = scratch("hot_reload");
let path = dir.path().join("ralph_loop.toml");
fs::write(&path, "[forward]\nmax_iterations = 7\n").unwrap();
assert_eq!(
ralph_loop_config(dir.path())
.expect("file present")
.forward
.max_iterations,
7
);
fs::write(&path, "[forward]\nmax_iterations = 42\n").unwrap();
assert_eq!(
ralph_loop_config(dir.path())
.expect("file present")
.forward
.max_iterations,
42
);
}
#[test]
fn broken_project_file_yields_none_not_the_global_leak() {
let dir = scratch("broken");
fs::write(dir.path().join("ralph_loop.toml"), "not toml [[[\n").unwrap();
assert!(ralph_loop_config(dir.path()).is_none());
}
#[test]
fn epistemic_section_is_deserialized_not_silently_dropped() {
let dir = scratch("epistemic_1640");
fs::write(
dir.path().join("ralph_loop.toml"),
r#"
[forward]
max_iterations = 5
[epistemic]
enabled = true
decay_enabled = false
decay_interval_hours = 48
contradiction_detection = true
source_required = false
"#,
)
.unwrap();
let config = ralph_loop_config(dir.path()).expect("file present");
assert_eq!(config.forward.max_iterations, 5);
assert!(config.epistemic.enabled);
assert!(!config.epistemic.decay_enabled);
assert_eq!(config.epistemic.decay_interval_hours, 48);
assert!(config.epistemic.contradiction_detection);
assert!(!config.epistemic.source_required);
}
#[test]
fn missing_epistemic_section_uses_defaults() {
let dir = scratch("epistemic_defaults");
fs::write(
dir.path().join("ralph_loop.toml"),
"[forward]\nmax_iterations = 10\n",
)
.unwrap();
let config = ralph_loop_config(dir.path()).expect("file present");
assert!(config.epistemic.enabled);
assert!(config.epistemic.decay_enabled);
assert_eq!(config.epistemic.decay_interval_hours, 720); assert!(config.epistemic.contradiction_detection);
assert!(config.epistemic.source_required);
}
#[test]
fn spec_checks_defaults_to_advisory_when_absent() {
let dir = scratch("spec_checks_default");
fs::write(
dir.path().join("ralph_loop.toml"),
"[verification]\nenabled = true\n",
)
.unwrap();
let config = ralph_loop_config(dir.path()).expect("file present");
assert_eq!(config.verification.spec_checks, SpecCheckPolicy::Advisory);
}
#[test]
fn spec_checks_parses_each_variant_and_refuses_a_typo() {
for (name, text, expected) in [
("block", "block", SpecCheckPolicy::Block),
("off", "off", SpecCheckPolicy::Off),
("advisory", "advisory", SpecCheckPolicy::Advisory),
] {
let dir = scratch(&format!("spec_checks_{name}"));
fs::write(
dir.path().join("ralph_loop.toml"),
format!("[verification]\nenabled = true\nspec_checks = \"{text}\"\n"),
)
.unwrap();
let config = ralph_loop_config(dir.path()).expect("file present");
assert_eq!(
config.verification.spec_checks, expected,
"spec_checks = {text:?} must parse as {name}"
);
}
let dir = scratch("spec_checks_typo");
fs::write(
dir.path().join("ralph_loop.toml"),
"[verification]\nspec_checks = \"warn\"\n",
)
.unwrap();
assert!(
ralph_loop_config(dir.path()).is_none(),
"an unknown policy must fail loudly, not fall back to advisory"
);
}
#[test]
fn bundled_template_documents_spec_checks_and_still_parses() {
let template = include_str!("../docs/reference/templates/ralph_loop.toml");
assert!(
template.contains("spec_checks = \"advisory\""),
"the bundled template must show the default"
);
let parsed: RalphLoopConfig = toml::from_str(template).expect("bundled template must parse");
assert_eq!(parsed.verification.spec_checks, SpecCheckPolicy::Advisory);
}