use super::*;
#[test]
fn setting_a_style_level_round_trips_through_daemon_config() {
for level in [StyleLevel::Full, StyleLevel::Plain, StyleLevel::Bare] {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
ShepToml::try_edit(&path, |doc| doc.set_style_level(level)).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
let cfg = DaemonConfig::load(Some(&written), &|_| None).unwrap();
assert_eq!(cfg.style.level.as_deref(), Some(level.to_string().as_str()));
}
}
#[test]
fn setting_a_style_level_leaves_the_rest_of_the_file_exactly_as_it_was() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
std::fs::write(&path, PRESERVATION_SEED).unwrap();
ShepToml::try_edit(&path, |doc| doc.set_style_level(StyleLevel::Plain)).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert_preserves_comments_and_key_order(&written);
let cfg = DaemonConfig::load(Some(&written), &|_| None).unwrap();
assert_eq!(cfg.style.level.as_deref(), Some("plain"));
}
#[test]
fn setting_a_style_level_twice_replaces_rather_than_appends() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
ShepToml::try_edit(&path, |doc| doc.set_style_level(StyleLevel::Full)).unwrap();
ShepToml::try_edit(&path, |doc| doc.set_style_level(StyleLevel::Bare)).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert_eq!(written.matches("level").count(), 1, "one key, not appended");
let cfg = DaemonConfig::load(Some(&written), &|_| None).unwrap();
assert_eq!(cfg.style.level.as_deref(), Some("bare"));
}
#[test]
fn setting_a_style_level_into_a_home_with_no_shep_toml_creates_one() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
assert!(!path.exists());
ShepToml::try_edit(&path, |doc| doc.set_style_level(StyleLevel::Bare)).unwrap();
assert!(path.exists());
let cfg =
DaemonConfig::load(Some(&std::fs::read_to_string(&path).unwrap()), &|_| None).unwrap();
assert_eq!(cfg.style.level.as_deref(), Some("bare"));
}
#[test]
fn the_starter_interpreters_are_written_active() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
ShepToml::edit(&path, |doc| doc.write_starter_interpreters()).unwrap();
let cfg =
DaemonConfig::load(Some(&std::fs::read_to_string(&path).unwrap()), &|_| None).unwrap();
assert_eq!(cfg.interpreters.get("js").map(String::as_str), Some("node"));
assert_eq!(
cfg.interpreters.get("mjs").map(String::as_str),
Some("node")
);
assert_eq!(
cfg.interpreters.get("cjs").map(String::as_str),
Some("node")
);
assert_eq!(
cfg.interpreters.get("py").map(String::as_str),
Some("python3")
);
assert_eq!(cfg.interpreters.get("rb").map(String::as_str), Some("ruby"));
assert_eq!(cfg.interpreters.get("sh").map(String::as_str), Some("sh"));
}
#[test]
fn the_starter_interpreters_carry_an_explanatory_comment() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
ShepToml::edit(&path, |doc| doc.write_starter_interpreters()).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert!(
written.contains("# Extension -> interpreter mapping"),
"no explanatory comment above [interpreters]:\n{written}"
);
assert!(
written.find("# Extension -> interpreter mapping").unwrap()
< written.find("[interpreters]").unwrap(),
"the comment must precede the table it explains:\n{written}"
);
assert!(
!written.contains('\u{2014}') && !written.contains('\u{2013}'),
"no em or en dashes in copy an operator reads:\n{written}"
);
}
#[test]
fn writing_the_starter_interpreters_twice_does_not_duplicate_or_clobber() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
ShepToml::edit(&path, |doc| doc.write_starter_interpreters()).unwrap();
let edited = std::fs::read_to_string(&path)
.unwrap()
.replace("js = \"node\"", "js = \"bun\"");
std::fs::write(&path, &edited).unwrap();
ShepToml::edit(&path, |doc| doc.write_starter_interpreters()).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert_eq!(
written.matches("[interpreters]").count(),
1,
"one table, not appended:\n{written}"
);
let cfg = DaemonConfig::load(Some(&written), &|_| None).unwrap();
assert_eq!(
cfg.interpreters.get("js").map(String::as_str),
Some("bun"),
"the operator's own edit must survive a second scaffold call"
);
}
#[test]
fn a_style_key_that_is_not_a_table_is_reported_and_the_file_is_never_rewritten() {
use std::os::unix::fs::MetadataExt as _;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("shep.toml");
let original = "style = \"full\"\n";
std::fs::write(&path, original).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
let before = std::fs::metadata(&path).unwrap();
let err = ShepToml::try_edit(&path, |doc| doc.set_style_level(StyleLevel::Bare))
.expect_err("style is a string here, not a table");
assert!(
matches!(
&err,
ShepTomlError::WrongShape { key, found, .. }
if *key == "style" && *found == "string"
),
"{err:?}"
);
assert_eq!(
err.to_string(),
format!(
"{}: [style] must be a table, found a string",
path.display()
)
);
assert_eq!(
std::fs::read_to_string(&path).unwrap(),
original,
"a refused write must leave the operator's file exactly as it was"
);
let after = std::fs::metadata(&path).unwrap();
assert_eq!(
before.ino(),
after.ino(),
"a refused write must not replace the file -- same inode, not just same bytes"
);
assert_eq!(
before.mode() & 0o777,
after.mode() & 0o777,
"a refused write must not touch the file's mode"
);
}