Files
empeve/tests/cli_integration.rs
vikingowl 6be61df8a0 fix: address code quality issues, validation gaps, and add test coverage
Phase 1 - Code Quality:
- Rename script/script.rs to script/types.rs (module inception fix)
- Apply Clippy lint fixes (is_none_or, is_some_and, char patterns, etc.)
- Implement FromStr for CatalogFile and Category
- Add filtered_targets() and filtered_repos() helpers to Config

Phase 2 - Validation & Error Handling:
- Add validate_repo_identifier() for GitHub shorthand validation
- Fix first-run setup to fail gracefully if no targets configured
- Improve import to collect failures and only save on success
- Add AssetInstallResult for detailed install failure tracking
- Fix lockfile timestamp documentation (Unix epoch, not RFC 3339)
- Add comprehensive RevType heuristics documentation
- Add checkout warning when local modifications will be discarded

Phase 3 - Test Coverage:
- Add tempfile, assert_cmd, predicates dev dependencies
- Add security tests (symlink boundaries, copy mode)
- Add git operations tests (init, head_commit, RevType parsing)
- Add lockfile tests (roundtrip, lock/get operations)
- Add CLI integration tests (help, validation, duplicates)
- Add config validation tests for new helper methods

All 48 tests pass, clippy clean, release build verified.
2026-01-26 10:19:10 +01:00

200 lines
5.2 KiB
Rust

//! CLI integration tests for empeve
//!
//! These tests exercise the binary's command-line interface to ensure
//! proper user-facing behavior.
use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::TempDir;
/// Get a command instance for empeve
#[allow(deprecated)]
fn empeve() -> Command {
Command::cargo_bin("empeve").unwrap()
}
#[test]
fn test_cli_help() {
empeve()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("Plugin manager for mpv"));
}
#[test]
fn test_cli_version() {
empeve()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains("empeve"));
}
#[test]
fn test_cli_help_add() {
empeve()
.args(["add", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("Add a repository"));
}
#[test]
fn test_cli_help_install() {
empeve()
.args(["install", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("Install all configured repositories"));
}
#[test]
fn test_cli_help_update() {
empeve()
.args(["update", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("Update all repositories"));
}
/// Test that the add command rejects invalid repo formats
#[test]
fn test_cli_add_invalid_repo_format() {
let temp = TempDir::new().unwrap();
let config_dir = temp.path().join(".config").join("empeve");
std::fs::create_dir_all(&config_dir).unwrap();
// Create minimal config
let config_path = config_dir.join("config.toml");
std::fs::write(&config_path, "[settings]\n").unwrap();
empeve()
.env("XDG_CONFIG_HOME", temp.path().join(".config"))
.args(["add", "invalid_repo_no_slash"])
.assert()
.failure()
.stderr(predicate::str::contains("invalid repository format"));
}
/// Test that the add command accepts valid GitHub shorthand
#[test]
fn test_cli_add_valid_repo_format() {
let temp = TempDir::new().unwrap();
let config_dir = temp.path().join(".config").join("empeve");
std::fs::create_dir_all(&config_dir).unwrap();
// Create minimal config
let config_path = config_dir.join("config.toml");
std::fs::write(&config_path, "[settings]\n").unwrap();
empeve()
.env("XDG_CONFIG_HOME", temp.path().join(".config"))
.args(["add", "user/repo"])
.assert()
.success()
.stdout(predicate::str::contains("Added"));
// Verify config was updated
let config_content = std::fs::read_to_string(&config_path).unwrap();
assert!(config_content.contains("user/repo"));
}
/// Test that adding a duplicate repo fails
#[test]
fn test_cli_add_duplicate_fails() {
let temp = TempDir::new().unwrap();
let config_dir = temp.path().join(".config").join("empeve");
std::fs::create_dir_all(&config_dir).unwrap();
// Create config with existing repo
let config_path = config_dir.join("config.toml");
std::fs::write(
&config_path,
r#"[settings]
[[repos]]
repo = "existing/repo"
"#,
)
.unwrap();
empeve()
.env("XDG_CONFIG_HOME", temp.path().join(".config"))
.args(["add", "existing/repo"])
.assert()
.failure()
.stderr(predicate::str::contains("already exists"));
}
/// Test that removing a non-existent repo fails
#[test]
fn test_cli_remove_nonexistent_fails() {
let temp = TempDir::new().unwrap();
let config_dir = temp.path().join(".config").join("empeve");
std::fs::create_dir_all(&config_dir).unwrap();
// Create empty config
let config_path = config_dir.join("config.toml");
std::fs::write(&config_path, "[settings]\n").unwrap();
empeve()
.env("XDG_CONFIG_HOME", temp.path().join(".config"))
.args(["remove", "nonexistent/repo"])
.assert()
.failure()
.stderr(predicate::str::contains("not found"));
}
/// Test status command with empty config
#[test]
fn test_cli_status_empty_config() {
let temp = TempDir::new().unwrap();
let config_dir = temp.path().join(".config").join("empeve");
std::fs::create_dir_all(&config_dir).unwrap();
// Create empty config
let config_path = config_dir.join("config.toml");
std::fs::write(&config_path, "[settings]\n").unwrap();
empeve()
.env("XDG_CONFIG_HOME", temp.path().join(".config"))
.arg("status")
.assert()
.success()
.stdout(predicate::str::contains("No repositories"));
}
/// Test that --target flag is recognized
#[test]
fn test_cli_target_flag() {
let temp = TempDir::new().unwrap();
let config_dir = temp.path().join(".config").join("empeve");
std::fs::create_dir_all(&config_dir).unwrap();
// Create config with target
let config_path = config_dir.join("config.toml");
let mpv_path = temp.path().join("mpv");
std::fs::create_dir_all(&mpv_path).unwrap();
std::fs::write(
&config_path,
format!(
r#"[settings]
[[targets]]
name = "mpv"
path = "{}"
"#,
mpv_path.display()
),
)
.unwrap();
empeve()
.env("XDG_CONFIG_HOME", temp.path().join(".config"))
.args(["--target", "mpv", "list"])
.assert()
.success();
}