browsing 0.1.4

Lightweight MCP/API for browser automation: navigate, get content (text), screenshot. Parallelism via RwLock.
Documentation
//! File operation integration tests

use browsing::tools::handlers::FileHandler;
use browsing::tools::handlers::Handler;
use browsing::tools::views::{ActionContext, ActionParams};
use std::collections::HashMap;

#[tokio::test]
async fn test_write_file_creates_file_with_content() {
    let temp_dir = tempfile::tempdir().unwrap();
    let file_path = temp_dir.path().join("test_write.txt");
    let file_path_str = file_path.to_str().unwrap();

    let mut params = HashMap::new();
    params.insert("path".to_string(), serde_json::json!(file_path_str));
    params.insert("content".to_string(), serde_json::json!("Hello, file operations!"));

    let action_params = ActionParams::new(&params).with_action_type("write_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await.unwrap();
    assert!(result.extracted_content.is_some());
    assert!(result
        .extracted_content
        .unwrap()
        .contains("Successfully wrote"));

    // Verify file exists and has correct content
    let read_content = std::fs::read_to_string(&file_path).unwrap();
    assert_eq!(read_content, "Hello, file operations!");
}

#[tokio::test]
async fn test_read_file_reads_existing_file() {
    let temp_dir = tempfile::tempdir().unwrap();
    let file_path = temp_dir.path().join("test_read.txt");
    let file_path_str = file_path.to_str().unwrap();

    // Pre-write file
    std::fs::write(&file_path, "Pre-existing content").unwrap();

    let mut params = HashMap::new();
    params.insert("path".to_string(), serde_json::json!(file_path_str));

    let action_params = ActionParams::new(&params).with_action_type("read_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await.unwrap();
    assert_eq!(
        result.extracted_content,
        Some("Pre-existing content".to_string())
    );
}

#[tokio::test]
async fn test_write_file_rejects_path_traversal() {
    let mut params = HashMap::new();
    params.insert("path".to_string(), serde_json::json!("../etc/passwd"));
    params.insert("content".to_string(), serde_json::json!("malicious"));

    let action_params = ActionParams::new(&params).with_action_type("write_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await;
    assert!(result.is_err());
    let err_msg = format!("{}", result.unwrap_err());
    assert!(err_msg.contains("path traversal"));
}

#[tokio::test]
async fn test_read_file_rejects_nonexistent_file() {
    let mut params = HashMap::new();
    params.insert(
        "path".to_string(),
        serde_json::json!("/tmp/nonexistent_file_12345.txt"),
    );

    let action_params = ActionParams::new(&params).with_action_type("read_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await;
    assert!(result.is_err());
    let err_msg = format!("{}", result.unwrap_err());
    assert!(err_msg.contains("does not exist"));
}

#[tokio::test]
async fn test_write_file_creates_parent_directories() {
    let temp_dir = tempfile::tempdir().unwrap();
    let nested_path = temp_dir.path().join("a/b/c/nested.txt");
    let nested_path_str = nested_path.to_str().unwrap();

    let mut params = HashMap::new();
    params.insert("path".to_string(), serde_json::json!(nested_path_str));
    params.insert("content".to_string(), serde_json::json!("nested content"));

    let action_params = ActionParams::new(&params).with_action_type("write_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await;
    assert!(result.is_ok());
    assert!(nested_path.exists());

    let content = std::fs::read_to_string(&nested_path).unwrap();
    assert_eq!(content, "nested content");
}

#[tokio::test]
async fn test_read_file_rejects_directory() {
    let temp_dir = tempfile::tempdir().unwrap();
    let dir_path = temp_dir.path().to_str().unwrap();

    let mut params = HashMap::new();
    params.insert("path".to_string(), serde_json::json!(dir_path));

    let action_params = ActionParams::new(&params).with_action_type("read_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await;
    assert!(result.is_err());
    let err_msg = format!("{}", result.unwrap_err());
    assert!(err_msg.contains("not a file"));
}

#[tokio::test]
async fn test_replace_file_replaces_text() {
    let temp_dir = tempfile::tempdir().unwrap();
    let file_path = temp_dir.path().join("replace_test.txt");
    std::fs::write(&file_path, "Hello old world old").unwrap();

    let mut params = HashMap::new();
    params.insert(
        "path".to_string(),
        serde_json::json!(file_path.to_str().unwrap()),
    );
    params.insert("old_text".to_string(), serde_json::json!("old"));
    params.insert("new_text".to_string(), serde_json::json!("new"));

    let action_params =
        ActionParams::new(&params).with_action_type("replace_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await.unwrap();
    assert!(result.extracted_content.unwrap().contains("2 occurrence"));

    let content = std::fs::read_to_string(&file_path).unwrap();
    assert_eq!(content, "Hello new world new");
}

#[tokio::test]
async fn test_replace_file_rejects_nonexistent_file() {
    let mut params = HashMap::new();
    params.insert(
        "path".to_string(),
        serde_json::json!("/tmp/nonexistent_replace.txt"),
    );
    params.insert("old_text".to_string(), serde_json::json!("foo"));
    params.insert("new_text".to_string(), serde_json::json!("bar"));

    let action_params =
        ActionParams::new(&params).with_action_type("replace_file".to_string());
    let mut context = ActionContext {
        browser: &mut browsing::browser::Browser::new(
            browsing::browser::BrowserProfile::default(),
        ),
        selector_map: None,
    };

    let result = FileHandler.handle(&action_params, &mut context).await;
    assert!(result.is_err());
    let err_msg = format!("{}", result.unwrap_err());
    assert!(err_msg.contains("does not exist"));
}