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.
This commit is contained in:
199
tests/cli_integration.rs
Normal file
199
tests/cli_integration.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
//! 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();
|
||||
}
|
||||
Reference in New Issue
Block a user