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