Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 147 additions & 3 deletions src/checks/precommit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,157 @@ mod tests {
use super::*;
use tempfile::TempDir;

// =========================================================================
// Constants tests
// =========================================================================

#[test]
fn test_config_exists() {
fn test_pre_commit_config_constant() {
assert_eq!(PRE_COMMIT_CONFIG, ".pre-commit-config.yaml");
}

// =========================================================================
// config_exists tests
// =========================================================================

#[test]
fn test_config_exists_no_file() {
let temp = TempDir::new().expect("create temp dir");
assert!(!config_exists(temp.path()));
}

#[test]
fn test_config_exists_with_file() {
let temp = TempDir::new().expect("create temp dir");
std::fs::write(temp.path().join(PRE_COMMIT_CONFIG), "repos: []").expect("write config");
assert!(config_exists(temp.path()));
}

#[test]
fn test_config_exists_empty_file() {
let temp = TempDir::new().expect("create temp dir");
std::fs::write(temp.path().join(PRE_COMMIT_CONFIG), "").expect("write empty config");
assert!(config_exists(temp.path()));
}

std::fs::write(temp.path().join(PRE_COMMIT_CONFIG), "repos: []")
.expect("write config");
#[test]
fn test_config_exists_valid_yaml() {
let temp = TempDir::new().expect("create temp dir");
std::fs::write(
temp.path().join(PRE_COMMIT_CONFIG),
r#"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
"#,
)
.expect("write yaml config");
assert!(config_exists(temp.path()));
}

// =========================================================================
// is_installed tests
// =========================================================================

#[test]
fn test_is_installed_returns_bool() {
// This test just verifies the function runs without error
// Result depends on whether pre-commit is installed on the system
let _ = is_installed();
}

// =========================================================================
// run_with_args tests (via async tests)
// =========================================================================

#[tokio::test]
async fn test_run_staged_without_precommit() {
// Skip this test if pre-commit is installed
if is_installed() {
return;
}

let temp = TempDir::new().expect("create temp dir");
std::fs::write(temp.path().join(PRE_COMMIT_CONFIG), "repos: []").expect("write config");

let result = run_staged(temp.path()).await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, Error::PreCommitNotFound));
}

#[tokio::test]
async fn test_run_all_without_precommit() {
// Skip this test if pre-commit is installed
if is_installed() {
return;
}

let temp = TempDir::new().expect("create temp dir");
std::fs::write(temp.path().join(PRE_COMMIT_CONFIG), "repos: []").expect("write config");

let result = run_all(temp.path()).await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, Error::PreCommitNotFound));
}

#[tokio::test]
async fn test_run_staged_without_config() {
// This test requires pre-commit to be installed
if !is_installed() {
return;
}

let temp = TempDir::new().expect("create temp dir");
// Don't create config file

let result = run_staged(temp.path()).await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, Error::PreCommitConfigNotFound { .. }));
}

#[tokio::test]
async fn test_run_all_without_config() {
// This test requires pre-commit to be installed
if !is_installed() {
return;
}

let temp = TempDir::new().expect("create temp dir");
// Don't create config file

let result = run_all(temp.path()).await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(err, Error::PreCommitConfigNotFound { .. }));
}

// =========================================================================
// Edge case tests
// =========================================================================

#[test]
fn test_config_exists_in_nested_dir() {
let temp = TempDir::new().expect("create temp dir");
let nested = temp.path().join("nested/dir");
std::fs::create_dir_all(&nested).expect("create nested dir");

// Config in nested dir
std::fs::write(nested.join(PRE_COMMIT_CONFIG), "repos: []").expect("write config");

// Should find config in nested, not in root
assert!(!config_exists(temp.path()));
assert!(config_exists(&nested));
}

#[test]
fn test_config_path_construction() {
let temp = TempDir::new().expect("create temp dir");
let expected_path = temp.path().join(PRE_COMMIT_CONFIG);
assert!(expected_path.ends_with(".pre-commit-config.yaml"));
}
}
70 changes: 30 additions & 40 deletions src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,17 @@ pub fn init(preset: Option<&str>, force: bool) -> Result<ExitCode> {
);
}
config
}
},
};

// Write config
let toml = toml::to_string_pretty(&config)
.map_err(|e| Error::Internal {
message: format!("Failed to serialize config: {e}"),
})?;
let toml = toml::to_string_pretty(&config).map_err(|e| Error::Internal {
message: format!("Failed to serialize config: {e}"),
})?;

std::fs::write(&config_path, toml).map_err(|e| Error::io("write config", e))?;

eprintln!(
"{} Created {}",
style("✓").green(),
config_path.display()
);
eprintln!("{} Created {}", style("✓").green(), config_path.display());

