project_structure.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_structure_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 root_files.insert("Containerfile".to_string()); 28 29 let mut required_directories = HashSet::new(); 30 required_directories.insert("ferris-proof-cli".to_string()); 31 required_directories.insert("ferris-proof-core".to_string()); 32 required_directories.insert("ferris-proof-config".to_string()); 33 required_directories.insert("ferris-proof-plugins".to_string()); 34 required_directories.insert(".github".to_string()); 35 required_directories.insert("docs".to_string()); 36 37 let crate_structure = vec![ 38 "ferris-proof-cli".to_string(), 39 "ferris-proof-core".to_string(), 40 "ferris-proof-config".to_string(), 41 "ferris-proof-plugins".to_string(), 42 ]; 43 44 Self { 45 root_files, 46 required_directories, 47 crate_structure, 48 } 49 } 50 51 fn validate_structure(&self, project_root: &Path) -> Result<(), String> { 52 // Check root files exist 53 for file in &self.root_files { 54 let file_path = project_root.join(file); 55 if !file_path.exists() { 56 // Debug: list what files actually exist 57 if let Ok(entries) = std::fs::read_dir(project_root) { 58 let existing_files: Vec<String> = entries 59 .filter_map(|e| e.ok()) 60 .filter_map(|e| e.file_name().into_string().ok()) 61 .collect(); 62 return Err(format!( 63 "Required root file missing: {}. Existing files: {:?}", 64 file, existing_files 65 )); 66 } 67 return Err(format!("Required root file missing: {}", file)); 68 } 69 } 70 71 // Check required directories exist 72 for dir in &self.required_directories { 73 let dir_path = project_root.join(dir); 74 if !dir_path.exists() || !dir_path.is_dir() { 75 return Err(format!("Required directory missing: {}", dir)); 76 } 77 } 78 79 // Check crate structure 80 for crate_name in &self.crate_structure { 81 let crate_path = project_root.join(crate_name); 82 let cargo_toml = crate_path.join("Cargo.toml"); 83 let src_dir = crate_path.join("src"); 84 let lib_rs = src_dir.join("lib.rs"); 85 86 if !cargo_toml.exists() { 87 return Err(format!("Crate {} missing Cargo.toml", crate_name)); 88 } 89 90 if !src_dir.exists() || !src_dir.is_dir() { 91 return Err(format!("Crate {} missing src directory", crate_name)); 92 } 93 94 // CLI crate has main.rs, others have lib.rs 95 if crate_name == "ferris-proof-cli" { 96 let main_rs = src_dir.join("main.rs"); 97 if !main_rs.exists() && !lib_rs.exists() { 98 return Err("CLI crate missing main.rs or lib.rs".to_string()); 99 } 100 } else if !lib_rs.exists() { 101 return Err(format!("Crate {} missing lib.rs", crate_name)); 102 } 103 } 104 105 Ok(()) 106 } 107 } 108 109 proptest! { 110 #[test] 111 /// **Feature: ferris-proof, Property 1: Project structure consistency** 112 /// For any valid FerrisProof workspace configuration, the project structure 113 /// should maintain consistency with required files and directories. 114 fn project_structure_consistency( 115 // Generate different project configurations 116 _include_optional_files in prop::bool::ANY, 117 _include_examples in prop::bool::ANY, 118 ) { 119 let expected_structure = ProjectStructure::ferris_proof_workspace(); 120 121 // Find the workspace root by looking for Cargo.toml with [workspace] 122 let mut current_dir = std::env::current_dir() 123 .expect("Failed to get current directory"); 124 125 // Walk up the directory tree to find workspace root 126 loop { 127 let cargo_toml = current_dir.join("Cargo.toml"); 128 if cargo_toml.exists() { 129 if let Ok(content) = std::fs::read_to_string(&cargo_toml) { 130 if content.contains("[workspace]") { 131 break; // Found workspace root 132 } 133 } 134 } 135 136 if let Some(parent) = current_dir.parent() { 137 current_dir = parent.to_path_buf(); 138 } else { 139 prop_assume!(false); // Skip test if can't find workspace root 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 // Find the workspace root by looking for Cargo.toml with [workspace] 207 let mut current_dir = std::env::current_dir() 208 .expect("Failed to get current directory"); 209 210 // Walk up the directory tree to find workspace root 211 loop { 212 let cargo_toml = current_dir.join("Cargo.toml"); 213 if cargo_toml.exists() { 214 if let Ok(content) = std::fs::read_to_string(&cargo_toml) { 215 if content.contains("[workspace]") { 216 break; // Found workspace root 217 } 218 } 219 } 220 221 if let Some(parent) = current_dir.parent() { 222 current_dir = parent.to_path_buf(); 223 } else { 224 prop_assume!(false); // Skip test if can't find workspace root 225 } 226 } 227 228 if crate_index >= expected_structure.crate_structure.len() { 229 return Ok(()); 230 } 231 232 let crate_name = &expected_structure.crate_structure[crate_index]; 233 let crate_cargo = current_dir.join(crate_name).join("Cargo.toml"); 234 235 if !crate_cargo.exists() { 236 return Ok(()); 237 } 238 239 let cargo_content = std::fs::read_to_string(&crate_cargo) 240 .expect("Failed to read crate Cargo.toml"); 241 242 // Check that workspace dependencies are used consistently 243 if cargo_content.contains("[dependencies]") { 244 // If crate uses workspace dependencies, they should use .workspace = true 245 let workspace_deps = ["serde", "tokio", "anyhow", "thiserror", "tracing"]; 246 247 for dep in &workspace_deps { 248 if cargo_content.contains(&format!("{}.workspace", dep)) { 249 prop_assert!( 250 cargo_content.contains(&format!("{}.workspace = true", dep)), 251 "Crate {} should use workspace dependency for {}", 252 crate_name, 253 dep 254 ); 255 } 256 } 257 } 258 259 // Check internal dependencies use path references 260 for other_crate in &expected_structure.crate_structure { 261 if other_crate != crate_name && cargo_content.contains(other_crate) { 262 prop_assert!( 263 cargo_content.contains(&format!("path = \"../{}\"", other_crate)), 264 "Crate {} should use path dependency for internal crate {}", 265 crate_name, 266 other_crate 267 ); 268 } 269 } 270 } 271 } 272 273 #[test] 274 fn test_current_project_structure() { 275 let expected_structure = ProjectStructure::ferris_proof_workspace(); 276 277 // Find the workspace root by looking for Cargo.toml with [workspace] 278 let mut current_dir = std::env::current_dir().expect("Failed to get current directory"); 279 280 // Walk up the directory tree to find workspace root 281 loop { 282 let cargo_toml = current_dir.join("Cargo.toml"); 283 if cargo_toml.exists() { 284 if let Ok(content) = std::fs::read_to_string(&cargo_toml) { 285 if content.contains("[workspace]") { 286 break; // Found workspace root 287 } 288 } 289 } 290 291 if let Some(parent) = current_dir.parent() { 292 current_dir = parent.to_path_buf(); 293 } else { 294 panic!("Could not find workspace root"); 295 } 296 } 297 298 match expected_structure.validate_structure(¤t_dir) { 299 Ok(()) => println!("✓ Project structure validation passed"), 300 Err(e) => panic!("Project structure validation failed: {}", e), 301 } 302 } 303 }