use std::path::{Path, PathBuf};
use crate::artifacts::{MalvinChecksBackup, MalvinConfigWorkspaceBackup, SessionDotfileBackups};
use crate::repo_gates::MALVIN_CHECKS_FILE;
use crate::seed_malvin_config;
use crate::test_utils::with_isolated_home;
fn workspace_paths(work: &Path) -> (PathBuf, PathBuf, PathBuf) {
(
work.join(MALVIN_CHECKS_FILE),
crate::malvin_config_path(work),
work.join(".gitignore"),
)
}
fn seed_workspace(work: &Path) {
std::fs::create_dir_all(work).unwrap();
std::fs::create_dir_all(work.join(".malvin")).unwrap();
let (m, _c, gi) = workspace_paths(work);
std::fs::write(&m, b"m\n").unwrap();
std::fs::write(&gi, b"g\n").unwrap();
seed_malvin_config(work, "c\n");
}
fn assert_workspace_restored(work: &Path) {
let (m, cfg, gi) = workspace_paths(work);
assert_eq!(std::fs::read_to_string(&m).unwrap(), "m\n");
assert_eq!(std::fs::read_to_string(&cfg).unwrap(), "c2\n");
assert_eq!(std::fs::read_to_string(&gi).unwrap(), "g\n");
}
fn empty_parts() -> crate::session_dotfile_backup::SessionDotfileParts {
crate::session_dotfile_backup::SessionDotfileParts {
malvin_checks: MalvinChecksBackup::Missing,
gitignore: crate::session_dotfile_backup::GitignoreBackup::Missing,
vision: crate::session_dotfile_backup::VisionBackup::Missing,
malvin_config_workspace: MalvinConfigWorkspaceBackup::Missing,
}
}
#[test]
fn session_snapshot_bundle_round_trip() {
with_isolated_home(|work| {
seed_workspace(work);
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let (m, _cfg, gi) = workspace_paths(work);
std::fs::write(&m, b"m2\n").unwrap();
std::fs::write(&gi, b"g2\n").unwrap();
seed_malvin_config(work, "c2\n");
bundle.restore(work).unwrap();
assert_workspace_restored(work);
});
}
#[test]
fn restore_excluding_malvin_checks_leaves_checks_unchanged() {
with_isolated_home(|work| {
seed_workspace(work);
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let (m, c, gi) = workspace_paths(work);
std::fs::write(&m, b"agent-edited\n").unwrap();
std::fs::write(&gi, b"g-agent\n").unwrap();
seed_malvin_config(work, "c-agent\n");
bundle.restore_excluding_malvin_checks(work).unwrap();
assert_eq!(std::fs::read_to_string(&m).unwrap(), "agent-edited\n");
assert_eq!(std::fs::read_to_string(&gi).unwrap(), "g\n");
assert_eq!(std::fs::read_to_string(&c).unwrap(), "c-agent\n");
crate::session_dotfile_backup::restore_workspace_session_dotfiles_excluding_malvin_checks(
work, &bundle,
)
.unwrap();
});
}
#[test]
fn restore_session_dotfiles_strips_legacy_root_checks_file() {
let tmp = tempfile::tempdir().unwrap();
let work = tmp.path();
std::fs::create_dir_all(work).unwrap();
std::fs::write(work.join(".malvin_checks"), "legacy\n").unwrap();
SessionDotfileBackups::from_parts(empty_parts())
.restore(work)
.unwrap();
assert!(!work.join(".malvin_checks").exists());
}
#[test]
fn vision_snapshot_round_trip() {
with_isolated_home(|work| {
seed_workspace(work);
std::fs::write(work.join("VISION.md"), b"vision\n").unwrap();
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let vi = work.join("VISION.md");
std::fs::write(&vi, b"tampered\n").unwrap();
bundle.restore(work).unwrap();
assert_eq!(std::fs::read(&vi).unwrap(), b"vision\n");
});
}
#[test]
fn vision_missing_at_snapshot_removes_agent_created_file() {
with_isolated_home(|work| {
std::fs::create_dir_all(work).unwrap();
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let vi = work.join("VISION.md");
std::fs::write(&vi, b"agent-created\n").unwrap();
bundle.restore(work).unwrap();
assert!(!vi.exists());
});
}
#[test]
fn gitignore_snapshot_round_trip() {
with_isolated_home(|work| {
seed_workspace(work);
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let gi = work.join(".gitignore");
std::fs::write(&gi, b"tampered\n").unwrap();
bundle.restore(work).unwrap();
assert_eq!(std::fs::read(&gi).unwrap(), b"g\n");
});
}
#[test]
fn gitignore_missing_at_snapshot_removes_agent_created_file() {
with_isolated_home(|work| {
std::fs::create_dir_all(work).unwrap();
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let gi = work.join(".gitignore");
std::fs::write(&gi, b"agent-created\n").unwrap();
bundle.restore(work).unwrap();
assert!(!gi.exists());
});
}
#[test]
fn workspace_malvin_config_missing_at_snapshot_removes_agent_created_file() {
with_isolated_home(|work| {
std::fs::create_dir_all(work.join(".malvin")).unwrap();
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let cfg = work.join(crate::MALVIN_CONFIG_REL);
std::fs::write(&cfg, b"agent-created\n").unwrap();
bundle.restore(work).unwrap();
assert!(!cfg.exists());
});
}
#[test]
fn gate_restore_restore_excludes_malvin_checks() {
with_isolated_home(|work| {
seed_workspace(work);
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
let (m, c, gi) = workspace_paths(work);
std::fs::write(&m, b"agent-checks\n").unwrap();
std::fs::write(&gi, b"g-agent\n").unwrap();
seed_malvin_config(work, "c-agent\n");
bundle.restore_excluding_malvin_checks(work).unwrap();
assert_eq!(std::fs::read_to_string(&m).unwrap(), "agent-checks\n");
assert_eq!(std::fs::read_to_string(&gi).unwrap(), "g\n");
assert_eq!(std::fs::read_to_string(&c).unwrap(), "c-agent\n");
});
}
#[test]
fn home_config_survives_session_restore() {
with_isolated_home(|work| {
seed_workspace(work);
let bundle = SessionDotfileBackups::snapshot(work).unwrap();
seed_malvin_config(work, "user-edited\n");
bundle.restore(work).unwrap();
assert_eq!(
std::fs::read_to_string(crate::malvin_config_path(work)).unwrap(),
"user-edited\n"
);
});
}