use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn setup_test_workspace() -> (TempDir, PathBuf, PathBuf) {
let temp = TempDir::new().unwrap();
let source_file = temp.path().join("source.txt");
let target_file = temp.path().join("target.txt");
fs::write(&source_file, "test content").unwrap();
(temp, source_file, target_file)
}
#[allow(dead_code)]
fn create_test_config(dir: &Path, files: Vec<(&str, &str)>) -> PathBuf {
let config_path = dir.join("twin.toml");
let mut config_content = String::from(
r#"
worktree_base = "./"
branch_prefix = "test/"
"#,
);
for (path, mapping_type) in files {
config_content.push_str(&format!(
r#"[[files]]
path = "{path}"
mapping_type = "{mapping_type}"
skip_if_exists = false
"#
));
}
fs::write(&config_path, config_content).unwrap();
config_path
}
#[test]
fn test_symlink_manager_initialization() {
use twin_cli::symlink::create_symlink_manager;
let manager = create_symlink_manager();
assert!(!manager
.as_ref()
.get_manual_instructions(&PathBuf::from("a"), &PathBuf::from("b"))
.is_empty());
}
#[test]
fn test_symlink_creation_with_permission() {
use twin_cli::symlink::create_symlink_manager;
let (_temp, source, target) = setup_test_workspace();
let manager = create_symlink_manager();
let result = manager.create_symlink(&source, &target);
match result {
Ok(info) => {
assert!(info.is_valid);
assert!(target.exists());
let content = fs::read_to_string(&target).unwrap();
assert_eq!(content, "test content");
}
Err(e) => {
eprintln!("Symlink creation failed (expected on CI): {e}");
}
}
}
#[test]
fn test_fallback_to_copy() {
use twin_cli::symlink::create_symlink_manager;
let (_temp, source, target) = setup_test_workspace();
let manager = create_symlink_manager();
let result = manager.create_symlink(&source, &target);
if result.is_ok() {
assert!(target.exists());
let content = fs::read_to_string(&target).unwrap();
assert_eq!(content, "test content");
}
}
#[test]
fn test_symlink_removal() {
use twin_cli::symlink::create_symlink_manager;
let (_temp, source, target) = setup_test_workspace();
let manager = create_symlink_manager();
if manager.create_symlink(&source, &target).is_ok() {
assert!(target.exists());
let remove_result = manager.remove_symlink(&target);
assert!(remove_result.is_ok());
assert!(!target.exists());
}
}
#[test]
#[cfg(unix)]
fn test_unix_symlink_specific() {
use std::os::unix::fs::symlink;
let (_temp, source, target) = setup_test_workspace();
symlink(&source, &target).unwrap();
assert!(target.exists());
let metadata = fs::symlink_metadata(&target).unwrap();
assert!(metadata.file_type().is_symlink());
let link_target = fs::read_link(&target).unwrap();
assert_eq!(link_target, source);
}
#[test]
#[cfg(windows)]
fn test_windows_symlink_fallback() {
use twin_cli::symlink::{SymlinkManager, WindowsSymlinkManager};
let manager = WindowsSymlinkManager::new();
let (_temp, source, target) = setup_test_workspace();
let result = manager.create_symlink(&source, &target);
if result.is_ok() {
assert!(target.exists());
let source_content = fs::read_to_string(&source).unwrap();
let target_content = fs::read_to_string(&target).unwrap();
assert_eq!(source_content, target_content);
}
}
#[test]
fn test_multiple_file_mappings() {
use twin_cli::symlink::create_symlink_manager;
let temp = TempDir::new().unwrap();
let manager = create_symlink_manager();
let files = vec![
("file1.txt", "content1"),
("file2.txt", "content2"),
("dir1/file3.txt", "content3"),
];
for (path, content) in &files {
let file_path = temp.path().join(path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(&file_path, content).unwrap();
}
for (path, _) in &files {
let source = temp.path().join(path);
let target = temp.path().join(format!("link_{path}"));
if let Some(parent) = target.parent() {
fs::create_dir_all(parent).unwrap();
}
let result = manager.create_symlink(&source, &target);
if result.is_ok() {
assert!(target.exists());
}
}
}
#[test]
fn test_skip_if_exists() {
use twin_cli::symlink::create_symlink_manager;
let (_temp, source, target) = setup_test_workspace();
let manager = create_symlink_manager();
fs::write(&target, "existing content").unwrap();
let result = manager.create_symlink(&source, &target);
if result.is_ok() {
let content = fs::read_to_string(&target).unwrap();
assert_eq!(content, "test content");
}
}
#[test]
fn test_environment_variable_debug_output() {
use std::env;
use twin_cli::symlink::create_symlink_manager;
unsafe {
env::set_var("TWIN_VERBOSE", "1");
env::set_var("TWIN_DEBUG", "1");
}
let (_temp, source, target) = setup_test_workspace();
let manager = create_symlink_manager();
let _ = manager.create_symlink(&source, &target);
unsafe {
env::remove_var("TWIN_VERBOSE");
env::remove_var("TWIN_DEBUG");
}
}
#[test]
fn test_invalid_source_path() {
use twin_cli::symlink::create_symlink_manager;
let temp = TempDir::new().unwrap();
let manager = create_symlink_manager();
let non_existent = temp.path().join("non_existent.txt");
let target = temp.path().join("target.txt");
let result = manager.create_symlink(&non_existent, &target);
assert!(result.is_err());
}
#[test]
fn test_directory_symlink() {
use twin_cli::symlink::create_symlink_manager;
let temp = TempDir::new().unwrap();
let manager = create_symlink_manager();
let source_dir = temp.path().join("source_dir");
fs::create_dir(&source_dir).unwrap();
fs::write(source_dir.join("file.txt"), "content").unwrap();
let target_dir = temp.path().join("target_dir");
let result = manager.create_symlink(&source_dir, &target_dir);
if result.is_ok() {
assert!(target_dir.exists());
assert!(target_dir.join("file.txt").exists());
}
}