use super::{McPaths, settings::SettingsScope, settings_storage};
const SEEN_KEY: &str = "welcome_release_notes_seen";
pub(crate) fn has_seen(paths: &McPaths, version: &str) -> anyhow::Result<bool> {
let raw = settings_storage::read_settings_json_or_empty(&paths.settings_file)?;
Ok(raw
.get(SEEN_KEY)
.and_then(|seen| seen.get(version))
.and_then(|v| v.as_bool())
== Some(true))
}
pub(crate) fn mark_seen(paths: &McPaths, version: &str) -> anyhow::Result<()> {
settings_storage::update_settings_json(paths, SettingsScope::Global, |raw| {
let seen = raw
.as_object_mut()
.expect("storage validates object")
.entry(SEEN_KEY)
.or_insert_with(|| serde_json::json!({}));
let seen = seen
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("{SEEN_KEY} must be an object"))?;
seen.insert(version.to_owned(), serde_json::Value::Bool(true));
Ok(())
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn release_notes_seen_is_global_versioned_and_preserves_unknown_settings() {
let temp = tempfile::tempdir().unwrap();
let paths = McPaths::from_root_and_project_dir(
temp.path().join("home"),
temp.path().join("project"),
);
std::fs::create_dir_all(&paths.root).unwrap();
std::fs::write(
&paths.settings_file,
r#"{"schema_version":2,"future":{"keep":42}}"#,
)
.unwrap();
assert!(!has_seen(&paths, "1.2.3").unwrap());
mark_seen(&paths, "1.2.3").unwrap();
mark_seen(&paths, "1.2.4").unwrap();
assert!(has_seen(&paths, "1.2.3").unwrap());
assert!(!has_seen(&paths, "1.2.3-beta.1").unwrap());
let other =
McPaths::from_root_and_project_dir(paths.root.clone(), temp.path().join("other"));
assert!(has_seen(&other, "1.2.4").unwrap());
let raw = settings_storage::read_settings_json_or_empty(&paths.settings_file).unwrap();
assert_eq!(raw["future"]["keep"], 42);
assert!(!paths.project_settings_file.exists());
}
#[test]
fn release_notes_seen_rejects_corrupt_storage_without_overwriting() {
let temp = tempfile::tempdir().unwrap();
let paths = McPaths::from_root(temp.path().to_owned());
for invalid in ["not json", r#"{"welcome_release_notes_seen":false}"#] {
std::fs::write(&paths.settings_file, invalid).unwrap();
assert!(mark_seen(&paths, "1.0.0").is_err());
assert_eq!(
std::fs::read_to_string(&paths.settings_file).unwrap(),
invalid
);
}
}
#[cfg(unix)]
#[test]
fn release_notes_seen_rejects_symlink() {
let temp = tempfile::tempdir().unwrap();
let paths = McPaths::from_root(temp.path().to_owned());
let target = temp.path().join("target.json");
std::fs::write(&target, "{}").unwrap();
std::os::unix::fs::symlink(&target, &paths.settings_file).unwrap();
assert!(has_seen(&paths, "1.0.0").is_err());
assert!(mark_seen(&paths, "1.0.0").is_err());
assert_eq!(std::fs::read_to_string(target).unwrap(), "{}");
}
}