/ ferris-proof-core / src / tests / project_structure.rs
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(&current_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(&current_dir) {
299              Ok(()) => println!("✓ Project structure validation passed"),
300              Err(e) => panic!("Project structure validation failed: {}", e),
301          }
302      }
303  }