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::history_cell::HistoryCell;use crate::legacy_core::config::Config;use crate::legacy_core::config::ConfigBuilder;use crate::legacy_core::config::PermissionProfileSnapshot;use crate::status::StatusAccountDisplay;use crate::status::remote_connection::RemoteConnectionStatus;use crate::test_support::PathBufExt;use crate::test_support::test_path_buf;use crate::token_usage::TokenUsage;use crate::token_usage::TokenUsageInfo;use chrono::Duration as ChronoDuration;use chrono::Local;use chrono::TimeZone;use chrono::Utc;use codex_app_server_protocol::AskForApproval;use codex_app_server_protocol::CreditsSnapshot;use codex_app_server_protocol::RateLimitSnapshot;use codex_app_server_protocol::RateLimitWindow;use codex_app_server_protocol::SpendControlLimitSnapshot;use codex_config::LoaderOverrides;use codex_model_provider_info::ModelProviderAwsAuthInfo;use codex_model_provider_info::ModelProviderInfo;use codex_models_manager::test_support::construct_model_info_offline_for_tests;use codex_models_manager::test_support::get_model_offline_for_tests;use codex_protocol::ThreadId;use codex_protocol::config_types::ApprovalsReviewer;use codex_protocol::config_types::ReasoningSummary;use codex_protocol::models::ActivePermissionProfile;use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;use codex_protocol::models::ManagedFileSystemPermissions;use codex_protocol::models::PermissionProfile;use codex_protocol::openai_models::ReasoningEffort;use codex_protocol::permissions::FileSystemAccessMode;use codex_protocol::permissions::FileSystemPath;use codex_protocol::permissions::FileSystemSandboxEntry;use codex_protocol::permissions::FileSystemSpecialPath;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_utils_absolute_path::AbsolutePathBuf;use insta::assert_snapshot;use pretty_assertions::assert_eq;use ratatui::prelude::*;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( /*network_enabled*/ 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 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(), /*token_info*/ None, &usage, &None, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ None, None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 600)), }), secondary: Some(RateLimitWindow { used_percent: 45, window_duration_mins: Some(10080), resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_200)), }), credits: None, individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, reasoning_effort_override, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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_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( /*network_enabled*/ 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, /*exclude_tmpdir_env_var*/ false, /*exclude_slash_tmp*/ 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, /*exclude_tmpdir_env_var*/ false, /*exclude_slash_tmp*/ 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, /*exclude_tmpdir_env_var*/ false, /*exclude_slash_tmp*/ 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, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ None, None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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), /*remote_connection*/ None, test_status_account_display().as_ref(), /*token_info*/ None, &usage, &None, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ &[], None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, "<none>".to_string(), /*refreshing_rate_limits*/ false, ); let rendered = render_lines(&composite.display_lines(/*width*/ 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, /*runtime_model_provider_base_url*/ None, /*remote_connection*/ None, test_status_account_display().as_ref(), /*token_info*/ None, &usage, &None, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ &[], None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, "<none>".to_string(), /*refreshing_rate_limits*/ false, ); let rendered = render_lines(&composite.display_lines(/*width*/ 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(/*width*/ 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(/*width*/ 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, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ None, None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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), /*thread_name*/ None, Some(forked_from), /*rate_limits*/ None, None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 86_400)), }), secondary: None, credits: None, individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 86_400), }), 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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(/*width*/ 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, /*seconds*/ 86_400)), }), secondary: Some(RateLimitWindow { used_percent: 50, window_duration_mins: Some(3 * 60), resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 172_800)), }), credits: None, individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let rendered = render_lines(&composite.display_lines(/*width*/ 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, 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let rendered = render_lines(&composite.display_lines(/*width*/ 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_hides_zero_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, 4, 5, 6, 7, 8) .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("0".to_string()), }), individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let rendered = render_lines(&composite.display_lines(/*width*/ 120)); assert!( rendered.iter().all(|line| !line.contains("Credits:")), "expected no Credits line, got {rendered:?}" );}#[tokio::test]async fn status_snapshot_hides_when_has_no_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, 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let rendered = render_lines(&composite.display_lines(/*width*/ 120)); assert!( rendered.iter().all(|line| !line.contains("Credits:")), "expected no Credits line when has_credits is false, 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, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ None, None, now, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let rendered = render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 600)), }), secondary: None, credits: None, individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, reasoning_effort_override, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ None, None, now, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*runtime_model_provider_base_url*/ None, Some(&remote_connection), account_display.as_ref(), Some(&token_info), &usage, &None, /*thread_name*/ None, /*forked_from*/ None, &[], None, now, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ Some(Some(ReasoningEffort::Medium)), "<none>".to_string(), /*refreshing_rate_limits*/ false, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 900)), }), secondary: Some(RateLimitWindow { used_percent: 30, window_duration_mins: Some(10_080), resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_700)), }), credits: None, individual_limit: 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, /*account_display*/ None, Some(&token_info), &usage, &None, /*thread_name*/ None, /*forked_from*/ None, std::slice::from_ref(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, /*refreshing_rate_limits*/ true, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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_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, /*seconds*/ 900)), }), secondary: Some(RateLimitWindow { used_percent: 30, window_duration_mins: Some(10_080), resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_700)), }), credits: Some(CreditsSnapshot { has_credits: true, unlimited: false, balance: Some("37.5".to_string()), }), individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, 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, /*account_display*/ None, Some(&token_info), &usage, &None, /*thread_name*/ None, /*forked_from*/ None, std::slice::from_ref(&rate_display), None, captured_at, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, /*refreshing_rate_limits*/ true, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 600)), }), secondary: Some(RateLimitWindow { used_percent: 40, window_duration_mins: Some(10_080), resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_800)), }), credits: None, individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, now, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*seconds*/ 1_200)), }), secondary: Some(RateLimitWindow { used_percent: 35, window_duration_mins: Some(10_080), resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_400)), }), credits: Some(CreditsSnapshot { has_credits: false, unlimited: false, balance: Some("80".to_string()), }), individual_limit: 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, /*thread_name*/ None, /*forked_from*/ None, Some(&rate_display), None, now, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 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, /*thread_name*/ None, /*forked_from*/ None, /*rate_limits*/ None, None, now, &model_slug, /*collaboration_mode*/ None, /*reasoning_effort_override*/ None, ); let rendered_lines = render_lines(&composite.display_lines(/*width*/ 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}" );}