scryer-engine 0.3.0

Tree-sitter AST indexing and reference resolution engine for Scryer code intelligence
use std::path::Path;

use scryer_engine::dependency::{
    CargoDependencyProvider, DependencyProvider, DependencySourceType,
};

#[tokio::test]
async fn test_discover_dependencies_scryer_workspace() -> anyhow::Result<()> {
    let provider = CargoDependencyProvider::new();
    let root = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    let deps = provider.discover_dependencies(root).await?;
    assert!(!deps.is_empty(), "Should discover workspace dependencies");

    // Workspace crates must not be classified as external dependencies
    let workspace_crates = ["scryer-engine", "scryer-db", "scryer-mcp", "scryer-cli"];
    for ws_crate in workspace_crates {
        assert!(
            deps.iter().all(|d| d.name != ws_crate),
            "Workspace member {ws_crate} should not be in external dependencies"
        );
    }

    // Direct dependencies of scryer-mcp workspace should be marked is_direct = true
    let direct_names = ["toasty", "tokio", "anyhow", "serde"];
    for name in direct_names {
        let found = deps.iter().find(|d| d.name == name);
        assert!(
            found.is_some(),
            "Expected direct dependency '{name}' to be discovered"
        );
        let dep = found.unwrap();
        assert!(
            dep.is_direct,
            "Dependency '{name}' should be classified as direct"
        );
        assert_eq!(
            dep.source_type,
            DependencySourceType::CratesIo,
            "Dependency '{name}' should have CratesIo source type"
        );
        assert!(
            dep.package_hash.is_some(),
            "Dependency '{name}' should have package checksum resolved from Cargo.lock"
        );
        assert!(
            dep.root_path.exists(),
            "Dependency '{name}' root_path must exist on disk: {}",
            dep.root_path.display()
        );

        let resolved_dir = provider.resolve_source_directory(dep)?;
        assert!(resolved_dir.exists());
    }

    // Transitive dependencies should have is_direct = false
    // E.g., proc-macro2 or quote or syn or libc
    let transitive_names = ["libc", "bitflags", "proc-macro2"];
    for name in transitive_names {
        if let Some(dep) = deps.iter().find(|d| d.name == name) {
            // If none of our workspace crates declared it directly, is_direct should be false
            // Note: If anything in workspace declared it directly, it could be true; let's check
            println!(
                "Transitive dep check for {}: is_direct={}",
                dep.name, dep.is_direct
            );
        }
    }

    Ok(())
}

#[tokio::test]
async fn test_discover_dependencies_non_existent_path_fails() {
    let provider = CargoDependencyProvider::new();
    let non_existent = Path::new("/path/that/does/not/exist/at/all");

    let result = provider.discover_dependencies(non_existent).await;
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(
        err_msg.contains("Cargo manifest not found"),
        "Expected error message indicating manifest not found, got: {err_msg}"
    );
}

#[tokio::test]
async fn test_resolve_source_directory_error_for_invalid_path() {
    let provider = CargoDependencyProvider::new();
    let invalid_dep = scryer_engine::dependency::DiscoveredDependency {
        name: "ghost-crate".to_string(),
        version: "0.1.0".to_string(),
        source_type: DependencySourceType::CratesIo,
        manifest_path: std::path::PathBuf::from("/nonexistent/Cargo.toml"),
        root_path: std::path::PathBuf::from("/nonexistent"),
        package_hash: None,
        is_direct: true,
        features: vec![],
    };

    let result = provider.resolve_source_directory(&invalid_dep);
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("does not exist"));
}