use crate::workspace_paths::{
MALVIN_DIR, MALVIN_USER_HOME_DIR, canonical_work_dir_for_logs, find_malvin_logs_root,
git_worktree_toplevel, is_malvin_workspace, legacy_malvin_checks_path,
malvin_acp_spawn_chamber_dir, malvin_advice_path, malvin_checks_path, malvin_config_path,
malvin_data_root, malvin_home_config_path, malvin_home_logs_root, malvin_home_snapshots_root,
malvin_logs_root, malvin_user_home_root, read_work_dir_manifest,
remove_legacy_malvin_checks_file, resolve_malvin_checks_path, snapshot_category_dir,
workspace_logs_hash, write_work_dir_manifest,
};
fn path_helpers_and_workspace_marker() {
let _ = crate::seed_malvin_config;
crate::test_utils::with_isolated_home(|work| {
assert_eq!(
malvin_checks_path(work),
crate::user_home_dir().join(".malvin").join("gates")
);
assert_eq!(malvin_advice_path(work), work.join(".malvin/advice.md"));
assert_eq!(malvin_config_path(work), malvin_home_config_path());
assert!(!is_malvin_workspace(work));
std::fs::create_dir_all(work.join(MALVIN_DIR)).unwrap();
assert!(is_malvin_workspace(work));
});
}
fn malvin_data_root_uses_git_toplevel_when_inside_repo() {
let tmp = tempfile::tempdir().unwrap();
let repo = tmp.path().join("repo");
std::fs::create_dir_all(repo.join("src")).unwrap();
assert!(
std::process::Command::new("git")
.args(["init"])
.current_dir(&repo)
.status()
.unwrap()
.success()
);
let sub = repo.join("src");
let repo = repo.canonicalize().expect("canonicalize");
assert_eq!(malvin_data_root(&sub), repo);
assert_eq!(malvin_checks_path(&sub), repo.join(".malvin").join("gates"));
assert_eq!(
malvin_acp_spawn_chamber_dir(&sub),
repo.join(".malvin").join("acp_spawn")
);
assert!(git_worktree_toplevel(&sub).is_some());
assert!(
crate::workspace_paths::workspace_paths_data_root::git_worktree_toplevel(&sub).is_some()
);
}
fn resolve_malvin_checks_path_falls_back_to_legacy_cwd_relative() {
crate::test_utils::with_isolated_home(|work| {
let legacy = legacy_malvin_checks_path(work);
std::fs::create_dir_all(legacy.parent().unwrap()).unwrap();
std::fs::write(&legacy, "legacy gate\n").unwrap();
assert_eq!(resolve_malvin_checks_path(work), legacy);
});
}
fn workspace_logs_hash_is_stable_hex() {
let tmp = tempfile::tempdir().unwrap();
let w = tmp.path().join("proj");
std::fs::create_dir_all(&w).unwrap();
let h1 = workspace_logs_hash(&w);
let h2 = workspace_logs_hash(&w);
assert_eq!(h1, h2);
assert_eq!(h1.len(), 16);
assert!(h1.bytes().all(|b| b.is_ascii_hexdigit()));
}
fn workspace_logs_hash_differs_for_different_paths() {
let tmp = tempfile::tempdir().unwrap();
let a = tmp.path().join("a");
let b = tmp.path().join("b");
std::fs::create_dir_all(&a).unwrap();
std::fs::create_dir_all(&b).unwrap();
assert_ne!(workspace_logs_hash(&a), workspace_logs_hash(&b));
}
fn malvin_user_home_root_uses_malvin_home_dir() {
let root = malvin_user_home_root();
assert!(root.ends_with(MALVIN_USER_HOME_DIR));
let temporary_fallback =
std::env::temp_dir().join(format!("malvin-test-home-{}", std::process::id()));
assert!(
root.starts_with(crate::user_home_dir()) || root.starts_with(temporary_fallback),
"unexpected Malvin home root: {}",
root.display()
);
}
fn malvin_logs_root_lives_under_home_not_workspace() {
let tmp = tempfile::tempdir().unwrap();
let w = tmp.path().join("ws");
std::fs::create_dir_all(&w).unwrap();
let root = malvin_logs_root(&w);
assert!(root.starts_with(malvin_home_logs_root()));
assert!(!root.starts_with(w));
}
fn malvin_snapshots_root_lives_under_home() {
let root = malvin_home_snapshots_root();
assert!(root.ends_with(".malvin/snapshots") || root.ends_with(".malvin\\snapshots"));
assert_eq!(snapshot_category_dir("gitignore"), root.join("gitignore"));
}
fn find_malvin_logs_root_none_until_bucket_exists() {
let tmp = tempfile::tempdir().unwrap();
let w = tmp.path().join("fresh");
std::fs::create_dir_all(&w).unwrap();
assert_eq!(find_malvin_logs_root(&w), None);
let bucket = malvin_logs_root(&w);
std::fs::create_dir_all(&bucket).unwrap();
assert_eq!(find_malvin_logs_root(&w).as_deref(), Some(bucket.as_path()));
}
fn work_dir_manifest_round_trip() {
let tmp = tempfile::tempdir().unwrap();
let ws = tmp.path().join("ws");
let run = tmp.path().join("run");
std::fs::create_dir_all(&ws).unwrap();
std::fs::create_dir_all(&run).unwrap();
write_work_dir_manifest(&run, &ws).unwrap();
let read = read_work_dir_manifest(&run).expect("manifest");
assert_eq!(read, canonical_work_dir_for_logs(&ws));
}
fn remove_legacy_malvin_checks_file_deletes_legacy_not_layout_checks() {
crate::test_utils::with_isolated_home(|work| {
assert!(
std::process::Command::new("git")
.args(["init"])
.current_dir(work)
.status()
.expect("git init")
.success()
);
std::fs::write(work.join(".malvin_checks"), "legacy\n").unwrap();
std::fs::create_dir_all(work.join(MALVIN_DIR)).unwrap();
std::fs::write(malvin_checks_path(work), "current\n").unwrap();
remove_legacy_malvin_checks_file(work);
assert!(!work.join(".malvin_checks").exists());
assert_eq!(
std::fs::read_to_string(malvin_checks_path(work)).unwrap(),
"current\n"
);
});
}
fn home_malvin_config_write_blocked_without_test_mutation_flag() {
use crate::malvin_config_file::{open_malvin_config, write_config_value};
crate::test_utils::with_isolated_home(|work| {
let cfg = malvin_config_path(work);
assert!(!cfg.exists());
open_malvin_config(work).expect("ensure default");
assert!(cfg.is_file());
crate::test_utils::revoke_home_malvin_config_mutation_for_test();
let value: toml::Value = toml::from_str("mem_limit_gb = 99").expect("toml");
assert!(
write_config_value(&cfg, &value).is_err(),
"write must fail without mutation consent"
);
crate::test_utils::allow_home_malvin_config_mutation_for_test();
});
}
#[test]
fn kiss_bundled_workspace_paths_tests() {
path_helpers_and_workspace_marker();
malvin_data_root_uses_git_toplevel_when_inside_repo();
resolve_malvin_checks_path_falls_back_to_legacy_cwd_relative();
workspace_logs_hash_is_stable_hex();
workspace_logs_hash_differs_for_different_paths();
malvin_user_home_root_uses_malvin_home_dir();
malvin_logs_root_lives_under_home_not_workspace();
malvin_snapshots_root_lives_under_home();
find_malvin_logs_root_none_until_bucket_exists();
work_dir_manifest_round_trip();
remove_legacy_malvin_checks_file_deletes_legacy_not_layout_checks();
home_malvin_config_write_blocked_without_test_mutation_flag();
}