/ cli_initialization_property_test.rs
cli_initialization_property_test.rs
  1  use ferris_proof_cli::commands::init;
  2  use ferris_proof_core::models::config::VerificationLevel;
  3  use proptest::prelude::*;
  4  use std::fs;
  5  use std::path::PathBuf;
  6  use tempfile::{tempdir, TempDir};
  7  use tokio::sync::Mutex;
  8  
  9  lazy_static::lazy_static! {
 10      // Use a tokio Mutex to make it async-aware
 11      static ref WORKING_DIR_MUTEX: Mutex<()> = Mutex::new(());
 12  }
 13  
 14  /// Represents the outcome of an `init` command execution for property testing.
 15  #[derive(Debug, Clone)]
 16  struct InitCommandOutcome {
 17      level: VerificationLevel,
 18      created_config: bool,
 19      created_specs_dir: bool,
 20      created_tests_dir: bool,
 21      config_content_valid: bool,
 22      temp_dir: Option<PathBuf>, // Keep track of the temp dir path for cleanup
 23  }
 24  
 25  /// Strategy to generate different verification levels for testing.
 26  fn verification_level_strategy() -> impl Strategy<Value = VerificationLevel> {
 27      prop_oneof![
 28          Just(VerificationLevel::Minimal),
 29          Just(VerificationLevel::Standard),
 30          Just(VerificationLevel::Strict),
 31          Just(VerificationLevel::Formal),
 32      ]
 33  }
 34  
 35  /// Runs the `init` command in a temporary directory and captures the outcome.
 36  async fn run_init_and_capture_outcome(level: VerificationLevel) -> (InitCommandOutcome, TempDir) {
 37      let dir = tempdir().unwrap();
 38      let project_path = dir.path().to_path_buf();
 39  
 40      // Set current directory for the test
 41      std::env::set_current_dir(&project_path).unwrap();
 42  
 43      let result = init::run(level.clone(), false, None).await;
 44      assert!(result.is_ok());
 45  
 46      let config_path = project_path.join("ferrisproof.toml");
 47      let created_config = config_path.exists();
 48      let created_specs_dir = project_path.join("specs").is_dir();
 49      let created_tests_dir = project_path.join("tests").is_dir();
 50  
 51      let config_content_valid = if created_config {
 52          let content = fs::read_to_string(&config_path).unwrap();
 53          content.contains(&format!("level = \"{}\"", level.to_string().to_lowercase()))
 54      } else {
 55          false
 56      };
 57  
 58      (
 59          InitCommandOutcome {
 60              level,
 61              created_config,
 62              created_specs_dir,
 63              created_tests_dir,
 64              config_content_valid,
 65              temp_dir: Some(project_path),
 66          },
 67          dir, // Return the TempDir to keep it in scope
 68      )
 69  }
 70  
 71  proptest! {
 72      #![proptest_config(ProptestConfig::with_cases(10))]
 73  
 74      /// Property: The `init` command should always create the configuration file and standard directories.
 75      #[test]
 76      fn prop_init_creates_required_files(level in verification_level_strategy()) {
 77          tokio::runtime::Runtime::new().unwrap().block_on(async {
 78              // Lock the async mutex
 79              let _guard = WORKING_DIR_MUTEX.lock().await;
 80  
 81              let (outcome, _dir) = run_init_and_capture_outcome(level).await;
 82  
 83              prop_assert!(outcome.created_config, "ferrisproof.toml should be created");
 84              prop_assert!(outcome.created_specs_dir, "specs directory should be created");
 85              prop_assert!(outcome.created_tests_dir, "tests directory should be created");
 86              prop_assert!(outcome.config_content_valid, "ferrisproof.toml should have the correct level");
 87          });
 88      }
 89  
 90      /// Property: The generated `ferrisproof.toml` should always contain the correct verification level.
 91      #[test]
 92      fn prop_init_config_contains_correct_level(level in verification_level_strategy()) {
 93          tokio::runtime::Runtime::new().unwrap().block_on(async {
 94              // Lock the async mutex
 95              let _guard = WORKING_DIR_MUTEX.lock().await;
 96  
 97              let (outcome, _dir) = run_init_and_capture_outcome(level).await;
 98  
 99              prop_assert!(outcome.config_content_valid, "Generated config must contain the correct verification level");
100          });
101      }
102  }
103  
104  #[tokio::test]
105  async fn test_init_standard_level() {
106      // Lock the async mutex
107      let _guard = WORKING_DIR_MUTEX.lock().await;
108      let dir = tempdir().unwrap();
109      std::env::set_current_dir(dir.path()).unwrap();
110  
111      let result = init::run(VerificationLevel::Standard, false, None).await;
112      assert!(result.is_ok());
113  
114      let config_path = dir.path().join("ferrisproof.toml");
115      assert!(config_path.exists());
116  
117      let content = fs::read_to_string(config_path).unwrap();
118      assert!(content.contains("level = \"standard\""));
119      assert!(content.contains("cache_enabled = true"));
120  
121      dir.close().unwrap();
122  }
123  
124  #[tokio::test]
125  async fn test_init_formal_level() {
126      // Lock the async mutex
127      let _guard = WORKING_DIR_MUTEX.lock().await;
128      let dir = tempdir().unwrap();
129      std::env::set_current_dir(dir.path()).unwrap();
130  
131      let result = init::run(VerificationLevel::Formal, false, None).await;
132      assert!(result.is_ok());
133  
134      let config_path = dir.path().join("ferrisproof.toml");
135      assert!(config_path.exists());
136  
137      let content = fs::read_to_string(config_path).unwrap();
138      assert!(content.contains("level = \"formal\""));
139      assert!(content.contains("[[profile.enabled_techniques]]"));
140  
141      dir.close().unwrap();
142  }