quvyta-cli 0.1.0-alpha.5

A small coding agent in the terminal: chat with a model that reads, searches, edits and runs in one folder, and asks before it changes anything (alpha)
Documentation
//! The endpoint against a stand-in server: what goes out, and what comes back.

use serde_json::json;

use super::fake::{FakeServer, says};
use super::{Delta, Endpoint, Key, ProviderError, Stop};

/// Not a key of anyone's: the characters spell what it is.
const MADE_UP: &str = "not-a-real-key-0000-wxyz";

fn endpoint(address: &str, header: &str) -> Endpoint {
    Endpoint {
        address: format!("{address}/"),
        model: "test-model".into(),
        key_header: header.into(),
        key: Some(Key::new(MADE_UP).expect("a key")),
        silence: Endpoint::SILENCE,
    }
}

#[test]
fn the_request_carries_the_model_the_stream_flag_and_the_key_in_its_header() {
    let server = FakeServer::start(vec![(200, says("Hello there"))]);
    let mut pieces = String::new();
    let reply = endpoint(&server.address, "api-key")
        .stream(
            &json!({"max_tokens": 64, "messages": [{"role": "user", "content": "hi"}]}),
            &mut |delta| {
                if let Delta::Text(text) = delta {
                    pieces.push_str(&text);
                }
            },
            &|| false,
        )
        .expect("a reply");
    assert_eq!(pieces, "Hello there");
    assert_eq!(reply.content, [json!({"type": "text", "text": "Hello there"})]);
    assert_eq!(reply.stop, Stop::EndTurn);

    let received = server.received();
    assert_eq!(received.len(), 1);
    let request = &received[0];
    assert_eq!(request.line, "POST /v1/messages HTTP/1.1", "the trailing slash of the address is not doubled");
    assert_eq!(request.header("api-key"), Some(MADE_UP));
    assert_eq!(request.header("x-api-key"), None, "the key goes only in the header that was named");
    assert_eq!(request.header("anthropic-version"), Some("2023-06-01"));
    assert_eq!(request.body["model"], "test-model");
    assert_eq!(request.body["stream"], true);
    assert_eq!(request.body["messages"][0]["content"], "hi");
}

#[test]
fn an_authorization_header_carries_the_key_as_a_bearer_token() {
    let server = FakeServer::start(vec![(200, says("ok"))]);
    endpoint(&server.address, "Authorization")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect("a reply");
    assert_eq!(server.received()[0].header("authorization"), Some(format!("Bearer {MADE_UP}").as_str()));
}

#[test]
fn a_refusal_says_what_the_server_said_and_never_the_key() {
    let body = json!({"type": "error", "error": {"type": "authentication_error", "message": "invalid api key"}});
    let server = FakeServer::start(vec![(401, body.to_string())]);
    let error = endpoint(&server.address, "x-api-key")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect_err("refused");
    let ProviderError::Refused { status, said, .. } = &error else { panic!("a refusal, got {error:?}") };
    assert_eq!((*status, said.as_str()), (401, "invalid api key"));
    assert!(!error.to_string().contains(MADE_UP));
    assert!(!format!("{error:?}").contains(MADE_UP));
}

#[test]
fn an_address_nobody_answers_is_unreachable_and_names_only_the_address() {
    // A port that was free a moment ago and is closed again: nothing listens there.
    let port = std::net::TcpListener::bind("127.0.0.1:0").expect("a port").local_addr().expect("addr").port();
    let error = endpoint(&format!("http://127.0.0.1:{port}"), "x-api-key")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect_err("nobody there");
    assert!(matches!(error, ProviderError::Unreachable { .. }), "{error:?}");
    assert!(!error.to_string().contains(MADE_UP));
}

#[test]
fn a_tool_call_comes_through_the_endpoint_whole() {
    let server = FakeServer::start(vec![(200, super::fake::calls("t1", "list_dir", &json!({"path": "."})))]);
    let mut announced = Vec::new();
    let reply = endpoint(&server.address, "x-api-key")
        .stream(&json!({"messages": []}), &mut |delta| announced.push(delta), &|| false)
        .expect("a reply");
    assert_eq!(announced, [Delta::ToolCall("list_dir".into())]);
    assert_eq!(reply.stop, Stop::ToolUse);
    assert_eq!(reply.content, [json!({"type": "tool_use", "id": "t1", "name": "list_dir", "input": {"path": "."}})]);
}

