Finished project

This commit is contained in:
ManOfGoldForever 2026-04-01 00:46:54 -04:00
parent 6849f1afae
commit 31179d64e5
12 changed files with 2207 additions and 3 deletions

280
tests/integration_test.rs Normal file
View file

@ -0,0 +1,280 @@
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
struct TestEnv {
home: TempDir,
themes: TempDir,
state_file: PathBuf,
}
impl TestEnv {
fn new() -> Self {
let home = TempDir::new().unwrap();
let themes = TempDir::new().unwrap();
let state_file = home.path().join(".config/theme-manager/state.json");
TestEnv {
home,
themes,
state_file,
}
}
fn make_theme_file(&self, theme: &str, rel: &str, content: &str) -> PathBuf {
let p = self.themes.path().join(theme).join(rel);
fs::create_dir_all(p.parent().unwrap()).unwrap();
fs::write(&p, content).unwrap();
p
}
fn target(&self, rel: &str) -> PathBuf {
self.home.path().join(rel)
}
fn dm(&self, args: &[&str]) -> std::process::Output {
let bin = env!("CARGO_BIN_EXE_dm");
Command::new(bin)
.args(args)
.env("DM_HOME", self.home.path())
.env("DM_THEMES_ROOT", self.themes.path())
.env("DM_STATE_FILE", &self.state_file)
.output()
.unwrap()
}
fn dm_stdout(&self, args: &[&str]) -> String {
let out = self.dm(args);
String::from_utf8_lossy(&out.stdout).to_string()
}
fn dm_success(&self, args: &[&str]) {
let out = self.dm(args);
if !out.status.success() {
panic!(
"dm {:?} failed:\nstdout: {}\nstderr: {}",
args,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
}
}
}
fn is_symlink(p: &Path) -> bool {
p.symlink_metadata()
.map(|m| m.file_type().is_symlink())
.unwrap_or(false)
}
// ---------------------------------------------------------------------------
// list
// ---------------------------------------------------------------------------
#[test]
fn list_shows_available_themes() {
let env = TestEnv::new();
fs::create_dir(env.themes.path().join("dracula")).unwrap();
fs::create_dir(env.themes.path().join("catppuccin")).unwrap();
let out = env.dm_stdout(&["list"]);
assert!(out.contains("dracula"), "expected dracula in: {out}");
assert!(out.contains("catppuccin"), "expected catppuccin in: {out}");
}
// ---------------------------------------------------------------------------
// status
// ---------------------------------------------------------------------------
#[test]
fn status_before_apply_reports_no_theme() {
let env = TestEnv::new();
let out = env.dm_stdout(&["status"]);
assert!(out.contains("No theme"), "got: {out}");
}
#[test]
fn status_after_apply_shows_theme_name() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# dracula");
env.dm_success(&["apply", "dracula"]);
let out = env.dm_stdout(&["status"]);
assert!(out.contains("dracula"), "got: {out}");
}
// ---------------------------------------------------------------------------
// apply from empty state
// ---------------------------------------------------------------------------
#[test]
fn apply_creates_symlinks_under_home() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# zsh");
env.make_theme_file("dracula", ".config/nvim/init.lua", "-- nvim");
env.dm_success(&["apply", "dracula"]);
assert!(is_symlink(&env.target(".zshrc")));
assert!(is_symlink(&env.target(".config/nvim/init.lua")));
}
#[test]
fn apply_output_shows_summary_counts() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "");
let out = env.dm_stdout(&["apply", "dracula"]);
assert!(out.contains("created=1"), "expected created=1 in: {out}");
}
// ---------------------------------------------------------------------------
// dry-run
// ---------------------------------------------------------------------------
#[test]
fn dry_run_makes_no_changes() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# zsh");
let out = env.dm_stdout(&["apply", "dracula", "--dry-run"]);
assert!(out.contains("Dry run"), "got: {out}");
assert!(
!env.target(".zshrc").exists(),
"symlink should not exist after dry-run"
);
}
#[test]
fn dry_run_shows_create_action() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "");
let out = env.dm_stdout(&["apply", "dracula", "--dry-run"]);
assert!(out.contains("create"), "got: {out}");
}
// ---------------------------------------------------------------------------
// conflict without --force
// ---------------------------------------------------------------------------
#[test]
fn apply_conflicts_on_unmanaged_file() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# theme");
fs::write(env.target(".zshrc"), "original").unwrap();
let out = env.dm(&["apply", "dracula"]);
assert!(!out.status.success(), "should fail on conflict");
// Original file must be untouched.
let content = fs::read_to_string(env.target(".zshrc")).unwrap();
assert_eq!(content, "original");
}
#[test]
fn apply_force_backs_up_and_replaces() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# theme");
fs::write(env.target(".zshrc"), "original").unwrap();
env.dm_success(&["apply", "dracula", "--force"]);
assert!(is_symlink(&env.target(".zshrc")));
}
// ---------------------------------------------------------------------------
// switch dracula → catppuccin
// ---------------------------------------------------------------------------
#[test]
fn switch_themes_replaces_managed_symlinks() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# dracula");
env.dm_success(&["apply", "dracula"]);
env.make_theme_file("catppuccin", ".zshrc", "# catppuccin");
env.dm_success(&["apply", "catppuccin"]);
let link = fs::read_link(env.target(".zshrc")).unwrap();
assert!(
link.to_string_lossy().contains("catppuccin"),
"link should point to catppuccin, got: {}",
link.display()
);
}
#[test]
fn status_updates_after_theme_switch() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "");
env.dm_success(&["apply", "dracula"]);
env.make_theme_file("catppuccin", ".zshrc", "");
env.dm_success(&["apply", "catppuccin"]);
let out = env.dm_stdout(&["status"]);
assert!(out.contains("catppuccin"), "got: {out}");
}
// ---------------------------------------------------------------------------
// rollback
// ---------------------------------------------------------------------------
#[test]
fn rollback_restores_backed_up_file() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# theme");
fs::write(env.target(".zshrc"), "original content").unwrap();
env.dm_success(&["apply", "dracula", "--force"]);
assert!(is_symlink(&env.target(".zshrc")));
env.dm_success(&["rollback"]);
let content = fs::read_to_string(env.target(".zshrc")).unwrap();
assert_eq!(content, "original content");
}
#[test]
fn rollback_removes_newly_created_symlinks() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# theme");
env.dm_success(&["apply", "dracula"]);
assert!(is_symlink(&env.target(".zshrc")));
env.dm_success(&["rollback"]);
assert!(env.target(".zshrc").symlink_metadata().is_err());
}
#[test]
fn rollback_clears_state() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "");
env.dm_success(&["apply", "dracula"]);
env.dm_success(&["rollback"]);
let out = env.dm_stdout(&["status"]);
assert!(out.contains("No theme"), "got: {out}");
}
// ---------------------------------------------------------------------------
// backup command
// ---------------------------------------------------------------------------
#[test]
fn backup_command_copies_managed_files() {
let env = TestEnv::new();
env.make_theme_file("dracula", ".zshrc", "# theme");
fs::write(env.target(".zshrc"), "original").unwrap();
env.dm_success(&["apply", "dracula", "--force"]);
// Apply already backed up; now apply a second time to switch, creating backup dir
// Just check the backup command runs without error when state exists.
let out = env.dm(&["backup"]);
assert!(out.status.success(), "backup should succeed");
}