openai-core 0.3.1

Rust SDK for OpenAI-compatible ecosystem
Documentation
use serde_json::json;
use wiremock::matchers::{body_json, header, method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};

use openai_core::{Client, Error};

fn test_client(server: &MockServer) -> Client {
    Client::builder()
        .api_key("sk-user")
        .admin_api_key("sk-admin")
        .base_url(server.uri())
        .disable_proxy_for_local_base_url(true)
        .build()
        .unwrap()
}

#[tokio::test]
async fn test_should_use_admin_organization_resources() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/organization/admin_api_keys"))
        .and(header("authorization", "Bearer sk-admin"))
        .and(body_json(json!({"name": "ops-admin"})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "id": "key_1",
            "object": "organization.admin_api_key",
            "name": "ops-admin",
            "value": "sk-admin-redacted"
        })))
        .mount(&server)
        .await;
    Mock::given(method("GET"))
        .and(path("/organization/audit_logs"))
        .and(query_param("actor_id", "user_1"))
        .and(query_param("limit", "20"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "list",
            "data": [{"id": "audit_1", "type": "login"}],
            "has_more": false
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/organization/certificates/activate"))
        .and(body_json(json!({"certificate_ids": ["cert_1"]})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "list",
            "data": [{"id": "cert_1", "active": true}]
        })))
        .mount(&server)
        .await;
    Mock::given(method("GET"))
        .and(path("/organization/usage/file_search_calls"))
        .and(query_param("start_time", "1700000000"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "organization.usage.file_search_calls",
            "data": [{"num_model_requests": 1}]
        })))
        .mount(&server)
        .await;

    let client = test_client(&server);

    let key = client
        .admin()
        .organization()
        .admin_api_keys()
        .create()
        .body_value(json!({"name": "ops-admin"}))
        .send()
        .await
        .unwrap();
    assert_eq!(key["id"], "key_1");
    assert_eq!(key["value"], "sk-admin-redacted");

    let audit_logs = client
        .admin()
        .organization()
        .audit_logs()
        .list()
        .limit(20)
        .extra_query("actor_id", "user_1")
        .send()
        .await
        .unwrap();
    assert_eq!(audit_logs.data[0]["id"], "audit_1");

    let activated = client
        .admin()
        .organization()
        .certificates()
        .activate()
        .body_value(json!({"certificate_ids": ["cert_1"]}))
        .send()
        .await
        .unwrap();
    assert_eq!(activated.data[0]["active"], true);

    let usage = client
        .admin()
        .organization()
        .usage()
        .file_search_calls()
        .extra_query("start_time", "1700000000")
        .send()
        .await
        .unwrap();
    assert_eq!(usage["data"][0]["num_model_requests"], 1);
}

#[tokio::test]
async fn test_should_require_admin_api_key_for_admin_resources() {
    let server = MockServer::start().await;
    let client = Client::builder()
        .api_key("sk-user")
        .base_url(server.uri())
        .disable_proxy_for_local_base_url(true)
        .build()
        .unwrap();

    let error = client
        .admin()
        .organization()
        .audit_logs()
        .list()
        .send()
        .await
        .unwrap_err();

    assert!(matches!(error, Error::MissingCredentials));
}

#[tokio::test]
async fn test_should_use_admin_project_resource_paths() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/organization/projects"))
        .and(query_param("include_archived", "false"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "list",
            "data": [{"id": "proj_1", "name": "Production"}],
            "has_more": false
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/organization/projects/proj_1/archive"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "id": "proj_1",
            "archived": true
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/organization/projects/proj_1/service_accounts"))
        .and(body_json(json!({"name": "worker"})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "id": "svc_1",
            "object": "organization.project.service_account",
            "api_key": {"value": "sk-service-redacted"}
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/projects/proj_1/users/user_1/roles"))
        .and(body_json(json!({"role_id": "role_1"})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "user.role",
            "role": {"id": "role_1"},
            "user": {"id": "user_1"}
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/projects/proj_1/groups/group_1/roles"))
        .and(body_json(json!({"role_id": "role_1"})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "group.role",
            "role": {"id": "role_1"},
            "group": {"id": "group_1"}
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path("/organization/projects/proj_1/model_permissions"))
        .and(body_json(json!({"allow": [{"model": "gpt-5.4"}]})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "organization.project.model_permissions",
            "allow": [{"model": "gpt-5.4"}]
        })))
        .mount(&server)
        .await;
    Mock::given(method("POST"))
        .and(path(
            "/organization/projects/proj_1/hosted_tool_permissions",
        ))
        .and(body_json(json!({"web_search": {"allowed": true}})))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "object": "organization.project.hosted_tool_permissions",
            "web_search": {"allowed": true}
        })))
        .mount(&server)
        .await;

    let client = test_client(&server);
    let projects = client
        .admin()
        .organization()
        .projects()
        .list()
        .extra_query("include_archived", "false")
        .send()
        .await
        .unwrap();
    assert_eq!(projects.data[0]["id"], "proj_1");

    let archived = client
        .admin()
        .organization()
        .projects()
        .archive("proj_1")
        .send()
        .await
        .unwrap();
    assert_eq!(archived["archived"], true);

    let service_account = client
        .admin()
        .organization()
        .projects()
        .service_accounts()
        .create("proj_1")
        .body_value(json!({"name": "worker"}))
        .send()
        .await
        .unwrap();
    assert_eq!(service_account["api_key"]["value"], "sk-service-redacted");

    let user_role = client
        .admin()
        .organization()
        .projects()
        .users()
        .roles()
        .create("proj_1", "user_1")
        .body_value(json!({"role_id": "role_1"}))
        .send()
        .await
        .unwrap();
    assert_eq!(user_role["object"], "user.role");

    let group_role = client
        .admin()
        .organization()
        .projects()
        .groups()
        .roles()
        .create("proj_1", "group_1")
        .body_value(json!({"role_id": "role_1"}))
        .send()
        .await
        .unwrap();
    assert_eq!(group_role["object"], "group.role");

    let model_permissions = client
        .admin()
        .organization()
        .projects()
        .model_permissions()
        .update("proj_1")
        .body_value(json!({"allow": [{"model": "gpt-5.4"}]}))
        .send()
        .await
        .unwrap();
    assert_eq!(model_permissions["allow"][0]["model"], "gpt-5.4");

    let hosted_tool_permissions = client
        .admin()
        .organization()
        .projects()
        .hosted_tool_permissions()
        .update("proj_1")
        .body_value(json!({"web_search": {"allowed": true}}))
        .send()
        .await
        .unwrap();
    assert_eq!(hosted_tool_permissions["web_search"]["allowed"], true);
}