python_proto_importer/commands/
check.rs

1use crate::config::AppConfig;
2use crate::verification::import_test::verify;
3use anyhow::{Context, Result};
4use std::path::Path;
5
6/// Execute the check command to verify existing generated Python code.
7///
8/// This command runs only the verification phase without regenerating any code.
9/// It loads the configuration from pyproject.toml and performs the same validation
10/// checks that are run at the end of the build process.
11///
12/// # Arguments
13///
14/// * `pyproject` - Optional path to the pyproject.toml file. If None, uses "pyproject.toml"
15///
16/// # Returns
17///
18/// Returns `Ok(())` if all verification checks pass, or an error if:
19/// - Configuration cannot be loaded
20/// - Import tests fail
21/// - Type checking fails (if configured)
22///
23/// # Verification Steps
24///
25/// 1. **Configuration Loading**: Reads and validates pyproject.toml settings
26/// 2. **Import Validation**: Attempts to import every generated Python module
27/// 3. **Type Checking**: Runs configured type checkers (mypy/pyright) if specified
28///
29/// # Use Cases
30///
31/// - **CI Integration**: Verify generated code without regenerating
32/// - **Development**: Quick validation after manual changes
33/// - **Debugging**: Isolate verification issues from generation issues
34///
35/// # Example
36///
37/// ```no_run
38/// use python_proto_importer::commands::check;
39///
40/// // Check with default pyproject.toml
41/// check(None)?;
42///
43/// // Check with custom config file
44/// check(Some("custom.toml"))?;
45/// # Ok::<(), anyhow::Error>(())
46/// ```
47pub fn check(pyproject: Option<&str>) -> Result<()> {
48    let cfg = AppConfig::load(pyproject.map(Path::new)).context("failed to load config")?;
49    verify(&cfg)
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use std::fs;
56    use std::io::Write;
57    use tempfile::TempDir;
58
59    fn create_test_config_file(dir: &Path, out_dir: &str) -> Result<String> {
60        let config_file = dir.join("pyproject.toml");
61        let mut file = fs::File::create(&config_file)?;
62        writeln!(file, "[tool.python_proto_importer]")?;
63        writeln!(file, "out = \"{}\"", out_dir)?;
64        writeln!(file, "proto_path = [\"proto\"]")?;
65        writeln!(file, "python_exe = \"python3\"")?;
66        Ok(config_file.to_string_lossy().to_string())
67    }
68
69    #[test]
70    fn test_check_invalid_config() {
71        let result = check(Some("nonexistent_config.toml"));
72        assert!(result.is_err());
73        assert!(
74            result
75                .unwrap_err()
76                .to_string()
77                .contains("failed to load config")
78        );
79    }
80
81    #[test]
82    fn test_check_with_empty_output_directory() {
83        let temp_dir = TempDir::new().unwrap();
84        let out_dir = temp_dir.path().join("empty_output");
85        fs::create_dir(&out_dir).unwrap();
86
87        let config_file =
88            create_test_config_file(temp_dir.path(), &out_dir.to_string_lossy()).unwrap();
89
90        // This should succeed because verify() handles empty directories gracefully
91        let result = check(Some(&config_file));
92        assert!(result.is_ok());
93    }
94
95    #[test]
96    fn test_check_nonexistent_output_directory() {
97        let temp_dir = TempDir::new().unwrap();
98        let out_dir = temp_dir.path().join("nonexistent_output");
99
100        let config_file =
101            create_test_config_file(temp_dir.path(), &out_dir.to_string_lossy()).unwrap();
102
103        // verify() should handle nonexistent output directory gracefully
104        let result = check(Some(&config_file));
105        assert!(result.is_ok());
106    }
107}