vibe-tests 0.0.1

Integration test framework for MCP servers with LLM-powered tool calling.
Documentation
//! MCP runner.
//! Spawns MCP server and waits for health check.

use std::time::Duration;

use tokio::net::TcpStream;

use crate::base::error::{TestError, TestsResult};

/// Spawns MCP server commands and waits for healthy response.
pub struct Runner {
    /// MCP server host URL for health check.
    host: String,
    /// Timeout for health check polling.
    timeout: Duration,
}

impl Runner {
    /// Creates a new Runner with MCP host and timeout.
    pub fn new(host: impl Into<String>, timeout: Duration) -> Self {
        Self {
            host: host.into(),
            timeout,
        }
    }

    /// Polls MCP host until TCP connection succeeds or timeout expires.
    pub async fn wait_healthy(&self) -> TestsResult<()> {
        let addr = self.addr()?;
        let start = std::time::Instant::now();
        loop {
            if start.elapsed() > self.timeout {
                return Err(TestError::Timeout(format!("MCP not ready at {}", addr)));
            }
            if TcpStream::connect(&addr).await.is_ok() {
                return Ok(());
            }
            tokio::time::sleep(Duration::from_millis(500)).await;
        }
    }

    /// Wait for MCP server to stop responding (sync, single check, 1s timeout).
    pub fn wait_dead(&self) -> TestsResult<()> {
        let addr = self.addr()?;
        let start = std::time::Instant::now();
        loop {
            if start.elapsed() > Duration::from_secs(1) {
                return Err(TestError::Timeout(format!(
                    "MCP server at {} did not stop after 1s",
                    addr
                )));
            }
            if std::net::TcpStream::connect(&addr).is_err() {
                return Ok(());
            }
            std::thread::sleep(Duration::from_millis(50));
        }
    }

    /// Extract host:port from mcp_host URL.
    fn addr(&self) -> TestsResult<String> {
        self.host
            .trim_start_matches("http://")
            .trim_start_matches("https://")
            .split('/')
            .next()
            .map(String::from)
            .ok_or_else(|| TestError::Setup(format!("Invalid mcp_host: {}", self.host)))
    }
}