project_setup_test.rs
1 use proptest::prelude::*; 2 use std::collections::HashSet; 3 use std::path::Path; 4 5 /// **Feature: ferris-proof, Property 1: Project structure consistency** 6 /// **Validates: Requirements 18.3** 7 /// 8 /// This property test verifies that FerrisProof maintains consistent project structure 9 /// across different initialization scenarios and configurations. 10 #[cfg(test)] 11 mod project_setup_tests { 12 use super::*; 13 14 /// Represents a valid FerrisProof project structure 15 #[derive(Debug, Clone)] 16 struct ProjectStructure { 17 pub root_files: HashSet<String>, 18 pub required_directories: HashSet<String>, 19 pub crate_structure: Vec<String>, 20 } 21 22 impl ProjectStructure { 23 fn ferris_proof_workspace() -> Self { 24 let mut root_files = HashSet::new(); 25 root_files.insert("Cargo.toml".to_string()); 26 root_files.insert("ReadMe.md".to_string()); 27 28 let mut required_directories = HashSet::new(); 29 required_directories.insert("ferris-proof-cli".to_string()); 30 required_directories.insert("ferris-proof-core".to_string()); 31 required_directories.insert("ferris-proof-config".to_string()); 32 required_directories.insert("ferris-proof-plugins".to_string()); 33 required_directories.insert(".github".to_string()); 34 required_directories.insert("docs".to_string()); 35 36 let crate_structure = vec![ 37 "ferris-proof-cli".to_string(), 38 "ferris-proof-core".to_string(), 39 "ferris-proof-config".to_string(), 40 "ferris-proof-plugins".to_string(), 41 ]; 42 43 Self { 44 root_files, 45 required_directories, 46 crate_structure, 47 } 48 } 49 50 fn validate_structure(&self, project_root: &Path) -> Result<(), String> { 51 // Check root files exist 52 for file in &self.root_files { 53 let file_path = project_root.join(file); 54 if !file_path.exists() { 55 return Err(format!("Required root file missing: {}", file)); 56 } 57 } 58 59 // Check required directories exist 60 for dir in &self.required_directories { 61 let dir_path = project_root.join(dir); 62 if !dir_path.exists() || !dir_path.is_dir() { 63 return Err(format!("Required directory missing: {}", dir)); 64 } 65 } 66 67 // Check crate structure 68 for crate_name in &self.crate_structure { 69 let crate_path = project_root.join(crate_name); 70 let cargo_toml = crate_path.join("Cargo.toml"); 71 let src_dir = crate_path.join("src"); 72 73 if !cargo_toml.exists() { 74 return Err(format!("Crate {} missing Cargo.toml", crate_name)); 75 } 76 77 if !src_dir.exists() || !src_dir.is_dir() { 78 return Err(format!("Crate {} missing src directory", crate_name)); 79 } 80 81 // CLI crate has main.rs, others have lib.rs 82 if crate_name == "ferris-proof-cli" { 83 let main_rs = src_dir.join("main.rs"); 84 let lib_rs = src_dir.join("lib.rs"); 85 if !main_rs.exists() && !lib_rs.exists() { 86 return Err("CLI crate missing main.rs or lib.rs".to_string()); 87 } 88 } else { 89 let lib_rs = src_dir.join("lib.rs"); 90 if !lib_rs.exists() { 91 return Err(format!("Crate {} missing lib.rs", crate_name)); 92 } 93 } 94 } 95 96 Ok(()) 97 } 98 } 99 100 fn find_workspace_root() -> Result<std::path::PathBuf, String> { 101 let mut current_dir = std::env::current_dir() 102 .map_err(|e| format!("Failed to get current directory: {}", e))?; 103 104 // Walk up the directory tree to find workspace root 105 loop { 106 let cargo_toml = current_dir.join("Cargo.toml"); 107 if cargo_toml.exists() { 108 if let Ok(content) = std::fs::read_to_string(&cargo_toml) { 109 if content.contains("[workspace]") { 110 return Ok(current_dir); // Found workspace root 111 } 112 } 113 } 114 115 if let Some(parent) = current_dir.parent() { 116 current_dir = parent.to_path_buf(); 117 } else { 118 return Err("Could not find workspace root".to_string()); 119 } 120 } 121 } 122 123 proptest! { 124 #[test] 125 /// **Feature: ferris-proof, Property 1: Project structure consistency** 126 /// For any valid FerrisProof workspace configuration, the project structure 127 /// should maintain consistency with required files and directories. 128 fn project_structure_consistency( 129 // Generate different project configurations 130 _include_optional_files in prop::bool::ANY, 131 _include_examples in prop::bool::ANY, 132 ) { 133 let expected_structure = ProjectStructure::ferris_proof_workspace(); 134 135 let current_dir = match find_workspace_root() { 136 Ok(dir) => dir, 137 Err(_) => { 138 prop_assume!(false); // Skip test if can't find workspace root 139 return Ok(()); 140 } 141 }; 142 143 // Validate the current project structure 144 let validation_result = expected_structure.validate_structure(¤t_dir); 145 146 prop_assert!( 147 validation_result.is_ok(), 148 "Project structure validation failed: {}", 149 validation_result.unwrap_err() 150 ); 151 152 // Additional consistency checks 153 154 // 1. Workspace Cargo.toml should list all crates 155 let workspace_cargo = current_dir.join("Cargo.toml"); 156 if workspace_cargo.exists() { 157 let cargo_content = std::fs::read_to_string(&workspace_cargo) 158 .expect("Failed to read workspace Cargo.toml"); 159 160 for crate_name in &expected_structure.crate_structure { 161 prop_assert!( 162 cargo_content.contains(&format!("\"{}\"", crate_name)), 163 "Workspace Cargo.toml missing crate: {}", 164 crate_name 165 ); 166 } 167 } 168 169 // 2. Each crate should have consistent naming 170 for crate_name in &expected_structure.crate_structure { 171 let crate_cargo = current_dir.join(crate_name).join("Cargo.toml"); 172 if crate_cargo.exists() { 173 let cargo_content = std::fs::read_to_string(&crate_cargo) 174 .expect("Failed to read crate Cargo.toml"); 175 176 prop_assert!( 177 cargo_content.contains(&format!("name = \"{}\"", crate_name)), 178 "Crate {} has inconsistent name in Cargo.toml", 179 crate_name 180 ); 181 } 182 } 183 184 // 3. Documentation structure should be consistent 185 let docs_dir = current_dir.join("docs"); 186 if docs_dir.exists() { 187 let getting_started = docs_dir.join("getting-started.md"); 188 prop_assert!( 189 getting_started.exists(), 190 "Documentation missing getting-started.md" 191 ); 192 } 193 } 194 } 195 196 proptest! { 197 #[test] 198 /// **Feature: ferris-proof, Property 1: Project structure consistency** 199 /// For any crate in the workspace, dependencies should be properly declared 200 /// and version constraints should be consistent. 201 fn crate_dependency_consistency( 202 crate_index in 0..4usize, // We have 4 crates 203 ) { 204 let expected_structure = ProjectStructure::ferris_proof_workspace(); 205 206 let current_dir = match find_workspace_root() { 207 Ok(dir) => dir, 208 Err(_) => { 209 prop_assume!(false); // Skip test if can't find workspace root 210 return Ok(()); 211 } 212 }; 213 214 if crate_index >= expected_structure.crate_structure.len() { 215 return Ok(()); 216 } 217 218 let crate_name = &expected_structure.crate_structure[crate_index]; 219 let crate_cargo = current_dir.join(crate_name).join("Cargo.toml"); 220 221 if !crate_cargo.exists() { 222 return Ok(()); 223 } 224 225 let cargo_content = std::fs::read_to_string(&crate_cargo) 226 .expect("Failed to read crate Cargo.toml"); 227 228 // Check that workspace dependencies are used consistently 229 if cargo_content.contains("[dependencies]") { 230 // If crate uses workspace dependencies, they should use .workspace = true 231 let workspace_deps = ["serde", "tokio", "anyhow", "thiserror", "tracing"]; 232 233 for dep in &workspace_deps { 234 if cargo_content.contains(&format!("{}.workspace", dep)) { 235 prop_assert!( 236 cargo_content.contains(&format!("{}.workspace = true", dep)), 237 "Crate {} should use workspace dependency for {}", 238 crate_name, 239 dep 240 ); 241 } 242 } 243 } 244 245 // Check internal dependencies use path references 246 for other_crate in &expected_structure.crate_structure { 247 if other_crate != crate_name && cargo_content.contains(other_crate) { 248 prop_assert!( 249 cargo_content.contains(&format!("path = \"../{}\"", other_crate)), 250 "Crate {} should use path dependency for internal crate {}", 251 crate_name, 252 other_crate 253 ); 254 } 255 } 256 } 257 } 258 259 #[test] 260 fn test_current_project_structure() { 261 let expected_structure = ProjectStructure::ferris_proof_workspace(); 262 263 let current_dir = find_workspace_root().expect("Could not find workspace root"); 264 265 match expected_structure.validate_structure(¤t_dir) { 266 Ok(()) => println!("✓ Project structure validation passed"), 267 Err(e) => panic!("Project structure validation failed: {}", e), 268 } 269 } 270 271 #[test] 272 fn test_ci_cd_configuration() { 273 let current_dir = find_workspace_root().expect("Could not find workspace root"); 274 275 // Check GitHub Actions configuration 276 let github_dir = current_dir.join(".github"); 277 let workflows_dir = github_dir.join("workflows"); 278 279 assert!(github_dir.exists(), "GitHub Actions directory missing"); 280 assert!(workflows_dir.exists(), "GitHub workflows directory missing"); 281 282 let ci_yml = workflows_dir.join("ci.yml"); 283 let release_yml = workflows_dir.join("release.yml"); 284 285 assert!(ci_yml.exists(), "CI workflow missing"); 286 assert!(release_yml.exists(), "Release workflow missing"); 287 288 // Check GitLab CI configuration 289 let gitlab_ci = current_dir.join(".gitlab-ci.yml"); 290 assert!(gitlab_ci.exists(), "GitLab CI configuration missing"); 291 292 println!("✓ CI/CD configuration validation passed"); 293 } 294 295 #[test] 296 fn test_documentation_structure() { 297 let current_dir = find_workspace_root().expect("Could not find workspace root"); 298 299 let docs_dir = current_dir.join("docs"); 300 assert!(docs_dir.exists(), "Documentation directory missing"); 301 302 // Check for key documentation files 303 let readme = current_dir.join("ReadMe.md"); 304 let contributing = current_dir.join("Contributing.md"); 305 let license = current_dir.join("Licence.md"); 306 307 assert!(readme.exists(), "README.md missing"); 308 assert!(contributing.exists(), "Contributing.md missing"); 309 assert!(license.exists(), "Licence.md missing"); 310 311 println!("✓ Documentation structure validation passed"); 312 } 313 }