unified-agent-api-codex 0.3.5

Async wrapper around the Codex CLI for programmatic prompting
Documentation
use super::*;

#[tokio::test]
async fn auth_helper_uses_app_scoped_home_without_mutating_env() {
    let _guard = env_guard_async().await;
    let temp = tempfile::tempdir().unwrap();
    let log_path = temp.path().join("auth.log");
    let app_home = temp.path().join("app-home");
    let caller_home = temp.path().join("caller-home");
    let previous_home = env::var("CODEX_HOME").ok();
    env::set_var("CODEX_HOME", &caller_home);
    env::set_var("AUTH_HELPER_LOG", &log_path);

    let script = r#"#!/usr/bin/env bash
set -e
echo "args:$*" >> "$AUTH_HELPER_LOG"
echo "CODEX_HOME=${CODEX_HOME:-missing}" >> "$AUTH_HELPER_LOG"
if [[ "$1" == "login" && "$2" == "status" ]]; then
  echo "Logged in using ChatGPT"
  exit 0
fi
echo "Not logged in" >&2
exit 1
"#;
    let binary = write_fake_codex(temp.path(), script);
    let helper = AuthSessionHelper::with_client(
        CodexClient::builder()
            .binary(&binary)
            .codex_home(&app_home)
            .build(),
    );

    let status = helper.status().await.unwrap();
    assert!(matches!(
        status,
        CodexAuthStatus::LoggedIn(CodexAuthMethod::ChatGpt)
    ));

    let logged = std_fs::read_to_string(&log_path).unwrap();
    assert!(logged.contains("args:login status"));
    assert!(logged.contains(&format!("CODEX_HOME={}", app_home.display())));

    assert_eq!(
        env::var("CODEX_HOME").unwrap(),
        caller_home.display().to_string()
    );

    env::remove_var("AUTH_HELPER_LOG");
    if let Some(previous) = previous_home {
        env::set_var("CODEX_HOME", previous);
    } else {
        env::remove_var("CODEX_HOME");
    }
}

#[tokio::test]
async fn ensure_api_key_login_runs_when_logged_out() {
    let _guard = env_guard_async().await;
    let temp = tempfile::tempdir().unwrap();
    let log_path = temp.path().join("login.log");
    let state_path = temp.path().join("api-key-state");
    let script = format!(
        r#"#!/usr/bin/env bash
set -e
echo "$@" >> "{log}"
if [[ "$1" == "login" && "$2" == "status" ]]; then
  if [[ -f "{state}" ]]; then
echo "Logged in using an API key - sk-already"
exit 0
  fi
  echo "Not logged in" >&2
  exit 1
fi
if [[ "$1" == "login" && "$2" == "--api-key" ]]; then
  echo "Logged in using an API key - $3" > "{state}"
  echo "Logged in using an API key - $3"
  exit 0
fi
echo "unexpected args: $*" >&2
exit 2
"#,
        log = log_path.display(),
        state = state_path.display()
    );
    let binary = write_fake_codex(temp.path(), &script);
    let helper = AuthSessionHelper::with_client(
        CodexClient::builder()
            .binary(&binary)
            .codex_home(temp.path().join("app-home"))
            .build(),
    );

    let status = helper.ensure_api_key_login("sk-test-key").await.unwrap();
    match status {
        CodexAuthStatus::LoggedIn(CodexAuthMethod::ApiKey { masked_key }) => {
            assert_eq!(masked_key.as_deref(), Some("sk-test-key"));
        }
        other => panic!("unexpected status: {other:?}"),
    }

    let second = helper.ensure_api_key_login("sk-other").await.unwrap();
    assert!(matches!(
        second,
        CodexAuthStatus::LoggedIn(CodexAuthMethod::ApiKey { .. })
    ));

    let log = std_fs::read_to_string(&log_path).unwrap();
    assert!(log.contains("login status"));
    assert!(log.contains("login --api-key sk-test-key"));
    assert_eq!(
        log.lines()
            .filter(|line| line.contains("--api-key"))
            .count(),
        1
    );
}

#[tokio::test]
async fn ensure_chatgpt_login_launches_when_needed() {
    let _guard = env_guard_async().await;
    let temp = tempfile::tempdir().unwrap();
    let log_path = temp.path().join("chatgpt.log");
    let state_path = temp.path().join("chatgpt-state");
    let script = format!(
        r#"#!/usr/bin/env bash
set -e
echo "$@" >> "{log}"
if [[ "$1" == "login" && "$2" == "status" ]]; then
  if [[ -f "{state}" ]]; then
echo "Logged in using ChatGPT"
exit 0
  fi
  echo "Not logged in" >&2
  exit 1
fi
if [[ "$1" == "login" && -z "$2" ]]; then
  echo "Logged in using ChatGPT" > "{state}"
  echo "Logged in using ChatGPT"
  exit 0
fi
echo "unknown args: $*" >&2
exit 2
"#,
        log = log_path.display(),
        state = state_path.display()
    );
    let binary = write_fake_codex(temp.path(), &script);
    let helper = AuthSessionHelper::with_client(
        CodexClient::builder()
            .binary(&binary)
            .codex_home(temp.path().join("app-home"))
            .build(),
    );

    let child = helper.ensure_chatgpt_login().await.unwrap();
    let child = child.expect("expected ChatGPT login child");
    let output = child.wait_with_output().await.unwrap();
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("Logged in using ChatGPT"));

    let second = helper.ensure_chatgpt_login().await.unwrap();
    assert!(second.is_none());

    let log = std_fs::read_to_string(&log_path).unwrap();
    assert!(log.lines().any(|line| line == "login"));
    assert_eq!(log.lines().filter(|line| line == &"login").count(), 1);
}

#[test]
fn parses_chatgpt_login() {
    let message = "Logged in using ChatGPT";
    let parsed = parse_login_success(message);
    assert!(matches!(
        parsed,
        Some(CodexAuthStatus::LoggedIn(CodexAuthMethod::ChatGpt))
    ));
}

#[test]
fn parses_api_key_login() {
    let message = "Logged in using an API key - sk-1234***abcd";
    let parsed = parse_login_success(message);
    match parsed {
        Some(CodexAuthStatus::LoggedIn(CodexAuthMethod::ApiKey { masked_key })) => {
            assert_eq!(masked_key.as_deref(), Some("sk-1234***abcd"));
        }
        other => panic!("unexpected status: {other:?}"),
    }
}

#[test]
fn parse_login_accepts_unknown_on_success() {
    let message = "Authenticated";
    assert!(parse_login_success(message).is_none());
    let status = CodexAuthStatus::LoggedIn(CodexAuthMethod::Unknown {
        raw: message.to_string(),
    });
    assert!(matches!(
        status,
        CodexAuthStatus::LoggedIn(CodexAuthMethod::Unknown { .. })
    ));
}