help-probe 0.1.0

CLI tool discovery and automation framework that extracts structured information from command help text
Documentation
use help_probe::cache::{CacheConfig, generate_cache_key, read_cache, write_cache};
use help_probe::model::ProbeResult;
use tempfile::TempDir;

#[test]
fn test_generate_cache_key() {
    let key1 = generate_cache_key("ls", &["--help".to_string()]);
    let key2 = generate_cache_key("ls", &["--help".to_string()]);
    let key3 = generate_cache_key("ls", &["-h".to_string()]);

    assert_eq!(key1, key2); // Same command + args = same key
    assert_ne!(key1, key3); // Different args = different key
}

#[test]
fn test_cache_read_write() {
    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: true,
        max_age_secs: None,
    };

    let result = ProbeResult {
        command: "test-cmd".to_string(),
        args: vec!["--help".to_string()],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "test output".to_string(),
        raw_stderr: String::new(),
    };

    // Write cache
    write_cache("test-cmd", &["--help".to_string()], &result, &config).unwrap();

    // Read cache
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_some());
    let cached_result = cached.unwrap();
    assert_eq!(cached_result.command, "test-cmd");
    assert_eq!(cached_result.raw_stdout, "test output");
}

#[test]
fn test_cache_miss() {
    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: true,
        max_age_secs: None,
    };

    // Try to read non-existent cache
    let cached = read_cache("nonexistent", &[], &config).unwrap();
    assert!(cached.is_none());
}

#[test]
fn test_cache_disabled() {
    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: false,
        max_age_secs: None,
    };

    let result = ProbeResult {
        command: "test-cmd".to_string(),
        args: vec![],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: String::new(),
        raw_stderr: String::new(),
    };

    // Write should be no-op
    write_cache("test-cmd", &[], &result, &config).unwrap();

    // Read should return None
    let cached = read_cache("test-cmd", &[], &config).unwrap();
    assert!(cached.is_none());
}

#[test]
fn test_default_cache_dir() {
    use help_probe::cache::default_cache_dir;

    let dir = default_cache_dir();
    assert!(dir.to_string_lossy().contains("cache"));
    assert!(dir.to_string_lossy().contains("help-probe"));
}

#[test]
fn test_cache_expiration() {
    use std::time::Duration;

    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: true,
        max_age_secs: Some(1), // 1 second expiration
    };

    let result = ProbeResult {
        command: "test-cmd".to_string(),
        args: vec!["--help".to_string()],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "test output".to_string(),
        raw_stderr: String::new(),
    };

    // Write cache
    write_cache("test-cmd", &["--help".to_string()], &result, &config).unwrap();

    // Read immediately - should work
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_some());

    // Wait for expiration
    std::thread::sleep(Duration::from_secs(2));

    // Read after expiration - should return None
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_none());
}

#[test]
fn test_cache_version_change() {
    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: true,
        max_age_secs: None,
    };

    let result1 = ProbeResult {
        command: "test-cmd".to_string(),
        args: vec!["--help".to_string()],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "version 1".to_string(),
        raw_stderr: String::new(),
    };

    // Write cache
    write_cache("test-cmd", &["--help".to_string()], &result1, &config).unwrap();

    // Read - should work
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_some());

    // Write different version (different help text)
    let result2 = ProbeResult {
        command: "test-cmd".to_string(),
        args: vec!["--help".to_string()],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "version 2 - different".to_string(),
        raw_stderr: String::new(),
    };

    write_cache("test-cmd", &["--help".to_string()], &result2, &config).unwrap();

    // Read should return new version
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_some());
    assert_eq!(cached.unwrap().raw_stdout, "version 2 - different");
}

#[test]
fn test_clear_cache() {
    use help_probe::cache::clear_cache;

    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: true,
        max_age_secs: None,
    };

    let result = ProbeResult {
        command: "test-cmd".to_string(),
        args: vec!["--help".to_string()],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "test output".to_string(),
        raw_stderr: String::new(),
    };

    // Write cache
    write_cache("test-cmd", &["--help".to_string()], &result, &config).unwrap();

    // Verify it exists
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_some());

    // Clear cache
    let cleared = clear_cache(Some("test-cmd"), Some(&["--help".to_string()]), &config).unwrap();
    assert_eq!(cleared, 1);

    // Verify it's gone
    let cached = read_cache("test-cmd", &["--help".to_string()], &config).unwrap();
    assert!(cached.is_none());
}

#[test]
fn test_clear_all_cache() {
    use help_probe::cache::clear_cache;

    let temp_dir = TempDir::new().unwrap();
    let config = CacheConfig {
        cache_dir: temp_dir.path().to_path_buf(),
        enabled: true,
        max_age_secs: None,
    };

    let result1 = ProbeResult {
        command: "cmd1".to_string(),
        args: vec![],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "output1".to_string(),
        raw_stderr: String::new(),
    };

    let result2 = ProbeResult {
        command: "cmd2".to_string(),
        args: vec![],
        exit_code: Some(0),
        timed_out: false,
        help_flag_detected: true,
        usage_blocks: vec![],
        options: vec![],
        subcommands: vec![],
        arguments: vec![],
        examples: vec![],
        environment_variables: vec![],
        validation_rules: vec![],
        raw_stdout: "output2".to_string(),
        raw_stderr: String::new(),
    };

    // Write multiple caches
    write_cache("cmd1", &[], &result1, &config).unwrap();
    write_cache("cmd2", &[], &result2, &config).unwrap();

    // Clear all cache
    let cleared = clear_cache(None, None, &config).unwrap();
    assert!(cleared >= 2);

    // Verify both are gone
    let cached1 = read_cache("cmd1", &[], &config).unwrap();
    let cached2 = read_cache("cmd2", &[], &config).unwrap();
    assert!(cached1.is_none());
    assert!(cached2.is_none());
}