sim-lib-openai-server 0.1.0

OpenAI-compatible gateway skeleton for SIM.
Documentation
use serde_json::Value;

use crate::{
    FILES_PATH, GatewayRequest, GatewayStateStore, THREAD_MESSAGES_ROUTE, THREADS_PATH,
    configure_routes,
};

#[test]
fn files_route_stores_json_upload_and_fetches_metadata() {
    let routes = configure_routes();
    let uploaded = routes.handle(&json_request(
        "POST",
        FILES_PATH,
        r#"{"filename":"notes.txt","purpose":"assistants","content":"hello file"}"#,
    ));
    let uploaded_json = response_json(&uploaded);
    let file_id = uploaded_json["id"].as_str().unwrap();

    assert_eq!(uploaded.status(), 200);
    assert!(file_id.starts_with("file_"));
    assert_eq!(uploaded_json["object"], "file");
    assert_eq!(uploaded_json["filename"], "notes.txt");
    assert_eq!(uploaded_json["bytes"], 10);
    assert_eq!(uploaded_json["storage_ref"]["kind"], "memory");

    let fetched = routes.handle(&GatewayRequest::get(format!("{FILES_PATH}/{file_id}")));
    assert_eq!(fetched.status(), 200);
    assert_eq!(response_json(&fetched), uploaded_json);
    let store = routes.state().store().lock().unwrap();
    assert_eq!(store.file_bytes(file_id).unwrap(), b"hello file");
}

#[test]
fn files_route_records_table_fs_storage_ref_metadata() {
    let uploaded = configure_routes().handle(&json_request(
        "POST",
        FILES_PATH,
        r#"{"filename":"fixture.txt","content":"table ref","storage_ref":{"kind":"table-fs","path":"fixtures/upload.txt"}}"#,
    ));
    let json = response_json(&uploaded);

    assert_eq!(uploaded.status(), 200);
    assert_eq!(json["storage_ref"]["kind"], "table-fs");
    assert_eq!(json["storage_ref"]["path"], "fixtures/upload.txt");
}

#[test]
fn threads_route_creates_reads_appends_and_lists_messages() {
    let routes = configure_routes();
    let created = routes.handle(&json_request(
        "POST",
        THREADS_PATH,
        r#"{"metadata":{"case":"stateful"}}"#,
    ));
    let created_json = response_json(&created);
    let thread_id = created_json["id"].as_str().unwrap();

    assert_eq!(created.status(), 200);
    assert!(thread_id.starts_with("thread_"));
    assert_eq!(created_json["object"], "thread");
    assert_eq!(created_json["metadata"]["case"], "stateful");

    let fetched = routes.handle(&GatewayRequest::get(format!("{THREADS_PATH}/{thread_id}")));
    assert_eq!(response_json(&fetched), created_json);

    let message_path = format!("{THREADS_PATH}/{thread_id}/messages");
    let appended = routes.handle(&json_request(
        "POST",
        &message_path,
        r#"{"role":"user","content":"stored context"}"#,
    ));
    let appended_json = response_json(&appended);
    assert_eq!(appended.status(), 200);
    assert_eq!(appended_json["object"], "thread.message");
    assert_eq!(appended_json["thread_id"], thread_id);
    assert_eq!(appended_json["content"], "stored context");

    let listed = routes.handle(&GatewayRequest::get(message_path));
    let listed_json = response_json(&listed);
    assert_eq!(listed.status(), 200);
    assert_eq!(listed_json["object"], "list");
    assert_eq!(listed_json["data"][0], appended_json);
}

#[test]
fn responses_route_can_include_prior_thread_messages() {
    let routes = configure_routes();
    let thread = routes.handle(&json_request("POST", THREADS_PATH, "{}"));
    let thread_id = response_json(&thread)["id"].as_str().unwrap().to_owned();
    let message_path = format!("{THREADS_PATH}/{thread_id}/messages");
    routes.handle(&json_request(
        "POST",
        &message_path,
        r#"{"role":"user","content":"stored context"}"#,
    ));

    let response = routes.handle(&json_request(
        "POST",
        crate::RESPONSES_PATH,
        &format!(r#"{{"model":"fixture/echo","input":"new question","thread_id":"{thread_id}"}}"#),
    ));
    let response_json = response_json(&response);

    assert_eq!(response.status(), 200);
    assert_eq!(response_json["output_text"], "new question stored context");
}

fn json_request(method: &str, path: &str, body: &str) -> GatewayRequest {
    GatewayRequest::new(
        method,
        path,
        vec![("Content-Type".to_owned(), "application/json".to_owned())],
        body.as_bytes().to_vec(),
    )
}

fn response_json(response: &crate::GatewayResponse) -> Value {
    serde_json::from_slice(response.body()).unwrap()
}

#[test]
fn thread_messages_route_constant_is_stable() {
    assert_eq!(THREAD_MESSAGES_ROUTE, "/v1/threads/{id}/messages");
}