|
| 1 | +use std::path::Path; |
| 2 | + |
| 3 | +use codeowners::runner::{self, RunConfig}; |
| 4 | + |
| 5 | +fn write_file(temp_dir: &Path, file_path: &str, content: &str) { |
| 6 | + let file_path = temp_dir.join(file_path); |
| 7 | + let _ = std::fs::create_dir_all(file_path.parent().unwrap()); |
| 8 | + std::fs::write(file_path, content).unwrap(); |
| 9 | +} |
| 10 | + |
| 11 | +#[test] |
| 12 | +fn test_file_owners_for_file() { |
| 13 | + let temp_dir = tempfile::tempdir().unwrap(); |
| 14 | + const DEFAULT_CODE_OWNERSHIP_YML: &str = r#"--- |
| 15 | +owned_globs: |
| 16 | + - "{app,components,config,frontend,lib,packs,spec,ruby}/**/*.{rb,rake,js,jsx,ts,tsx,json,yml,erb}" |
| 17 | +unowned_globs: |
| 18 | + - config/code_ownership.yml |
| 19 | +javascript_package_paths: |
| 20 | + - javascript/packages/** |
| 21 | +vendored_gems_path: gems |
| 22 | +team_file_glob: |
| 23 | + - config/teams/**/*.yml |
| 24 | +"#; |
| 25 | + write_file(temp_dir.path(), "config/code_ownership.yml", DEFAULT_CODE_OWNERSHIP_YML); |
| 26 | + ["a", "b", "c"].iter().for_each(|name| { |
| 27 | + let team_yml = format!( |
| 28 | + "name: {}\ngithub:\n team: \"@{}\"\n members:\n - {}member\n", |
| 29 | + name, name, name |
| 30 | + ); |
| 31 | + write_file(temp_dir.path(), &format!("config/teams/{}.yml", name), &team_yml); |
| 32 | + }); |
| 33 | + write_file( |
| 34 | + temp_dir.path(), |
| 35 | + "app/consumers/deep/nesting/nestdir/deep_file.rb", |
| 36 | + "# @team b\nclass DeepFile end;", |
| 37 | + ); |
| 38 | + |
| 39 | + let run_config = RunConfig { |
| 40 | + project_root: temp_dir.path().to_path_buf(), |
| 41 | + codeowners_file_path: temp_dir.path().join(".github/CODEOWNERS").to_path_buf(), |
| 42 | + config_path: temp_dir.path().join("config/code_ownership.yml").to_path_buf(), |
| 43 | + no_cache: true, |
| 44 | + }; |
| 45 | + |
| 46 | + let file_owner = runner::file_owner_for_file(&run_config, "app/consumers/deep/nesting/nestdir/deep_file.rb") |
| 47 | + .unwrap() |
| 48 | + .unwrap(); |
| 49 | + assert_eq!(file_owner.team.name, "b"); |
| 50 | + assert_eq!(file_owner.team.github_team, "@b"); |
| 51 | + assert!(file_owner.team.path.to_string_lossy().ends_with("config/teams/b.yml")); |
| 52 | +} |
| 53 | + |
| 54 | +#[test] |
| 55 | +fn test_teams_for_files_from_codeowners() { |
| 56 | + let project_root = Path::new("tests/fixtures/valid_project"); |
| 57 | + let file_paths = [ |
| 58 | + "javascript/packages/items/item.ts", |
| 59 | + "config/teams/payroll.yml", |
| 60 | + "ruby/app/models/bank_account.rb", |
| 61 | + "made/up/file.rb", |
| 62 | + "ruby/ignored_files/git_ignored.rb", |
| 63 | + ]; |
| 64 | + let run_config = RunConfig { |
| 65 | + project_root: project_root.to_path_buf(), |
| 66 | + codeowners_file_path: project_root.join(".github/CODEOWNERS").to_path_buf(), |
| 67 | + config_path: project_root.join("config/code_ownership.yml").to_path_buf(), |
| 68 | + no_cache: true, |
| 69 | + }; |
| 70 | + let teams = runner::teams_for_files_from_codeowners( |
| 71 | + &run_config, |
| 72 | + &file_paths.iter().map(|s| s.to_string()).collect::<Vec<String>>(), |
| 73 | + ) |
| 74 | + .unwrap(); |
| 75 | + assert_eq!(teams.len(), 5); |
| 76 | + assert_eq!( |
| 77 | + teams |
| 78 | + .get("javascript/packages/items/item.ts") |
| 79 | + .unwrap() |
| 80 | + .as_ref() |
| 81 | + .map(|t| t.name.as_str()), |
| 82 | + Some("Payroll") |
| 83 | + ); |
| 84 | + assert_eq!( |
| 85 | + teams |
| 86 | + .get("config/teams/payroll.yml") |
| 87 | + .unwrap() |
| 88 | + .as_ref() |
| 89 | + .map(|t| t.name.as_str()), |
| 90 | + Some("Payroll") |
| 91 | + ); |
| 92 | + assert_eq!( |
| 93 | + teams |
| 94 | + .get("ruby/app/models/bank_account.rb") |
| 95 | + .unwrap() |
| 96 | + .as_ref() |
| 97 | + .map(|t| t.name.as_str()), |
| 98 | + Some("Payments") |
| 99 | + ); |
| 100 | + assert_eq!(teams.get("made/up/file.rb").unwrap().as_ref().map(|t| t.name.as_str()), None); |
| 101 | + assert_eq!( |
| 102 | + teams |
| 103 | + .get("ruby/ignored_files/git_ignored.rb") |
| 104 | + .unwrap() |
| 105 | + .as_ref() |
| 106 | + .map(|t| t.name.as_str()), |
| 107 | + None |
| 108 | + ); |
| 109 | +} |
| 110 | + |
| 111 | +#[test] |
| 112 | +fn test_for_team_reads_codeowners() { |
| 113 | + let td = tempfile::tempdir().unwrap(); |
| 114 | + // minimal config |
| 115 | + const DEFAULT_CODE_OWNERSHIP_YML: &str = r#"--- |
| 116 | +owned_globs: |
| 117 | + - "app/**/*" |
| 118 | +unowned_globs: |
| 119 | + - config/code_ownership.yml |
| 120 | +team_file_glob: |
| 121 | + - config/teams/**/*.yml |
| 122 | +vendored_gems_path: gems |
| 123 | +javascript_package_paths: |
| 124 | + - javascript/packages/** |
| 125 | +"#; |
| 126 | + write_file(td.path(), "config/code_ownership.yml", DEFAULT_CODE_OWNERSHIP_YML); |
| 127 | + |
| 128 | + // team file for Foo |
| 129 | + write_file( |
| 130 | + td.path(), |
| 131 | + "config/teams/foo.yml", |
| 132 | + "name: Foo\ngithub:\n team: \"@Foo\"\n members:\n - user\n", |
| 133 | + ); |
| 134 | + // provide a CODEOWNERS file referencing @Foo |
| 135 | + write_file(td.path(), ".github/CODEOWNERS", "/app/** @Foo\n"); |
| 136 | + |
| 137 | + let rc = RunConfig { |
| 138 | + project_root: td.path().to_path_buf(), |
| 139 | + codeowners_file_path: td.path().join(".github/CODEOWNERS"), |
| 140 | + config_path: td.path().join("config/code_ownership.yml"), |
| 141 | + no_cache: true, |
| 142 | + }; |
| 143 | + |
| 144 | + // Ensure CODEOWNERS file matches generator output to avoid out-of-date errors |
| 145 | + let _ = runner::generate(&rc, false); |
| 146 | + let res = runner::for_team(&rc, "Foo"); |
| 147 | + assert!(res.io_errors.is_empty(), "unexpected io errors: {:?}", res.io_errors); |
| 148 | + assert!(res.validation_errors.is_empty()); |
| 149 | + assert!(res.info_messages.iter().any(|m| m.contains("# Code Ownership Report for `Foo` Team"))); |
| 150 | +} |
| 151 | + |
| 152 | +#[test] |
| 153 | +fn test_validate_and_generate_and_validate() { |
| 154 | + let td = tempfile::tempdir().unwrap(); |
| 155 | + // config and team so generation has inputs |
| 156 | + const DEFAULT_CODE_OWNERSHIP_YML: &str = r#"--- |
| 157 | +owned_globs: |
| 158 | + - "**/*" |
| 159 | +team_file_glob: |
| 160 | + - config/teams/**/*.yml |
| 161 | +vendored_gems_path: gems |
| 162 | +javascript_package_paths: |
| 163 | + - javascript/packages/** |
| 164 | +"#; |
| 165 | + write_file(td.path(), "config/code_ownership.yml", DEFAULT_CODE_OWNERSHIP_YML); |
| 166 | + write_file( |
| 167 | + td.path(), |
| 168 | + "config/teams/foo.yml", |
| 169 | + "name: Foo\ngithub:\n team: \"@Foo\"\n members:\n - user\nowned_globs:\n - \"app/**\"\n - \"config/code_ownership.yml\"\n", |
| 170 | + ); |
| 171 | + // create a file to be matched (no annotation to avoid multi-source ownership) |
| 172 | + write_file(td.path(), "app/x.rb", "puts :x\n"); |
| 173 | + |
| 174 | + let rc = RunConfig { |
| 175 | + project_root: td.path().to_path_buf(), |
| 176 | + codeowners_file_path: td.path().join(".github/CODEOWNERS"), |
| 177 | + config_path: td.path().join("config/code_ownership.yml"), |
| 178 | + no_cache: true, |
| 179 | + }; |
| 180 | + |
| 181 | + let gv = runner::generate_and_validate(&rc, vec![], true); |
| 182 | + assert!(gv.io_errors.is_empty(), "io: {:?}", gv.io_errors); |
| 183 | + assert!(gv.validation_errors.is_empty(), "val: {:?}", gv.validation_errors); |
| 184 | + // file should exist after generate |
| 185 | + let content = std::fs::read_to_string(td.path().join(".github/CODEOWNERS")).unwrap(); |
| 186 | + assert!(!content.is_empty()); |
| 187 | +} |
| 188 | + |
| 189 | + |
0 commit comments