Luigit
repositories / smith

smith

There are many coding harnesses - but this one is fast

owned by admin

smith-harness/tests/http_effect.rs

Raw
//! End-to-end provider traffic through the typed HTTP effect
//! (`SMH-SPEC-SPEC0001`, Network transport).

//! Integration tests drive a real socket, a real executor, and the
//! durable session store.

use smith_harness::runtime::{RuntimeConfig, RuntimeProvider, build_agent};
use std::io::{Read, Write};
use std::net::TcpListener;

use smith_core::frame::{Frame, read_frames};
use smith_core::session::{EntryContent, PROVIDER_HTTP_KIND};

/// Serve one OpenAI-shaped SSE response and return the port.
fn spawn_sse_server(body: &'static str) -> u16 {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let port = listener.local_addr().unwrap().port();
    std::thread::spawn(move || {
        let (mut socket, _) = listener.accept().unwrap();
        let mut buffer = [0u8; 8192];
        let _ = socket.read(&mut buffer);
        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nConnection: close\r\n\r\n{body}"
        );
        socket.write_all(response.as_bytes()).unwrap();
    });
    port
}

fn provider_http_records(path: &std::path::Path) -> Vec<serde_json::Value> {
    let bytes = std::fs::read(path).unwrap();
    let config = smith::config::Config::default_valid();
    read_frames(&mut bytes.as_slice(), config.max_frame_bytes)
        .frames
        .into_iter()
        .filter_map(|frame| match frame {
            Frame::Known { entry } => match EntryContent::decode(&entry.content).unwrap() {
                EntryContent::Meta { kind, detail } if kind == PROVIDER_HTTP_KIND => Some(detail),
                _ => None,
            },
            Frame::Unknown { .. } | smith_core::Frame::Header { .. } => None,
        })
        .collect()
}

#[test]
fn provider_round_trips_through_http_effect_with_durable_redacted_records() {
    let dir = std::env::temp_dir().join(format!("smith_http_effect_{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();

    let sse = "data: {\"choices\":[{\"delta\":{\"content\":\"hello \"}}]}\n\n\
               data: {\"choices\":[{\"delta\":{\"content\":\"world\"}}]}\n\n\
               data: {\"choices\":[{\"finish_reason\":\"stop\",\"delta\":{}}],\"usage\":{\"prompt_tokens\":1,\"completion_tokens\":2}}\n\n\
               data: [DONE]\n\n";
    let port = spawn_sse_server(sse);

    let config = RuntimeConfig {
        provider: RuntimeProvider::OpenAiCompatible {
            base_url: format!("http://127.0.0.1:{port}/v1"),
            model: "gpt-test".to_string(),
        },
        api_key: Some("sk-integration-secret".to_string()),
        ..RuntimeConfig::mock(&dir, Vec::new())
    };
    let mut agent = build_agent(&config).unwrap();
    let outcome = agent.run_turn("say it").unwrap();
    assert_eq!(outcome.text, "hello world");

    let records = provider_http_records(&config.session_path);
    assert_eq!(records.len(), 2, "intent and outcome records");

    let intent = &records[0];
    assert_eq!(intent["phase"], "intent");
    assert_eq!(
        intent["url"],
        format!("http://127.0.0.1:{port}/v1/chat/completions")
    );
    let headers = intent["headers"].as_array().unwrap();
    let authorization = headers
        .iter()
        .find(|header| {
            header["name"]
                .as_str()
                .unwrap_or_default()
                .eq_ignore_ascii_case("authorization")
        })
        .unwrap();
    assert_eq!(authorization["value"], "<redacted>");

    let outcome_record = &records[1];
    assert_eq!(outcome_record["phase"], "outcome");
    assert_eq!(outcome_record["status"], 200);
    assert_eq!(outcome_record["outcome"], "completed");

    let _ = std::fs::remove_dir_all(&dir);
}

#[test]
fn explicit_credential_wins_over_missing_env() {
    // No store, no env: only the explicit override can resolve.
    let dir = std::env::temp_dir().join(format!("smith_http_explicit_{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();
    let store = smith_ai::auth::AuthStore::at(dir.join("empty-store"));
    assert!(
        store
            .load(smith_ai::models::ProviderKind::Anthropic)
            .unwrap()
            .is_none()
    );

    let config = RuntimeConfig {
        provider: RuntimeProvider::Anthropic {
            base_url: "https://example.test".to_string(),
            model: "m".to_string(),
        },
        api_key: Some("sk-explicit".to_string()),
        ..RuntimeConfig::mock(&dir, Vec::new())
    };
    let _agent = build_agent(&config).unwrap();
    let _ = std::fs::remove_dir_all(&dir);
}