use metacrate_grid_agent::{ AgentPreferences, ConfigLoader, MapEnvironment, OperatingMode, OperatorScreen, OperatorTui, PreferencesPanel, TuiInput, TuiRenderOptions, }; use std::fs; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; fn temporary(name: &str) -> PathBuf { let directory = std::env::temp_dir().join(format!( "metacrate-preferences-{}-{name}", SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos() )); fs::create_dir_all(&directory).unwrap(); directory } #[test] fn yaml_is_primary_and_preferences_preserve_unknown_settings() { let directory = temporary("yaml"); let path = directory.join("config.yml"); fs::write( &path, r"schema_version: 1 integrated: true llm: endpoint_url: https://ai.invalid/v1/chat/completions api_key: private-key model: local-model grid: login_url: https://grid.invalid/login avatar_name: Service Avatar password: private-password authorized_avatar_uuids: - 00000000-0000-4000-8000-000000000001 behavior: idle_interval_seconds: 321 ", ) .unwrap(); let config = ConfigLoader::new() .with_file(&path) .with_environment(MapEnvironment::default()) .load() .unwrap(); assert_eq!(config.mode, OperatingMode::Integrated); assert_eq!(config.llm.model.as_deref(), Some("local-model")); assert_eq!(config.behavior.idle_interval.as_secs(), 321); let mut preferences = AgentPreferences::load(&path).unwrap(); preferences.set_llm_model("replacement-model".into()); preferences.save(&path).unwrap(); let saved = fs::read_to_string(&path).unwrap(); assert!(saved.contains("idle_interval_seconds: 321")); assert!(saved.contains("model: replacement-model")); assert!(!format!("{preferences:?}").contains("private-key")); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt as _; assert_eq!( fs::metadata(&path).unwrap().permissions().mode() & 0o777, 0o600 ); } fs::remove_dir_all(directory).unwrap(); } #[test] fn dotenv_import_is_explicit_and_preferences_panel_never_renders_secrets() { let directory = temporary("import"); let dotenv = directory.join("legacy.env"); let config = directory.join("config.yml"); fs::write( &dotenv, "OPENAPI_URL=https://ai.invalid/v1/chat/completions\nOPENAPI_KEY=very-secret-key\nOPENAPI_MODEL=test-model\nGRID_LOGIN_URL=https://grid.invalid/login\nGRID_USER=Test Avatar\nGRID_PASSWORD=very-secret-password\n", ) .unwrap(); let mut preferences = AgentPreferences::load(&config).unwrap(); preferences.import_dotenv(&dotenv).unwrap(); preferences.set_privileged_users(vec!["00000000-0000-4000-8000-000000000001".into()]); preferences.save(&config).unwrap(); let panel = PreferencesPanel::open(config.clone()).unwrap(); let summary = panel.summary(); assert_eq!(summary.llm_model.as_deref(), Some("test-model")); assert!(summary.llm_api_key_configured && summary.grid_password_configured); assert_eq!(summary.privileged_users.len(), 1); let mut tui = OperatorTui::with_preferences(config).unwrap(); tui.screen = OperatorScreen::Preferences; let rendered = tui.render(TuiRenderOptions { width: 120, height: 30, color: false, }); assert!(rendered.contains("LLM API key: [configured]")); assert!(rendered.contains("grid password: [configured]")); assert!(!rendered.contains("very-secret")); let _ = tui.reduce(TuiInput::PreferenceNext); let _ = tui.reduce(TuiInput::PreferenceEditOrCommit); for value in "https://replacement.invalid/v1/chat".chars() { let _ = tui.reduce(TuiInput::PreferenceCharacter(value)); } let _ = tui.reduce(TuiInput::PreferenceEditOrCommit); let _ = tui.reduce(TuiInput::PreferenceSave); fs::remove_dir_all(directory).unwrap(); }