//! Round26 closure coverage for near-threshold app, credentials, threads, //! and memory_sources paths. //! //! Uses only temp workspaces, local files, and intentionally unreachable //! loopback endpoints. use std::path::{Path, PathBuf}; use std::sync::{Mutex, OnceLock}; use std::time::{Duration, SystemTime}; use filetime::FileTime; use serde_json::Value; use tempfile::{Builder, TempDir}; use openhuman_core::openhuman::app_state::{snapshot, update_local_state, StoredAppStatePatch}; use openhuman_core::openhuman::config::Config; use openhuman_core::openhuman::credentials::profiles::{AuthProfile, AuthProfilesStore, TokenSet}; use openhuman_core::openhuman::memory::{ AppendConversationMessageRequest, ConversationMessageRecord, CreateConversationThreadRequest, GenerateConversationThreadTitleRequest, UpsertConversationThreadRequest, }; use openhuman_core::openhuman::memory_sources::reconcile::ensure_composio_sources; use openhuman_core::openhuman::threads::ops::{ message_append, thread_create_new, thread_generate_title, thread_upsert, }; use openhuman_core::openhuman::threads::welcome_migration::migrate_welcome_agent_artifacts; static ENV_LOCK: OnceLock> = OnceLock::new(); struct EnvGuard { key: &'static str, old: Option, } impl EnvGuard { fn set(key: &'static str, value: &str) -> Self { let old = std::env::var(key).ok(); std::env::set_var(key, value); Self { key, old } } fn set_path(key: &'static str, path: &Path) -> Self { let old = std::env::var(key).ok(); std::env::set_var(key, path.as_os_str()); Self { key, old } } fn unset(key: &'static str) -> Self { let old = std::env::var(key).ok(); std::env::remove_var(key); Self { key, old } } } impl Drop for EnvGuard { fn drop(&mut self) { match &self.old { Some(value) => std::env::set_var(self.key, value), None => std::env::remove_var(self.key), } } } struct Harness { _tmp: TempDir, root: PathBuf, workspace: PathBuf, _guards: Vec, } fn env_lock() -> std::sync::MutexGuard<'static, ()> { ENV_LOCK .get_or_init(|| Mutex::new(())) .lock() .unwrap_or_else(|poisoned| poisoned.into_inner()) } async fn setup(prefix: &str) -> Harness { std::fs::create_dir_all("target").expect("target dir"); let tmp = Builder::new() .prefix(prefix) .tempdir_in("target") .expect("tempdir"); let root = tmp.path().join("openhuman"); let workspace = root.join("workspace"); std::fs::create_dir_all(&workspace).expect("workspace"); let guards = vec![ EnvGuard::set_path("OPENHUMAN_WORKSPACE", &root), EnvGuard::set_path("HOME", tmp.path()), EnvGuard::set("OPENHUMAN_KEYRING_BACKEND", "file"), EnvGuard::set("OPENHUMAN_MEMORY_EMBED_STRICT", "false"), EnvGuard::unset("BACKEND_URL"), EnvGuard::unset("VITE_BACKEND_URL"), EnvGuard::unset("OPENHUMAN_API_URL"), EnvGuard::unset("OPENHUMAN_CORE_RPC_URL"), EnvGuard::unset("OPENHUMAN_CORE_PORT"), ]; let mut config = Config { workspace_dir: workspace.clone(), config_path: root.join("config.toml"), api_url: Some("http://127.0.0.1:9".to_string()), onboarding_completed: false, chat_onboarding_completed: true, ..Config::default() }; config.secrets.encrypt = false; config.composio.mode = "backend".to_string(); config.save().await.expect("save config"); Harness { _tmp: tmp, root, workspace, _guards: guards, } } fn write_transcript(path: &Path, agent: &str) { let body = format!( "{{\"_meta\":{{\"agent\":\"{agent}\",\"thread_id\":\"round26-thread\",\"dispatcher\":\"native\"}}}}\n{{\"role\":\"user\",\"content\":\"hello\"}}\n" ); std::fs::create_dir_all(path.parent().unwrap()).expect("transcript dir"); std::fs::write(path, body).expect("write transcript"); } #[tokio::test] async fn round26_app_state_quarantines_corrupt_local_file_and_preserves_patch_noops() { let _lock = env_lock(); let harness = setup("round26-app-state-").await; let state_dir = harness.workspace.join("state"); std::fs::create_dir_all(&state_dir).expect("state dir"); let app_state_path = state_dir.join("app-state.json"); std::fs::write(&app_state_path, b"{ not valid json").expect("corrupt app state"); let snapshot = snapshot().await.expect("snapshot with corrupt state").value; assert!(!snapshot.onboarding_completed); assert!(snapshot.chat_onboarding_completed); assert!(snapshot.local_state.encryption_key.is_none()); assert!(snapshot.local_state.onboarding_tasks.is_none()); assert!( !app_state_path.exists(), "corrupt file should be quarantined" ); assert!( std::fs::read_dir(&state_dir) .expect("state listing") .flatten() .any(|entry| entry .file_name() .to_string_lossy() .contains("json.corrupted")), "quarantine file should remain for diagnostics" ); let unchanged = update_local_state(StoredAppStatePatch { keyring_consent: None, encryption_key: None, onboarding_tasks: None, }) .await .expect("noop patch") .value; assert!(unchanged.encryption_key.is_none()); assert!(unchanged.onboarding_tasks.is_none()); } #[test] fn round26_credentials_profiles_reclaim_stale_locks_and_reject_bad_active_profile() { let _lock = env_lock(); let runtime = tokio::runtime::Runtime::new().expect("runtime"); let harness = runtime.block_on(setup("round26-credentials-")); let store = AuthProfilesStore::new(&harness.root, false); let lock_path = harness.root.join("auth-profiles.lock"); std::fs::write(&lock_path, "pid=4294967295\n").expect("stale pid lock"); let empty = store.load().expect("stale pid lock should be reclaimed"); assert!(empty.profiles.is_empty()); assert!(!lock_path.exists()); std::fs::write(&lock_path, "not-a-pid\n").expect("malformed lock"); let old = FileTime::from_system_time(SystemTime::now() - Duration::from_secs(3)); filetime::set_file_mtime(&lock_path, old).expect("old lock mtime"); store .upsert_profile( AuthProfile::new_token("anthropic", "default", "sk-round26".to_string()), false, ) .expect("malformed old lock should be reclaimed"); assert!(!lock_path.exists()); let missing_active = store .set_active_profile("anthropic", "anthropic:missing") .expect_err("missing active profile"); assert!(missing_active .to_string() .contains("Auth profile not found")); let oauth = AuthProfile::new_oauth( "gmail", "round26", TokenSet { access_token: "access-round26".to_string(), refresh_token: None, id_token: None, expires_at: None, token_type: None, scope: None, }, ); let oauth_id = oauth.id.clone(); store.upsert_profile(oauth, true).expect("upsert oauth"); let loaded = store.load().expect("load oauth"); assert_eq!(loaded.active_profiles.get("gmail"), Some(&oauth_id)); assert_eq!( loaded .profiles .get(&oauth_id) .and_then(|profile| profile.token_set.as_ref()) .map(|tokens| tokens.access_token.as_str()), Some("access-round26") ); } #[tokio::test] async fn round26_threads_generate_fallback_titles_and_migrate_in_place_transcripts() { let _lock = env_lock(); let harness = setup("round26-threads-").await; let created = thread_create_new(CreateConversationThreadRequest { labels: Some(vec!["round26".to_string()]), personality_id: None, }) .await .expect("create thread") .value .data .expect("created thread"); message_append(AppendConversationMessageRequest { thread_id: created.id.clone(), message: ConversationMessageRecord { id: "msg-round26-user".to_string(), content: "/please summarize this long launch checklist for me".to_string(), message_type: "text".to_string(), extra_metadata: Value::Null, sender: "user".to_string(), created_at: "2026-05-30T00:00:00Z".to_string(), }, }) .await .expect("append user message"); let titled = thread_generate_title(GenerateConversationThreadTitleRequest { thread_id: created.id.clone(), assistant_message: None, }) .await .expect("fallback title") .value .data .expect("titled thread"); assert_eq!( titled.title, "please summarize this long launch checklist for me" ); let custom = thread_upsert(UpsertConversationThreadRequest { id: "round26-custom-title".to_string(), title: "Already Named Thread".to_string(), created_at: "2026-05-30T00:00:01Z".to_string(), parent_thread_id: None, labels: Some(vec!["work".to_string()]), personality_id: Some("p1".to_string()), }) .await .expect("upsert custom") .value .data .expect("custom thread"); let skipped = thread_generate_title(GenerateConversationThreadTitleRequest { thread_id: custom.id, assistant_message: Some("assistant text".to_string()), }) .await .expect("skip custom title") .value .data .expect("skipped title"); assert_eq!(skipped.title, "Already Named Thread"); let transcript = harness.workspace.join("session_raw/round26-chat.jsonl"); write_transcript(&transcript, "welcome"); let result = migrate_welcome_agent_artifacts(&harness.workspace).expect("migration"); assert_eq!(result.transcripts_updated, 1); assert_eq!(result.transcript_files_renamed, 0); assert!(transcript.exists()); let rewritten = std::fs::read_to_string(&transcript).expect("rewritten transcript"); assert!(rewritten.contains("\"agent\":\"orchestrator\"")); } #[tokio::test] async fn round26_memory_sources_reconcile_handles_unavailable_composio_without_registry_changes() { let _lock = env_lock(); let harness = setup("round26-memory-sources-").await; ensure_composio_sources().await; let config = Config::load_or_init().await.expect("reload config"); assert_eq!(config.config_path, harness.root.join("config.toml")); assert!(config.memory_sources.is_empty()); }