mod common;
use common::*;
use std::process::Command;
async fn bootstrap_test_environment() -> testcontainers::ContainerAsync<testcontainers::GenericImage> {
let container = setup_fedora_container().await;
let container_id = container.id();
let status = Command::new("docker")
.args(&["exec", container_id, "git", "init", "--bare", "/tmp/mock-gitlab-repo.git"])
.status()
.expect("Failed to initialize mock bare git remote");
assert!(status.success());
let status = Command::new("docker")
.args(&[
"exec",
container_id,
"mkdir",
"-p",
"/root/.config/fp-dotfiles-manager",
])
.status()
.expect("Failed to create config dir");
assert!(status.success());
let status = Command::new("docker")
.args(&[
"exec",
container_id,
"ssh-keygen",
"-t",
"ed25519",
"-N",
"",
"-f",
"/root/.config/fp-dotfiles-manager/ssh_gitlab",
])
.status()
.expect("Failed to generate ed25519 SSH keys");
assert!(status.success());
let status = Command::new("docker")
.args(&[
"exec",
container_id,
"git",
"config",
"--global",
"user.name",
"Test User",
])
.status()
.expect("Failed to configure git user.name");
assert!(status.success());
let status = Command::new("docker")
.args(&[
"exec",
container_id,
"git",
"config",
"--global",
"user.email",
"test@example.com",
])
.status()
.expect("Failed to configure git user.email");
assert!(status.success());
let status = Command::new("docker")
.args(&[
"exec",
container_id,
"mkdir",
"-p",
"/root/.config/chezmoi",
])
.status()
.expect("Failed to create chezmoi config dir");
assert!(status.success());
let status = Command::new("docker")
.args(&[
"exec",
container_id,
"age-keygen",
"-o",
"/root/.config/chezmoi/.age-private-key.txt",
])
.status()
.expect("Failed to generate age keys");
assert!(status.success());
let output = Command::new("docker")
.args(&[
"exec",
container_id,
"cat",
"/root/.config/chezmoi/.age-private-key.txt",
])
.output()
.expect("Failed to read age key");
let content = String::from_utf8_lossy(&output.stdout);
let mut public_key = String::new();
for line in content.lines() {
if line.starts_with("# public key: ") {
public_key = line["# public key: ".len()..].trim().to_string();
break;
}
}
let chezmoi_toml_content = format!(
r#"encryption = "age"
[age]
identity = "/root/.config/chezmoi/.age-private-key.txt"
recipient = "{}"
"#,
public_key
);
write_file_in_container(
&container,
"/root/.config/chezmoi/chezmoi.toml",
&chezmoi_toml_content
);
run_manager_success(
&container,
&[
"setup",
"--repo-url",
"/tmp/mock-gitlab-repo.git",
"--ssh-key",
"/root/.config/fp-dotfiles-manager/ssh_gitlab",
"--force",
],
).await;
container
}
#[tokio::test]
async fn test_sync_and_discovery_pipeline() {
let container = bootstrap_test_environment().await;
write_file_in_container(
&container,
"/root/.config/fp-dotfiles-manager/tracked",
".bashrc\n.config/my_app\n"
);
write_file_in_container(&container, "/root/.bashrc", "alias ll='ls -l'\n");
write_file_in_container(&container, "/root/.config/my_app/config.json", "{\"theme\": \"dark\"}");
let stdout = run_manager_success(&container, &["sync"]).await;
assert!(stdout.contains("Adding 2 new file(s)"));
assert!(stdout.contains("Sync engine finished"));
let bashrc_source = read_file_in_container(&container, "/root/.local/share/chezmoi/dot_bashrc");
assert_eq!(bashrc_source.trim(), "alias ll='ls -l'");
let my_app_config_source = read_file_in_container(&container, "/root/.local/share/chezmoi/dot_config/my_app/config.json");
assert_eq!(my_app_config_source.trim(), "{\"theme\": \"dark\"}");
write_file_in_container(&container, "/root/.bashrc", "alias ll='ls -lh'\n");
let stdout = run_manager_success(&container, &["sync"]).await;
assert!(stdout.contains("Re-adding 1 modified managed file(s)"));
let bashrc_source_updated = read_file_in_container(&container, "/root/.local/share/chezmoi/dot_bashrc");
assert_eq!(bashrc_source_updated.trim(), "alias ll='ls -lh'");
let git_log = Command::new("docker")
.args(&[
"exec",
container.id(),
"git",
"-C",
"/tmp/mock-gitlab-repo.git",
"log",
"-1",
"--oneline",
])
.output()
.expect("Failed to check Git server logs");
assert!(git_log.status.success());
let log_str = String::from_utf8_lossy(&git_log.stdout);
assert!(log_str.contains("chore: auto-sync dotfiles"));
}
#[tokio::test]
async fn test_sync_secret_encryption_override() {
let container = bootstrap_test_environment().await;
write_file_in_container(
&container,
"/root/.config/fp-dotfiles-manager/tracked",
".config/my_app\n"
);
write_file_in_container(
&container,
"/root/.config/fp-dotfiles-manager/tracked_encrypted",
".config/my_app/secrets.json\n"
);
write_file_in_container(&container, "/root/.config/my_app/public.json", "{\"status\": \"public\"}");
write_file_in_container(&container, "/root/.config/my_app/secrets.json", "{\"api_key\": \"SUPER_SECRET_123\"}");
let stdout = run_manager_success(&container, &["sync"]).await;
assert!(stdout.contains("Encrypting & adding 1 new file(s)"));
assert!(stdout.contains("Adding 1 new file(s)"));
let public_source = read_file_in_container(&container, "/root/.local/share/chezmoi/dot_config/my_app/public.json");
assert_eq!(public_source.trim(), "{\"status\": \"public\"}");
let ls_output = Command::new("docker")
.args(&[
"exec",
container.id(),
"ls",
"/root/.local/share/chezmoi/dot_config/my_app/",
])
.output()
.expect("Failed to list chezmoi directory");
assert!(ls_output.status.success());
let files_list = String::from_utf8_lossy(&ls_output.stdout);
let files: Vec<&str> = files_list.lines().map(|l| l.trim()).filter(|l| !l.is_empty()).collect();
assert!(files.iter().any(|f| f.starts_with("encrypted_") && f.contains("secrets")));
assert!(!files.contains(&"secrets.json")); }
#[tokio::test]
async fn test_sync_path_exclusions() {
let container = bootstrap_test_environment().await;
write_file_in_container(
&container,
"/root/.config/fp-dotfiles-manager/tracked",
".config/my_app\n!.config/my_app/cache_*\n"
);
write_file_in_container(&container, "/root/.config/my_app/main.conf", "enabled=true");
write_file_in_container(&container, "/root/.config/my_app/cache_data.db", "BINARY_TRASH");
run_manager_success(&container, &["sync"]).await;
let main_conf = read_file_in_container(&container, "/root/.local/share/chezmoi/dot_config/my_app/main.conf");
assert_eq!(main_conf.trim(), "enabled=true");
let ls_output = Command::new("docker")
.args(&[
"exec",
container.id(),
"ls",
"/root/.local/share/chezmoi/dot_config/my_app/",
])
.output()
.expect("Failed to list chezmoi directory");
assert!(ls_output.status.success());
let files_list = String::from_utf8_lossy(&ls_output.stdout);
assert!(!files_list.contains("cache_data.db"));
}