rumdl 0.2.21

A fast Markdown linter written in Rust (Ru(st) MarkDown Linter)
Documentation
/// Comprehensive tests for Windows VS Code detection issue #22
/// These tests simulate the exact failure scenarios that were occurring
/// and verify that our fix handles them correctly.
#[cfg(test)]
mod comprehensive_windows_tests {
    use std::env;
    use std::fs;
    use std::process::Command;
    use tempfile::TempDir;

    /// Test that reproduces the exact Windows issue scenario:
    /// - Command exists and `cmd --version` works
    /// - But `which cmd` or `where cmd` fails or gives wrong result
    /// - Our fix should still detect the command correctly
    #[test]
    fn test_reproduce_windows_issue_scenario() {
        // This test simulates the exact problem reported in issue #22

        // Create a temporary directory and fake VS Code executable
        let temp_dir = TempDir::new().expect("Could not create temp dir");
        let fake_vscode_path = temp_dir.path().join(if cfg!(windows) { "code.exe" } else { "code" });

        // Create a fake VS Code that responds to --version
        let script_content = if cfg!(windows) {
            "@echo off\nif \"%1\"==\"--version\" echo 1.85.0\n"
        } else {
            "#!/bin/bash\nif [ \"$1\" = \"--version\" ]; then echo \"1.85.0\"; fi\n"
        };

        fs::write(&fake_vscode_path, script_content).expect("Could not write fake VS Code");

        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perms = fs::metadata(&fake_vscode_path).unwrap().permissions();
            perms.set_mode(0o755);
            fs::set_permissions(&fake_vscode_path, perms).unwrap();
        }

        // Test 1: Direct execution should work
        let direct_result = Command::new(&fake_vscode_path).arg("--version").output();

        match direct_result {
            Ok(output) if output.status.success() => {
                println!("✓ Direct execution works: {}", String::from_utf8_lossy(&output.stdout));
            }
            Ok(output) => {
                println!("Direct execution failed with status: {}", output.status);
                return; // Skip the rest if we can't even create a working fake command
            }
            Err(e) => {
                println!("Could not execute fake command: {e}");
                return;
            }
        }

        // Test 2: which/where will likely fail since it's not in PATH
        let lookup_cmd = if cfg!(windows) { "where" } else { "which" };
        let lookup_result = Command::new(lookup_cmd)
            .arg("code")  // Look for the real 'code' command, not our fake one
            .output();

        println!(
            "Lookup command '{lookup_cmd} code' result: {}",
            lookup_result.map(|o| o.status.success()).unwrap_or(false)
        );

        // Test 3: Verify our new command_exists logic would work
        // This simulates what VsCodeExtension::command_exists does
        let would_find_real_code = simulate_new_command_exists("code");
        let would_find_fake_code = simulate_new_command_exists(fake_vscode_path.to_str().unwrap());

        println!("Real 'code' command detection: {would_find_real_code}");
        println!("Fake 'code' command detection: {would_find_fake_code}");

