use std::{fs, path::PathBuf};
use dotr_dear::{
cli::{DeployArgs, ImportArgs, InitArgs, PrintVarsArgs, UpdateArgs, run_cli},
config::Config,
context::Context,
package::get_pkg_name_and_rel_path,
utils,
};
mod common;
const PLAYGROUND_DIR: &str = "tests/playground";
const NVIM_PATH: &str = "src/nvim/";
const BASHRC_PATH: &str = "src/.bashrc";
const ZSHRC_PATH: &str = "src/.zshrc";
const VIMRC_PATH: &str = "src/.vimrc";
const GITCONFIG_PATH: &str = "src/.gitconfig";
const TMUX_PATH: &str = "src/tmux/";
const ALACRITTY_PATH: &str = "src/config/alacritty/";
struct TestFixture {
cwd: PathBuf,
}
impl TestFixture {
fn new() -> Self {
let cwd = PathBuf::from(PLAYGROUND_DIR);
common::setup(&cwd);
Self { cwd }
}
fn get_cli(&self, command: Option<dotr_dear::cli::Command>) -> dotr_dear::cli::Cli {
dotr_dear::cli::Cli {
command,
working_dir: Some(PLAYGROUND_DIR.to_string()),
}
}
fn init(&self) {
run_cli(self.get_cli(Some(dotr_dear::cli::Command::Init(InitArgs {}))))
.expect("Init failed");
}
fn import(&self, path: &str) {
run_cli(
self.get_cli(Some(dotr_dear::cli::Command::Import(ImportArgs {
path: path.to_string(),
..Default::default()
}))),
)
.expect("Import failed");
}
fn deploy(&self, packages: Option<Vec<String>>) {
run_cli(
self.get_cli(Some(dotr_dear::cli::Command::Deploy(DeployArgs {
packages,
profile: None,
ignore_errors: false,
clean: Some(false),
dry_run: false,
..Default::default()
}))),
)
.expect("Deploy failed");
}
fn update(&self, packages: Option<Vec<String>>) {
run_cli(
self.get_cli(Some(dotr_dear::cli::Command::Update(UpdateArgs {
packages,
profile: None,
ignore_errors: false,
clean: Some(false),
dry_run: false,
}))),
)
.expect("Update failed");
}
fn get_config(&self) -> Config {
Config::from_path(&self.cwd).expect("Failed to load config")
}
fn get_package_name(&self, path: &str) -> String {
let args = ImportArgs {
path: path.to_string(),
..Default::default()
};
get_pkg_name_and_rel_path(&args, &self.cwd).unwrap().0
}
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 assert_file_contains(&self, path: &str, content: &str, message: &str) {
let file_path = self.cwd.join(path);
let file_content = fs::read_to_string(&file_path)
.unwrap_or_else(|_| panic!("Failed to read file: {}", path));
assert!(file_content.contains(content), "{}", message);
}
fn write_file(&self, path: &str, content: &str) {
let file_path = self.cwd.join(path);
fs::write(file_path, content).unwrap_or_else(|_| panic!("Failed to write file: {}", path));
}
fn get_context_variables(&self) -> toml::Table {
let config = self.get_config();
let (mut ctx, _) =
Context::new(&self.cwd, &config, &None, false).expect("Failed to create context");
ctx.extend_variables(config.variables.clone());
ctx.get_context_variables()
}
}
impl Drop for TestFixture {
fn drop(&mut self) {
common::teardown(&self.cwd);
}
}
#[test]
fn test_no_command() {
let fixture = TestFixture::new();
let _ = run_cli(fixture.get_cli(None));
fixture.assert_file_not_exists("config.toml", "config.toml should not be created");
fixture.assert_file_not_exists("dotfiles", "dotfiles directory should not be created");
}
#[test]
fn test_init_config() {
let fixture = TestFixture::new();
fixture.init();
fixture.assert_file_exists("config.toml", "config.toml should be created");
fixture.assert_file_exists("dotfiles", "dotfiles directory should be created");
fixture.assert_file_exists(".gitignore", ".gitignore should be created");
fixture.assert_file_contains(
".gitignore",
".uservariables.toml",
".gitignore should contain .uservariables.toml",
);
}
#[test]
fn test_import_dots() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.import(BASHRC_PATH);
let config = fixture.get_config();
let nvim_package_name = fixture.get_package_name(NVIM_PATH);
let bashrc_package_name = fixture.get_package_name(BASHRC_PATH);
assert!(
config.packages.contains_key(&nvim_package_name),
"Config should contain the nvim package"
);
let nvim_package = config.packages.get(&nvim_package_name).unwrap();
assert!(
nvim_package.dest.ends_with(NVIM_PATH),
"Package dest should match the imported path"
);
assert_eq!(
nvim_package.src,
format!("dotfiles/{}", nvim_package_name),
"Package src should be correctly set"
);
assert!(
config.packages.contains_key(&bashrc_package_name),
"Config should contain the bashrc package"
);
fixture.assert_file_exists(
&format!("dotfiles/{}/init.lua", nvim_package_name),
"nvim init.lua should be copied to dotfiles",
);
}
#[test]
fn test_canonical_linking() {
let fixture = TestFixture::new();
let path_from_home = utils::resolve_path("~/.config", &fixture.cwd).unwrap();
let path_from_root = utils::resolve_path("/Volumes/Repos/", &fixture.cwd).unwrap();
let path_from_cwd = utils::resolve_path("src/nvim", &fixture.cwd).unwrap();
assert!(path_from_home.is_absolute());
assert!(path_from_root.is_absolute());
assert!(path_from_cwd.is_absolute());
}
#[test]
fn test_deploy_all_packages() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.import(BASHRC_PATH);
fixture.write_file("src/nvim/init.lua", "-- Modified init.lua\n");
fixture.write_file("src/.bashrc", "# Modified bashrc\n");
fixture.deploy(None);
fixture.assert_file_exists(
"src/nvim/init.lua.dotrbak",
"nvim init.lua backup should exist",
);
fixture.assert_file_exists("src/.bashrc.dotrbak", "bashrc backup should exist");
fixture.assert_file_exists("src/nvim/init.lua", "nvim init.lua should be deployed");
fixture.assert_file_exists("src/.bashrc", "bashrc should be deployed");
}
#[test]
fn test_deploy_specific_package() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.import(BASHRC_PATH);
fixture.write_file("src/nvim/init.lua", "-- Modified init.lua\n");
let nvim_package_name = fixture.get_package_name(NVIM_PATH);
fixture.deploy(Some(vec![nvim_package_name]));
fixture.assert_file_exists(
"src/nvim/init.lua.dotrbak",
"nvim init.lua backup should exist",
);
fixture.assert_file_exists("src/nvim/init.lua", "nvim init.lua should be deployed");
fixture.assert_file_not_exists(
"src/.bashrc.dotrbak",
"bashrc should NOT have been deployed",
);
}
#[test]
fn test_deploy_multiple_specific_packages() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.import(BASHRC_PATH);
fixture.write_file("src/nvim/init.lua", "-- Modified init.lua\n");
fixture.write_file("src/.bashrc", "# Modified bashrc\n");
let nvim_package_name = fixture.get_package_name(NVIM_PATH);
let bashrc_package_name = fixture.get_package_name(BASHRC_PATH);
fixture.deploy(Some(vec![nvim_package_name, bashrc_package_name]));
fixture.assert_file_exists(
"src/nvim/init.lua.dotrbak",
"nvim init.lua backup should exist",
);
fixture.assert_file_exists("src/nvim/init.lua", "nvim init.lua should be deployed");
fixture.assert_file_exists("src/.bashrc.dotrbak", "bashrc backup should exist");
fixture.assert_file_exists("src/.bashrc", "bashrc should be deployed");
}
#[test]
fn test_update_specific_package() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.import(BASHRC_PATH);
fixture.deploy(None);
fixture.write_file("src/nvim/init.lua", "-- Modified nvim config\n");
fixture.write_file("src/.bashrc", "# Modified bashrc\n");
let nvim_package_name = fixture.get_package_name(NVIM_PATH);
fixture.update(Some(vec![nvim_package_name.clone()]));
fixture.assert_file_contains(
&format!("dotfiles/{}/init.lua", nvim_package_name),
"Modified nvim config",
"nvim config should be updated in dotfiles",
);
let bashrc_package_name = fixture.get_package_name(BASHRC_PATH);
let bashrc_content = fs::read_to_string(
fixture
.cwd
.join(format!("dotfiles/{}", bashrc_package_name)),
)
.expect("Failed to read bashrc");
assert!(
!bashrc_content.contains("Modified bashrc"),
"bashrc should NOT be updated in dotfiles"
);
}
#[test]
fn test_update_multiple_specific_packages() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.import(BASHRC_PATH);
fixture.deploy(None);
fixture.write_file("src/nvim/init.lua", "-- Updated nvim config\n");
fixture.write_file("src/.bashrc", "# Updated bashrc\n");
let nvim_package_name = fixture.get_package_name(NVIM_PATH);
let bashrc_package_name = fixture.get_package_name(BASHRC_PATH);
fixture.update(Some(vec![
nvim_package_name.clone(),
bashrc_package_name.clone(),
]));
fixture.assert_file_contains(
&format!("dotfiles/{}/init.lua", nvim_package_name),
"Updated nvim config",
"nvim config should be updated",
);
fixture.assert_file_contains(
&format!("dotfiles/{}", bashrc_package_name),
"Updated bashrc",
"bashrc should be updated",
);
}
#[test]
fn test_deploy_nonexistent_package() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(NVIM_PATH);
fixture.write_file("src/nvim/init.lua", "-- Modified init.lua\n");
let result = run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::Deploy(DeployArgs {
packages: Some(vec!["nonexistent_package".to_string()]),
profile: None,
ignore_errors: false,
clean: Some(false),
dry_run: false,
..Default::default()
}))),
);
assert!(
result.is_err(),
"Deploy with nonexistent package should error"
);
assert!(
result.unwrap_err().to_string().contains("not found"),
"Error should mention package not found"
);
fixture.assert_file_not_exists(
"src/nvim/init.lua.dotrbak",
"No backup should be created for filtered out packages",
);
}
#[test]
fn test_import_multiple_files() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(BASHRC_PATH);
fixture.import(ZSHRC_PATH);
fixture.import(VIMRC_PATH);
fixture.import(GITCONFIG_PATH);
let config = fixture.get_config();
let bashrc_name = fixture.get_package_name(BASHRC_PATH);
let zshrc_name = fixture.get_package_name(ZSHRC_PATH);
let vimrc_name = fixture.get_package_name(VIMRC_PATH);
let gitconfig_name = fixture.get_package_name(GITCONFIG_PATH);
assert!(config.packages.contains_key(&bashrc_name));
assert!(config.packages.contains_key(&zshrc_name));
assert!(config.packages.contains_key(&vimrc_name));
assert!(config.packages.contains_key(&gitconfig_name));
fixture.assert_file_exists(
&format!("dotfiles/{}", bashrc_name),
"bashrc should be backed up",
);
fixture.assert_file_exists(
&format!("dotfiles/{}", zshrc_name),
"zshrc should be backed up",
);
fixture.assert_file_exists(
&format!("dotfiles/{}", vimrc_name),
"vimrc should be backed up",
);
fixture.assert_file_exists(
&format!("dotfiles/{}", gitconfig_name),
"gitconfig should be backed up",
);
fixture.assert_file_contains(
&format!("dotfiles/{}", bashrc_name),
"Bashrc configuration",
"bashrc content should match",
);
fixture.assert_file_contains(
&format!("dotfiles/{}", gitconfig_name),
"Test User",
"gitconfig content should match",
);
}
#[test]
fn test_import_nested_directories() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(TMUX_PATH);
fixture.import(ALACRITTY_PATH);
let config = fixture.get_config();
let tmux_name = fixture.get_package_name(TMUX_PATH);
let alacritty_name = fixture.get_package_name(ALACRITTY_PATH);
assert!(config.packages.contains_key(&tmux_name));
assert!(config.packages.contains_key(&alacritty_name));
fixture.assert_file_exists(
&format!("dotfiles/{}/tmux.conf", tmux_name),
"tmux.conf should be backed up",
);
fixture.assert_file_exists(
&format!("dotfiles/{}/theme.conf", tmux_name),
"theme.conf should be backed up",
);
fixture.assert_file_exists(
&format!("dotfiles/{}/alacritty.yml", alacritty_name),
"alacritty.yml should be backed up",
);
fixture.assert_file_contains(
&format!("dotfiles/{}/tmux.conf", tmux_name),
"set -g mouse on",
"tmux.conf content should match",
);
fixture.assert_file_contains(
&format!("dotfiles/{}/alacritty.yml", alacritty_name),
"padding",
"alacritty.yml content should match",
);
}
#[test]
fn test_deploy_all_file_types() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(BASHRC_PATH);
fixture.import(ZSHRC_PATH);
fixture.import(NVIM_PATH);
fixture.import(TMUX_PATH);
fixture.write_file("src/.bashrc", "# Modified bashrc\n");
fixture.write_file("src/.zshrc", "# Modified zshrc\n");
fixture.write_file("src/nvim/init.lua", "-- Modified init.lua\n");
fixture.write_file("src/tmux/tmux.conf", "# Modified tmux.conf\n");
fixture.write_file("src/tmux/theme.conf", "# Modified theme.conf\n");
fixture.deploy(None);
fixture.assert_file_exists("src/.bashrc.dotrbak", "bashrc backup should exist");
fixture.assert_file_exists("src/.zshrc.dotrbak", "zshrc backup should exist");
fixture.assert_file_exists(
"src/nvim/init.lua.dotrbak",
"nvim init.lua backup should exist",
);
fixture.assert_file_exists(
"src/tmux/tmux.conf.dotrbak",
"tmux.conf backup should exist",
);
fixture.assert_file_exists(
"src/tmux/theme.conf.dotrbak",
"theme.conf backup should exist",
);
fixture.assert_file_exists("src/.bashrc", "bashrc should be deployed");
fixture.assert_file_exists("src/.zshrc", "zshrc should be deployed");
fixture.assert_file_exists("src/nvim/init.lua", "nvim init.lua should be deployed");
fixture.assert_file_exists("src/tmux/tmux.conf", "tmux.conf should be deployed");
fixture.assert_file_exists("src/tmux/theme.conf", "theme.conf should be deployed");
}
#[test]
fn test_update_preserves_changes() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(VIMRC_PATH);
fixture.import(TMUX_PATH);
fixture.deploy(None);
fixture.write_file(
"src/.vimrc",
"\" Updated vimrc\nset number\nset relativenumber\n",
);
fixture.write_file("src/tmux/tmux.conf", "# Updated tmux\nset -g mouse off\n");
fixture.update(None);
let vimrc_name = fixture.get_package_name(VIMRC_PATH);
let tmux_name = fixture.get_package_name(TMUX_PATH);
fixture.assert_file_contains(
&format!("dotfiles/{}", vimrc_name),
"Updated vimrc",
"vimrc should be updated",
);
fixture.assert_file_contains(
&format!("dotfiles/{}", vimrc_name),
"relativenumber",
"vimrc should contain new content",
);
fixture.assert_file_contains(
&format!("dotfiles/{}/tmux.conf", tmux_name),
"Updated tmux",
"tmux.conf should be updated",
);
fixture.assert_file_contains(
&format!("dotfiles/{}/tmux.conf", tmux_name),
"mouse off",
"tmux.conf should contain modified content",
);
}
#[test]
fn test_deploy_preserves_directory_structure() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(ALACRITTY_PATH);
fixture.write_file(
"src/config/alacritty/alacritty.yml",
"# Modified alacritty\n",
);
let alacritty_name = fixture.get_package_name(ALACRITTY_PATH);
fixture.deploy(Some(vec![alacritty_name]));
fixture.assert_file_exists(
"src/config/alacritty/alacritty.yml.dotrbak",
"alacritty.yml backup should exist",
);
fixture.assert_file_exists(
"src/config/alacritty/alacritty.yml",
"alacritty.yml should be deployed",
);
fixture.assert_file_contains(
"src/config/alacritty/alacritty.yml",
"window:",
"deployed file should have correct content",
);
}
#[test]
fn test_mixed_files_and_directories() {
let fixture = TestFixture::new();
fixture.init();
fixture.import(BASHRC_PATH);
fixture.import(GITCONFIG_PATH);
fixture.import(NVIM_PATH);
fixture.import(TMUX_PATH);
let config = fixture.get_config();
assert_eq!(config.packages.len(), 4, "Should have 4 packages");
fixture.write_file("src/.bashrc", "# Modified bashrc\n");
fixture.write_file("src/.gitconfig", "# Modified gitconfig\n");
fixture.write_file("src/nvim/init.lua", "-- Modified init.lua\n");
fixture.write_file("src/tmux/theme.conf", "# Modified theme.conf\n");
fixture.deploy(None);
fixture.assert_file_exists("src/.bashrc", "bashrc deployed");
fixture.assert_file_exists("src/.gitconfig", "gitconfig deployed");
fixture.assert_file_exists("src/nvim/init.lua", "nvim/init.lua deployed");
fixture.assert_file_exists("src/tmux/tmux.conf", "tmux/tmux.conf deployed");
fixture.write_file("src/nvim/init.lua", "-- Modified nvim\n");
fixture.write_file("src/tmux/theme.conf", "# Modified theme\n");
let nvim_name = fixture.get_package_name(NVIM_PATH);
let tmux_name = fixture.get_package_name(TMUX_PATH);
fixture.update(Some(vec![nvim_name.clone(), tmux_name.clone()]));
fixture.assert_file_contains(
&format!("dotfiles/{}/init.lua", nvim_name),
"Modified nvim",
"nvim should be updated",
);
fixture.assert_file_contains(
&format!("dotfiles/{}/theme.conf", tmux_name),
"Modified theme",
"tmux theme should be updated",
);
}
#[test]
fn test_print_vars_empty() {
let fixture = TestFixture::new();
fixture.init();
let _ = run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::PrintVars(PrintVarsArgs {
profile: None,
}))),
);
let ctx_vars = fixture.get_context_variables();
assert!(
ctx_vars.contains_key("HOME"),
"HOME environment variable should be present"
);
}
#[test]
fn test_print_vars_with_custom_variables() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
config
.variables
.insert("EDITOR".to_string(), toml::Value::String("vim".to_string()));
config.variables.insert(
"SHELL".to_string(),
toml::Value::String("/bin/zsh".to_string()),
);
config.variables.insert(
"USER_EMAIL".to_string(),
toml::Value::String("test@example.com".to_string()),
);
config.save(&fixture.cwd).expect("Failed to save config");
let _ = run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::PrintVars(PrintVarsArgs {
profile: None,
}))),
);
let config = fixture.get_config();
assert_eq!(
config.variables.get("EDITOR"),
Some(&toml::Value::String("vim".to_string()))
);
assert_eq!(
config.variables.get("SHELL"),
Some(&toml::Value::String("/bin/zsh".to_string()))
);
assert_eq!(
config.variables.get("USER_EMAIL"),
Some(&toml::Value::String("test@example.com".to_string()))
);
let ctx_vars = fixture.get_context_variables();
assert!(
ctx_vars.contains_key("HOME"),
"HOME environment variable should be present in Context"
);
}
#[test]
fn test_variables_persist_after_save() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
config.variables.insert(
"TEST_VAR".to_string(),
toml::Value::String("test_value".to_string()),
);
config.variables.insert(
"ANOTHER_VAR".to_string(),
toml::Value::String("another_value".to_string()),
);
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
assert_eq!(
reloaded_config.variables.get("TEST_VAR"),
Some(&toml::Value::String("test_value".to_string()))
);
assert_eq!(
reloaded_config.variables.get("ANOTHER_VAR"),
Some(&toml::Value::String("another_value".to_string()))
);
}
#[test]
fn test_home_variable_always_present() {
let fixture = TestFixture::new();
fixture.init();
let ctx_vars = fixture.get_context_variables();
assert!(ctx_vars.contains_key("HOME"));
let home = ctx_vars.get("HOME").expect("HOME variable not found");
if let toml::Value::String(s) = home {
assert!(!s.is_empty());
} else {
panic!("HOME should be a string value");
}
}
#[test]
fn test_variables_with_special_characters() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
config.variables.insert(
"PATH".to_string(),
toml::Value::String("/usr/local/bin:/usr/bin:/bin".to_string()),
);
config.variables.insert(
"PS1".to_string(),
toml::Value::String("[\\u@\\h \\W]$ ".to_string()),
);
config.variables.insert(
"COMPLEX_VAR".to_string(),
toml::Value::String("value with spaces and $pecial ch@rs".to_string()),
);
config.save(&fixture.cwd).expect("Failed to save config");
let config = fixture.get_config();
assert_eq!(
config.variables.get("PATH"),
Some(&toml::Value::String(
"/usr/local/bin:/usr/bin:/bin".to_string()
))
);
assert_eq!(
config.variables.get("PS1"),
Some(&toml::Value::String("[\\u@\\h \\W]$ ".to_string()))
);
assert_eq!(
config.variables.get("COMPLEX_VAR"),
Some(&toml::Value::String(
"value with spaces and $pecial ch@rs".to_string()
))
);
}
#[test]
fn test_variables_do_not_interfere_with_packages() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
config.variables.insert(
"MY_VAR".to_string(),
toml::Value::String("my_value".to_string()),
);
config.save(&fixture.cwd).expect("Failed to save config");
fixture.import(BASHRC_PATH);
fixture.import(NVIM_PATH);
let config = fixture.get_config();
assert_eq!(
config.variables.get("MY_VAR"),
Some(&toml::Value::String("my_value".to_string()))
);
assert_eq!(config.packages.len(), 2);
let bashrc_name = fixture.get_package_name(BASHRC_PATH);
let nvim_name = fixture.get_package_name(NVIM_PATH);
assert!(config.packages.contains_key(&bashrc_name));
assert!(config.packages.contains_key(&nvim_name));
}
#[test]
fn test_config_variables_override_environment_variables() {
let fixture = TestFixture::new();
fixture.init();
let original_home = std::env::var("HOME").expect("HOME should be set in environment");
let custom_home = "/custom/home/path";
let mut config = fixture.get_config();
config.variables.insert(
"HOME".to_string(),
toml::Value::String(custom_home.to_string()),
);
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
assert_eq!(
reloaded_config.variables.get("HOME"),
Some(&toml::Value::String(custom_home.to_string())),
"Config HOME should override environment HOME"
);
assert_ne!(
reloaded_config.variables.get("HOME"),
Some(&toml::Value::String(original_home.clone())),
"Config HOME should be different from environment HOME"
);
let ctx_vars = fixture.get_context_variables();
assert_eq!(
ctx_vars.get("HOME"),
Some(&toml::Value::String(custom_home.to_string())),
"Context should use config HOME over environment HOME"
);
}
#[test]
fn test_nested_variables_simple() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let mut database_config = toml::map::Map::new();
database_config.insert(
"host".to_string(),
toml::Value::String("localhost".to_string()),
);
database_config.insert("port".to_string(), toml::Value::Integer(5432));
database_config.insert("name".to_string(), toml::Value::String("mydb".to_string()));
config
.variables
.insert("database".to_string(), toml::Value::Table(database_config));
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
let db_var = reloaded_config.variables.get("database");
assert!(db_var.is_some(), "database variable should exist");
if let Some(toml::Value::Table(db_table)) = db_var {
assert_eq!(
db_table.get("host"),
Some(&toml::Value::String("localhost".to_string()))
);
assert_eq!(db_table.get("port"), Some(&toml::Value::Integer(5432)));
assert_eq!(
db_table.get("name"),
Some(&toml::Value::String("mydb".to_string()))
);
} else {
panic!("database should be a table");
}
}
#[test]
fn test_nested_variables_deep() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let mut port_config = toml::map::Map::new();
port_config.insert("http".to_string(), toml::Value::Integer(8080));
port_config.insert("https".to_string(), toml::Value::Integer(8443));
let mut server_config = toml::map::Map::new();
server_config.insert(
"host".to_string(),
toml::Value::String("0.0.0.0".to_string()),
);
server_config.insert("ports".to_string(), toml::Value::Table(port_config));
let mut app_config = toml::map::Map::new();
app_config.insert("name".to_string(), toml::Value::String("myapp".to_string()));
app_config.insert("server".to_string(), toml::Value::Table(server_config));
config
.variables
.insert("app".to_string(), toml::Value::Table(app_config));
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
let app_var = reloaded_config.variables.get("app");
assert!(app_var.is_some(), "app variable should exist");
if let Some(toml::Value::Table(app_table)) = app_var {
assert_eq!(
app_table.get("name"),
Some(&toml::Value::String("myapp".to_string()))
);
if let Some(toml::Value::Table(server_table)) = app_table.get("server") {
assert_eq!(
server_table.get("host"),
Some(&toml::Value::String("0.0.0.0".to_string()))
);
if let Some(toml::Value::Table(ports_table)) = server_table.get("ports") {
assert_eq!(ports_table.get("http"), Some(&toml::Value::Integer(8080)));
assert_eq!(ports_table.get("https"), Some(&toml::Value::Integer(8443)));
} else {
panic!("ports should be a table");
}
} else {
panic!("server should be a table");
}
} else {
panic!("app should be a table");
}
}
#[test]
fn test_nested_variables_with_arrays() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let hosts = vec![
toml::Value::String("server1.example.com".to_string()),
toml::Value::String("server2.example.com".to_string()),
toml::Value::String("server3.example.com".to_string()),
];
let mut cluster_config = toml::map::Map::new();
cluster_config.insert(
"name".to_string(),
toml::Value::String("production".to_string()),
);
cluster_config.insert("hosts".to_string(), toml::Value::Array(hosts));
cluster_config.insert("replicas".to_string(), toml::Value::Integer(3));
config
.variables
.insert("cluster".to_string(), toml::Value::Table(cluster_config));
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
let cluster_var = reloaded_config.variables.get("cluster");
assert!(cluster_var.is_some(), "cluster variable should exist");
if let Some(toml::Value::Table(cluster_table)) = cluster_var {
assert_eq!(
cluster_table.get("name"),
Some(&toml::Value::String("production".to_string()))
);
assert_eq!(
cluster_table.get("replicas"),
Some(&toml::Value::Integer(3))
);
if let Some(toml::Value::Array(hosts_array)) = cluster_table.get("hosts") {
assert_eq!(hosts_array.len(), 3);
assert_eq!(
hosts_array[0],
toml::Value::String("server1.example.com".to_string())
);
assert_eq!(
hosts_array[1],
toml::Value::String("server2.example.com".to_string())
);
assert_eq!(
hosts_array[2],
toml::Value::String("server3.example.com".to_string())
);
} else {
panic!("hosts should be an array");
}
} else {
panic!("cluster should be a table");
}
}
#[test]
fn test_nested_variables_mixed_types() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let mut settings = toml::map::Map::new();
settings.insert("debug".to_string(), toml::Value::Boolean(true));
settings.insert("timeout".to_string(), toml::Value::Float(30.5));
settings.insert("retries".to_string(), toml::Value::Integer(3));
settings.insert(
"endpoint".to_string(),
toml::Value::String("https://api.example.com".to_string()),
);
config
.variables
.insert("settings".to_string(), toml::Value::Table(settings));
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
let settings_var = reloaded_config.variables.get("settings");
assert!(settings_var.is_some(), "settings variable should exist");
if let Some(toml::Value::Table(settings_table)) = settings_var {
assert_eq!(
settings_table.get("debug"),
Some(&toml::Value::Boolean(true))
);
assert_eq!(
settings_table.get("timeout"),
Some(&toml::Value::Float(30.5))
);
assert_eq!(
settings_table.get("retries"),
Some(&toml::Value::Integer(3))
);
assert_eq!(
settings_table.get("endpoint"),
Some(&toml::Value::String("https://api.example.com".to_string()))
);
} else {
panic!("settings should be a table");
}
}
#[test]
fn test_nested_variables_print() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
let mut api_config = toml::map::Map::new();
api_config.insert(
"key".to_string(),
toml::Value::String("secret123".to_string()),
);
api_config.insert("timeout".to_string(), toml::Value::Integer(30));
config
.variables
.insert("api".to_string(), toml::Value::Table(api_config));
config.variables.insert(
"version".to_string(),
toml::Value::String("1.0.0".to_string()),
);
config.save(&fixture.cwd).expect("Failed to save config");
let _ = run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::PrintVars(PrintVarsArgs {
profile: None,
}))),
);
let reloaded_config = fixture.get_config();
assert!(reloaded_config.variables.contains_key("api"));
assert!(reloaded_config.variables.contains_key("version"));
}
#[test]
fn test_nested_variables_do_not_interfere_with_flat_variables() {
let fixture = TestFixture::new();
fixture.init();
let mut config = fixture.get_config();
config
.variables
.insert("EDITOR".to_string(), toml::Value::String("vim".to_string()));
config.variables.insert(
"SHELL".to_string(),
toml::Value::String("/bin/bash".to_string()),
);
let mut db_config = toml::map::Map::new();
db_config.insert(
"host".to_string(),
toml::Value::String("localhost".to_string()),
);
db_config.insert("port".to_string(), toml::Value::Integer(3306));
config
.variables
.insert("database".to_string(), toml::Value::Table(db_config));
config.save(&fixture.cwd).expect("Failed to save config");
let reloaded_config = fixture.get_config();
assert_eq!(
reloaded_config.variables.get("EDITOR"),
Some(&toml::Value::String("vim".to_string()))
);
assert_eq!(
reloaded_config.variables.get("SHELL"),
Some(&toml::Value::String("/bin/bash".to_string()))
);
if let Some(toml::Value::Table(db_table)) = reloaded_config.variables.get("database") {
assert_eq!(
db_table.get("host"),
Some(&toml::Value::String("localhost".to_string()))
);
assert_eq!(db_table.get("port"), Some(&toml::Value::Integer(3306)));
} else {
panic!("database should be a table");
}
}
#[test]
fn test_import_with_custom_name() {
let fixture = TestFixture::new();
fixture.init();
run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::Import(ImportArgs {
path: BASHRC_PATH.to_string(),
name: Some("custom_bashrc".to_string()),
profile: None,
..Default::default()
}))),
)
.expect("Import with custom name failed");
let config = fixture.get_config();
assert!(
config.packages.contains_key("f_custom_bashrc"),
"Package should use custom name"
);
assert!(
!config.packages.contains_key("f_bashrc"),
"Package should not use default name"
);
}
#[test]
fn test_import_directory_with_custom_name() {
let fixture = TestFixture::new();
fixture.init();
run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::Import(ImportArgs {
path: NVIM_PATH.to_string(),
name: Some("my_nvim_config".to_string()),
profile: None,
..Default::default()
}))),
)
.expect("Import directory with custom name failed");
let config = fixture.get_config();
assert!(
config.packages.contains_key("d_my_nvim_config"),
"Directory package should use custom name with d_ prefix"
);
assert!(
!config.packages.contains_key("d_nvim"),
"Package should not use default name"
);
}
#[test]
fn test_custom_name_replaces_special_characters() {
let fixture = TestFixture::new();
fixture.init();
run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::Import(ImportArgs {
path: BASHRC_PATH.to_string(),
name: Some("my-config.v2".to_string()),
profile: None,
..Default::default()
}))),
)
.expect("Import with special chars in name failed");
let config = fixture.get_config();
assert!(
config.packages.contains_key("f_my_config_v2"),
"Special characters should be replaced"
);
}
#[test]
fn test_custom_name_with_profile() {
let fixture = TestFixture::new();
fixture.init();
run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::Import(ImportArgs {
path: BASHRC_PATH.to_string(),
name: Some("work_bashrc".to_string()),
profile: Some("work".to_string()),
..Default::default()
}))),
)
.expect("Import with custom name and profile failed");
let config = fixture.get_config();
assert!(
config.packages.contains_key("f_work_bashrc"),
"Package should use custom name"
);
let profile = config.profiles.get("work").expect("Profile should exist");
assert!(
profile.dependencies.contains(&"f_work_bashrc".to_string()),
"Profile should contain package with custom name"
);
}
#[test]
fn test_deploy_package_with_custom_name() {
let fixture = TestFixture::new();
fixture.init();
run_cli(
fixture.get_cli(Some(dotr_dear::cli::Command::Import(ImportArgs {
path: BASHRC_PATH.to_string(),
name: Some("mybash".to_string()),
profile: None,
..Default::default()
}))),
)
.expect("Import failed");
let bashrc_content = fs::read_to_string(fixture.cwd.join("src/.bashrc")).unwrap();
fs::write(
fixture.cwd.join("src/.bashrc"),
format!("# Modified\n{}", bashrc_content),
)
.unwrap();
fixture.deploy(Some(vec!["f_mybash".to_string()]));
fixture.assert_file_exists("src/.bashrc", "File should be deployed");
}