use super::new_status_output;
use super::new_status_output_with_rate_limits;
use super::new_status_output_with_rate_limits_handle;
use super::rate_limit_snapshot_display;
use super::rate_limits::RateLimitSnapshotDisplay;
use super::rate_limits::RateLimitWindowDisplay;
use super::rate_limits::SpendControlLimitSnapshotDisplay;
use super::rate_limits::StatusRateLimitData;
use super::rate_limits::compose_rate_limit_data_many;
use crate::tui_internal::history_cell::HistoryCell;
use crate::tui_internal::history_cell::PlainHistoryCell;
use crate::tui_internal::keymap::RuntimeKeymap;
use crate::tui_internal::legacy_core::config::Config;
use crate::tui_internal::legacy_core::config::ConfigBuilder;
use crate::tui_internal::legacy_core::config::PermissionProfileSnapshot;
use crate::tui_internal::pager_overlay::TranscriptOverlay;
use crate::tui_internal::status::StatusAccountDisplay;
use crate::tui_internal::status::remote_connection::RemoteConnectionStatus;
use crate::tui_internal::test_support::PathBufExt;
use crate::tui_internal::test_support::test_path_buf;
use crate::tui_internal::token_usage::TokenUsage;
use crate::tui_internal::token_usage::TokenUsageInfo;
use app_test_support::ChatGptAuthFixture;
use app_test_support::write_chatgpt_auth;
use app_test_support::write_models_cache;
use chrono::Duration as ChronoDuration;
use chrono::Local;
use chrono::TimeZone;
use chrono::Utc;
use lemurclaw_core::app_server_protocol::AskForApproval;
use lemurclaw_core::app_server_protocol::CreditsSnapshot;
use lemurclaw_core::app_server_protocol::RateLimitSnapshot;
use lemurclaw_core::app_server_protocol::RateLimitWindow;
use lemurclaw_core::app_server_protocol::SpendControlLimitSnapshot;
use lemurclaw_core::config::LoaderOverrides;
use lemurclaw_core::config::types::AuthCredentialsStoreMode;
use lemurclaw_core::model_provider_info::ModelProviderAwsAuthInfo;
use lemurclaw_core::model_provider_info::ModelProviderInfo;
use lemurclaw_core::models_manager::test_support::construct_model_info_offline_for_tests;
use lemurclaw_core::models_manager::test_support::get_model_offline_for_tests;
use lemurclaw_core::protocol::ThreadId;
use lemurclaw_core::protocol::config_types::ApprovalsReviewer;
use lemurclaw_core::protocol::config_types::ReasoningSummary;
use lemurclaw_core::protocol::models::ActivePermissionProfile;
use lemurclaw_core::protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
use lemurclaw_core::protocol::models::ManagedFileSystemPermissions;
use lemurclaw_core::protocol::models::PermissionProfile;
use lemurclaw_core::protocol::openai_models::ReasoningEffort;
use lemurclaw_core::protocol::permissions::FileSystemAccessMode;
use lemurclaw_core::protocol::permissions::FileSystemPath;
use lemurclaw_core::protocol::permissions::FileSystemSandboxEntry;
use lemurclaw_core::protocol::permissions::FileSystemSpecialPath;
use lemurclaw_core::protocol::permissions::NetworkSandboxPolicy;
use lemurclaw_core::utils_absolute_path::AbsolutePathBuf;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
use ratatui::prelude::*;
use std::sync::Arc;
use tempfile::TempDir;
use unicode_width::UnicodeWidthStr;
#[test]
fn stale_monthly_limit_marks_fresh_rolling_snapshot_stale() {
let now = Local::now();
let snapshot = RateLimitSnapshotDisplay {
limit_name: "codex".to_string(),
captured_at: now,
primary: Some(RateLimitWindowDisplay {
used_percent: 20.0,
resets_at: Some("soon".to_string()),
window_minutes: Some(300),
}),
secondary: None,
credits: None,
individual_limit: Some(SpendControlLimitSnapshotDisplay {
captured_at: now - ChronoDuration::minutes(20),
percent_remaining: 68.0,
used: "8,000".to_string(),
limit: "25,000".to_string(),
resets_at: Some("later".to_string()),
}),
};
assert!(matches!(
compose_rate_limit_data_many(&[snapshot], now),
StatusRateLimitData::Stale(_)
));
}
fn app_server_workspace_write_profile(network_enabled: bool) -> PermissionProfile {
PermissionProfile::Managed {
network: if network_enabled {
NetworkSandboxPolicy::Enabled
} else {
NetworkSandboxPolicy::Restricted
},
file_system: ManagedFileSystemPermissions::Restricted {
entries: vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { subpath: None },
},
access: FileSystemAccessMode::Write,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::SlashTmp,
},
access: FileSystemAccessMode::Write,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Tmpdir,
},
access: FileSystemAccessMode::Write,
},
],
glob_scan_max_depth: None,
},
}
}
async fn test_config(temp_home: &TempDir) -> Config {
let mut config = ConfigBuilder::default()
.codex_home(temp_home.path().to_path_buf())
.loader_overrides(LoaderOverrides::without_managed_config_for_tests())
.build()
.await
.expect("load config");
config.approvals_reviewer = ApprovalsReviewer::User;
config
.permissions
.set_permission_profile(app_server_workspace_write_profile(
true,
))
.expect("set permission profile");
config
}
fn set_workspace_cwd(config: &mut Config, cwd: AbsolutePathBuf) {
config.cwd = cwd.clone();
config.workspace_roots = vec![cwd];
config
.permissions
.set_workspace_roots(config.workspace_roots.clone());
}
fn test_status_account_display() -> Option<StatusAccountDisplay> {
None
}
fn token_info_for(model_slug: &str, config: &Config, usage: &TokenUsage) -> TokenUsageInfo {
let context_window =
construct_model_info_offline_for_tests(model_slug, &config.to_models_manager_config())
.context_window;
TokenUsageInfo {
total_token_usage: usage.clone(),
last_token_usage: usage.clone(),
model_context_window: context_window,
}
}
fn render_lines(lines: &[Line<'static>]) -> Vec<String> {
lines
.iter()
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.collect()
}
fn sanitize_directory(lines: Vec<String>) -> Vec<String> {
let frame_width = lines
.iter()
.find(|line| line.starts_with('â•'))
.map(|line| UnicodeWidthStr::width(line.as_str()));
lines
.into_iter()
.map(|line| {
if let (Some(frame_width), Some(dir_pos), Some(pipe_idx)) =
(frame_width, line.find("Directory: "), line.rfind('│'))
{
let prefix = &line[..dir_pos + "Directory: ".len()];
let suffix = &line[pipe_idx..];
let replacement = "[[workspace]]";
let content_width = frame_width.saturating_sub(
UnicodeWidthStr::width(prefix) + UnicodeWidthStr::width(suffix),
);
let mut rebuilt = prefix.to_string();
rebuilt.push_str(replacement);
let replacement_width = UnicodeWidthStr::width(replacement);
if content_width > replacement_width {
rebuilt.push_str(&" ".repeat(content_width - replacement_width));
}
rebuilt.push_str(suffix);
rebuilt
} else {
line
}
})
.collect()
}
fn buffer_to_text(buffer: &Buffer, width: u16) -> String {
let lines = buffer
.content
.chunks(usize::from(width))
.map(|row| {
row.iter()
.map(|cell| {
let symbol = cell.symbol();
symbol
.strip_prefix("\x1b]8;;")
.and_then(|symbol| symbol.split_once('\x07'))
.and_then(|(_, symbol)| symbol.strip_suffix("\x1b]8;;\x07"))
.unwrap_or(symbol)
})
.collect::<String>()
.trim_end()
.to_string()
})
.collect::<Vec<_>>();
sanitize_directory(lines).join("\n")
}
fn reset_at_from(captured_at: &chrono::DateTime<chrono::Local>, seconds: i64) -> i64 {
(*captured_at + ChronoDuration::seconds(seconds))
.with_timezone(&Utc)
.timestamp()
}
fn permissions_text_for(config: &Config) -> Option<String> {
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let composite = new_status_output(
config,
test_status_account_display().as_ref(),
None,
&usage,
&None,
None,
None,
None,
None,
captured_at,
&model_slug,
None,
None,
);
render_lines(&composite.display_lines( 80))
.iter()
.find(|line| line.contains("Permissions:"))
.and_then(|line| {
line.split("Permissions:")
.nth(1)
.map(str::trim)
.map(|text| text.trim_end_matches('│'))
.map(str::trim)
.map(ToString::to_string)
})
}
#[tokio::test]
async fn status_snapshot_includes_reasoning_details() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config.model_reasoning_summary = Some(ReasoningSummary::Detailed);
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
config
.permissions
.set_permission_profile(PermissionProfile::workspace_write())
.expect("set permission profile");
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
output_tokens: 900,
reasoning_output_tokens: 150,
total_tokens: 2_250,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 72,
window_duration_mins: Some(300),
resets_at: Some(reset_at_from(&captured_at, 600)),
}),
secondary: Some(RateLimitWindow {
used_percent: 45,
window_duration_mins: Some(10080),
resets_at: Some(reset_at_from(&captured_at, 1_200)),
}),
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let reasoning_effort_override = Some(Some(ReasoningEffort::High));
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
reasoning_effort_override,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_shows_chatgpt_plan_without_email() {
let temp_home = TempDir::new().expect("temp home");
write_models_cache(temp_home.path()).expect("write models cache");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config.cli_auth_credentials_store_mode = AuthCredentialsStoreMode::File;
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
write_chatgpt_auth(
temp_home.path(),
ChatGptAuthFixture::new("access-chatgpt").plan_type("enterprise"),
AuthCredentialsStoreMode::File,
)
.expect("write email-less ChatGPT auth");
let mut app_server = crate::tui_internal::start_embedded_app_server_for_picker(&config)
.await
.expect("start embedded app server");
let bootstrap = app_server
.bootstrap(&config)
.await
.expect("bootstrap app server session");
app_server.shutdown().await.expect("shut down app server");
let account_display = bootstrap
.status_account_display
.expect("bootstrap should return ChatGPT account display");
assert_eq!(
account_display,
StatusAccountDisplay::ChatGpt {
email: None,
plan: Some("Enterprise".to_string()),
}
);
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let composite = new_status_output(
&config,
Some(&account_display),
None,
&usage,
&None,
None,
None,
None,
None,
captured_at,
&model_slug,
None,
None,
);
let sanitized =
sanitize_directory(render_lines(&composite.display_lines( 80))).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_permissions_non_default_workspace_write_uses_workspace_label() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
config
.permissions
.set_permission_profile(app_server_workspace_write_profile(
true,
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Custom (workspace with network access, Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_named_read_only_profile_shows_builtin_label() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::read_only(),
ActivePermissionProfile::read_only(),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Read Only (Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_read_only_profile_shows_additional_writable_roots() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
let extra_root = test_path_buf("/workspace/extra").abs();
let file_system_policy = PermissionProfile::read_only()
.file_system_sandbox_policy()
.with_additional_writable_roots(config.cwd.as_path(), std::slice::from_ref(&extra_root));
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
ActivePermissionProfile::read_only(),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Read Only (Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_named_workspace_profile_shows_builtin_label() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::workspace_write(),
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Workspace (Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_workspace_auto_review_shows_reviewer_label() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.approvals_reviewer = ApprovalsReviewer::AutoReview;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::workspace_write(),
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Workspace (Approve for me)")
);
}
#[tokio::test]
async fn status_permissions_named_profile_shows_additional_writable_roots() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
let extra_root = test_path_buf("/workspace/extra").abs();
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::workspace_write_with(
std::slice::from_ref(&extra_root),
NetworkSandboxPolicy::Restricted,
false,
false,
),
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Workspace (Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_workspace_roots_show_additional_directories() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
let extra_root = test_path_buf("/workspace/extra").abs();
config.workspace_roots = vec![config.cwd.clone(), extra_root.clone()];
config
.permissions
.set_workspace_roots(config.workspace_roots.clone());
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::workspace_write(),
ActivePermissionProfile::new(":workspace"),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config),
Some(format!(
"Workspace [{}] (Ask for approval)",
extra_root.display()
))
);
}
#[tokio::test]
async fn status_permissions_workspace_roots_include_profile_defined_directories() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
let profile_root = test_path_buf("/workspace/shared").abs();
config
.permissions
.set_permission_profile_from_session_snapshot(
PermissionProfileSnapshot::active_with_profile_workspace_roots(
PermissionProfile::workspace_write_with(
std::slice::from_ref(&profile_root),
NetworkSandboxPolicy::Restricted,
false,
false,
),
ActivePermissionProfile::new(":workspace"),
vec![profile_root.clone()],
),
)
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config),
Some(format!(
"Workspace [{}] (Ask for approval)",
profile_root.display()
))
);
}
#[tokio::test]
async fn status_permissions_broadened_workspace_profile_shows_builtin_label() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::workspace_write_with(
&[],
NetworkSandboxPolicy::Enabled,
false,
false,
),
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Workspace with network access (Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_user_defined_profile_shows_name() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::read_only(),
ActivePermissionProfile::new("locked"),
))
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Profile locked (read-only, Ask for approval)")
);
}
#[tokio::test]
async fn status_snapshot_shows_active_user_defined_profile() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::read_only(),
ActivePermissionProfile::new("locked"),
))
.expect("set permission profile");
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
test_status_account_display().as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
None,
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_model_provider_uses_bedrock_runtime_base_url_and_gates_usage_link() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model_provider_id = "amazon-bedrock".to_string();
config.model_provider =
ModelProviderInfo::create_amazon_bedrock_provider(Some(ModelProviderAwsAuthInfo {
profile: None,
region: Some("eu-west-1".to_string()),
}));
config.model_provider.base_url =
Some("https://bedrock-mantle.us-east-1.api.aws/openai/v1".to_string());
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let runtime_base_url = "https://bedrock-mantle.eu-west-1.api.aws/openai/v1";
let (composite, _handle) = new_status_output_with_rate_limits_handle(
&config,
Some(runtime_base_url),
None,
test_status_account_display().as_ref(),
None,
&usage,
&None,
None,
None,
&[],
None,
captured_at,
&model_slug,
None,
None,
"<none>".to_string(),
false,
);
let rendered = render_lines(&composite.display_lines( 120)).join("\n");
assert!(
rendered.contains(&format!("Amazon Bedrock - {runtime_base_url}")),
"expected /status to render runtime Bedrock URL, got: {rendered}"
);
assert!(
!rendered.contains("bedrock-mantle.us-east-1"),
"expected /status to ignore configured Bedrock base URL, got: {rendered}"
);
assert!(
!rendered.contains("https://chatgpt.com/codex/settings/usage"),
"expected /status to hide ChatGPT usage link for Bedrock, got: {rendered}"
);
config.model_provider_id = "openai-proxy".to_string();
config.model_provider = ModelProviderInfo {
name: "OpenAI Proxy".to_string(),
base_url: Some("https://openai-proxy.example/v1".to_string()),
requires_openai_auth: true,
..ModelProviderInfo::default()
};
let (composite, _handle) = new_status_output_with_rate_limits_handle(
&config,
None,
None,
test_status_account_display().as_ref(),
None,
&usage,
&None,
None,
None,
&[],
None,
captured_at,
&model_slug,
None,
None,
"<none>".to_string(),
false,
);
let rendered = render_lines(&composite.display_lines( 120)).join("\n");
assert!(
rendered.contains("https://chatgpt.com/codex/settings/usage"),
"expected /status to show ChatGPT usage link for OpenAI-auth proxy, got: {rendered}"
);
let wide_destinations: Vec<String> = composite
.display_hyperlink_lines( 120)
.into_iter()
.flat_map(|line| line.hyperlinks.into_iter())
.map(|link| link.destination)
.collect();
assert_eq!(
wide_destinations,
vec!["https://chatgpt.com/codex/settings/usage"]
);
let narrow_destinations: Vec<String> = composite
.display_hyperlink_lines( 24)
.into_iter()
.flat_map(|line| line.hyperlinks.into_iter())
.map(|link| link.destination)
.collect();
assert_eq!(narrow_destinations, Vec::<String>::new());
}
#[tokio::test]
async fn status_snapshot_shows_auto_review_permissions() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
config.approvals_reviewer = ApprovalsReviewer::AutoReview;
config
.permissions
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
PermissionProfile::workspace_write(),
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE),
))
.expect("set permission profile");
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
test_status_account_display().as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
None,
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_permissions_full_disk_managed_with_network_is_danger_full_access() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
config
.permissions
.set_permission_profile(PermissionProfile::Managed {
network: NetworkSandboxPolicy::Enabled,
file_system: ManagedFileSystemPermissions::Unrestricted,
})
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Custom (danger-full-access, Ask for approval)")
);
}
#[tokio::test]
async fn status_permissions_full_disk_managed_without_network_is_external_sandbox() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config
.permissions
.approval_policy
.set(AskForApproval::OnRequest.to_core())
.expect("set approval policy");
config
.permissions
.set_permission_profile(PermissionProfile::Managed {
network: NetworkSandboxPolicy::Restricted,
file_system: ManagedFileSystemPermissions::Unrestricted,
})
.expect("set permission profile");
assert_eq!(
permissions_text_for(&config).as_deref(),
Some("Custom (external-sandbox, Ask for approval)")
);
}
#[tokio::test]
async fn status_snapshot_includes_forked_from() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 800,
cached_input_tokens: 0,
output_tokens: 400,
reasoning_output_tokens: 0,
total_tokens: 1_200,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 8, 9, 10, 11, 12)
.single()
.expect("valid time");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let session_id =
ThreadId::from_string("0f0f3c13-6cf9-4aa4-8b80-7d49c2f1be2e").expect("session id");
let forked_from =
ThreadId::from_string("e9f18a88-8081-4e51-9d4e-8af5cde2d8dd").expect("forked id");
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&Some(session_id),
None,
Some(forked_from),
None,
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_includes_monthly_limit() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 800,
cached_input_tokens: 0,
output_tokens: 400,
reasoning_output_tokens: 0,
total_tokens: 1_200,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 5, 6, 7, 8, 9)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 12,
window_duration_mins: Some(43_200),
resets_at: Some(reset_at_from(&captured_at, 86_400)),
}),
secondary: None,
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_includes_enterprise_monthly_credit_limit() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 800,
cached_input_tokens: 0,
output_tokens: 400,
reasoning_output_tokens: 0,
total_tokens: 1_200,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 5, 6, 7, 8, 9)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: None,
individual_limit: Some(SpendControlLimitSnapshot {
limit: "25000".to_string(),
used: "8000".to_string(),
remaining_percent: 68,
resets_at: reset_at_from(&captured_at, 86_400),
}),
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 92));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
let mut rendered_lines = render_lines(&composite.display_lines( 46));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(
"status_snapshot_wraps_enterprise_monthly_credit_details_in_narrow_terminal",
sanitized
);
}
#[tokio::test]
async fn status_snapshot_uses_generic_limit_labels_for_unsupported_windows() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 800,
cached_input_tokens: 0,
output_tokens: 400,
reasoning_output_tokens: 0,
total_tokens: 1_200,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 5, 6, 7, 8, 9)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 35,
window_duration_mins: Some(2 * 60),
resets_at: Some(reset_at_from(&captured_at, 86_400)),
}),
secondary: Some(RateLimitWindow {
used_percent: 50,
window_duration_mins: Some(3 * 60),
resets_at: Some(reset_at_from(&captured_at, 172_800)),
}),
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_shows_unlimited_credits() {
let temp_home = TempDir::new().expect("temp home");
let config = test_config(&temp_home).await;
let account_display = test_status_account_display();
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 2, 3, 4, 5, 6)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: Some(CreditsSnapshot {
has_credits: true,
unlimited: true,
balance: None,
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let rendered = render_lines(&composite.display_lines( 120));
assert!(
rendered
.iter()
.any(|line| line.contains("Credits:") && line.contains("Unlimited")),
"expected Credits: Unlimited line, got {rendered:?}"
);
}
#[tokio::test]
async fn status_snapshot_shows_positive_credits() {
let temp_home = TempDir::new().expect("temp home");
let config = test_config(&temp_home).await;
let account_display = test_status_account_display();
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 3, 4, 5, 6, 7)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: Some(CreditsSnapshot {
has_credits: true,
unlimited: false,
balance: Some("12.5".to_string()),
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let rendered = render_lines(&composite.display_lines( 120));
assert!(
rendered
.iter()
.any(|line| line.contains("Credits:") && line.contains("13 credits")),
"expected Credits line with rounded credits, got {rendered:?}"
);
}
#[tokio::test]
async fn status_snapshot_shows_available_credits_without_display_balance() {
let temp_home = TempDir::new().expect("temp home");
let config = test_config(&temp_home).await;
let account_display = test_status_account_display();
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 4, 5, 6, 7, 8)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
for balance in [
None,
Some(String::new()),
Some("0".to_string()),
Some("not-a-number".to_string()),
Some("inf".to_string()),
] {
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: Some(CreditsSnapshot {
has_credits: true,
unlimited: false,
balance,
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let rendered = render_lines(&composite.display_lines( 120));
assert!(
rendered
.iter()
.any(|line| line.contains("Credits:") && line.contains("Available")),
"expected Credits: Available line, got {rendered:?}"
);
}
}
#[tokio::test]
async fn status_snapshot_respects_unlimited_without_has_credits_flag() {
let temp_home = TempDir::new().expect("temp home");
let config = test_config(&temp_home).await;
let account_display = test_status_account_display();
let usage = TokenUsage::default();
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 5, 6, 7, 8, 9)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: Some(CreditsSnapshot {
has_credits: false,
unlimited: true,
balance: None,
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let rendered = render_lines(&composite.display_lines( 120));
assert!(
rendered
.iter()
.any(|line| line.contains("Credits:") && line.contains("Unlimited")),
"expected Credits: Unlimited line, got {rendered:?}"
);
}
#[tokio::test]
async fn status_card_token_usage_excludes_cached_tokens() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
output_tokens: 900,
reasoning_output_tokens: 0,
total_tokens: 2_100,
};
let now = chrono::Local
.with_ymd_and_hms(2024, 1, 1, 0, 0, 0)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
None,
None,
now,
&model_slug,
None,
None,
);
let rendered = render_lines(&composite.display_lines( 120));
assert!(
rendered.iter().all(|line| !line.contains("cached")),
"cached tokens should not be displayed, got: {rendered:?}"
);
}
#[tokio::test]
async fn status_snapshot_truncates_in_narrow_terminal() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
config.model_provider_id = "openai".to_string();
config.model_reasoning_summary = Some(ReasoningSummary::Detailed);
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
output_tokens: 900,
reasoning_output_tokens: 150,
total_tokens: 2_250,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 72,
window_duration_mins: Some(300),
resets_at: Some(reset_at_from(&captured_at, 600)),
}),
secondary: None,
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let reasoning_effort_override = Some(Some(ReasoningEffort::High));
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
reasoning_effort_override,
);
let mut rendered_lines = render_lines(&composite.display_lines( 70));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_shows_missing_limits_message() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
output_tokens: 250,
reasoning_output_tokens: 0,
total_tokens: 750,
};
let now = chrono::Local
.with_ymd_and_hms(2024, 2, 3, 4, 5, 6)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
None,
None,
now,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_uses_default_reasoning_when_config_empty() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
output_tokens: 250,
reasoning_output_tokens: 0,
total_tokens: 750,
};
let now = chrono::Local
.with_ymd_and_hms(2024, 2, 3, 4, 5, 6)
.single()
.expect("timestamp");
let remote_connection = RemoteConnectionStatus {
address: "unix:///tmp/codex-home/app-server-control/app-server-control.sock".to_string(),
version: "v0.133.0".to_string(),
};
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let (composite, _) = new_status_output_with_rate_limits_handle(
&config,
None,
Some(&remote_connection),
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
&[],
None,
now,
&model_slug,
None,
Some(Some(ReasoningEffort::Medium)),
"<none>".to_string(),
false,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_shows_refreshing_limits_notice() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
output_tokens: 250,
reasoning_output_tokens: 0,
total_tokens: 750,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 45,
window_duration_mins: Some(300),
resets_at: Some(reset_at_from(&captured_at, 900)),
}),
secondary: Some(RateLimitWindow {
used_percent: 30,
window_duration_mins: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 2_700)),
}),
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output_with_rate_limits(
&config,
None,
Some(&token_info),
&usage,
&None,
None,
None,
std::slice::from_ref(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
true,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn transcript_overlay_remeasures_status_after_rate_limit_refresh() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let usage = TokenUsage::default();
let now = Local
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let (status, handle) = new_status_output_with_rate_limits_handle(
&config,
None,
None,
None,
None,
&usage,
&None,
None,
None,
&[],
None,
now,
&model_slug,
None,
None,
"<none>".to_string(),
true,
);
let mut overlay =
TranscriptOverlay::new(vec![Arc::new(status)], RuntimeKeymap::defaults().pager);
let area = Rect::new(
0, 0, 80, 30,
);
let mut buffer = Buffer::empty(area);
overlay.render(area, &mut buffer);
let before = buffer_to_text(&buffer, area.width);
handle.finish_rate_limit_refresh(
&[RateLimitSnapshotDisplay {
limit_name: "spark".to_string(),
captured_at: now,
primary: Some(RateLimitWindowDisplay {
used_percent: 45.0,
resets_at: Some("soon".to_string()),
window_minutes: Some(300),
}),
secondary: Some(RateLimitWindowDisplay {
used_percent: 30.0,
resets_at: Some("later".to_string()),
window_minutes: Some(10_080),
}),
credits: None,
individual_limit: None,
}],
now,
);
overlay.insert_cell(Arc::new(PlainHistoryCell::new(vec!["next message".into()])));
buffer = Buffer::empty(area);
overlay.render(area, &mut buffer);
let after = buffer_to_text(&buffer, area.width);
assert!(
after.contains("spark limit"),
"status output was clipped: {after:?}"
);
assert!(
after.contains("5h limit"),
"status output was clipped: {after:?}"
);
assert!(
after.contains("Weekly limit"),
"status output was clipped: {after:?}"
);
insta::assert_snapshot!(
"transcript_overlay_status_rate_limit_refresh",
format!("before:\n{before}\n\nafter:\n{after}")
);
}
#[tokio::test]
async fn status_snapshot_includes_credits_and_limits() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 1_500,
cached_input_tokens: 100,
output_tokens: 600,
reasoning_output_tokens: 0,
total_tokens: 2_200,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 7, 8, 9, 10, 11)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 45,
window_duration_mins: Some(300),
resets_at: Some(reset_at_from(&captured_at, 900)),
}),
secondary: Some(RateLimitWindow {
used_percent: 30,
window_duration_mins: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 2_700)),
}),
credits: Some(CreditsSnapshot {
has_credits: true,
unlimited: false,
balance: None,
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_shows_unavailable_limits_message() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
output_tokens: 250,
reasoning_output_tokens: 0,
total_tokens: 750,
};
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
.single()
.expect("timestamp");
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_treats_refreshing_empty_limits_as_unavailable() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let usage = TokenUsage {
input_tokens: 500,
cached_input_tokens: 0,
output_tokens: 250,
reasoning_output_tokens: 0,
total_tokens: 750,
};
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: None,
secondary: None,
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
.single()
.expect("timestamp");
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output_with_rate_limits(
&config,
None,
Some(&token_info),
&usage,
&None,
None,
None,
std::slice::from_ref(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
true,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_shows_stale_limits_message() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex-max".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 1_200,
cached_input_tokens: 200,
output_tokens: 900,
reasoning_output_tokens: 150,
total_tokens: 2_250,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 72,
window_duration_mins: Some(300),
resets_at: Some(reset_at_from(&captured_at, 600)),
}),
secondary: Some(RateLimitWindow {
used_percent: 40,
window_duration_mins: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 1_800)),
}),
credits: None,
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let now = captured_at + ChronoDuration::minutes(20);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
now,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_snapshot_cached_limits_hide_credits_without_flag() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model = Some("gpt-5.1-codex".to_string());
set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs());
let account_display = test_status_account_display();
let usage = TokenUsage {
input_tokens: 900,
cached_input_tokens: 200,
output_tokens: 350,
reasoning_output_tokens: 0,
total_tokens: 1_450,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 9, 10, 11, 12, 13)
.single()
.expect("timestamp");
let snapshot = RateLimitSnapshot {
limit_id: None,
limit_name: None,
primary: Some(RateLimitWindow {
used_percent: 60,
window_duration_mins: Some(300),
resets_at: Some(reset_at_from(&captured_at, 1_200)),
}),
secondary: Some(RateLimitWindow {
used_percent: 35,
window_duration_mins: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 2_400)),
}),
credits: Some(CreditsSnapshot {
has_credits: false,
unlimited: false,
balance: Some("80".to_string()),
}),
individual_limit: None,
spend_control_reached: None,
plan_type: None,
rate_limit_reached_type: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let now = captured_at + ChronoDuration::minutes(20);
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = token_info_for(&model_slug, &config, &usage);
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&usage,
&None,
None,
None,
Some(&rate_display),
None,
now,
&model_slug,
None,
None,
);
let mut rendered_lines = render_lines(&composite.display_lines( 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
}
}
let sanitized = sanitize_directory(rendered_lines).join("\n");
assert_snapshot!(sanitized);
}
#[tokio::test]
async fn status_context_window_uses_last_usage() {
let temp_home = TempDir::new().expect("temp home");
let mut config = test_config(&temp_home).await;
config.model_context_window = Some(272_000);
let account_display = test_status_account_display();
let total_usage = TokenUsage {
input_tokens: 12_800,
cached_input_tokens: 0,
output_tokens: 879,
reasoning_output_tokens: 0,
total_tokens: 102_000,
};
let last_usage = TokenUsage {
input_tokens: 12_800,
cached_input_tokens: 0,
output_tokens: 879,
reasoning_output_tokens: 0,
total_tokens: 13_679,
};
let now = chrono::Local
.with_ymd_and_hms(2024, 6, 1, 12, 0, 0)
.single()
.expect("timestamp");
let model_slug = get_model_offline_for_tests(config.model.as_deref());
let token_info = TokenUsageInfo {
total_token_usage: total_usage.clone(),
last_token_usage: last_usage,
model_context_window: config.model_context_window,
};
let composite = new_status_output(
&config,
account_display.as_ref(),
Some(&token_info),
&total_usage,
&None,
None,
None,
None,
None,
now,
&model_slug,
None,
None,
);
let rendered_lines = render_lines(&composite.display_lines( 80));
let context_line = rendered_lines
.into_iter()
.find(|line| line.contains("Context window"))
.expect("context line");
assert!(
context_line.contains("13.7K used / 272K"),
"expected context line to reflect last usage tokens, got: {context_line}"
);
assert!(
!context_line.contains("102K"),
"context line should not use total aggregated tokens, got: {context_line}"
);
}