tandem-server 0.4.25

HTTP server for Tandem engine APIs
Documentation
use super::*;
use axum::{routing::get, Router};
use tokio::net::TcpListener;

#[tokio::test]
async fn provider_route_returns_known_providers_without_synthetic_default_models() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
        .method("GET")
        .uri("/provider")
        .body(Body::empty())
        .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    assert_eq!(resp.status(), StatusCode::OK);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    let all = payload
        .get("all")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default();
    let openai = all
        .iter()
        .find(|entry| entry.get("id").and_then(Value::as_str) == Some("openai"))
        .cloned()
        .expect("openai entry");

    assert_eq!(
        openai
            .get("models")
            .and_then(Value::as_object)
            .map(|m| m.len()),
        Some(0)
    );
    assert_eq!(
        openai.get("catalog_source").and_then(Value::as_str),
        Some("empty")
    );
    assert_eq!(
        openai.get("catalog_status").and_then(Value::as_str),
        Some("unavailable")
    );
}

#[tokio::test]
async fn provider_auth_set_writes_protected_audit_record() {
    let state = test_state().await;
    let app = app_router(state.clone());
    let req = Request::builder()
        .method("PUT")
        .uri("/auth/openai")
        .header("content-type", "application/json")
        .body(Body::from(json!({"token": "sk-test"}).to_string()))
        .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    assert_eq!(resp.status(), StatusCode::OK);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    assert_eq!(payload.get("ok").and_then(Value::as_bool), Some(true));
    let audit = tokio::fs::read_to_string(&state.protected_audit_path)
        .await
        .expect("protected audit file");
    assert!(audit.contains("\"event_type\":\"provider.secret.updated\""));
    assert!(audit.contains("\"providerID\":\"openai\""));
}

#[tokio::test]
async fn provider_route_marks_config_models_as_config_catalogs() {
    let state = test_state().await;
    state
        .config
        .patch_project(json!({
            "providers": {
                "openai": {
                    "url": "https://api.openai.com/v1",
                    "models": {
                        "gpt-4.1-mini": {
                            "name": "GPT 4.1 Mini",
                            "context_length": 128000
                        }
                    }
                }
            }
        }))
        .await
        .expect("patch project");
    state
        .providers
        .reload(state.config.get().await.into())
        .await;

    let app = app_router(state);
    let req = Request::builder()
        .method("GET")
        .uri("/provider")
        .body(Body::empty())
        .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    assert_eq!(resp.status(), StatusCode::OK);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    let all = payload
        .get("all")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default();
    let openai = all
        .iter()
        .find(|entry| entry.get("id").and_then(Value::as_str) == Some("openai"))
        .cloned()
        .expect("openai entry");

    assert_eq!(
        openai.get("catalog_source").and_then(Value::as_str),
        Some("config")
    );
    assert_eq!(
        openai.get("catalog_status").and_then(Value::as_str),
        Some("ok")
    );
    assert!(
        openai
            .get("models")
            .and_then(Value::as_object)
            .and_then(|models| models.get("gpt-4.1-mini"))
            .is_some(),
        "expected configured model to appear in catalog"
    );
}

#[tokio::test]
async fn provider_route_uses_runtime_auth_for_remote_catalog_fetch() {
    let listener = TcpListener::bind("127.0.0.1:0")
        .await
        .expect("bind listener");
    let addr = listener.local_addr().expect("local addr");
    let app = Router::new().route(
        "/v1/models",
        get(|| async {
            Json(json!({
                "data": [
                    { "id": "gpt-4.1-mini", "name": "GPT 4.1 Mini", "context_length": 128000 }
                ]
            }))
        }),
    );
    let server = tokio::spawn(async move {
        axum::serve(listener, app)
            .await
            .expect("serve test provider");
    });

    let state = test_state().await;
    state
        .config
        .patch_project(json!({
            "providers": {
                "openai": {
                    "url": format!("http://{addr}/v1")
                }
            }
        }))
        .await
        .expect("patch project");
    state
        .auth
        .write()
        .await
        .insert("openai".to_string(), "test-key".to_string());
    state
        .providers
        .reload(state.config.get().await.into())
        .await;

    let app = app_router(state);
    let req = Request::builder()
        .method("GET")
        .uri("/provider")
        .body(Body::empty())
        .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    server.abort();

    assert_eq!(resp.status(), StatusCode::OK);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    let all = payload
        .get("all")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default();
    let openai = all
        .iter()
        .find(|entry| entry.get("id").and_then(Value::as_str) == Some("openai"))
        .cloned()
        .expect("openai entry");

    assert_eq!(
        openai.get("catalog_source").and_then(Value::as_str),
        Some("remote")
    );
    assert!(
        openai
            .get("models")
            .and_then(Value::as_object)
            .and_then(|models| models.get("gpt-4.1-mini"))
            .is_some(),
        "expected runtime-auth-backed remote catalog"
    );
}