gemini_crate 0.1.0

A robust Rust client library for Google's Gemini AI API with built-in error handling, retry logic, and comprehensive model support
Documentation
use crate::client::GeminiClient;
use crate::config::Config;
use mockito::Server;

#[tokio::test]
async fn test_list_models() {
    let mut server = Server::new_async().await;
    let url = server.url();

    let _m = server
        .mock("GET", "/models")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(
            r#"{
            "models": [
                {
                    "name": "models/gemini-pro",
                    "version": "1.0.0",
                    "displayName": "Gemini Pro",
                    "description": "The best model for text generation.",
                    "inputTokenLimit": 1024,
                    "outputTokenLimit": 8192,
                    "supportedGenerationMethods": ["generateContent", "countTokens"]
                }
            ]
        }"#,
        )
        .create();

    let config = Config::from_api_key("test_api_key".to_string());
    let client = GeminiClient::with_config(config).with_base_url(&url);
    let result = client.list_models().await;

    assert!(result.is_ok());
    let response = result.unwrap();
    assert_eq!(response.models.len(), 1);
    assert_eq!(response.models[0].name, "models/gemini-pro");
}

#[tokio::test]
async fn test_list_models_with_retry() {
    let mut server = Server::new_async().await;
    let url = server.url();

    server.mock("GET", "/models").with_status(503).create();
    server.mock("GET", "/models").with_status(503).create();
    server
        .mock("GET", "/models")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(r#"{ "models": [] }"#)
        .create();

    let config = Config::from_api_key("test_api_key".to_string());
    let client = GeminiClient::with_config(config).with_base_url(&url);
    let result = client.list_models().await;

    assert!(result.is_ok());
}

#[tokio::test]
async fn test_list_models_bad_request() {
    let mut server = Server::new_async().await;
    let url = server.url();

    server.mock("GET", "/models").with_status(400).create();

    let config = Config::from_api_key("test_api_key".to_string());
    let client = GeminiClient::with_config(config).with_base_url(&url);
    let result = client.list_models().await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_generate_text() {
    let mut server = Server::new_async().await;
    let url = server.url();
    let model = "gemini-pro";
    let path = format!("/models/{}:generateContent", model);

    let _m = server
        .mock("POST", path.as_str())
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(
            r#"{
            "candidates": [
                {
                    "content": {
                        "parts": [
                            {
                                "text": "Hello, world!"
                            }
                        ],
                        "role": "model"
                    }
                }
            ]
        }"#,
        )
        .create();

    let config = Config::from_api_key("test_api_key".to_string());
    let client = GeminiClient::with_config(config).with_base_url(&url);
    let result = client.generate_text(model, "Hello").await;

    assert!(result.is_ok());
    let text = result.unwrap();
    assert_eq!(text, "Hello, world!");
}

#[tokio::test]
async fn test_generate_text_with_retry() {
    let mut server = Server::new_async().await;
    let url = server.url();
    let model = "gemini-pro";
    let path = format!("/models/{}:generateContent", model);

    // Fail the first two times
    server.mock("POST", path.as_str()).with_status(503).create();
    server.mock("POST", path.as_str()).with_status(503).create();

    // Succeed the third time
    server
        .mock("POST", path.as_str())
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(
            r#"{
            "candidates": [
                {
                    "content": {
                        "parts": [
                            {
                                "text": "Success!"
                            }
                        ],
                        "role": "model"
                    }
                }
            ]
        }"#,
        )
        .create();

    let config = Config::from_api_key("test_api_key".to_string());
    let client = GeminiClient::with_config(config).with_base_url(&url);
    let result = client.generate_text(model, "Hello").await;

    assert!(result.is_ok());
    let text = result.unwrap();
    assert_eq!(text, "Success!");
}

#[tokio::test]
async fn test_generate_text_bad_request() {
    let mut server = Server::new_async().await;
    let url = server.url();
    let model = "gemini-pro";
    let path = format!("/models/{}:generateContent", model);

    let _m = server
        .mock("POST", path.as_str())
        .with_status(400)
        .with_body("Invalid request")
        .create();

    let config = Config::from_api_key("test_api_key".to_string());
    let client = GeminiClient::with_config(config).with_base_url(&url);
    let result = client.generate_text(model, "Invalid Prompt").await;

    assert!(result.is_err());
}