sidekick 0.2.0

Seamless integration between Claude Code and Neovim. Protects your workflow by safely coordinating AI edits with your unsaved work.
Documentation
//! Integration tests for hook processing

use sidekick::hook::{Hook, HookEvent, HookOutput, PermissionDecision, Tool, parse_hook};

#[test]
fn test_parse_pre_tool_use_edit_hook() {
    let json = r#"{
        "session_id": "test-session",
        "transcript_path": "/tmp/transcript",
        "cwd": "/test/dir",
        "hook_event_name": "PreToolUse",
        "tool_name": "Edit",
        "tool_input": {
            "file_path": "test.txt",
            "old_string": "old",
            "new_string": "new"
        }
    }"#;

    let hook = parse_hook(json).expect("Failed to parse hook");

    let Hook::Tool(h) = hook else {
        panic!("Expected Tool hook");
    };

    assert_eq!(h.session_id, "test-session");
    assert_eq!(h.cwd, "/test/dir");
    assert_eq!(h.hook_event_name, HookEvent::PreToolUse);

    match h.tool {
        Tool::Edit(input) => {
            assert_eq!(input.file_path, "test.txt");
            assert_eq!(input.old_string, Some("old".to_string()));
            assert_eq!(input.new_string, Some("new".to_string()));
        }
        _ => panic!("Expected Edit tool"),
    }
}

#[test]
fn test_parse_post_tool_use_write_hook() {
    let json = r#"{
        "session_id": "test-session",
        "transcript_path": "/tmp/transcript",
        "cwd": "/test/dir",
        "hook_event_name": "PostToolUse",
        "tool_name": "Write",
        "tool_input": {
            "file_path": "test.txt",
            "content": "file content"
        }
    }"#;

    let hook = parse_hook(json).expect("Failed to parse hook");

    let Hook::Tool(h) = hook else {
        panic!("Expected Tool hook");
    };

    assert_eq!(h.hook_event_name, HookEvent::PostToolUse);

    match h.tool {
        Tool::Write(input) => {
            assert_eq!(input.file_path, "test.txt");
            assert_eq!(input.content, Some("file content".to_string()));
        }
        _ => panic!("Expected Write tool"),
    }
}

#[test]
fn test_parse_bash_hook() {
    let json = r#"{
        "session_id": "test-session",
        "transcript_path": "/tmp/transcript",
        "cwd": "/test/dir",
        "hook_event_name": "PreToolUse",
        "tool_name": "Bash",
        "tool_input": {
            "command": "ls -la",
            "description": "List files"
        }
    }"#;

    let hook = parse_hook(json).expect("Failed to parse hook");

    let Hook::Tool(h) = hook else {
        panic!("Expected Tool hook");
    };

    match h.tool {
        Tool::Bash(input) => {
            assert_eq!(input.command, "ls -la");
            assert_eq!(input.description, "List files");
        }
        _ => panic!("Expected Bash tool"),
    }
}

#[test]
fn test_hook_output_allow() {
    let output = HookOutput::new();
    let json = output.to_json().expect("Failed to serialize");

    assert_eq!(json, "{}");
}

#[test]
fn test_hook_output_deny() {
    let output = HookOutput::new().with_permission_decision(
        PermissionDecision::Deny,
        Some("File has unsaved changes".to_string()),
    );

    let json = output.to_json().expect("Failed to serialize");

    assert!(json.contains("\"permissionDecision\":\"deny\""));
    assert!(json.contains("\"permissionDecisionReason\":\"File has unsaved changes\""));
    assert!(json.contains("\"hookEventName\":\"PreToolUse\""));
}

#[test]
fn test_hook_output_with_system_message() {
    let output = HookOutput::new().with_system_message("Test message");

    let json = output.to_json().expect("Failed to serialize");

    assert!(json.contains("\"systemMessage\":\"Test message\""));
}

#[test]
fn test_parse_multiedit_hook() {
    let json = r#"{
        "session_id": "test-session",
        "transcript_path": "/tmp/transcript",
        "cwd": "/test/dir",
        "hook_event_name": "PreToolUse",
        "tool_name": "MultiEdit",
        "tool_input": {
            "file_path": "test.txt"
        }
    }"#;

    let hook = parse_hook(json).expect("Failed to parse hook");

    let Hook::Tool(h) = hook else {
        panic!("Expected Tool hook");
    };

    match h.tool {
        Tool::MultiEdit(input) => {
            assert_eq!(input.file_path, "test.txt");
        }
        _ => panic!("Expected MultiEdit tool"),
    }
}

#[test]
fn test_parse_user_prompt_submit_hook() {
    let json = r#"{
        "session_id": "test-session",
        "transcript_path": "/tmp/transcript",
        "cwd": "/test/dir",
        "hook_event_name": "UserPromptSubmit",
        "prompt": "test prompt"
    }"#;

    let hook = parse_hook(json).expect("Failed to parse hook");

    assert!(matches!(hook, Hook::UserPrompt));
}

#[test]
fn test_hook_output_with_additional_context() {
    let output = HookOutput::new().with_additional_context("Selected code here");

    let json = output.to_json().expect("Failed to serialize");

    assert!(json.contains("\"additionalContext\":\"Selected code here\""));
    assert!(json.contains("\"hookEventName\":\"UserPromptSubmit\""));
}