#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use std::path::PathBuf;
use std::process::{Command, Stdio};
fn seed_managed_openai_realm_config(root: &std::path::Path) {
seed_managed_openai_realm_config_with_ids(root, "default_openai", "default_openai");
}
fn seed_managed_openai_realm_config_with_ids(
root: &std::path::Path,
auth_profile_id: &str,
binding_id: &str,
) {
seed_openai_realm_config_with_ids_and_method(
root,
auth_profile_id,
binding_id,
"openai_api",
"api_key",
);
}
fn seed_openai_realm_config_with_ids_and_method(
root: &std::path::Path,
auth_profile_id: &str,
binding_id: &str,
backend_kind: &str,
auth_method: &str,
) {
let config = format!(
r#"
[realm.dev]
default_binding = "{binding_id}"
[realm.dev.backend.openai_backend]
provider = "openai"
backend_kind = "{backend_kind}"
[realm.dev.auth.{auth_profile_id}]
provider = "openai"
auth_method = "{auth_method}"
source = {{ kind = "managed_store" }}
[realm.dev.binding.{binding_id}]
backend_profile = "openai_backend"
auth_profile = "{auth_profile_id}"
"#
);
let mut realm_ids = std::collections::BTreeSet::from(["dev".to_string()]);
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
realm_ids.insert(meerkat_core::derive_workspace_realm_id(&cwd));
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
realm_ids.insert(meerkat_core::derive_workspace_realm_id(&manifest_dir));
if let Some(workspace_root) = manifest_dir.parent() {
realm_ids.insert(meerkat_core::derive_workspace_realm_id(workspace_root));
}
for realm_id in realm_ids {
let realm_dir = root.join("realms").join(realm_id);
std::fs::create_dir_all(&realm_dir).expect("mkdir realm config dir");
std::fs::write(realm_dir.join("config.toml"), &config).expect("write realm config");
}
}
fn token_file_path(root: &std::path::Path) -> PathBuf {
token_file_path_for_binding(root, "default_openai")
}
fn token_file_path_for_binding(root: &std::path::Path, binding_id: &str) -> PathBuf {
#[cfg(target_os = "macos")]
let config_root = root.join("Library").join("Application Support");
#[cfg(not(target_os = "macos"))]
let config_root = root.join("config");
config_root
.join("meerkat")
.join("credentials")
.join("dev")
.join(format!("{binding_id}.json"))
}
fn seed_token_file(root: &std::path::Path, body: &str) {
seed_token_file_for_binding(root, "default_openai", body);
}
fn seed_token_file_for_binding(root: &std::path::Path, binding_id: &str, body: &str) {
let token_file = token_file_path_for_binding(root, binding_id);
let token_dir = token_file.parent().expect("token file has parent");
std::fs::create_dir_all(token_dir).expect("mkdir token dir");
std::fs::write(token_file, body).expect("write token file");
}
fn token_file_exists(root: &std::path::Path) -> bool {
token_file_path(root).exists()
}
fn rkat_binary() -> Option<PathBuf> {
if let Some(path) = std::env::var_os("CARGO_BIN_EXE_rkat") {
let p = PathBuf::from(path);
if p.exists() {
return Some(p.canonicalize().unwrap_or(p));
}
}
if let Some(target_dir) = std::env::var_os("CARGO_TARGET_DIR") {
let target_dir = PathBuf::from(target_dir);
for profile in ["debug", "release"] {
let candidate = target_dir.join(profile).join("rkat");
if candidate.exists() {
return Some(candidate);
}
}
}
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace_root = manifest_dir.parent()?;
let codex_debug = workspace_root.join("target-codex/debug/rkat");
if codex_debug.exists() {
return Some(codex_debug);
}
let codex_release = workspace_root.join("target-codex/release/rkat");
if codex_release.exists() {
return Some(codex_release);
}
None
}
#[test]
fn rkat_auth_help_lists_all_subcommands() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let out = Command::new(&rkat)
.args(["auth", "--help"])
.stdin(Stdio::null())
.output()
.expect("rkat auth --help must spawn");
assert!(
out.status.success(),
"rkat auth --help must exit 0; stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
for subcommand in ["login", "logout", "profile", "status", "binding"] {
assert!(
stdout.contains(subcommand),
"rkat auth --help must mention `{subcommand}` subcommand; stdout:\n{stdout}"
);
}
}
#[test]
fn rkat_auth_logout_clears_malformed_token_file() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_token_file(tmp.path(), "{ malformed token json");
let logout = Command::new(&rkat)
.args(["auth", "logout", "default_openai"])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth logout must spawn");
if !logout.status.success() {
let stderr = String::from_utf8_lossy(&logout.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
panic!("logout must clear malformed token file; stderr={stderr}");
}
assert!(
!token_file_exists(tmp.path()),
"logout must remove malformed persisted credentials"
);
}
#[test]
fn rkat_auth_profile_delete_clears_malformed_token_file() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_managed_openai_realm_config(tmp.path());
seed_token_file(tmp.path(), "{ malformed token json");
let delete = Command::new(&rkat)
.args([
"--state-root",
tmp.path().join("realms").to_str().expect("utf8 path"),
"--realm",
"dev",
"auth",
"profile-delete",
"--realm",
"dev",
"default_openai",
"--yes",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth profile-delete must spawn");
if !delete.status.success() {
let stderr = String::from_utf8_lossy(&delete.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
panic!("profile delete must clear malformed token file; stderr={stderr}");
}
assert!(
!token_file_exists(tmp.path()),
"profile delete must remove malformed persisted credentials"
);
}
#[test]
fn rkat_auth_profile_delete_clears_binding_scoped_token_when_profile_id_differs() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_managed_openai_realm_config_with_ids(tmp.path(), "openai_managed", "default_openai");
seed_token_file_for_binding(tmp.path(), "default_openai", "{ malformed token json");
let delete = Command::new(&rkat)
.args([
"--state-root",
tmp.path().join("realms").to_str().expect("utf8 path"),
"--realm",
"dev",
"auth",
"profile-delete",
"--realm",
"dev",
"openai_managed",
"--yes",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth profile-delete must spawn");
if !delete.status.success() {
let stderr = String::from_utf8_lossy(&delete.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
panic!("profile delete must clear binding-scoped token file; stderr={stderr}");
}
let stdout = String::from_utf8_lossy(&delete.stdout);
assert!(
stdout.contains("deleted: dev:default_openai"),
"profile delete should report the binding-scoped token key; stdout:\n{stdout}"
);
assert!(
!token_file_path_for_binding(tmp.path(), "default_openai").exists(),
"profile delete must remove the token file keyed by the owning binding"
);
}
#[test]
fn rkat_auth_status_hides_token_metadata_without_lifecycle() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_managed_openai_realm_config(tmp.path());
seed_token_file(
tmp.path(),
r#"{
"auth_mode": "api_key",
"primary_secret": "sk-stale",
"refresh_token": "refresh-stale",
"expires_at": 1800000000,
"last_refresh": 1700000000,
"scopes": ["email"],
"account_id": "acct-stale"
}"#,
);
let status = Command::new(&rkat)
.args([
"--state-root",
tmp.path().join("realms").to_str().expect("utf8 path"),
"--realm",
"dev",
"auth",
"status",
"--realm",
"dev",
"default_openai",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth status must spawn");
if !status.status.success() {
let stderr = String::from_utf8_lossy(&status.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
panic!("status must succeed with seeded config; stderr={stderr}");
}
let stdout = String::from_utf8_lossy(&status.stdout);
assert!(
stdout.contains("state: missing_credential"),
"status should report missing_credential without a live AuthMachine lifecycle; stdout:\n{stdout}"
);
for forbidden in [
"auth_mode:",
"has_secret:",
"has_refresh:",
"expires_at:",
"last_refresh:",
"account_id:",
"scopes:",
"acct-stale",
"email",
] {
assert!(
!stdout.contains(forbidden),
"status must not expose token-derived `{forbidden}` without lifecycle; stdout:\n{stdout}"
);
}
}
#[test]
fn rkat_auth_status_fails_closed_on_malformed_token_storage() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_managed_openai_realm_config(tmp.path());
seed_token_file(tmp.path(), "{ malformed token json");
let status = Command::new(&rkat)
.args([
"--state-root",
tmp.path().join("realms").to_str().expect("utf8 path"),
"--realm",
"dev",
"auth",
"status",
"--realm",
"dev",
"default_openai",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth status must spawn");
let stderr = String::from_utf8_lossy(&status.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
assert!(
!status.status.success(),
"status must fail closed on malformed token storage; stdout:\n{}",
String::from_utf8_lossy(&status.stdout)
);
assert!(
stderr.contains("TokenStore rehydration failed"),
"failure must surface the typed token-store fault; stderr:\n{stderr}"
);
}
#[test]
fn rkat_auth_refresh_uses_binding_scoped_token_when_profile_id_differs() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_openai_realm_config_with_ids_and_method(
tmp.path(),
"openai_managed",
"default_openai",
"chatgpt_backend",
"managed_chatgpt_oauth",
);
seed_token_file(
tmp.path(),
r#"{
"auth_mode": "chatgpt_oauth",
"primary_secret": "fresh-chatgpt-access",
"refresh_token": "refresh-token",
"expires_at": 1893456000,
"last_refresh": 1700000000,
"scopes": ["openid", "email"],
"account_id": "acct-fresh",
"metadata": {
"meerkat_auth_lifecycle": {
"published": true,
"version": 2,
"expires_at": 1893456000
}
}
}"#,
);
let refresh = Command::new(&rkat)
.args([
"--state-root",
tmp.path().join("realms").to_str().expect("utf8 path"),
"--realm",
"dev",
"auth",
"refresh",
"--realm",
"dev",
"openai_managed",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth refresh must spawn");
if !refresh.status.success() {
let stderr = String::from_utf8_lossy(&refresh.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
if stderr.contains("token endpoint error") || stderr.contains("stale credential material") {
assert!(
!stderr.contains("missing secret for auth resolution"),
"refresh must resolve the binding-scoped token before reaching the provider; stderr={stderr}"
);
eprintln!("SKIP: fake refresh token was rejected after binding-scoped resolution");
return;
}
panic!("refresh must use the binding-scoped token key; stderr={stderr}");
}
let stdout = String::from_utf8_lossy(&refresh.stdout);
assert!(
stdout.contains("profile: dev:openai_managed"),
"refresh should still report the requested auth profile; stdout:\n{stdout}"
);
assert!(
stdout.contains("refresh: ok"),
"refresh must not skip when credentials exist under the owning binding; stdout:\n{stdout}"
);
assert!(
!stdout.contains("reason: no persisted credential"),
"refresh must not preflight token storage by auth profile id; stdout:\n{stdout}"
);
}
#[test]
fn rkat_auth_status_resolves_binding_that_references_auth_profile() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
seed_managed_openai_realm_config_with_ids(tmp.path(), "openai_managed", "default_openai");
let status = Command::new(&rkat)
.args([
"--state-root",
tmp.path().join("realms").to_str().expect("utf8 path"),
"--realm",
"dev",
"auth",
"status",
"--realm",
"dev",
"openai_managed",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path().join("config"))
.stdin(Stdio::null())
.output()
.expect("rkat auth status must spawn");
assert!(
status.status.success(),
"status must resolve binding-scoped lease identity from auth profile; stderr={}",
String::from_utf8_lossy(&status.stderr)
);
let stdout = String::from_utf8_lossy(&status.stdout);
assert!(
stdout.contains("binding_id: default_openai"),
"status must report the binding that owns the auth lease key; stdout:\n{stdout}"
);
assert!(
stdout.contains("state: missing_credential"),
"status should still be missing_credential without a live AuthMachine lifecycle; stdout:\n{stdout}"
);
}
#[test]
fn rkat_auth_profile_list_returns_cleanly_for_empty_realm() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
let out = Command::new(&rkat)
.args(["auth", "profile", "list", "--realm", "nonexistent-realm"])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path())
.stdin(Stdio::null())
.output()
.expect("rkat auth profile list must spawn");
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.is_empty(),
"non-zero exit must include stderr diagnostic"
);
assert!(
!stderr.contains("thread 'main' panicked"),
"CLI must not panic; stderr:\n{stderr}"
);
}
}
#[test]
fn rkat_run_auth_binding_flag_is_registered_and_routes() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
std::fs::create_dir_all(tmp.path().join(".rkat")).expect("mkdir .rkat");
let out = Command::new(&rkat)
.args(["run", "--auth-binding", "nonexistent:binding", "hi"])
.current_dir(tmp.path())
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path())
.env("RKAT_TEST_CLIENT", "1") .stdin(Stdio::null())
.output()
.expect("rkat run must spawn");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("unrecognized argument")
&& !stderr.contains("unexpected argument")
&& !stderr.contains("found argument '--auth-binding'"),
"--auth-binding must be a registered CLI flag; stderr:\n{stderr}"
);
assert!(
!stderr.contains("thread 'main' panicked"),
"rkat run must not panic on unknown realm; stderr:\n{stderr}"
);
}
#[test]
fn rkat_auth_logout_clears_scripted_login_token_key() {
let Some(rkat) = rkat_binary() else {
eprintln!("SKIP: rkat binary unavailable");
return;
};
let tmp = tempfile::TempDir::new().expect("tempdir");
let login = Command::new(&rkat)
.args([
"auth",
"login",
"openai",
"--non-interactive",
"--secret",
"sk-test",
])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path())
.stdin(Stdio::null())
.output()
.expect("rkat auth login must spawn");
if !login.status.success() {
let stderr = String::from_utf8_lossy(&login.stderr);
if stderr.contains("requires the `anthropic`, `openai`, and `gemini`") {
eprintln!("SKIP: auth provider features unavailable");
return;
}
panic!("scripted login failed: {stderr}");
}
let logout = Command::new(&rkat)
.args(["auth", "logout", "default_openai"])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path())
.stdin(Stdio::null())
.output()
.expect("rkat auth logout must spawn");
assert!(
logout.status.success(),
"logout must clear scripted login token; stderr={}",
String::from_utf8_lossy(&logout.stderr)
);
let repeat = Command::new(&rkat)
.args(["auth", "logout", "default_openai"])
.env("HOME", tmp.path())
.env("XDG_CONFIG_HOME", tmp.path())
.stdin(Stdio::null())
.output()
.expect("repeat rkat auth logout must spawn");
assert!(
!repeat.status.success(),
"repeat logout must not report success for already-cleared credentials"
);
let stderr = String::from_utf8_lossy(&repeat.stderr);
assert!(
stderr.contains("No credentials found"),
"repeat logout should explain that no token remains; stderr={stderr}"
);
assert!(
!stderr.contains("thread 'main' panicked"),
"repeat logout must not panic; stderr={stderr}"
);
}