if let Some(p) = preset {
eprintln!(" Using preset: {p}");
Expand Down Expand Up @@ -162,8 +157,7 @@ pub fn uninstall() -> Result<ExitCode> {
}

// Check if it's our hook
let content =
std::fs::read_to_string(&hook_path).map_err(|e| Error::io("read hook", e))?;
let content = std::fs::read_to_string(&hook_path).map_err(|e| Error::io("read hook", e))?;

if !content.contains(HOOK_MARKER) {
eprintln!(
Expand Down Expand Up @@ -299,7 +293,8 @@ pub fn detect() -> Result<ExitCode> {
}

eprintln!();
eprintln!("TTY: stdin={}, stdout={}",
eprintln!(
"TTY: stdin={}, stdout={}",
std::io::stdin().is_terminal(),
std::io::stdout().is_terminal()
);
Expand All @@ -311,13 +306,13 @@ pub fn detect() -> Result<ExitCode> {
pub fn list(mode: Option<&str>) -> Result<ExitCode> {
let config = Config::load_or_default()?;

let mode: Option<Mode> = mode
.map(|m| m.parse())
.transpose()
.map_err(|e: String| Error::ConfigInvalid {
field: "mode".to_string(),
message: e,
})?;
let mode: Option<Mode> =
mode.map(|m| m.parse())
.transpose()
.map_err(|e: String| Error::ConfigInvalid {
field: "mode".to_string(),
message: e,
})?;

// Print checks by mode
if mode.is_none() || mode == Some(Mode::Human) {
Expand Down Expand Up @@ -352,18 +347,16 @@ fn print_check(config: &Config, name: &str) {
/// Validate configuration.
pub fn validate() -> Result<ExitCode> {
match Config::load() {
Ok(config) => {
match config.validate() {
Ok(()) => {
eprintln!("{} Configuration is valid", style("✓").green());
Ok(ExitCode::SUCCESS)
}
Err(e) => {
eprintln!("{} Configuration validation failed: {e}", style("✗").red());
Ok(ExitCode::FAILURE)
}
}
}
Ok(config) => match config.validate() {
Ok(()) => {
eprintln!("{} Configuration is valid", style("✓").green());
Ok(ExitCode::SUCCESS)
},
Err(e) => {
eprintln!("{} Configuration validation failed: {e}", style("✗").red());
Ok(ExitCode::FAILURE)
},
},
Err(Error::ConfigNotFound { path }) => {
eprintln!(
"{} Configuration not found: {}",
Expand All @@ -372,11 +365,11 @@ pub fn validate() -> Result<ExitCode> {
);
eprintln!(" Run: apc init");
Ok(ExitCode::FAILURE)
}
},
Err(e) => {
eprintln!("{} Failed to load configuration: {e}", style("✗").red());
Ok(ExitCode::FAILURE)
}
},
}
}

Expand All @@ -396,15 +389,12 @@ pub fn config(raw: bool) -> Result<ExitCode> {
}

Ok(ExitCode::SUCCESS)
}
},
Err(Error::ConfigNotFound { .. }) => {
eprintln!(
"{} No configuration file found",
style("!").yellow()
);
eprintln!("{} No configuration file found", style("!").yellow());
eprintln!(" Run: apc init");
Ok(ExitCode::FAILURE)
}
},
Err(e) => Err(e),
}
}
Expand Down
13 changes: 6 additions & 7 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ pub fn run() -> Result<ExitCode> {
Some(Commands::Uninstall) => commands::uninstall(),
Some(Commands::Run { mode, check, all }) => {
commands::run(mode.as_deref(), check.as_deref(), all)
}
},
Some(Commands::Detect) => commands::detect(),
Some(Commands::List { mode }) => commands::list(mode.as_deref()),
Some(Commands::Validate) => commands::validate(),
Some(Commands::Config { raw }) => commands::config(raw),
Some(Commands::Completions { shell }) => {
commands::completions(shell);
Ok(ExitCode::SUCCESS)
}
},
None => commands::run(None, None, false),
}
}
Expand All @@ -184,8 +184,7 @@ fn setup_logging(verbose: bool, quiet: bool) {
"info"
};

let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(filter));
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(filter));

tracing_subscriber::fmt()
.with_env_filter(env_filter)
Expand All @@ -200,14 +199,14 @@ fn setup_color(choice: ColorChoice) {
ColorChoice::Always => {
console::set_colors_enabled(true);
console::set_colors_enabled_stderr(true);
}
},
ColorChoice::Never => {
console::set_colors_enabled(false);
console::set_colors_enabled_stderr(false);
}
},
ColorChoice::Auto => {
// Let console crate auto-detect
}
},
}
}

Expand Down
Loading