mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
[codex-cli] Show reset details in redemption picker (#30488)
## Why Users can see that usage-limit reset credits are available, but not which credits they have, when each one expires, or which credit will be consumed. The TUI should use the supported rate-limit RPC for that information without maintaining a second reset-credit request path. ## What changed - load reset-credit details through the existing `account/rateLimits/read` refresh when the user opens **Redeem usage limit reset** - show available credits sorted by expiry, using the backend title when present and a scope-based fallback otherwise - consume the exact selected credit and preserve its idempotency key across retries - fall back to the existing generic reset action when the RPC returns a positive count without detail rows - remove the dedicated list-RPC event, request, response-combining, and TUI state plumbing - handle a selected credit becoming unavailable without incorrectly caching the user's total count as zero - handle forward-compatible unknown reset types with the existing scope-based fallback label ## TUI preview Rendered from the final standard-width and narrow `insta` snapshots in this PR. <img width="1280" height="570" alt="pr30488-reset-picker-preview" src="https://github.com/user-attachments/assets/8b84cd33-cb35-4a37-aaed-dbd6b62bff51" /> ## Validation - `just test -p codex-tui chatwidget::tests::usage` (32 passed) - `just fix -p codex-tui` - no pending `insta` snapshots Uses the reset-credit details added by #30395. Fixes #29618.
This commit is contained in:
10 files changed
+526
-256
No files matched your search
@@ -1,11 +1,81 @@
|
||||
use super::super::reset_credits::RateLimitResetScope;
|
||||
use super::super::reset_credits::ResetCreditOption;
|
||||
use super::super::reset_credits::reset_credit_options;
|
||||
use super::*;
|
||||
use chrono::TimeZone;
|
||||
use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditOutcome;
|
||||
use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditResponse;
|
||||
use codex_app_server_protocol::RateLimitResetCredit;
|
||||
use codex_app_server_protocol::RateLimitResetCreditStatus;
|
||||
use codex_app_server_protocol::RateLimitResetCreditsSummary;
|
||||
use codex_app_server_protocol::RateLimitResetType;
|
||||
use pretty_assertions::assert_eq;
|
||||
use uuid::Uuid;
|
||||
|
||||
const TEST_OVERLAY_VIEW_ID: &str = "usage-test-overlay";
|
||||
|
||||
fn reset_credits(available_count: i64) -> RateLimitResetCreditsSummary {
|
||||
RateLimitResetCreditsSummary {
|
||||
available_count,
|
||||
credits: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn detailed_reset_credits(
|
||||
available_count: i64,
|
||||
credits: Vec<RateLimitResetCredit>,
|
||||
) -> RateLimitResetCreditsSummary {
|
||||
RateLimitResetCreditsSummary {
|
||||
available_count,
|
||||
credits: Some(credits),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_credit(id: &str, expires_at: Option<i64>) -> RateLimitResetCredit {
|
||||
RateLimitResetCredit {
|
||||
id: id.to_string(),
|
||||
reset_type: RateLimitResetType::CodexRateLimits,
|
||||
status: RateLimitResetCreditStatus::Available,
|
||||
granted_at: 0,
|
||||
expires_at,
|
||||
title: None,
|
||||
description: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_credit_with_title(id: &str, expires_at: Option<i64>, title: &str) -> RateLimitResetCredit {
|
||||
RateLimitResetCredit {
|
||||
title: Some(title.to_string()),
|
||||
..reset_credit(id, expires_at)
|
||||
}
|
||||
}
|
||||
|
||||
fn expiry_timestamp(day: u32, hour: u32, minute: u32) -> i64 {
|
||||
chrono::Local
|
||||
.with_ymd_and_hms(2026, 6, day, hour, minute, 0)
|
||||
.single()
|
||||
.expect("valid test timestamp")
|
||||
.timestamp()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_credit_options_use_scope_label_for_unknown_reset_type() {
|
||||
let mut credit = reset_credit("future-credit", /*expires_at*/ None);
|
||||
credit.reset_type = RateLimitResetType::Unknown;
|
||||
|
||||
assert_eq!(
|
||||
reset_credit_options(
|
||||
&detailed_reset_credits(/*available_count*/ 1, vec![credit]),
|
||||
RateLimitResetScope::WeeklyAndFiveHour,
|
||||
),
|
||||
vec![ResetCreditOption {
|
||||
credit_id: Some("future-credit".to_string()),
|
||||
name: "Full reset (Weekly + 5h)".to_string(),
|
||||
description: "Does not expire.".to_string(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_command_opens_menu_when_reset_is_available_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
@@ -14,10 +84,7 @@ async fn usage_command_opens_menu_when_reset_is_available_snapshot() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
@@ -38,10 +105,7 @@ async fn usage_command_disables_reset_after_cached_zero_snapshot() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 0,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
@@ -69,10 +133,7 @@ async fn usage_menu_refresh_enables_newly_available_reset() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 0,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
@@ -85,10 +146,7 @@ async fn usage_menu_refresh_enables_newly_available_reset() {
|
||||
chat.finish_usage_menu_rate_limit_refresh(
|
||||
/*request_id*/ 1,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None,
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
@@ -104,10 +162,7 @@ async fn usage_menu_refresh_failure_preserves_disabled_known_zero() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 0,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
@@ -137,10 +192,7 @@ async fn account_update_invalidates_usage_menu_refresh_when_visible_state_is_unc
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
startup_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 0,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
assert_matches!(
|
||||
@@ -157,10 +209,7 @@ async fn account_update_invalidates_usage_menu_refresh_when_visible_state_is_unc
|
||||
chat.finish_usage_menu_rate_limit_refresh(
|
||||
/*request_id*/ 1,
|
||||
vec![snapshot(/*percent*/ 92.0)],
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None,
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
);
|
||||
|
||||
assert_eq!(chat.available_rate_limit_reset_credits, None);
|
||||
@@ -206,10 +255,7 @@ async fn usage_menu_rate_limit_reset_entry_opens_reset_flow() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
chat.dispatch_command(SlashCommand::Usage);
|
||||
|
||||
@@ -227,13 +273,38 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
|
||||
let loading_request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
record_popup(&chat, &mut states);
|
||||
let first_expiry = expiry_timestamp(/*day*/ 18, /*hour*/ 9, /*minute*/ 39);
|
||||
let second_expiry = expiry_timestamp(/*day*/ 27, /*hour*/ 8, /*minute*/ 59);
|
||||
let mut rate_limit_snapshot = snapshot(/*percent*/ 50.0);
|
||||
rate_limit_snapshot.limit_id = Some("codex".to_string());
|
||||
rate_limit_snapshot
|
||||
.primary
|
||||
.as_mut()
|
||||
.expect("primary window")
|
||||
.window_duration_mins = Some(5 * 60);
|
||||
rate_limit_snapshot.secondary = Some(RateLimitWindow {
|
||||
used_percent: 50,
|
||||
window_duration_mins: Some(7 * 24 * 60),
|
||||
resets_at: None,
|
||||
});
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
loading_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
vec![rate_limit_snapshot],
|
||||
Ok(detailed_reset_credits(
|
||||
/*available_count*/ 2,
|
||||
vec![
|
||||
reset_credit_with_title(
|
||||
"credit-2",
|
||||
Some(second_expiry),
|
||||
"Full reset (Weekly + 5 hr)",
|
||||
),
|
||||
reset_credit_with_title(
|
||||
"credit-1",
|
||||
Some(first_expiry),
|
||||
"Full reset (Weekly + 5 hr)",
|
||||
),
|
||||
],
|
||||
)),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
|
||||
@@ -242,10 +313,7 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
empty_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 0,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
|
||||
@@ -264,6 +332,7 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
assert!(!chat.finish_rate_limit_reset_consume(
|
||||
consuming_request_id,
|
||||
"redeem-1".to_string(),
|
||||
/*credit_id*/ None,
|
||||
Err("request timed out".to_string()),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
@@ -300,16 +369,36 @@ async fn rate_limit_reset_popup_states_snapshot() {
|
||||
assert!(chat.finish_post_consume_reset_credits_refresh(
|
||||
success_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
record_popup(&chat, &mut states);
|
||||
|
||||
assert_chatwidget_snapshot!("rate_limit_reset_popup_states", states.join("\n---\n"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rate_limit_reset_picker_wraps_expiry_details_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
let expiry = expiry_timestamp(/*day*/ 18, /*hour*/ 9, /*minute*/ 39);
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(detailed_reset_credits(
|
||||
/*available_count*/ 2,
|
||||
vec![
|
||||
reset_credit("credit-2", /*expires_at*/ None),
|
||||
reset_credit("credit-1", Some(expiry)),
|
||||
],
|
||||
)),
|
||||
));
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"rate_limit_reset_picker_narrow",
|
||||
render_bottom_popup(&chat, /*width*/ 44)
|
||||
);
|
||||
}
|
||||
|
||||
#[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;
|
||||
@@ -321,10 +410,10 @@ async fn usage_limit_reset_confirmation_uses_monthly_copy_for_monthly_limits_sna
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
free_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(detailed_reset_credits(
|
||||
/*available_count*/ 1,
|
||||
vec![reset_credit("free-credit", /*expires_at*/ None)],
|
||||
)),
|
||||
));
|
||||
states.push(format!(
|
||||
"Free:\n{}",
|
||||
@@ -337,10 +426,7 @@ async fn usage_limit_reset_confirmation_uses_monthly_copy_for_monthly_limits_sna
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
go_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
states.push(format!("Go:\n{}", render_bottom_popup(&chat, /*width*/ 80)));
|
||||
|
||||
@@ -356,10 +442,10 @@ async fn usage_limit_reset_confirmation_uses_monthly_copy_for_monthly_limits_sna
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
business_request_id,
|
||||
vec![monthly_business_snapshot],
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(detailed_reset_credits(
|
||||
/*available_count*/ 1,
|
||||
vec![reset_credit("business-credit", /*expires_at*/ None)],
|
||||
)),
|
||||
));
|
||||
states.push(format!(
|
||||
"Business with monthly window:\n{}",
|
||||
@@ -379,10 +465,7 @@ async fn rate_limit_reset_confirmation_selects_cancel_by_default() {
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
@@ -391,6 +474,49 @@ async fn rate_limit_reset_confirmation_selects_cancel_by_default() {
|
||||
assert!(rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rate_limit_reset_picker_starts_with_soonest_expiries_and_keeps_all_rows_reachable() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.plan_type = Some(PlanType::Business);
|
||||
let request_id = chat.show_rate_limit_reset_loading_popup();
|
||||
let first_expiry = expiry_timestamp(/*day*/ 18, /*hour*/ 9, /*minute*/ 39);
|
||||
let credits = (0..9)
|
||||
.rev()
|
||||
.map(|index| {
|
||||
reset_credit(
|
||||
&format!("credit-{index}"),
|
||||
Some(first_expiry + i64::from(index) * 86_400),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(detailed_reset_credits(/*available_count*/ 9, credits)),
|
||||
));
|
||||
|
||||
let rendered = render_bottom_popup(&chat, /*width*/ 80);
|
||||
assert!(
|
||||
rendered.contains("Expires 09:39 on 18 Jun 2026."),
|
||||
"{rendered}"
|
||||
);
|
||||
assert!(!rendered.contains("Full reset ("), "{rendered}");
|
||||
|
||||
for _ in 0..9 {
|
||||
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::ConsumeRateLimitResetCredit {
|
||||
idempotency_key,
|
||||
credit_id,
|
||||
}) if Uuid::parse_str(&idempotency_key).is_ok()
|
||||
&& credit_id.as_deref() == Some("credit-8")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rate_limit_reset_confirmation_can_use_reset() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
@@ -398,19 +524,18 @@ async fn rate_limit_reset_confirmation_can_use_reset() {
|
||||
assert!(chat.finish_rate_limit_reset_credits_refresh(
|
||||
request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
|
||||
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::ConsumeRateLimitResetCredit { idempotency_key })
|
||||
if Uuid::parse_str(&idempotency_key).is_ok()
|
||||
Ok(AppEvent::ConsumeRateLimitResetCredit {
|
||||
idempotency_key,
|
||||
credit_id,
|
||||
}) if Uuid::parse_str(&idempotency_key).is_ok() && credit_id.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -421,6 +546,7 @@ async fn rate_limit_reset_retry_reuses_idempotency_key() {
|
||||
assert!(!chat.finish_rate_limit_reset_consume(
|
||||
request_id,
|
||||
"stable-redeem-id".to_string(),
|
||||
Some("credit-1".to_string()),
|
||||
Err("response lost".to_string()),
|
||||
));
|
||||
|
||||
@@ -428,8 +554,11 @@ async fn rate_limit_reset_retry_reuses_idempotency_key() {
|
||||
|
||||
assert_matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::ConsumeRateLimitResetCredit { idempotency_key })
|
||||
if idempotency_key == "stable-redeem-id"
|
||||
Ok(AppEvent::ConsumeRateLimitResetCredit {
|
||||
idempotency_key,
|
||||
credit_id,
|
||||
}) if idempotency_key == "stable-redeem-id"
|
||||
&& credit_id.as_deref() == Some("credit-1")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,10 +570,7 @@ async fn no_credit_outcome_disables_reset_entry_in_usage_menu() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
startup_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 1,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
let consume_request_id = chat.show_rate_limit_reset_consuming_popup();
|
||||
assert!(!finish_reset_consume_outcome(
|
||||
@@ -466,6 +592,23 @@ async fn no_credit_outcome_disables_reset_entry_in_usage_menu() {
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenTokenActivity));
|
||||
|
||||
chat.available_rate_limit_reset_credits = Some(2);
|
||||
let consume_request_id = chat.show_rate_limit_reset_consuming_popup();
|
||||
assert!(!chat.finish_rate_limit_reset_consume(
|
||||
consume_request_id,
|
||||
"redeem-selected".to_string(),
|
||||
Some("stale-credit".to_string()),
|
||||
Ok(consume_response(
|
||||
ConsumeAccountRateLimitResetCreditOutcome::NoCredit
|
||||
)),
|
||||
));
|
||||
assert_eq!(chat.available_rate_limit_reset_credits, None);
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 80).contains("That reset is no longer available.")
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenRateLimitResetCredits));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -489,10 +632,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
dismiss_popup(&mut chat);
|
||||
assert!(chat.bottom_pane.no_modal_or_popup_active());
|
||||
@@ -523,10 +663,7 @@ 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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
@@ -542,10 +679,7 @@ async fn failed_post_consume_refresh_does_not_keep_stale_reset_count() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
startup_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
let consume_request_id = chat.show_rate_limit_reset_consuming_popup();
|
||||
assert!(finish_reset_consume_outcome(
|
||||
@@ -582,10 +716,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
assert!(chat.bottom_pane.no_modal_or_popup_active());
|
||||
}
|
||||
@@ -599,10 +730,7 @@ async fn clearing_pending_reset_hint_preserves_in_flight_redemption() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
|
||||
chat.clear_pending_rate_limit_reset_hint();
|
||||
@@ -625,10 +753,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
assert_eq!(
|
||||
chat.bottom_pane.active_view_id(),
|
||||
@@ -636,10 +761,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 usage limit resets available.")
|
||||
);
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 80).contains("2 usage limit resets available."));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -657,10 +779,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 1)),
|
||||
));
|
||||
assert_eq!(
|
||||
chat.bottom_pane.active_view_id(),
|
||||
@@ -703,10 +822,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
let rendered = lines_to_single_string(
|
||||
&chat
|
||||
@@ -729,10 +845,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
|
||||
assert!(chat.usage_history_insertion_blocked());
|
||||
@@ -763,10 +876,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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_none());
|
||||
}
|
||||
@@ -779,10 +889,7 @@ async fn starting_rate_limit_reset_redemption_clears_deferred_startup_hint() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_some());
|
||||
|
||||
@@ -800,10 +907,7 @@ 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,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 0)),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_none());
|
||||
}
|
||||
@@ -818,10 +922,7 @@ async fn startup_check_shows_reset_hint_for_workspace_account_with_credit() {
|
||||
assert!(chat.finish_rate_limit_reset_hint_refresh(
|
||||
hint_request_id,
|
||||
Vec::new(),
|
||||
Ok(RateLimitResetCreditsSummary {
|
||||
available_count: 2,
|
||||
credits: None
|
||||
}),
|
||||
Ok(reset_credits(/*available_count*/ 2)),
|
||||
));
|
||||
assert!(chat.pending_rate_limit_reset_hint().is_some());
|
||||
assert_eq!(chat.available_rate_limit_reset_credits, Some(2));
|
||||
@@ -842,6 +943,7 @@ fn finish_reset_consume_outcome(
|
||||
chat.finish_rate_limit_reset_consume(
|
||||
request_id,
|
||||
idempotency_key.to_string(),
|
||||
/*credit_id*/ None,
|
||||
Ok(consume_response(outcome)),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user