/// Some providers repeat the key they were sent in their refusal ("Incorrect API key provided:
/// ..."); it never reaches the screen or the conversation through qcli.
#[test]
fn a_refusal_that_repeats_the_key_shows_it_never() {
    let body = json!({"error": {"message": format!("Incorrect API key provided: {MADE_UP}. Check it.")}});
    let server = FakeServer::start(vec![(401, body.to_string())]);
    let error = endpoint(&server.address, "authorization")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect_err("refused");
    let shown = error.to_string();
    assert!(!shown.contains(MADE_UP), "{shown}");
    assert!(shown.contains("Incorrect API key provided"), "the rest of the sentence stays: {shown}");
    assert!(!format!("{error:?}").contains(MADE_UP));

    // The same words in an error event in the middle of a reply.
    let event =
        json!({"type": "error", "error": {"type": "authentication_error", "message": format!("bad key {MADE_UP}")}});
    let server = FakeServer::start(vec![(200, super::fake::sse(&[event]))]);
    let error = endpoint(&server.address, "x-api-key")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect_err("stopped");
    assert!(!error.to_string().contains(MADE_UP), "{error}");
}

/// A server that goes quiet in the middle of a reply is given up on after a while, and the person
/// is told; the reply does not wait for it for ever.
#[test]
fn a_server_that_goes_quiet_in_the_middle_of_a_reply_is_given_up_on() {
    let half = super::fake::sse(&[
        json!({"type": "message_start", "message": {"usage": {"input_tokens": 3}}}),
        json!({"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}),
        json!({"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Half"}}),
    ]);
    let server = FakeServer::start_holding(vec![(200, half)], std::time::Duration::from_secs(600));
    let mut quiet = endpoint(&server.address, "x-api-key");
    quiet.silence = std::time::Duration::from_secs(2);
    let started = std::time::Instant::now();
    let error = quiet.stream(&json!({"messages": []}), &mut |_| {}, &|| false).expect_err("given up on");
    assert!(started.elapsed() < std::time::Duration::from_secs(120), "it did not wait for ever");
    assert!(error.to_string().contains("sent nothing for 2 seconds"), "the person is told: {error}");
}

/// Esc stops a reply that is waiting for the server, not only one that is moving.
#[test]
fn a_stop_ends_a_reply_that_is_waiting_for_the_server() {
    let half = super::fake::sse(&[
        json!({"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}),
        json!({"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Half"}}),
    ]);
    let server = FakeServer::start_holding(vec![(200, half)], std::time::Duration::from_secs(600));
    let started = std::time::Instant::now();
    // The person presses Esc a second in, while the server has gone quiet after its last piece.
    let pressed = || started.elapsed() > std::time::Duration::from_secs(1);
    let _ = endpoint(&server.address, "x-api-key").stream(&json!({"messages": []}), &mut |_| {}, &pressed);
    assert!(
        started.elapsed() < std::time::Duration::from_secs(60),
        "the stop was heard while the server was quiet, long before the silence limit"
    );
}

/// An endpoint of a relay on this machine that adds the key itself: no key goes with it.
fn keyless(address: &str, header: &str) -> Endpoint {
    Endpoint {
        address: format!("{address}/"),
        model: "relay-model".into(),
        key_header: header.into(),
        key: None,
        silence: Endpoint::SILENCE,
    }
}

#[test]
fn an_endpoint_without_a_key_sends_no_key_header() {
    let server = FakeServer::start(vec![(200, says("hello"))]);
    let reply = keyless(&server.address, "x-api-key")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect("a reply");
    assert_eq!(reply.content, [json!({"type": "text", "text": "hello"})]);

    let received = server.received();
    assert_eq!(received.len(), 1);
    assert_eq!(received[0].header("x-api-key"), None, "no key header at all");
    assert_eq!(received[0].header("authorization"), None);
    assert_eq!(received[0].body["model"], "relay-model", "the endpoint's model goes out");
}

#[test]
fn an_endpoint_without_a_key_sends_no_authorization_header_either() {
    let server = FakeServer::start(vec![(200, says("hello"))]);
    keyless(&server.address, "authorization")
        .stream(&json!({"messages": []}), &mut |_| {}, &|| false)
        .expect("a reply");
    let received = server.received();
    assert_eq!(received.len(), 1);
    assert_eq!(received[0].header("authorization"), None, "not even a bearer with an empty key");
    assert_eq!(received[0].header("x-api-key"), None);
}