r2x-ast 0.0.2

AST discovery utilities for the r2x CLI
Documentation
use super::*;
use std::collections::HashMap;
use std::fs;
use tempfile::TempDir;

#[test]
fn test_infer_argument_type_string() {
    let extractor = PluginExtractor {
        python_file_path: PathBuf::from("test.py"),
        package_root: PathBuf::from("."),
        package_prefix: "test".to_string(),
        content: "def register_plugin(): pass".to_string(),
        import_map: HashMap::new(),
        current_module: "test.module".to_string(),
    };

    assert_eq!(extractor.infer_argument_type(r#""hello""#), "string");
    assert_eq!(extractor.infer_argument_type("'hello'"), "string");
}

#[test]
fn test_infer_argument_type_number() {
    let extractor = PluginExtractor {
        python_file_path: PathBuf::from("test.py"),
        package_root: PathBuf::from("."),
        package_prefix: "test".to_string(),
        content: "def register_plugin(): pass".to_string(),
        import_map: HashMap::new(),
        current_module: "test.module".to_string(),
    };

    assert_eq!(extractor.infer_argument_type("42"), "number");
    assert_eq!(extractor.infer_argument_type("3.14"), "float");
}

#[test]
fn test_infer_argument_type_enum() {
    let extractor = PluginExtractor {
        python_file_path: PathBuf::from("test.py"),
        package_root: PathBuf::from("."),
        package_prefix: "test".to_string(),
        content: "def register_plugin(): pass".to_string(),
        import_map: HashMap::new(),
        current_module: "test.module".to_string(),
    };

    assert_eq!(extractor.infer_argument_type("IOType.STDOUT"), "enum_value");
}

#[test]
fn test_infer_argument_type_class() {
    let extractor = PluginExtractor {
        python_file_path: PathBuf::from("test.py"),
        package_root: PathBuf::from("."),
        package_prefix: "test".to_string(),
        content: "def register_plugin(): pass".to_string(),
        import_map: HashMap::new(),
        current_module: "test.module".to_string(),
    };

    assert_eq!(
        extractor.infer_argument_type("ReEDSParser"),
        "class_reference"
    );
    assert_eq!(extractor.infer_argument_type("MyClass"), "class_reference");
}

#[test]
fn test_extract_plugins_from_package_constructor_style() -> Result<()> {
    let content = r#"
from r2x_core import Package, ParserPlugin, UpgraderPlugin, BasePlugin

class ReEDSConfig: ...

class ReEDSParser:
    def __init__(self, config, path: str, data_store=None):
        pass

class ReEDSUpgrader:
    def __init__(self, path: str, steps=None):
        pass

def add_pcm_defaults(system): ...

def register_plugin() -> Package:
    return Package(
        name="r2x-reeds",
        plugins=[
            ParserPlugin(
                name="reeds-parser",
                obj=ReEDSParser,
                call_method="build_system",
                config=ReEDSConfig,
            ),
            UpgraderPlugin(
                name="reeds-upgrader",
                obj=ReEDSUpgrader,
            ),
            BasePlugin(
                name="add-pcm-defaults",
                obj=add_pcm_defaults,
            ),
        ],
    )
"#;

    let temp_dir = TempDir::new()?;
    let pkg_root = temp_dir.path().join("r2x_reeds");
    fs::create_dir_all(&pkg_root)?;
    let plugin_file = pkg_root.join("plugin.py");
    fs::write(&plugin_file, content)?;

    let extractor = PluginExtractor::new(
        plugin_file,
        "r2x_reeds.plugin".to_string(),
        pkg_root.clone(),
    )?;
    let plugins = extractor.extract_plugins()?;

    assert_eq!(plugins.len(), 3);
    assert_eq!(plugins[0].name, "reeds-parser");
    assert_eq!(plugins[0].entry, "r2x_reeds.plugin.ReEDSParser");
    assert_eq!(plugins[0].kind, PluginKind::Parser);
    assert_eq!(
        plugins[0].invocation.method.as_deref(),
        Some("build_system")
    );
    assert!(!plugins[0].invocation.constructor.is_empty());
    assert_eq!(plugins[1].kind, PluginKind::Upgrader);
    assert_eq!(
        plugins[2].invocation.implementation,
        ImplementationType::Function
    );

    Ok(())
}

#[test]
fn test_extract_plugins_from_manifest_add_style() -> Result<()> {
    let content = r#"
from r2x_core import PluginManifest, PluginSpec

class DemoParser:
    def __init__(self, config, json_path: str):
        self.config = config

manifest = PluginManifest(package="demo")

manifest.add(PluginSpec.parser(name="demo.parser", entry=DemoParser))
"#;

    let temp_dir = TempDir::new()?;
    let pkg_root = temp_dir.path().join("demo");
    fs::create_dir_all(&pkg_root)?;
    let plugin_file = pkg_root.join("plugin.py");
    fs::write(&plugin_file, content)?;

    let extractor =
        PluginExtractor::new(plugin_file, "demo.plugin".to_string(), pkg_root.clone())?;
    let plugins = extractor.extract_plugins()?;

    assert_eq!(plugins.len(), 1);
    assert_eq!(plugins[0].name, "demo.parser");
    assert_eq!(plugins[0].entry, "demo.plugin.DemoParser");
    assert_eq!(plugins[0].kind, PluginKind::Parser);
    assert_eq!(plugins[0].invocation.implementation, ImplementationType::Class);
    assert!(!plugins[0].invocation.constructor.is_empty());

    Ok(())
}