mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
[codex] Fix usage-limit reset copy and state (#28793)
## Why The reset flow introduced in #28154 still describes earned reset credits as "rate-limit resets" and uses generic reset-scope copy. It can also retain a stale available-credit count after redemption or an account change, leaving the reset action enabled after the last credit is used. This follow-up updates terminology only within that reset feature. Existing rate-limit wording elsewhere in the CLI and TUI is unchanged. ## What changed - Rename reset-specific `/usage` menu items, startup hints, and reset dialogs to "usage limit reset." - Describe monthly resets for Free, Go, and accounts that report a monthly usage window; otherwise describe the current 5-hour and weekly limits. - Recheck a cached zero balance when `/usage` is reopened, and refresh the balance after redemption so the final reset immediately disables the action. - Correlate async refresh results before updating snapshots and clear account-derived reset state, warnings, prompts, and status surfaces when the account changes. ## Validation - `just test -p codex-tui chatwidget::tests::usage` — 29 passed. - `just test -p codex-tui chatwidget::tests::status_command_tests` — 7 passed. - Account-boundary prompt and plan-mode prompt regression tests passed. - `cargo insta pending-snapshots` from `codex-rs/tui` — no pending snapshots.\ <img width="814" height="318" alt="image" src="https://github.com/user-attachments/assets/2a460e96-458b-4805-8d9f-c759382d21a4" /> view for monthly <img width="905" height="243" alt="image" src="https://github.com/user-attachments/assets/179f88e3-08fb-4af5-8dc6-ce6a944ed681" />
This commit is contained in:
20 files changed
+496
-112
No files matched your search
@@ -1032,6 +1032,39 @@ async fn rate_limit_switch_prompt_shows_once_per_session() {
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn account_update_clears_derived_usage_limit_state_and_prompt() {
|
||||
let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let mut limits = snapshot(/*percent*/ 95.0);
|
||||
limits.rate_limit_reached_type = Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached);
|
||||
chat.on_rate_limit_snapshot(Some(limits));
|
||||
chat.maybe_show_pending_rate_limit_prompt();
|
||||
|
||||
assert!(chat.rate_limit_warnings.primary_index > 0);
|
||||
assert!(chat.codex_rate_limit_reached_type.is_some());
|
||||
assert!(matches!(
|
||||
chat.rate_limit_switch_prompt,
|
||||
RateLimitSwitchPromptState::Shown
|
||||
));
|
||||
assert!(!chat.bottom_pane.no_modal_or_popup_active());
|
||||
|
||||
chat.update_account_state(
|
||||
/*status_account_display*/ None, /*plan_type*/ None,
|
||||
/*has_chatgpt_account*/ true, /*has_codex_backend_auth*/ true,
|
||||
);
|
||||
|
||||
assert_eq!(chat.rate_limit_warnings.primary_index, 0);
|
||||
assert_eq!(chat.rate_limit_warnings.secondary_index, 0);
|
||||
assert_eq!(chat.codex_rate_limit_reached_type, None);
|
||||
assert!(matches!(
|
||||
chat.rate_limit_switch_prompt,
|
||||
RateLimitSwitchPromptState::Idle
|
||||
));
|
||||
assert!(chat.rate_limit_snapshots_by_limit_id.is_empty());
|
||||
assert!(chat.bottom_pane.no_modal_or_popup_active());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rate_limit_switch_prompt_respects_hidden_notice() {
|
||||
let (mut chat, _, _) = make_chatwidget_manual(Some("gpt-5")).await;
|
||||
|
||||
@@ -46,8 +46,7 @@ async fn status_command_refresh_updates_cached_limits_for_future_status_outputs(
|
||||
other => panic!("expected rate-limit refresh request, got {other:?}"),
|
||||
};
|
||||
|
||||
chat.on_rate_limit_snapshot(Some(snapshot(/*percent*/ 92.0)));
|
||||
chat.finish_status_rate_limit_refresh(first_request_id);
|
||||
chat.finish_status_rate_limit_refresh(first_request_id, vec![snapshot(/*percent*/ 92.0)]);
|
||||
drain_insert_history(&mut rx);
|
||||
|
||||
chat.dispatch_command(SlashCommand::Status);
|
||||
@@ -170,10 +169,31 @@ async fn status_command_overlapping_refreshes_update_matching_cells_only() {
|
||||
"expected /status to avoid transient refresh text in terminal history, got: {second_rendered}"
|
||||
);
|
||||
|
||||
chat.finish_status_rate_limit_refresh(first_request_id);
|
||||
chat.finish_status_rate_limit_refresh(first_request_id, Vec::new());
|
||||
pretty_assertions::assert_eq!(chat.refreshing_status_outputs.len(), 1);
|
||||
|
||||
chat.on_rate_limit_snapshot(Some(snapshot(/*percent*/ 92.0)));
|
||||
chat.finish_status_rate_limit_refresh(second_request_id);
|
||||
chat.finish_status_rate_limit_refresh(second_request_id, vec![snapshot(/*percent*/ 92.0)]);
|
||||
assert!(chat.refreshing_status_outputs.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn account_update_rejects_stale_status_rate_limit_snapshots() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
chat.dispatch_command(SlashCommand::Status);
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::InsertHistoryCell(_)));
|
||||
let request_id = match rx.try_recv() {
|
||||
Ok(AppEvent::RefreshRateLimits {
|
||||
origin: RateLimitRefreshOrigin::StatusCommand { request_id },
|
||||
}) => request_id,
|
||||
other => panic!("expected status refresh request, got {other:?}"),
|
||||
};
|
||||
|
||||
chat.update_account_state(
|
||||
/*status_account_display*/ None, /*plan_type*/ None,
|
||||
/*has_chatgpt_account*/ true, /*has_codex_backend_auth*/ true,
|
||||
);
|
||||
chat.finish_status_rate_limit_refresh(request_id, vec![snapshot(/*percent*/ 92.0)]);
|
||||
|
||||
assert!(chat.rate_limit_snapshots_by_limit_id.is_empty());
|
||||
}
|
||||
@@ -13,6 +13,7 @@ async fn usage_command_opens_menu_when_reset_is_available_snapshot() {
|
||||
let request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
|
||||
@@ -27,12 +28,13 @@ async fn usage_command_opens_menu_when_reset_is_available_snapshot() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_command_can_recheck_reset_availability_after_cached_zero_snapshot() {
|
||||
async fn usage_command_disables_reset_after_cached_zero_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
|
||||
@@ -42,11 +44,109 @@ async fn usage_command_can_recheck_reset_availability_after_cached_zero_snapshot
|
||||
"usage_command_menu_without_resets",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
);
|
||||
assert_matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::RefreshRateLimits {
|
||||
origin: RateLimitRefreshOrigin::UsageMenu { request_id: 1 }
|
||||
})
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenTokenActivity));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_menu_refresh_enables_newly_available_reset() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
assert_matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::RefreshRateLimits {
|
||||
origin: RateLimitRefreshOrigin::UsageMenu { request_id: 1 }
|
||||
})
|
||||
);
|
||||
chat.finish_usage_menu_rate_limit_refresh(
|
||||
/*request_id*/ 1,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenRateLimitResetCredits));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_menu_refresh_failure_preserves_disabled_known_zero() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
assert_matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::RefreshRateLimits {
|
||||
origin: RateLimitRefreshOrigin::UsageMenu { request_id: 1 }
|
||||
})
|
||||
);
|
||||
chat.finish_usage_menu_rate_limit_refresh(
|
||||
/*request_id*/ 1,
|
||||
Vec::new(),
|
||||
Err("backend unavailable".to_string()),
|
||||
);
|
||||
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 80).contains("No usage limit resets available."));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenTokenActivity));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn account_update_invalidates_usage_menu_refresh_when_visible_state_is_unchanged() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let startup_request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
startup_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
assert_matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::RefreshRateLimits {
|
||||
origin: RateLimitRefreshOrigin::UsageMenu { request_id: 1 }
|
||||
})
|
||||
);
|
||||
|
||||
chat.update_account_state(
|
||||
/*status_account_display*/ None, /*plan_type*/ None,
|
||||
/*has_chatgpt_account*/ true, /*has_codex_backend_auth*/ true,
|
||||
);
|
||||
chat.finish_usage_menu_rate_limit_refresh(
|
||||
/*request_id*/ 1,
|
||||
vec![snapshot(/*percent*/ 92.0)],
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
);
|
||||
|
||||
assert_eq!(chat.available_rate_limit_reset_credits, None);
|
||||
assert!(chat.rate_limit_snapshots_by_limit_id.is_empty());
|
||||
assert!(chat.bottom_pane.no_modal_or_popup_active());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_command_can_check_reset_availability_before_startup_refresh_finishes_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
@@ -65,7 +165,7 @@ async fn usage_command_can_check_reset_availability_before_startup_refresh_finis
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_command_disables_reset_for_workspace_accounts() {
|
||||
async fn usage_command_can_check_reset_availability_for_workspace_accounts() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
chat.plan_type = Some(PlanType::Business);
|
||||
@@ -74,7 +174,7 @@ async fn usage_command_disables_reset_for_workspace_accounts() {
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenTokenActivity));
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenRateLimitResetCredits));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -84,6 +184,7 @@ async fn usage_menu_rate_limit_reset_entry_opens_reset_flow() {
|
||||
let request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
@@ -104,6 +205,7 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
record_popup(&chat, &mut states);
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
loading_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
@@ -112,6 +214,7 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
let empty_request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
empty_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
@@ -120,6 +223,7 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
let load_error_request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
load_error_request_id,
|
||||
Vec::new(),
|
||||
Err("backend unavailable".to_string()),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
@@ -165,6 +269,7 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
record_popup(&chat, &mut states);
|
||||
assert!(chat.finish_post_consume_reset_credits_refresh(
|
||||
success_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
@@ -172,12 +277,66 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
assert_chatwidget_snapshot!("rate_limit_reset_popup_states", states.join("\n---\n"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_limit_reset_confirmation_uses_monthly_copy_for_monthly_limits_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let mut states = Vec::new();
|
||||
|
||||
chat.plan_type = Some(PlanType::Free);
|
||||
let free_request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
free_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
states.push(format!(
|
||||
"Free:\n{}",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
));
|
||||
|
||||
dismiss_popup(&mut chat);
|
||||
chat.plan_type = Some(PlanType::Go);
|
||||
let go_request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
go_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
states.push(format!("Go:\n{}", render_bottom_popup(&chat, /*width*/ 80)));
|
||||
|
||||
dismiss_popup(&mut chat);
|
||||
let mut monthly_business_snapshot = snapshot(/*percent*/ 50.0);
|
||||
monthly_business_snapshot.plan_type = Some(PlanType::Business);
|
||||
monthly_business_snapshot
|
||||
.primary
|
||||
.as_mut()
|
||||
.expect("monthly business snapshot primary window")
|
||||
.window_duration_mins = Some(30 * 24 * 60);
|
||||
let business_request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
business_request_id,
|
||||
vec![monthly_business_snapshot],
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
states.push(format!(
|
||||
"Business with monthly window:\n{}",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
));
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"usage_limit_reset_confirmation_monthly",
|
||||
states.join("\n---\n")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rate_limit_reset_confirmation_selects_cancel_by_default() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
|
||||
@@ -193,6 +352,7 @@ async fn rate_limit_reset_confirmation_can_use_reset() {
|
||||
let request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
|
||||
@@ -226,12 +386,13 @@ async fn rate_limit_reset_retry_reuses_idempotency_key() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_credit_outcome_allows_reset_availability_recheck() {
|
||||
async fn no_credit_outcome_disables_reset_entry_in_usage_menu() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
let startup_request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
startup_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
let consume_request_id = chat.show_rate_limit_reset_consuming_popup();
|
||||
@@ -244,10 +405,16 @@ async fn no_credit_outcome_allows_reset_availability_recheck() {
|
||||
dismiss_popup(&mut chat);
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
assert_matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::RefreshRateLimits {
|
||||
origin: RateLimitRefreshOrigin::UsageMenu { request_id: 2 }
|
||||
})
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenRateLimitResetCredits));
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenTokenActivity));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -270,6 +437,7 @@ async fn rate_limit_reset_redemption_cannot_be_dismissed_while_in_flight() {
|
||||
|
||||
assert!(chat.finish_post_consume_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
dismiss_popup(&mut chat);
|
||||
@@ -300,11 +468,12 @@ async fn already_redeemed_is_an_idempotent_success() {
|
||||
));
|
||||
assert!(chat.finish_post_consume_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
.contains("Usage reset. You have 0 rate-limit resets left.")
|
||||
.contains("Usage reset. You have 0 usage limit resets left.")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -315,6 +484,7 @@ async fn failed_post_consume_refresh_does_not_keep_stale_reset_count() {
|
||||
let startup_request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
startup_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
let consume_request_id = chat.show_rate_limit_reset_consuming_popup();
|
||||
@@ -327,6 +497,7 @@ async fn failed_post_consume_refresh_does_not_keep_stale_reset_count() {
|
||||
|
||||
assert!(chat.finish_post_consume_reset_credits_refresh(
|
||||
consume_request_id,
|
||||
Vec::new(),
|
||||
Err("backend unavailable".to_string()),
|
||||
));
|
||||
dismiss_popup(&mut chat);
|
||||
@@ -334,7 +505,7 @@ async fn failed_post_consume_refresh_does_not_keep_stale_reset_count() {
|
||||
|
||||
let rendered = render_bottom_popup(&chat, /*width*/ 80);
|
||||
assert!(rendered.contains("Check reset availability."));
|
||||
assert!(!rendered.contains("You have 2 rate-limit resets available."));
|
||||
assert!(!rendered.contains("You have 2 usage limit resets available."));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -350,6 +521,7 @@ async fn account_change_invalidates_pending_reset_requests() {
|
||||
|
||||
assert!(!chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
assert!(chat.bottom_pane.no_modal_or_popup_active());
|
||||
@@ -363,6 +535,7 @@ async fn clearing_pending_reset_hint_preserves_in_flight_redemption() {
|
||||
let hint_request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
|
||||
@@ -385,6 +558,7 @@ async fn rate_limit_reset_load_result_updates_popup_beneath_overlay() {
|
||||
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
assert_eq!(
|
||||
@@ -395,7 +569,7 @@ async fn rate_limit_reset_load_result_updates_popup_beneath_overlay() {
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
.contains("You have 2 rate-limit resets available.")
|
||||
.contains("You have 2 usage limit resets available.")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -413,6 +587,7 @@ async fn rate_limit_reset_success_updates_popup_beneath_overlay() {
|
||||
));
|
||||
assert!(chat.finish_post_consume_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 1 }),
|
||||
));
|
||||
assert_eq!(
|
||||
@@ -423,7 +598,7 @@ async fn rate_limit_reset_success_updates_popup_beneath_overlay() {
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
.contains("Usage reset. You have 1 rate-limit reset left.")
|
||||
.contains("Usage reset. You have 1 usage limit reset left.")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -455,6 +630,7 @@ async fn startup_check_shows_available_reset_hint_snapshot() {
|
||||
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
let rendered = lines_to_single_string(
|
||||
@@ -477,6 +653,7 @@ async fn startup_reset_hint_waits_for_active_output_snapshot() {
|
||||
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
|
||||
@@ -507,6 +684,7 @@ async fn opening_rate_limit_reset_flow_invalidates_in_flight_startup_hint() {
|
||||
|
||||
assert!(!chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_none());
|
||||
@@ -519,6 +697,7 @@ async fn starting_rate_limit_reset_redemption_clears_deferred_startup_hint() {
|
||||
let hint_request_id = chat.start_rate_limit_reset_startup_check();
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_some());
|
||||
@@ -536,13 +715,14 @@ async fn startup_check_omits_reset_hint_when_none_are_available() {
|
||||
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 0 }),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn startup_check_omits_reset_hint_for_workspace_accounts() {
|
||||
async fn startup_check_shows_reset_hint_for_workspace_account_with_credit() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
set_chatgpt_auth(&mut chat);
|
||||
chat.plan_type = Some(PlanType::Business);
|
||||
@@ -550,10 +730,11 @@ async fn startup_check_omits_reset_hint_for_workspace_accounts() {
|
||||
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary { available_count: 2 }),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_none());
|
||||
assert_eq!(chat.available_rate_limit_reset_credits, None);
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_some());
|
||||
assert_eq!(chat.available_rate_limit_reset_credits, Some(2));
|
||||
}
|
||||
|
||||
fn consume_response(
|
||||
|
||||
Reference in new issue
Block a user