use super::*;
use crate::profile::{AppState, ClaudeCredentials, OAuthToken, Profile, profile_dir};
use crate::runtime::open_pid_file;
fn single_profile_config(name: &str, refresh_token: &str) -> AppConfig {
use std::collections::BTreeMap;
let profile = Profile {
name: name.to_string(),
base_url: None,
api_key: None,
auto_start: false,
env: BTreeMap::new(),
fallback_threshold: None,
credentials: Some(ClaudeCredentials {
claude_ai_oauth: Some(OAuthToken {
access_token: "at".to_string(),
refresh_token: Some(refresh_token.to_string()),
expires_at: None,
scopes: None,
subscription_type: None,
}),
}),
usage: None,
fetch_status: None,
};
let mut config = AppConfig {
state: AppState::default(),
profiles: vec![profile],
};
config.state.profiles.push(name.to_string());
config
}
#[test]
fn no_live_session_included_with_force_false() {
let config = single_profile_config("test-oauth-no-session-force-false", "rt-abc");
let candidates = rotation_candidates(&config, false);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].0, "test-oauth-no-session-force-false");
assert_eq!(candidates[0].1, "rt-abc");
}
#[test]
fn no_live_session_included_with_force_true() {
let config = single_profile_config("test-oauth-no-session-force-true", "rt-def");
let candidates = rotation_candidates(&config, true);
assert_eq!(candidates.len(), 1);
assert_eq!(candidates[0].0, "test-oauth-no-session-force-true");
}
#[test]
fn live_session_excluded_when_force_false() {
let name = "test-oauth-live-session-guard";
let sessions = profile_dir(name).expect("profile_dir").join("sessions");
std::fs::create_dir_all(&sessions).expect("create sessions dir");
let pid_file = sessions.join("test-pid");
let file = open_pid_file(&pid_file).expect("open pid file");
file.lock().expect("lock pid file");
let config = single_profile_config(name, "rt-ghi");
let candidates = rotation_candidates(&config, false);
assert!(
candidates.is_empty(),
"force=false should exclude a profile with a live session"
);
drop(file);
}
#[test]
fn live_session_included_when_force_true() {
let name = "test-oauth-live-session-force";
let sessions = profile_dir(name).expect("profile_dir").join("sessions");
std::fs::create_dir_all(&sessions).expect("create sessions dir");
let pid_file = sessions.join("test-pid");
let file = open_pid_file(&pid_file).expect("open pid file");
file.lock().expect("lock pid file");
let config = single_profile_config(name, "rt-jkl");
let candidates = rotation_candidates(&config, true);
assert_eq!(
candidates.len(),
1,
"force=true should include a profile with a live session"
);
assert_eq!(candidates[0].0, name);
drop(file);
}
#[test]
fn profile_without_refresh_token_excluded() {
use std::collections::BTreeMap;
let profile = Profile {
name: "test-oauth-no-rt".to_string(),
base_url: None,
api_key: None,
auto_start: false,
env: BTreeMap::new(),
fallback_threshold: None,
credentials: Some(ClaudeCredentials {
claude_ai_oauth: Some(OAuthToken {
access_token: "at".to_string(),
refresh_token: None,
expires_at: None,
scopes: None,
subscription_type: None,
}),
}),
usage: None,
fetch_status: None,
};
let mut config = AppConfig {
state: AppState::default(),
profiles: vec![profile],
};
config.state.profiles.push("test-oauth-no-rt".to_string());
assert!(rotation_candidates(&config, false).is_empty());
assert!(rotation_candidates(&config, true).is_empty());
}