use assert_cmd::cargo::cargo_bin_cmd;
use log::LevelFilter;
use log::{debug, info};
use predicates::str::contains;
use rusty_todo_md::logger;
use std::fs;
use std::sync::Once;
use tempfile::tempdir;
mod utils;
use utils::init_repo;
static INIT: Once = Once::new();
fn init_logger() {
INIT.call_once(|| {
env_logger::Builder::from_default_env()
.format(logger::format_logger)
.filter_level(LevelFilter::Debug)
.is_test(true)
.try_init()
.ok();
});
}
#[test]
fn test_run_cli_in_non_git_directory() {
init_logger();
info!("Starting test: test_run_cli_in_non_git_directory");
let temp = tempdir().expect("failed to create temp dir");
debug!("Created temporary directory: {:?}", temp.path());
let mut cmd = cargo_bin_cmd!("rusty-todo-md");
debug!("Running CLI binary in temporary directory");
cmd.current_dir(&temp)
.arg("--todo-path")
.arg("TODO.md")
.arg("dummy_file.rs");
cmd.assert()
.failure()
.stderr(contains("Error opening repository"));
info!("Test completed: test_run_cli_in_non_git_directory");
}
#[test]
fn test_run_cli_with_unreadable_file() {
init_logger();
info!("Starting test: test_run_cli_with_unreadable_file");
let (temp_dir, _repo) = init_repo().expect("Failed to initialize test repo");
let repo_dir = temp_dir.path();
let file_path = repo_dir.join("unreadable.rs");
fs::write(&file_path, " // TODO: test unreadable").expect("failed to write file");
debug!("Created unreadable file at: {:?}", file_path);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&file_path)
.expect("failed to get metadata")
.permissions();
perms.set_mode(0o000);
fs::set_permissions(&file_path, perms).expect("failed to set permissions");
debug!("Set file permissions to unreadable for: {:?}", file_path);
}
let mut cmd = cargo_bin_cmd!("rusty-todo-md");
debug!("Running CLI binary in repository directory");
cmd.current_dir(repo_dir)
.arg("--todo-path")
.arg("TODO.md")
.arg(file_path.to_str().expect("file path valid"));
cmd.assert()
.success()
.stderr(contains("Warning: Could not read file"));
info!("Test completed: test_run_cli_with_unreadable_file");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&file_path)
.expect("failed to get metadata")
.permissions();
perms.set_mode(0o644);
fs::set_permissions(&file_path, perms).expect("failed to reset permissions");
debug!("Restored file permissions for: {:?}", file_path);
}
}
#[test]
fn test_sync_todo_file_fallback_mechanism() {
init_logger();
info!("Starting test: test_sync_todo_file_fallback_mechanism");
let (temp_dir, repo) = init_repo().expect("Failed to initialize test repo");
let repo_dir = temp_dir.path();
debug!("Initialized repository at: {:?}", repo_dir);
let test_file = repo_dir.join("test.rs");
fs::write(
&test_file,
"// TODO: implement feature A\n// FIXME: fix bug B\n",
)
.expect("failed to write test file");
debug!("Created test file at: {:?}", test_file);
let todo_path = repo_dir.join("TODO.md");
let corrupted_content = r#"This is completely invalid content that doesn't match any regex pattern
And this line will also fail validation
No markdown headers or bullet points here
Just plain text that should trigger validation failure
"#;
fs::write(&todo_path, corrupted_content).expect("failed to write corrupted TODO.md");
debug!("Created corrupted TODO.md at: {:?}", todo_path);
let mut index = repo.index().expect("Failed to get index");
index
.add_path(std::path::Path::new("test.rs"))
.expect("Failed to add test.rs");
index.write().expect("Failed to write index");
debug!("Staged test file with git2");
let tree_id = index.write_tree().expect("Failed to write tree");
let tree = repo.find_tree(tree_id).expect("Failed to find tree");
let sig =
git2::Signature::now("Test User", "test@example.com").expect("Failed to create signature");
repo.commit(
Some("HEAD"),
&sig,
&sig,
"Add test file",
&tree,
&[&repo.head().unwrap().peel_to_commit().unwrap()],
)
.expect("Failed to commit");
debug!("Committed test file with git2");
let mut cmd = cargo_bin_cmd!("rusty-todo-md");
debug!("Running CLI binary to test fallback mechanism");
cmd.current_dir(repo_dir)
.env("RUST_LOG", "debug")
.arg("--todo-path")
.arg("TODO.md")
.arg(test_file.to_str().expect("test file path valid"));
cmd.assert().success();
assert!(todo_path.exists(), "TODO.md should exist after fallback");
let final_content = fs::read_to_string(&todo_path).expect("failed to read final TODO.md");
debug!("Final TODO.md content: {}", final_content);
assert!(
final_content.contains("implement feature A"),
"Should contain TODO from test file"
);
assert!(
final_content.contains("fix bug B") || final_content.contains("TODO"),
"Should contain content from test file"
);
assert!(
!final_content.contains("This is completely invalid"),
"Corrupted content should be gone"
);
assert!(final_content.contains("# TODO"), "Should have TODO header");
assert!(
final_content.contains("## test.rs"),
"Should have file section header"
);
info!("Test completed: test_sync_todo_file_fallback_mechanism");
}