nils-codex-cli 0.7.3

CLI crate for nils-codex-cli in the nils-cli workspace.
Documentation
use codex_cli::rate_limits::client::{UsageRequest, fetch_usage, read_tokens};
use nils_test_support::http::{HttpResponse, LoopbackServer};
use nils_test_support::{EnvGuard, GlobalStateLock};
use pretty_assertions::assert_eq;
use std::fs;

fn write_target(dir: &tempfile::TempDir, contents: &str) -> std::path::PathBuf {
    let path = dir.path().join("target.json");
    fs::write(&path, contents).expect("write target");
    path
}

#[test]
fn rate_limits_client_read_tokens_supports_root_account_id() {
    let dir = tempfile::TempDir::new().expect("tempdir");
    let target = write_target(
        &dir,
        r#"{"tokens":{"access_token":"tok"},"account_id":"acct"}"#,
    );
    let (token, account) = read_tokens(&target).expect("tokens");
    assert_eq!(token, "tok");
    assert_eq!(account.as_deref(), Some("acct"));
}

#[test]
fn rate_limits_client_fetch_usage_errors_include_body_preview() {
    let server = LoopbackServer::new().expect("server");
    server.add_route(
        "GET",
        "/wham/usage",
        HttpResponse::new(500, "hello\nworld\n"),
    );

    let dir = tempfile::TempDir::new().expect("tempdir");
    let target = write_target(
        &dir,
        r#"{"tokens":{"access_token":"tok","account_id":"acct"}}"#,
    );

    let request = UsageRequest {
        target_file: target,
        refresh_on_401: false,
        base_url: server.url(),
        connect_timeout_seconds: 1,
        max_time_seconds: 3,
    };

    let err = match fetch_usage(&request) {
        Ok(_) => panic!("expected fetch_usage to error"),
        Err(err) => err.to_string(),
    };
    assert!(err.contains("HTTP 500"));
    assert!(err.contains("body:"));
    assert!(err.contains("hello world"));
}

#[test]
fn rate_limits_client_fetch_usage_errors_without_body_when_empty() {
    let server = LoopbackServer::new().expect("server");
    server.add_route("GET", "/wham/usage", HttpResponse::new(500, ""));

    let dir = tempfile::TempDir::new().expect("tempdir");
    let target = write_target(&dir, r#"{"tokens":{"access_token":"tok"}}"#);

    let request = UsageRequest {
        target_file: target,
        refresh_on_401: false,
        base_url: server.url(),
        connect_timeout_seconds: 1,
        max_time_seconds: 3,
    };

    let err = match fetch_usage(&request) {
        Ok(_) => panic!("expected fetch_usage to error"),
        Err(err) => err.to_string(),
    };
    assert!(err.contains("HTTP 500"));
    assert!(!err.contains("body:"));
}

#[test]
fn rate_limits_client_fetch_usage_invalid_json_is_error() {
    let server = LoopbackServer::new().expect("server");
    server.add_route("GET", "/wham/usage", HttpResponse::new(200, "not-json"));

    let dir = tempfile::TempDir::new().expect("tempdir");
    let target = write_target(&dir, r#"{"tokens":{"access_token":"tok"}}"#);

    let request = UsageRequest {
        target_file: target,
        refresh_on_401: false,
        base_url: server.url(),
        connect_timeout_seconds: 1,
        max_time_seconds: 3,
    };

    let err = match fetch_usage(&request) {
        Ok(_) => panic!("expected fetch_usage to error"),
        Err(err) => err.to_string(),
    };
    assert!(err.contains("invalid JSON"));
}

#[test]
fn rate_limits_client_fetch_usage_refreshes_on_401_when_enabled() {
    let lock = GlobalStateLock::new();
    let _auth = EnvGuard::remove(&lock, "CODEX_AUTH_FILE");
    let _secrets = EnvGuard::remove(&lock, "CODEX_SECRET_DIR");

    let server = LoopbackServer::new().expect("server");
    server.add_route("GET", "/wham/usage", HttpResponse::new(401, ""));

    let dir = tempfile::TempDir::new().expect("tempdir");
    let target = write_target(&dir, r#"{"tokens":{"access_token":"tok"}}"#);

    let request = UsageRequest {
        target_file: target,
        refresh_on_401: true,
        base_url: server.url(),
        connect_timeout_seconds: 1,
        max_time_seconds: 3,
    };

    let err = match fetch_usage(&request) {
        Ok(_) => panic!("expected fetch_usage to error"),
        Err(err) => err.to_string(),
    };
    assert!(err.contains("HTTP 401"));

    let requests = server.take_requests();
    assert_eq!(
        requests
            .iter()
            .filter(|r| r.method == "GET" && r.path == "/wham/usage")
            .count(),
        2
    );
}