help-probe 0.1.0

CLI tool discovery and automation framework that extracts structured information from command help text
Documentation
use help_probe::{
    ProbeConfig, discover_all_subcommands, discover_subcommand_hierarchy, probe_command,
};

/// Test that probe_command automatically adds help flag when none is present
#[tokio::test]
async fn test_automatic_help_flag_detection() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Test with a command that doesn't have a help flag
    // Should automatically add --help
    let result = probe_command("ls", &[], &config).await;

    assert!(result.is_ok());
    let result = result.unwrap();

    // Should have detected or added a help flag
    assert!(result.help_flag_detected);
    // Args should contain a help flag
    assert!(!result.args.is_empty());
    assert!(result.args.iter().any(|arg| {
        matches!(
            arg.as_str(),
            "--help" | "-h" | "help" | "--usage" | "-?" | "/?"
        )
    }));

    // Should have successfully parsed help output
    assert!(!result.raw_stdout.is_empty() || !result.raw_stderr.is_empty());
}

/// Test that probe_command preserves existing help flags
#[tokio::test]
async fn test_preserves_existing_help_flag() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Test with a command that already has a help flag
    let result = probe_command("ls", &["--help".to_string()], &config).await;

    assert!(result.is_ok());
    let result = result.unwrap();

    // Should have detected the help flag
    assert!(result.help_flag_detected);
    // Args should still contain --help
    assert!(result.args.contains(&"--help".to_string()));

    // Should have successfully parsed help output
    assert!(!result.raw_stdout.is_empty() || !result.raw_stderr.is_empty());
}

/// Test that probe_command works with different help flag formats
#[tokio::test]
async fn test_different_help_flag_formats() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Test with -h flag
    let result = probe_command("ls", &["-h".to_string()], &config).await;
    assert!(result.is_ok());
    let result = result.unwrap();
    assert!(result.help_flag_detected);
    assert!(result.args.contains(&"-h".to_string()));

    // Test with --usage flag (if supported)
    let result = probe_command("ls", &["--usage".to_string()], &config).await;
    assert!(result.is_ok());
    let result = result.unwrap();
    assert!(result.help_flag_detected);
}

/// Test that probe_command automatically finds working help flag
#[tokio::test]
async fn test_automatic_help_flag_selection() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Test with a command that supports --help
    // Should automatically try and find --help works
    let result = probe_command("cargo", &[], &config).await;

    assert!(result.is_ok());
    let result = result.unwrap();

    // Should have found a working help flag
    assert!(result.help_flag_detected);
    // Should have some output (help text)
    assert!(!result.raw_stdout.is_empty() || !result.raw_stderr.is_empty());

    // Should have parsed some information
    assert!(
        !result.usage_blocks.is_empty()
            || !result.options.is_empty()
            || !result.subcommands.is_empty()
    );
}

/// Test that probe_command handles commands with no help flag gracefully
#[tokio::test]
async fn test_fallback_to_default_help_flag() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Test with a command that might not support standard help flags
    // Should fall back to --help
    let result = probe_command("echo", &[], &config).await;

    // Even if echo doesn't have help, we should get a result
    // (echo will just echo nothing, but we should handle it)
    assert!(result.is_ok());
    let result = result.unwrap();

    // Args should have a help flag attempted
    assert!(!result.args.is_empty());
}

/// Test that probe_command works with commands that have additional args
#[tokio::test]
async fn test_automatic_help_flag_with_extra_args() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Test with a command that has other args but no help flag
    // Should append help flag to existing args
    let result = probe_command("git", &["status".to_string()], &config).await;

    // This might fail if git status doesn't work, but the important thing
    // is that we tried to add a help flag
    if let Ok(result) = result {
        // If it succeeded, should have help flag
        if result.help_flag_detected {
            assert!(!result.args.is_empty());
        }
    }
}

/// Test that probe_command correctly identifies help text even with non-zero exit codes
#[tokio::test]
async fn test_help_text_detection_with_nonzero_exit() {
    let config = ProbeConfig {
        timeout_secs: 3,
        require_help_flag: false,
        cache: None,
    };

    // Some commands return non-zero for help but still print it
    // Test that we can detect help text even in this case
    let result = probe_command("ls", &[], &config).await;

    assert!(result.is_ok());
    let result = result.unwrap();

    // Should have detected help flag or help text
    assert!(
        result.help_flag_detected || !result.raw_stdout.is_empty() || !result.raw_stderr.is_empty()
    );
}

/// Test timeout handling during help flag detection
#[tokio::test]
async fn test_help_flag_detection_timeout() {
    let config = ProbeConfig {
        timeout_secs: 1, // Short timeout
        require_help_flag: false,
        cache: None,
    };

    // Should still work with short timeout
    let result = probe_command("ls", &[], &config).await;

    // Should either succeed or timeout gracefully
    assert!(result.is_ok() || result.is_err());
}

/// Test discover_subcommand_hierarchy
#[tokio::test]
async fn test_discover_subcommand_hierarchy() {
    let config = ProbeConfig {
        timeout_secs: 5,
        require_help_flag: false,
        cache: None,
    };

    // Test with cargo which has subcommands
    let result = discover_subcommand_hierarchy("cargo", &config, 2).await;

    // Should succeed
    assert!(result.is_ok());
    let subcommands = result.unwrap();

    // Should have found some subcommands
    assert!(!subcommands.is_empty());
}

/// Test discover_all_subcommands
#[tokio::test]
async fn test_discover_all_subcommands() {
    let config = ProbeConfig {
        timeout_secs: 5,
        require_help_flag: false,
        cache: None,
    };

    // Test with cargo which has subcommands
    let result = discover_all_subcommands("cargo", &config, Some(2)).await;

    // Should succeed
    assert!(result.is_ok());
    let tree = result.unwrap();

    // Should have the command name
    assert_eq!(tree.command, "cargo");

    // Should have some subcommands or options
    assert!(!tree.subcommands.is_empty() || !tree.options.is_empty());
}