use std::{fs, path::PathBuf};
use dotr_dear::{
cli::{Cli, Command, DeployArgs, UpdateArgs, run_cli},
config::Config,
package::Package,
};
struct TestFixture {
cwd: PathBuf,
}
impl TestFixture {
fn new() -> Self {
let temp_dir =
std::env::temp_dir().join(format!("dotr_clean_test_{}", uuid::Uuid::new_v4()));
fs::create_dir_all(&temp_dir).expect("Failed to create temp dir");
Self { cwd: temp_dir }
}
fn get_cli(&self, command: Option<Command>) -> Cli {
Cli {
command,
working_dir: Some(self.cwd.to_str().unwrap().to_string()),
}
}
fn init(&self) {
run_cli(self.get_cli(Some(Command::Init(dotr_dear::cli::InitArgs {}))))
.expect("Init failed");
}
fn get_config(&self) -> Config {
Config::from_path(&self.cwd).expect("Failed to load config")
}
fn assert_file_exists(&self, path: &str, message: &str) {
assert!(self.cwd.join(path).exists(), "{}", message);
}
fn assert_file_not_exists(&self, path: &str, message: &str) {
assert!(!self.cwd.join(path).exists(), "{}", message);
}
fn write_file(&self, path: &str, content: &str) {
let file_path = self.cwd.join(path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).expect("Failed to create parent dir");
}
fs::write(file_path, content).expect("Failed to write file");
}
}
impl Drop for TestFixture {
fn drop(&mut self) {
fs::remove_dir_all(&self.cwd).ok();
}
}
#[test]
fn test_deploy_clean_by_default() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "test_pkg".to_string(),
src: "dotfiles/test_pkg".to_string(),
dest: "deploy_dest/test".to_string(),
..Default::default()
};
config.packages.insert("test_pkg".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("test_pkg".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("dotfiles/test_pkg/file1.txt", "file1");
fixture.write_file("dotfiles/test_pkg/file2.txt", "file2");
fixture.write_file("deploy_dest/test/file1.txt", "old");
fixture.write_file("deploy_dest/test/extra.txt", "should be removed");
let result = run_cli(fixture.get_cli(Some(Command::Deploy(DeployArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: None,
dry_run: false,
..Default::default()
}))));
assert!(result.is_ok(), "Deploy should succeed");
fixture.assert_file_exists("deploy_dest/test/file1.txt", "file1 should exist");
fixture.assert_file_exists("deploy_dest/test/file2.txt", "file2 should exist");
fixture.assert_file_not_exists(
"deploy_dest/test/extra.txt",
"extra file should be removed by default clean",
);
}
#[test]
fn test_update_clean_by_default() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "backup_pkg".to_string(),
src: "dotfiles/backup_pkg".to_string(),
dest: "source/backup".to_string(),
..Default::default()
};
config.packages.insert("backup_pkg".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("backup_pkg".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("source/backup/file1.txt", "file1");
fixture.write_file("source/backup/file2.txt", "file2");
fixture.write_file("dotfiles/backup_pkg/file1.txt", "old");
fixture.write_file("dotfiles/backup_pkg/extra.txt", "should be removed");
let result = run_cli(fixture.get_cli(Some(Command::Update(UpdateArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: None,
dry_run: false,
}))));
assert!(result.is_ok(), "Update should succeed");
fixture.assert_file_exists("dotfiles/backup_pkg/file1.txt", "file1 should exist");
fixture.assert_file_exists("dotfiles/backup_pkg/file2.txt", "file2 should exist");
fixture.assert_file_not_exists(
"dotfiles/backup_pkg/extra.txt",
"extra file should be removed by default clean",
);
}
#[test]
fn test_deploy_explicit_no_clean_overrides_default() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "no_clean_pkg".to_string(),
src: "dotfiles/no_clean_pkg".to_string(),
dest: "deploy_dest/no_clean".to_string(),
..Default::default()
};
config.packages.insert("no_clean_pkg".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("no_clean_pkg".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("dotfiles/no_clean_pkg/file1.txt", "file1");
fixture.write_file("dotfiles/no_clean_pkg/file2.txt", "file2");
fixture.write_file("deploy_dest/no_clean/file1.txt", "old");
fixture.write_file("deploy_dest/no_clean/extra.txt", "should remain");
let result = run_cli(fixture.get_cli(Some(Command::Deploy(DeployArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: Some(false),
dry_run: false,
..Default::default()
}))));
assert!(result.is_ok(), "Deploy should succeed");
fixture.assert_file_exists("deploy_dest/no_clean/file1.txt", "file1 should exist");
fixture.assert_file_exists("deploy_dest/no_clean/file2.txt", "file2 should exist");
fixture.assert_file_exists(
"deploy_dest/no_clean/extra.txt",
"extra file should remain when clean=false",
);
}
#[test]
fn test_package_clean_false_overrides_default() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "pkg_no_clean".to_string(),
src: "dotfiles/pkg_no_clean".to_string(),
dest: "deploy_dest/pkg_no_clean".to_string(),
clean: false, ..Default::default()
};
config.packages.insert("pkg_no_clean".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("pkg_no_clean".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("dotfiles/pkg_no_clean/file1.txt", "file1");
fixture.write_file("dotfiles/pkg_no_clean/file2.txt", "file2");
fixture.write_file("deploy_dest/pkg_no_clean/file1.txt", "old");
fixture.write_file("deploy_dest/pkg_no_clean/extra.txt", "should remain");
let result = run_cli(fixture.get_cli(Some(Command::Deploy(DeployArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: None,
dry_run: false,
..Default::default()
}))));
assert!(result.is_ok(), "Deploy should succeed");
fixture.assert_file_exists("deploy_dest/pkg_no_clean/file1.txt", "file1 should exist");
fixture.assert_file_exists("deploy_dest/pkg_no_clean/file2.txt", "file2 should exist");
fixture.assert_file_exists(
"deploy_dest/pkg_no_clean/extra.txt",
"extra file should remain when package clean=false",
);
}
#[test]
fn test_cli_clean_arg_overrides_package_setting() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "override_pkg".to_string(),
src: "dotfiles/override_pkg".to_string(),
dest: "deploy_dest/override".to_string(),
clean: false, ..Default::default()
};
config.packages.insert("override_pkg".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("override_pkg".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("dotfiles/override_pkg/file1.txt", "file1");
fixture.write_file("dotfiles/override_pkg/file2.txt", "file2");
fixture.write_file("deploy_dest/override/file1.txt", "old");
fixture.write_file("deploy_dest/override/extra.txt", "should be removed");
let result = run_cli(fixture.get_cli(Some(Command::Deploy(DeployArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: Some(true),
dry_run: false,
..Default::default()
}))));
assert!(result.is_ok(), "Deploy should succeed");
fixture.assert_file_exists("deploy_dest/override/file1.txt", "file1 should exist");
fixture.assert_file_exists("deploy_dest/override/file2.txt", "file2 should exist");
fixture.assert_file_not_exists(
"deploy_dest/override/extra.txt",
"extra file should be removed when CLI arg overrides package setting",
);
}
#[test]
fn test_package_clean_true_explicit() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "explicit_clean".to_string(),
src: "dotfiles/explicit_clean".to_string(),
dest: "deploy_dest/explicit_clean".to_string(),
clean: true, ..Default::default()
};
config
.packages
.insert("explicit_clean".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("explicit_clean".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("dotfiles/explicit_clean/file1.txt", "file1");
fixture.write_file("deploy_dest/explicit_clean/extra.txt", "should be removed");
let result = run_cli(fixture.get_cli(Some(Command::Deploy(DeployArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: None,
dry_run: false,
..Default::default()
}))));
assert!(result.is_ok(), "Deploy should succeed");
fixture.assert_file_exists("deploy_dest/explicit_clean/file1.txt", "file1 should exist");
fixture.assert_file_not_exists(
"deploy_dest/explicit_clean/extra.txt",
"extra file should be removed when package clean=true",
);
}
#[test]
fn test_update_with_package_clean_false() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "update_no_clean".to_string(),
src: "dotfiles/update_no_clean".to_string(),
dest: "source/update_no_clean".to_string(),
clean: false,
..Default::default()
};
config
.packages
.insert("update_no_clean".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("update_no_clean".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("source/update_no_clean/file1.txt", "file1");
fixture.write_file("source/update_no_clean/file2.txt", "file2");
fixture.write_file("dotfiles/update_no_clean/file1.txt", "old");
fixture.write_file("dotfiles/update_no_clean/extra.txt", "should remain");
let result = run_cli(fixture.get_cli(Some(Command::Update(UpdateArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: None,
dry_run: false,
}))));
assert!(result.is_ok(), "Update should succeed");
fixture.assert_file_exists("dotfiles/update_no_clean/file1.txt", "file1 should exist");
fixture.assert_file_exists("dotfiles/update_no_clean/file2.txt", "file2 should exist");
fixture.assert_file_exists(
"dotfiles/update_no_clean/extra.txt",
"extra file should remain when package clean=false",
);
}
#[test]
fn test_update_cli_override_package_clean() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let package = Package {
name: "update_override".to_string(),
src: "dotfiles/update_override".to_string(),
dest: "source/update_override".to_string(),
clean: false,
..Default::default()
};
config
.packages
.insert("update_override".to_string(), package);
config
.profiles
.get_mut("default")
.unwrap()
.dependencies
.push("update_override".to_string());
config.save(&fixture.cwd).expect("Failed to save config");
fixture.write_file("source/update_override/file1.txt", "file1");
fixture.write_file("dotfiles/update_override/extra.txt", "should be removed");
let result = run_cli(fixture.get_cli(Some(Command::Update(UpdateArgs {
packages: None,
profile: None,
ignore_errors: false,
clean: Some(true),
dry_run: false,
}))));
assert!(result.is_ok(), "Update should succeed");
fixture.assert_file_exists("dotfiles/update_override/file1.txt", "file1 should exist");
fixture.assert_file_not_exists(
"dotfiles/update_override/extra.txt",
"extra file should be removed when CLI overrides package setting",
);
}