codewhale-tui 0.9.8

Terminal UI for open-source and open-weight coding models
Documentation
use super::*;
use crate::tools::spec::{ToolExecutionOutcome, ToolTerminalStatus};

#[tokio::test]
async fn missing_binary_maps_to_bounded_failed_machine_contract() {
    let temporary = tempfile::tempdir().expect("tempdir");
    let missing = temporary.path().join("definitely-not-pdftotext");
    let input = temporary.path().join("input.pdf");
    std::fs::write(&input, b"%PDF-1.7\n%%EOF").expect("fixture");
    let error = extract_path(
        &input,
        None,
        PdfTextCommand::test(missing.as_os_str(), Duration::from_secs(1), None),
    )
    .await
    .expect_err("missing binary");
    assert_eq!(error, PdfTextError::BinaryUnavailable);

    let error = into_tool_error(error);
    let payload = match &error {
        ToolError::NotAvailable { message } => {
            assert!(message.len() < 512, "{message}");
            serde_json::from_str::<serde_json::Value>(message).expect("JSON failure payload")
        }
        other => panic!("unexpected mapped error: {other:?}"),
    };
    assert_eq!(payload["type"], "binary_unavailable");
    assert_eq!(payload["binary"], "pdftotext");
    assert_eq!(
        ToolExecutionOutcome::from_legacy(Err(error)).status,
        ToolTerminalStatus::Failed
    );
}

#[cfg(unix)]
fn executable_script(contents: &str) -> (tempfile::TempDir, std::path::PathBuf) {
    use std::os::unix::fs::PermissionsExt;

    let temporary = tempfile::tempdir().expect("tempdir");
    let binary = temporary.path().join("fake-pdftotext");
    std::fs::write(&binary, contents).expect("fake binary");
    let mut permissions = std::fs::metadata(&binary).expect("metadata").permissions();
    permissions.set_mode(0o700);
    std::fs::set_permissions(&binary, permissions).expect("executable");
    (temporary, binary)
}

#[cfg(unix)]
#[tokio::test]
async fn shared_adapter_forwards_page_window_and_returns_stdout() {
    let (temporary, binary) = executable_script(
        "#!/bin/sh\nprintf 'args:%s\\n' \"$*\"\nprintf 'page one\\fpage two\\n'\n",
    );
    let request = PdfTextCommand::test(binary.as_os_str(), Duration::from_secs(1), None);
    let input = temporary.path().join("input with spaces.pdf");
    std::fs::write(&input, b"fixture bytes").expect("fixture");
    let text = extract_path(&input, Some((2, 4)), request)
        .await
        .expect("fake extraction");
    assert!(text.contains("-layout -f 2 -l 4"), "{text}");
    assert!(text.contains(input.to_string_lossy().as_ref()), "{text}");
    assert!(text.ends_with("page one\u{c}page two\n"), "{text:?}");

    let staged = extract_bytes(b"fetched fixture bytes", request)
        .await
        .expect("fake fetched extraction");
    assert!(staged.contains("-layout"), "{staged}");
    assert!(staged.ends_with("page one\u{c}page two\n"), "{staged:?}");
}

#[cfg(unix)]
#[tokio::test]
async fn child_execution_is_timeout_and_cancellation_bounded() {
    let (temporary, binary) = executable_script("#!/bin/sh\nexec sleep 10\n");
    let input = temporary.path().join("input.pdf");
    std::fs::write(&input, b"fixture bytes").expect("fixture");
    let started = std::time::Instant::now();
    let error = extract_path(
        &input,
        None,
        PdfTextCommand::test(binary.as_os_str(), Duration::from_millis(50), None),
    )
    .await
    .expect_err("timeout");
    assert_eq!(error, PdfTextError::TimedOut);
    assert!(started.elapsed() < Duration::from_secs(2));

    let cancel = CancellationToken::new();
    let cancel_after_spawn = cancel.clone();
    tokio::spawn(async move {
        tokio::time::sleep(Duration::from_millis(50)).await;
        cancel_after_spawn.cancel();
    });
    let started = std::time::Instant::now();
    let error = extract_path(
        &input,
        None,
        PdfTextCommand::test(binary.as_os_str(), Duration::from_secs(10), Some(&cancel)),
    )
    .await
    .expect_err("cancelled");
    assert_eq!(error, PdfTextError::Cancelled);
    assert!(started.elapsed() < Duration::from_secs(2));
}

#[tokio::test]
async fn bounded_reader_drains_but_retains_only_the_prefix() {
    let output = read_bounded(&b"0123456789"[..], 4).await.expect("read");
    assert_eq!(output.bytes, b"0123");
    assert!(output.truncated);
}

#[test]
fn stderr_sanitizer_removes_terminal_control_bytes() {
    assert_eq!(sanitized_text(b"bad\x1b[31m\0\nnext"), "bad�[31m�\nnext");
}