#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(unix)]
use super::*;
use crate::claude::force_link_profile_credentials;
use crate::profile::{
AppState, ClaudeCredentials, OAuthToken, Profile, claude_dir, read_json_file, save_app_state,
save_profile,
};
use crate::testutil::HomeSandbox;
fn seed_active_linked() {
let mut p = Profile::new("active".to_string(), None, None);
p.credentials = Some(ClaudeCredentials {
claude_ai_oauth: Some(OAuthToken {
access_token: "stored-a".to_string(),
refresh_token: Some("stored-r".to_string()),
expires_at: None,
scopes: None,
subscription_type: None,
}),
});
save_profile(&p).expect("save profile");
force_link_profile_credentials("active").expect("link active");
let state = AppState {
active_profile: Some("active".into()),
profiles: vec!["active".into()],
..Default::default()
};
save_app_state(&state).expect("save state");
}
fn call_switch(name: &str) -> CallToolResult {
let server = ClauthServer::new();
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("runtime");
rt.block_on(async {
server
.switch(Parameters(SwitchArgs {
name: name.to_string(),
}))
.await
})
.expect("switch returns a tool result, never a transport error")
}
#[test]
fn unknown_target_is_rejected_without_stripping_live_creds() {
let _home = HomeSandbox::new();
seed_active_linked();
let live = claude_dir().expect("claude dir").join(".credentials.json");
assert!(
live.symlink_metadata().is_ok(),
"precondition: live credentials are linked",
);
let result = call_switch("ghost");
assert_eq!(
result.is_error,
Some(true),
"an unknown profile name must be a tool error",
);
assert!(
live.symlink_metadata().is_ok(),
"the live credentials symlink must survive a failed switch to an unknown name",
);
}
fn seed_active_plus_target() {
seed_active_linked();
let mut target = Profile::new("target".to_string(), None, None);
target.credentials = Some(ClaudeCredentials {
claude_ai_oauth: Some(OAuthToken {
access_token: "target-a".to_string(),
refresh_token: Some("target-r".to_string()),
expires_at: None,
scopes: None,
subscription_type: None,
}),
});
save_profile(&target).expect("save target");
let state = AppState {
active_profile: Some("active".into()),
profiles: vec!["active".into(), "target".into()],
..Default::default()
};
save_app_state(&state).expect("save state");
}
#[test]
fn valid_switch_repoints_active_through_the_blocking_task() {
let _home = HomeSandbox::new();
seed_active_plus_target();
let result = call_switch("target");
assert_ne!(
result.is_error,
Some(true),
"a clean switch to a known profile must succeed through the spawn_blocking wrap",
);
let live: ClaudeCredentials =
read_json_file(&claude_dir().expect("claude dir").join(".credentials.json"))
.expect("read live creds");
assert_eq!(
live.refresh_token(),
Some("target-r"),
"the switch ends with the active link pointing at target's stored creds",
);
}