|
| 1 | +use std::fs; |
| 2 | +use std::path::{Path, PathBuf}; |
| 3 | +use std::process::Command; |
| 4 | + |
| 5 | +use codeowners::runner::{self, RunConfig}; |
| 6 | + |
| 7 | +fn copy_dir_recursive(from: &Path, to: &Path) { |
| 8 | + fs::create_dir_all(to).expect("failed to create destination root"); |
| 9 | + for entry in fs::read_dir(from).expect("failed to read source dir") { |
| 10 | + let entry = entry.expect("failed to read dir entry"); |
| 11 | + let file_type = entry.file_type().expect("failed to read file type"); |
| 12 | + let src_path = entry.path(); |
| 13 | + let dest_path = to.join(entry.file_name()); |
| 14 | + if file_type.is_dir() { |
| 15 | + copy_dir_recursive(&src_path, &dest_path); |
| 16 | + } else if file_type.is_file() { |
| 17 | + // Ensure parent exists then copy |
| 18 | + if let Some(parent) = dest_path.parent() { |
| 19 | + fs::create_dir_all(parent).expect("failed to create parent dir"); |
| 20 | + } |
| 21 | + fs::copy(&src_path, &dest_path).expect("failed to copy file"); |
| 22 | + } |
| 23 | + } |
| 24 | +} |
| 25 | + |
| 26 | +fn init_git_repo(path: &Path) { |
| 27 | + // Initialize a new git repository in the temp project |
| 28 | + let status = Command::new("git") |
| 29 | + .arg("init") |
| 30 | + .current_dir(path) |
| 31 | + .output() |
| 32 | + .expect("failed to run git init"); |
| 33 | + assert!( |
| 34 | + status.status.success(), |
| 35 | + "git init failed: {}", |
| 36 | + String::from_utf8_lossy(&status.stderr) |
| 37 | + ); |
| 38 | + |
| 39 | + // Configure a dummy identity to appease git if commits ever happen; not strictly needed for staging |
| 40 | + let _ = Command::new("git") |
| 41 | + .arg("config") |
| 42 | + .arg("user.email") |
| 43 | + |
| 44 | + .current_dir(path) |
| 45 | + .output(); |
| 46 | + let _ = Command::new("git") |
| 47 | + .arg("config") |
| 48 | + .arg("user.name") |
| 49 | + .arg("Test User") |
| 50 | + .current_dir(path) |
| 51 | + .output(); |
| 52 | +} |
| 53 | + |
| 54 | +fn is_file_staged(repo_root: &Path, rel_path: &str) -> bool { |
| 55 | + // Use git diff --name-only --cached to list staged files |
| 56 | + let output = Command::new("git") |
| 57 | + .arg("diff") |
| 58 | + .arg("--name-only") |
| 59 | + .arg("--cached") |
| 60 | + .current_dir(repo_root) |
| 61 | + .output() |
| 62 | + .expect("failed to run git diff --cached"); |
| 63 | + assert!( |
| 64 | + output.status.success(), |
| 65 | + "git diff failed: {}", |
| 66 | + String::from_utf8_lossy(&output.stderr) |
| 67 | + ); |
| 68 | + let stdout = String::from_utf8_lossy(&output.stdout); |
| 69 | + stdout.lines().any(|line| line.trim() == rel_path) |
| 70 | +} |
| 71 | + |
| 72 | +fn build_run_config(project_root: &Path, codeowners_rel_path: &str) -> RunConfig { |
| 73 | + let project_root = project_root.canonicalize().expect("failed to canonicalize project root"); |
| 74 | + let codeowners_file_path = project_root.join(codeowners_rel_path); |
| 75 | + let config_path = project_root.join("config/code_ownership.yml"); |
| 76 | + RunConfig { |
| 77 | + project_root, |
| 78 | + codeowners_file_path, |
| 79 | + config_path, |
| 80 | + no_cache: true, |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +#[test] |
| 85 | +fn test_generate_stages_codeowners() { |
| 86 | + let temp_dir = tempfile::tempdir().expect("failed to create tempdir"); |
| 87 | + let temp_root = temp_dir.path().to_path_buf(); |
| 88 | + |
| 89 | + // Copy the valid project fixture into a temporary git repo |
| 90 | + let fixture_root = PathBuf::from("tests/fixtures/valid_project"); |
| 91 | + copy_dir_recursive(&fixture_root, &temp_root); |
| 92 | + init_git_repo(&temp_root); |
| 93 | + |
| 94 | + // Run generate with staging enabled, targeting the standard CODEOWNERS path |
| 95 | + let run_config = build_run_config(&temp_root, ".github/CODEOWNERS"); |
| 96 | + let result = runner::generate(&run_config, true); |
| 97 | + assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors); |
| 98 | + assert!( |
| 99 | + result.validation_errors.is_empty(), |
| 100 | + "validation_errors: {:?}", |
| 101 | + result.validation_errors |
| 102 | + ); |
| 103 | + |
| 104 | + // Assert CODEOWNERS file exists and is staged |
| 105 | + let rel_path = ".github/CODEOWNERS"; |
| 106 | + assert!(run_config.codeowners_file_path.exists(), "CODEOWNERS file was not created"); |
| 107 | + assert!(is_file_staged(&run_config.project_root, rel_path), "CODEOWNERS file was not staged"); |
| 108 | +} |
| 109 | + |
| 110 | +#[test] |
| 111 | +fn test_generate_and_validate_stages_codeowners() { |
| 112 | + let temp_dir = tempfile::tempdir().expect("failed to create tempdir"); |
| 113 | + let temp_root = temp_dir.path().to_path_buf(); |
| 114 | + |
| 115 | + // Copy the valid project fixture into a temporary git repo |
| 116 | + let fixture_root = PathBuf::from("tests/fixtures/valid_project"); |
| 117 | + copy_dir_recursive(&fixture_root, &temp_root); |
| 118 | + init_git_repo(&temp_root); |
| 119 | + |
| 120 | + // Run generate_and_validate with staging enabled |
| 121 | + let run_config = build_run_config(&temp_root, ".github/CODEOWNERS"); |
| 122 | + let result = runner::generate_and_validate(&run_config, vec![], true); |
| 123 | + assert!(result.io_errors.is_empty(), "io_errors: {:?}", result.io_errors); |
| 124 | + assert!( |
| 125 | + result.validation_errors.is_empty(), |
| 126 | + "validation_errors: {:?}", |
| 127 | + result.validation_errors |
| 128 | + ); |
| 129 | + |
| 130 | + // Assert CODEOWNERS file exists and is staged |
| 131 | + let rel_path = ".github/CODEOWNERS"; |
| 132 | + assert!(run_config.codeowners_file_path.exists(), "CODEOWNERS file was not created"); |
| 133 | + assert!(is_file_staged(&run_config.project_root, rel_path), "CODEOWNERS file was not staged"); |
| 134 | +} |
0 commit comments