/ ferris-proof-core / src / tests / project_setup_test.rs
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(&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              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(&current_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  }