malvin 0.2.5

Non-interactive research and coding agent
use std::path::PathBuf;
use std::process::Stdio;

use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, Command};

fn mock_bridge_js() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/cursor_sdk/mock_bridge.js")
}

async fn spawn_mock() -> (
    Child,
    tokio::process::ChildStdin,
    BufReader<tokio::process::ChildStdout>,
) {
    let node = super::node_resolve::resolve_node_bin().expect("modern node for mock bridge");
    let mut child = Command::new(node)
        .arg(mock_bridge_js())
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn mock");
    let stdin = child.stdin.take().expect("stdin");
    let stdout = BufReader::new(child.stdout.take().expect("stdout"));
    (child, stdin, stdout)
}

async fn cursor_mock_write_line(stdin: &mut tokio::process::ChildStdin, line: &str) {
    stdin.write_all(line.as_bytes()).await.unwrap();
    stdin.flush().await.unwrap();
}

async fn read_until(reader: &mut BufReader<tokio::process::ChildStdout>, needle: &str) -> String {
    let mut line = String::new();
    for _ in 0..8 {
        line.clear();
        let n = reader.read_line(&mut line).await.unwrap();
        if n == 0 || line.contains(needle) {
            break;
        }
    }
    line
}

#[tokio::test]
async fn mock_bridge_create_send_close() {
    assert!(mock_bridge_js().is_file(), "mock_bridge.js missing");
    let (mut child, mut stdin, mut reader) = spawn_mock().await;
    cursor_mock_write_line(
        &mut stdin,
        "{\"op\":\"create\",\"cwd\":\"/tmp\",\"model\":\"auto\",\"apiKey\":\"k\"}\n",
    )
    .await;
    let ok = read_until(&mut reader, "\"ok\"").await;
    assert!(ok.contains("\"event\":\"ok\""), "{ok}");
    cursor_mock_write_line(&mut stdin, "{\"op\":\"send\",\"prompt\":\"hello\"}\n").await;
    let done = read_until(&mut reader, "run_done").await;
    assert!(done.contains("inputTokens"), "{done}");
    cursor_mock_write_line(&mut stdin, "{\"op\":\"close\"}\n").await;
    let _ = child.wait().await;
}