use super::{ find_next_for_strategy, strategy_metric };
use crate::usage::sort::sort_indices;
use crate::usage::types::{ AccountQuota, SortStrategy, PreferStrategy };
use crate::usage::test_support::
{
FAR_FUTURE_MS,
mk_aq_sort, mk_aq_with_7d_reset, mk_aq_with_7d_reset_util,
reset_iso_at,
};
#[ test ]
fn test_find_next_for_strategy_some_when_eligible_none_when_all_current()
{
let now = 0u64;
let mut a = mk_aq_sort( "a@test.com", 20.0, FAR_FUTURE_MS );
a.is_current = true;
let b = mk_aq_sort( "b@test.com", 30.0, FAR_FUTURE_MS ); let c = mk_aq_sort( "c@test.com", 60.0, FAR_FUTURE_MS ); let accounts = vec![ a, b, c ];
let winner_a = find_next_for_strategy( &accounts, SortStrategy::Name, PreferStrategy::Any, now, false );
assert!( winner_a.is_some(), "find_next_for_strategy must return Some when eligible candidates exist" );
let winner_idx = winner_a.unwrap();
assert_eq!(
accounts[ winner_idx ].name, "b@test.com",
"name strategy winner must be b@test.com (first eligible alphabetically); got index {winner_idx}",
);
let mut a2 = mk_aq_sort( "a@test.com", 20.0, FAR_FUTURE_MS );
let mut b2 = mk_aq_sort( "b@test.com", 30.0, FAR_FUTURE_MS );
let mut c2 = mk_aq_sort( "c@test.com", 60.0, FAR_FUTURE_MS );
a2.is_current = true;
b2.is_current = true;
c2.is_current = true;
let all_current = vec![ a2, b2, c2 ];
let winner_b = find_next_for_strategy( &all_current, SortStrategy::Name, PreferStrategy::Any, now, false );
assert!( winner_b.is_none(), "find_next_for_strategy must return None when all accounts are is_current" );
}
#[ test ]
fn test_all_strategies_skip_occupied_elsewhere()
{
let now = 0u64;
let mut a = mk_aq_sort( "occupied@test.com", 50.0, FAR_FUTURE_MS );
a.is_occupied_elsewhere = true;
let b = mk_aq_sort( "free@test.com", 50.0, FAR_FUTURE_MS );
let mut c = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
c.is_current = true;
let accounts = vec![ a, b, c ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: must pick free@test.com (index 1), skipping occupied@test.com",
);
}
let mut a2 = mk_aq_sort( "occupied@test.com", 50.0, FAR_FUTURE_MS );
a2.is_occupied_elsewhere = true;
let mut c2 = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
c2.is_current = true;
let no_free = vec![ a2, c2 ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &no_free, strategy, PreferStrategy::Any, now, false );
assert!(
result.is_none(),
"{strategy:?}: must return None when only occupied + current remain",
);
}
}
#[ test ]
fn test_all_strategies_skip_h_exhausted()
{
let now = 0u64;
let a = mk_aq_sort( "exhausted@test.com", 92.0, FAR_FUTURE_MS );
let b = mk_aq_sort( "healthy@test.com", 70.0, FAR_FUTURE_MS );
let mut c = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
c.is_current = true;
let accounts = vec![ a, b, c ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: must pick healthy@test.com (index 1), skipping h-exhausted (8% left)",
);
}
let boundary = mk_aq_sort( "boundary@test.com", 85.0, FAR_FUTURE_MS );
let b2 = mk_aq_sort( "healthy@test.com", 70.0, FAR_FUTURE_MS );
let mut c2 = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
c2.is_current = true;
let boundary_accounts = vec![ boundary, b2, c2 ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &boundary_accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: utilization=85.0 (exactly 15% left) must be treated as h-exhausted",
);
}
let a3 = mk_aq_sort( "exhausted@test.com", 92.0, FAR_FUTURE_MS );
let mut c3 = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
c3.is_current = true;
let no_healthy = vec![ a3, c3 ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &no_healthy, strategy, PreferStrategy::Any, now, false );
assert!(
result.is_none(),
"{strategy:?}: must return None when only h-exhausted + current remain",
);
}
}
#[ test ]
fn test_cc_five_hour_none_not_h_exhausted()
{
let now = 0u64;
let mut a = mk_aq_sort( "no5h@test.com", 50.0, FAR_FUTURE_MS );
if let Ok( ref mut d ) = a.result { d.five_hour = None; }
let mut b = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
b.is_current = true;
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 0 ),
"{strategy:?}: five_hour=None must NOT be treated as h-exhausted",
);
}
}
#[ test ]
fn test_cc_h_exhausted_boundary_below_threshold()
{
let now = 0u64;
let a = mk_aq_sort( "just_below@test.com", 84.9, FAR_FUTURE_MS );
let mut b = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
b.is_current = true;
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 0 ),
"{strategy:?}: utilization=84.9 (15.1% left) must be eligible — only >= 85.0 is h-exhausted",
);
}
}
#[ test ]
fn test_cc_occupied_and_h_exhausted_skipped()
{
let now = 0u64;
let mut a = mk_aq_sort( "both@test.com", 92.0, FAR_FUTURE_MS );
a.is_occupied_elsewhere = true;
let b = mk_aq_sort( "good@test.com", 50.0, FAR_FUTURE_MS );
let mut c = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
c.is_current = true;
let accounts = vec![ a, b, c ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: account with both occupied + h-exhausted must be skipped",
);
}
}
#[ test ]
fn test_cc_is_active_skips_account()
{
let now = 0u64;
let mut a = mk_aq_sort( "active@test.com", 50.0, FAR_FUTURE_MS );
a.is_active = true;
let b = mk_aq_sort( "free@test.com", 50.0, FAR_FUTURE_MS );
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: is_active=true must be skipped; free@test.com (index 1) must be selected",
);
}
let mut a2 = mk_aq_sort( "active@test.com", 50.0, FAR_FUTURE_MS );
a2.is_active = true;
let all_active = vec![ a2 ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &all_active, strategy, PreferStrategy::Any, now, false );
assert!( result.is_none(), "{strategy:?}: all-active must return None" );
}
}
#[ test ]
fn test_cc_expired_ok_account_skipped()
{
let now_secs : u64 = 2_000;
let a = mk_aq_sort( "expired@test.com", 50.0, 1_000 );
let b = mk_aq_sort( "valid@test.com", 50.0, FAR_FUTURE_MS );
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now_secs, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: expired Ok account must be skipped; valid@test.com (index 1) must win",
);
}
let at_boundary = mk_aq_sort( "boundary@test.com", 50.0, now_secs * 1000 );
let accounts_boundary = vec![ at_boundary ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts_boundary, strategy, PreferStrategy::Any, now_secs, false );
assert!(
result.is_none(),
"{strategy:?}: boundary-expired account (0 secs remaining) must be skipped",
);
}
let one_sec_left = mk_aq_sort( "one_sec@test.com", 50.0, ( now_secs + 1 ) * 1000 );
let accounts_valid = vec![ one_sec_left ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts_valid, strategy, PreferStrategy::Any, now_secs, false );
assert_eq!(
result, Some( 0 ),
"{strategy:?}: account with 1 second remaining must be eligible",
);
}
}
#[ test ]
fn test_cc_gate_ownership_rejects_non_owned()
{
let now = 0u64;
let mut a = mk_aq_sort( "aaa_unowned@test.com", 50.0, FAR_FUTURE_MS );
a.is_owned = false;
let b = mk_aq_sort( "zzz_owned@test.com", 50.0, FAR_FUTURE_MS );
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let with_gate = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, true );
assert_eq!(
with_gate, Some( 1 ),
"{strategy:?} gate_ownership=true: aaa_unowned (is_owned=false) must be skipped; zzz_owned (index 1) must win",
);
let no_gate = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
no_gate, Some( 0 ),
"{strategy:?} gate_ownership=false: aaa_unowned (is_owned=false, index 0) must be selected \
— gate disabled means unowned accounts pass; if guard were absent, gate_ownership=true \
would also return Some(0) instead of Some(1) above",
);
}
let mut a2 = mk_aq_sort( "aaa_unowned@test.com", 50.0, FAR_FUTURE_MS );
a2.is_owned = false;
let all_unowned = vec![ a2 ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &all_unowned, strategy, PreferStrategy::Any, now, true );
assert!(
result.is_none(),
"{strategy:?}: all-non-owned with gate_ownership=true must return None",
);
}
}
#[ test ]
fn test_cc_err_account_skipped_via_find_next_for_strategy()
{
let now = 0u64;
let mut a = mk_aq_sort( "aaa_error@test.com", 50.0, FAR_FUTURE_MS );
a.result = Err( "missing accessToken".to_string() );
let b = mk_aq_sort( "zzz_valid@test.com", 50.0, FAR_FUTURE_MS );
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert_eq!(
result, Some( 1 ),
"{strategy:?}: Err account (aaa_error, index 0) must be skipped by gate 3; zzz_valid (index 1) must win",
);
}
}
#[ test ]
fn test_cc_expired_ok_with_gate_ownership_true()
{
let now_secs : u64 = 2_000;
let a = mk_aq_sort( "aaa_expired_owned@test.com", 50.0, 1_000 ); let b = mk_aq_sort( "zzz_valid_owned@test.com", 50.0, FAR_FUTURE_MS );
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now_secs, true );
assert_eq!(
result, Some( 1 ),
"{strategy:?} gate_ownership=true: expired owned account (gate 5) must be skipped; zzz_valid_owned (index 1) must win",
);
}
}
#[ test ]
fn test_cc_five_hour_none_with_gate_ownership_true()
{
let now = 0u64;
let mut a = mk_aq_sort( "no5h_owned@test.com", 50.0, FAR_FUTURE_MS );
if let Ok( ref mut d ) = a.result { d.five_hour = None; }
let mut b = mk_aq_sort( "current@test.com", 50.0, FAR_FUTURE_MS );
b.is_current = true;
let accounts = vec![ a, b ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, true );
assert_eq!(
result, Some( 0 ),
"{strategy:?}: five_hour=None with gate_ownership=true must be eligible (owned, not h-exhausted)",
);
}
}
#[ test ]
fn test_sort_renew_tiebreaker_alphabetical_when_equal_renewal()
{
let now_secs : u64 = 1_700_000_000;
let result_ab = find_next_for_strategy(
&[
mk_aq_with_7d_reset( "a@test.com", 77.0, now_secs, 10_800 ),
mk_aq_with_7d_reset( "b@test.com", 0.0, now_secs, 10_800 ),
],
SortStrategy::Renew,
PreferStrategy::Any,
now_secs,
false,
);
assert_eq!(
result_ab,
Some( 0 ),
"A-first: must pick a@test.com (index 0, alphabetically first)",
);
let result_ba = find_next_for_strategy(
&[
mk_aq_with_7d_reset( "b@test.com", 0.0, now_secs, 10_800 ),
mk_aq_with_7d_reset( "a@test.com", 77.0, now_secs, 10_800 ),
],
SortStrategy::Renew,
PreferStrategy::Any,
now_secs,
false,
);
assert_eq!(
result_ba,
Some( 1 ),
"B-first: must pick a@test.com (index 1) — name tiebreaker fires over B",
);
}
#[ doc = "bug_reproducer(BUG-229)" ]
#[ test ]
fn mre_bug229_sort_renew_subscription_sooner_than_7d_ranks_first()
{
let now : u64 = 1_700_000_000;
let acct_a = mk_aq_with_7d_reset( "a@test.com", 30.0, now, 3600 );
let mut acct_b = mk_aq_with_7d_reset( "b@test.com", 30.0, now, 86400 );
acct_b.renewal_at = Some( reset_iso_at( now, 1800 ) );
let accounts = vec![ acct_a, acct_b ];
let indices = sort_indices( &accounts, SortStrategy::Renew, None, PreferStrategy::Any, now);
assert_eq!(
accounts[ indices[ 0 ] ].name, "b@test.com",
"BUG-229: sort::renew must rank b first (sub 30min < a 7d 1h); got: {}",
accounts[ indices[ 0 ] ].name,
);
assert_eq!(
accounts[ indices[ 1 ] ].name, "a@test.com",
"BUG-229: sort::renew must rank a second; got: {}",
accounts[ indices[ 1 ] ].name,
);
}
#[ doc = "bug_reproducer(BUG-229)" ]
#[ test ]
fn mre_bug229_find_next_renew_picks_account_with_sooner_subscription()
{
let now : u64 = 1_700_000_000;
let mut acct_a = mk_aq_sort( "a@test.com", 30.0, FAR_FUTURE_MS );
acct_a.is_current = true;
let mut acct_b = mk_aq_with_7d_reset( "b@test.com", 30.0, now, 86400 );
acct_b.renewal_at = Some( reset_iso_at( now, 1800 ) );
let acct_c = mk_aq_with_7d_reset( "c@test.com", 30.0, now, 3600 );
let accounts = vec![ acct_a, acct_b, acct_c ];
let winner = find_next_for_strategy( &accounts, SortStrategy::Renew, PreferStrategy::Any, now, false );
assert_eq!(
winner, Some( 1 ),
"BUG-229: sort::renew must pick b (sub 30min < c 7d 1h); got: {winner:?}",
);
assert_eq!( accounts[ winner.unwrap() ].name, "b@test.com",
"BUG-229: winner name must be b@test.com" );
}
#[ doc = "bug_reproducer(BUG-229)" ]
#[ test ]
fn mre_bug229_strategy_metric_renew_exact_sub_shows_both_timers()
{
let now = 1_700_000_000_u64;
let data = claude_quota::OauthUsageData
{
five_hour : Some( claude_quota::PeriodUsage { utilization : 30.0, resets_at : None } ),
seven_day : Some( claude_quota::PeriodUsage
{
utilization : 50.0,
resets_at : Some( reset_iso_at( now, 86400 ) ), } ),
seven_day_sonnet : None,
};
let aq = AccountQuota
{
name : "test@example.com".to_string(),
is_current : false,
is_active : false,
is_occupied_elsewhere : false,
expires_at_ms : FAR_FUTURE_MS,
result : Ok( data ),
account : None,
host : String::new(),
role : String::new(),
renewal_at : Some( reset_iso_at( now, 3600 ) ), cached : false,
cache_age_secs : None,
is_owned : true,
owner : String::new(),
};
let metric = strategy_metric( &aq, SortStrategy::Renew, PreferStrategy::Any, now);
assert!(
metric.contains( "$ren" ),
"BUG-229: renew metric with exact sub must show '$ren' event: {metric}",
);
assert!(
!metric.contains( "~in" ),
"BUG-229: exact sub renewal must not have '~in' estimation prefix: {metric}",
);
assert!(
!metric.contains( "7d resets in" ),
"BUG-229: renew metric must not show old '7d resets in' format: {metric}",
);
assert!(
!metric.contains( "session" ),
"BUG-229: renew metric must not show session%%: {metric}",
);
assert!(
!metric.contains( "5h resets" ),
"BUG-229: renew metric must not show 5h timer: {metric}",
);
}
#[ doc = "bug_reproducer(BUG-229)" ]
#[ test ]
fn mre_bug229_strategy_metric_renew_no_sub_shows_7d_only()
{
let now = 1_700_000_000_u64;
let data = claude_quota::OauthUsageData
{
five_hour : Some( claude_quota::PeriodUsage { utilization : 30.0, resets_at : None } ),
seven_day : Some( claude_quota::PeriodUsage
{
utilization : 50.0,
resets_at : Some( reset_iso_at( now, 3600 ) ),
} ),
seven_day_sonnet : None,
};
let aq = AccountQuota
{
name : "test@example.com".to_string(),
is_current : false,
is_active : false,
is_occupied_elsewhere : false,
expires_at_ms : FAR_FUTURE_MS,
result : Ok( data ),
account : None,
host : String::new(),
role : String::new(),
renewal_at : None, cached : false,
cache_age_secs : None,
is_owned : true,
owner : String::new(),
};
let metric = strategy_metric( &aq, SortStrategy::Renew, PreferStrategy::Any, now);
assert!(
metric.contains( "+7d" ),
"BUG-229: renew metric without sub must show '+7d' event: {metric}",
);
assert!(
!metric.contains( "renews" ),
"BUG-229: renew metric without sub must not show 'renews': {metric}",
);
assert!(
!metric.contains( "7d resets in" ),
"BUG-229: renew metric must not show old '7d resets in' format: {metric}",
);
assert!(
!metric.contains( "session" ),
"BUG-229: renew metric must not show session%%: {metric}",
);
}
#[ test ]
fn test_sort_renews_ascending()
{
let now : u64 = 1_700_000_000;
let mut acct_soon = mk_aq_sort( "soon_renew@test.com", 50.0, FAR_FUTURE_MS );
acct_soon.renewal_at = Some( reset_iso_at( now, 3_600 ) );
let mut acct_later = mk_aq_sort( "later_renew@test.com", 50.0, FAR_FUTURE_MS );
acct_later.renewal_at = Some( reset_iso_at( now, 86_400 ) );
let acct_none = mk_aq_sort( "no_renew@test.com", 50.0, FAR_FUTURE_MS );
let accounts = vec![ acct_later, acct_none, acct_soon ];
let idx = sort_indices( &accounts, SortStrategy::Renews, None, PreferStrategy::Any, now);
assert_eq!(
accounts[ idx[ 0 ] ].name, "soon_renew@test.com",
"sort::renews: soonest renewal must be first",
);
assert_eq!(
accounts[ idx[ 1 ] ].name, "later_renew@test.com",
"sort::renews: later renewal must be second",
);
assert_eq!(
accounts[ idx[ 2 ] ].name, "no_renew@test.com",
"sort::renews: no renewal data must be last (scored as u64::MAX)",
);
}
#[ doc = "bug_reproducer(BUG-260)" ]
#[ test ]
fn mre_bug260_renew_nondeterministic_when_fully_tied()
{
let now_secs : u64 = 1_700_000_000;
let zorro = mk_aq_with_7d_reset( "zorro@test.com", 0.0, now_secs, 10_800 );
let alice = mk_aq_with_7d_reset( "alice@test.com", 0.0, now_secs, 10_800 );
let result = find_next_for_strategy(
&[ zorro, alice ],
SortStrategy::Renew,
PreferStrategy::Any,
now_secs,
false,
);
assert_eq!(
result,
Some( 1 ),
"BUG-260: tied candidates must resolve alphabetically; alice@test.com (index 1) must win over zorro@test.com (index 0)",
);
}
#[ doc = "bug_reproducer(BUG-292)" ]
#[ test ]
fn mre_bug292_renew_skips_weekly_exhausted_even_with_soonest_renewal()
{
let now = 0u64;
let exhausted = mk_aq_with_7d_reset_util( "exhausted@test.com", 0.0, 96.0, now, 3_600 );
let healthy = mk_aq_with_7d_reset_util( "healthy@test.com", 0.0, 40.0, now, 86_400 );
let idx = find_next_for_strategy( &[ exhausted, healthy ], SortStrategy::Renew, PreferStrategy::Any, now, false );
assert!( idx.is_some(), "BUG-292: renew must find a candidate (healthy@test.com is eligible)" );
assert_eq!(
idx.unwrap(), 1,
"BUG-292: renew must skip exhausted@test.com (prefer_weekly=4.0 ≤ 5.0) and pick healthy@test.com (index 1); got {idx:?}",
);
}
#[ doc = "bug_reproducer(BUG-291)" ]
#[ test ]
fn mre_bug291_renew_next_tiebreaker_matches_sort_indices()
{
let now = 0u64;
let alice_s = mk_aq_with_7d_reset_util( "alice@test.com", 80.0, 10.0, now, 3_600 );
let bob_s = mk_aq_with_7d_reset_util( "bob@test.com", 20.0, 60.0, now, 3_600 );
let sorted = sort_indices( &[ alice_s, bob_s ], SortStrategy::Renew, None, PreferStrategy::Any, now);
assert_eq!(
sorted[ 0 ], 1,
"BUG-291: sort_indices(Renew) must rank bob (prefer_weekly=40) before alice (prefer_weekly=90); got {sorted:?}",
);
let alice_n = mk_aq_with_7d_reset_util( "alice@test.com", 80.0, 10.0, now, 3_600 );
let bob_n = mk_aq_with_7d_reset_util( "bob@test.com", 20.0, 60.0, now, 3_600 );
let idx = find_next_for_strategy( &[ alice_n, bob_n ], SortStrategy::Renew, PreferStrategy::Any, now, false );
assert_eq!(
idx, Some( 1 ),
"BUG-291: sort::renew tiebreaker must match sort_indices(Renew) — bob (prefer_weekly=40) must win, not alice; got {idx:?}",
);
}
#[ test ]
fn mre_bug_gap8_find_first_eligible_at_exactly_85_utilization()
{
let now = 0u64;
let acct = mk_aq_sort( "aaa@test.com", 85.0, FAR_FUTURE_MS ); let accounts = vec![ acct ];
for strategy in [ SortStrategy::Renew, SortStrategy::Name, SortStrategy::Renews ]
{
let result = find_next_for_strategy( &accounts, strategy, PreferStrategy::Any, now, false );
assert!(
result.is_none(),
"{strategy:?}: account with five_hour.utilization=85.0 must be skipped by gate 4 (>= 85.0); got: {result:?}",
);
}
}