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