litellm-rs 0.8.2

A high-performance AI Gateway written in Rust, providing OpenAI-compatible APIs with intelligent routing, load balancing, and enterprise features
Documentation
#[tokio::test]
async fn app_factory_serves_public_stable_inference_openapi_contract() {
    const SENTINEL_API_KEY: &str = "sk-must-not-appear-in-openapi";
    const SENTINEL_PROVIDER_NAME: &str = "runtime-provider-must-not-appear";

    let mut config = valid_http_test_config();
    config.gateway.providers[0].api_key = SENTINEL_API_KEY.to_string();
    config.gateway.providers[0].name = SENTINEL_PROVIDER_NAME.to_string();
    config.gateway.auth.enable_jwt = true;
    config.gateway.auth.enable_api_key = true;
    config.gateway.auth.allow_anonymous = false;
    config.gateway.auth.jwt_secret = "AaaAaaAaaAaaAaaAaaAaaAaaAaaAaa1!".to_string();
    config.gateway.monitoring.metrics.enabled = false;
    config.gateway.storage.database.enabled = false;
    config.gateway.storage.redis.enabled = false;
    config.gateway.pricing.source = None;

    let server = match HttpServer::new(&config).await {
        Ok(server) => server,
        Err(error) => panic!("server startup failed: {error}"),
    };
    let app = actix_test::init_service(HttpServer::create_app(web::Data::new(
        server.state().clone(),
    )))
    .await;

    let request = actix_test::TestRequest::get()
        .uri("/openapi.json")
        .to_request();
    let response = actix_test::call_service(&app, request).await;

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(
        response.headers().get(header::CONTENT_TYPE),
        Some(&header::HeaderValue::from_static("application/json"))
    );
    let body = actix_test::read_body(response).await;
    let contract: serde_json::Value =
        serde_json::from_slice(&body).expect("OpenAPI response must be valid JSON");

    assert_eq!(contract["openapi"], "3.2.0");
    assert!(contract["paths"]["/v1/chat/completions"]["post"].is_object());
    assert!(contract["paths"]["/v1/responses/{response_id}"]["get"].is_object());
    assert!(contract["paths"]["/v1/models"]["get"].is_object());
    assert!(contract["paths"].get("/admin").is_none());
    let body = String::from_utf8_lossy(&body);
    assert!(!body.contains(SENTINEL_API_KEY));
    assert!(!body.contains(SENTINEL_PROVIDER_NAME));

    let admin_request = actix_test::TestRequest::get()
        .uri("/admin/openapi.json")
        .to_request();
    let admin_response = actix_test::call_service(&app, admin_request).await;
    assert_eq!(admin_response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn app_factory_serves_admin_openapi_without_merging_into_inference_contract() {
    const SENTINEL_API_KEY: &str = "sk-must-not-appear-in-admin-openapi";
    const SENTINEL_PROVIDER_NAME: &str = "runtime-provider-must-not-appear-admin";

    let mut config = valid_http_test_config();
    config.gateway.providers[0].api_key = SENTINEL_API_KEY.to_string();
    config.gateway.providers[0].name = SENTINEL_PROVIDER_NAME.to_string();
    config.gateway.auth.enable_jwt = true;
    config.gateway.auth.jwt_secret = format!("Aa1!{}", uuid::Uuid::new_v4());
    config.gateway.auth.enable_api_key = false;
    config.gateway.auth.allow_anonymous = true;
    config.gateway.monitoring.metrics.enabled = false;
    config.gateway.storage.database.enabled = false;
    config.gateway.storage.redis.enabled = false;
    config.gateway.pricing.source = None;

    let server = match HttpServer::new(&config).await {
        Ok(server) => server,
        Err(error) => panic!("server startup failed: {error}"),
    };
    let token = crate::server::test_admin_token(server.state()).await;
        let app = actix_test::init_service(HttpServer::create_app(web::Data::new(
        server.state().clone(),
    )))
    .await;

    let admin_request = actix_test::TestRequest::get()
        .uri("/admin/openapi.json")
            .insert_header(("Authorization", format!("Bearer {token}")))
        .to_request();
    let admin_response = actix_test::call_service(&app, admin_request).await;
    assert_eq!(admin_response.status(), StatusCode::OK);
    assert_eq!(
        admin_response.headers().get(header::CONTENT_TYPE),
        Some(&header::HeaderValue::from_static("application/json"))
    );
    let admin_body = actix_test::read_body(admin_response).await;
    let admin_contract: serde_json::Value =
        serde_json::from_slice(&admin_body).expect("admin OpenAPI must be valid JSON");
    assert_eq!(admin_contract["openapi"], "3.2.0");
    assert!(admin_contract["paths"]["/admin/routing/inventory"]["get"].is_object());
    assert!(admin_contract["paths"]["/admin/request-ledger"]["get"].is_object());
    assert!(admin_contract["paths"]["/v1/keys"]["post"].is_object());
    assert!(admin_contract["paths"]["/v1/teams"]["get"].is_object());
    assert!(admin_contract["paths"]["/v1/budget/summary"]["get"].is_object());
    assert!(admin_contract["paths"]["/auth/login"]["post"].is_object());
    assert!(admin_contract["paths"].get("/admin/dashboard").is_none());
    let admin_text = String::from_utf8_lossy(&admin_body);
    assert!(!admin_text.contains(SENTINEL_API_KEY));
    assert!(!admin_text.contains(SENTINEL_PROVIDER_NAME));

    let inference_request = actix_test::TestRequest::get()
        .uri("/openapi.json")
        .to_request();
    let inference_response = actix_test::call_service(&app, inference_request).await;
    assert_eq!(inference_response.status(), StatusCode::OK);
    let inference_body = actix_test::read_body(inference_response).await;
    let inference_contract: serde_json::Value =
        serde_json::from_slice(&inference_body).expect("inference OpenAPI must be valid JSON");
    assert!(inference_contract["paths"]["/v1/chat/completions"]["post"].is_object());
    assert!(inference_contract["paths"].get("/admin").is_none());
    assert!(inference_contract["paths"].get("/admin/openapi.json").is_none());
    assert!(inference_contract["paths"].get("/v1/keys").is_none());
}