        // The test passes if our logic can find at least one working command - see output for details
    }

    /// Simulate our new command_exists logic without importing the actual function
    fn simulate_new_command_exists(cmd: &str) -> bool {
        // Primary approach: try direct execution
        if let Ok(output) = Command::new(cmd).arg("--version").output()
            && output.status.success()
        {
            return true;
        }

        // Fallback: platform-appropriate lookup
        let lookup_cmd = if cfg!(windows) { "where" } else { "which" };
        Command::new(lookup_cmd)
            .arg(cmd)
            .output()
            .map(|output| output.status.success())
            .unwrap_or(false)
    }

    /// Test PATH manipulation scenarios that could cause the original issue
    #[test]
    fn test_path_manipulation_scenarios() {
        // Save original PATH
        let original_path = env::var("PATH").unwrap_or_default();

        // Create a temporary executable in a custom directory. Use a real working
        // binary (the rumdl test binary, which responds to `--version`) rather than a
        // shell/batch script: on Windows a `.exe` containing batch text is not a valid
        // executable, so direct execution of it would always fail.
        let temp_dir = TempDir::new().expect("Could not create temp dir");
        let custom_bin_dir = temp_dir.path().join("custom_bin");
        fs::create_dir_all(&custom_bin_dir).expect("Could not create custom bin dir");

        let fake_cmd_path = custom_bin_dir.join(if cfg!(windows) { "testcmd.exe" } else { "testcmd" });
        fs::copy(env!("CARGO_BIN_EXE_rumdl"), &fake_cmd_path).expect("Could not copy test binary");

        // Test scenario 1: Command in PATH, both approaches should work
        let new_path = format!(
            "{}{}{}",
            custom_bin_dir.display(),
            if cfg!(windows) { ";" } else { ":" },
            original_path
        );
        unsafe {
            env::set_var("PATH", &new_path);
        }

        let direct_works_1 = simulate_new_command_exists("testcmd");
        println!("With command in PATH - direct approach: {direct_works_1}");

        // Test scenario 2: Simulate broken PATH for lookup but direct execution still works
        // (This is tricky to test reliably, but we can at least test that direct execution works)
        let direct_absolute_works = simulate_new_command_exists(fake_cmd_path.to_str().unwrap());
        println!("Direct absolute path approach: {direct_absolute_works}");

        // Restore original PATH
        unsafe {
            env::set_var("PATH", original_path);
        }

        // The key test: direct execution of absolute paths should work even when PATH is broken
        assert!(direct_absolute_works, "Direct execution with absolute path should work");
    }

    /// Test Windows-specific command scenarios
    #[test]
    fn test_windows_specific_command_scenarios() {
        if !cfg!(windows) {
            println!("Skipping Windows-specific test on non-Windows platform");
            return;
        }

        // Test common Windows commands that might behave differently
        let windows_commands = [
            "cmd",        // Windows command prompt
            "where",      // Windows equivalent of 'which'
            "powershell", // PowerShell (if available)
        ];

        for cmd in &windows_commands {
            let direct_result = Command::new(cmd)
                .arg("/?")  // Windows help flag
                .output();

            let version_result = Command::new(cmd).arg("--version").output();

            let where_result = Command::new("where").arg(cmd).output();

            println!("Command '{cmd}':");
            println!("  Help (/?) works: {}", direct_result.is_ok());
            println!("  Version (--version) works: {}", version_result.is_ok());
            println!(
                "  Found by 'where': {}",
                where_result.map(|o| o.status.success()).unwrap_or(false)
            );
        }

        // This test always passes - it's diagnostic
    }

    /// Test command extension handling on Windows
    #[test]
    fn test_windows_command_extensions() {
        if !cfg!(windows) {
            println!("Skipping Windows extension test on non-Windows platform");
            return;
        }

        // Create test files with different Windows executable extensions
        let temp_dir = TempDir::new().expect("Could not create temp dir");
        let extensions = ["exe", "bat", "cmd"];

        for ext in &extensions {
            let cmd_path = temp_dir.path().join(format!("testcmd.{ext}"));

            let content = match *ext {
                "exe" => {
                    // Can't easily create a real .exe, skip this one
                    continue;
                }
                "bat" | "cmd" => "@echo off\nif \"%1\"==\"--version\" echo test-version-1.0\n",
                _ => continue,
            };

            fs::write(&cmd_path, content).expect("Could not write test file");

            // Test that our approach can handle different extensions
            let works = simulate_new_command_exists(cmd_path.to_str().unwrap());
            println!("Command with .{ext} extension works: {works}");
        }

        // Windows extension test completed
    }

    /// Integration test that verifies our fix handles the exact VS Code scenario
    #[test]
    fn test_vscode_integration_scenario() {
        use rumdl_lib::vscode::VsCodeExtension;

        // Test the actual VsCodeExtension::new() behavior
        match VsCodeExtension::new() {
            Ok(extension) => {
                println!("✓ VS Code extension creation succeeded - VS Code is available");

                // Try to check if it's actually installed
                match extension.is_installed() {
                    Ok(installed) => {
                        println!("  Extension installed: {installed}");
                    }
                    Err(e) => {
                        println!("  Could not check if extension is installed: {e}");
                    }
                }
            }
            Err(e) => {
                println!("VS Code not found: {e}");
                // This is expected in most CI environments
                assert!(
                    e.contains("VS Code (or compatible editor) not found"),
                    "Error message should indicate VS Code not found"
                );
            }
        }

        // Test find_all_editors to see what's available
        let available_editors = VsCodeExtension::find_all_editors();
        println!("Available editors found: {}", available_editors.len());
        for (cmd, name) in &available_editors {
            println!("  - {name} (command: {cmd})");
        }

        // Test passes regardless - this is diagnostic
    }
}