use aim_core::app::progress::{OperationEvent, OperationStage}; use aim_core::app::update::{build_update_plan, execute_updates, execute_updates_with_reporter}; use aim_core::domain::app::{AppRecord, InstallMetadata, InstallScope}; use aim_core::domain::update::{ChannelPreference, UpdateChannelKind, UpdateStrategy}; use tempfile::tempdir; #[test] fn empty_registry_produces_empty_plan() { let plan = build_update_plan(&[]).unwrap(); assert!(plan.items.is_empty()); } #[test] fn installed_apps_are_carried_into_review_plan() { let apps = [AppRecord { stable_id: "bat".to_owned(), display_name: "Bat".to_owned(), source_input: None, source: None, installed_version: None, update_strategy: None, metadata: Vec::new(), install: None, }]; let plan = build_update_plan(&apps).unwrap(); assert_eq!(plan.items.len(), 1); assert_eq!(plan.items[0].stable_id, "bat"); assert_eq!(plan.items[0].selection_reason, "install-origin-match"); } #[test] fn update_plan_uses_alternate_channel_after_preferred_failure() { let apps = [AppRecord { stable_id: "t3code".to_owned(), display_name: "T3 Code".to_owned(), source_input: Some("pingdotgg/t3code".to_owned()), source: None, installed_version: Some("0.0.11".to_owned()), update_strategy: Some(UpdateStrategy { preferred: ChannelPreference { kind: UpdateChannelKind::GitHubReleases, locator: "fail://github".to_owned(), reason: "install-origin-match".to_owned(), }, alternates: vec![ChannelPreference { kind: UpdateChannelKind::ElectronBuilder, locator: "https://example.test/latest-linux.yml".to_owned(), reason: "metadata-guided".to_owned(), }], }), metadata: Vec::new(), install: None, }]; let plan = build_update_plan(&apps).unwrap(); assert_eq!( plan.items[0].selected_channel.kind.as_str(), "electron-builder" ); assert_eq!(plan.items[0].selection_reason, "preferred-channel-failed"); } #[test] fn failed_update_keeps_previous_app_record() { let install_home = tempdir().unwrap(); let previous = AppRecord { stable_id: "legacy-bat".to_owned(), display_name: "Legacy Bat".to_owned(), source_input: None, source: None, installed_version: Some("0.9.0".to_owned()), update_strategy: None, metadata: Vec::new(), install: Some(InstallMetadata { scope: InstallScope::User, payload_path: None, desktop_entry_path: None, icon_path: None, }), }; let result = execute_updates(std::slice::from_ref(&previous), install_home.path()).unwrap(); assert_eq!(result.apps, vec![previous]); assert_eq!(result.updated_count(), 0); assert_eq!(result.failed_count(), 1); } #[test] fn update_execution_reports_per_app_lifecycle_events() { let install_home = tempdir().unwrap(); let app = AppRecord { stable_id: "legacy-bat".to_owned(), display_name: "Legacy Bat".to_owned(), source_input: None, source: None, installed_version: Some("0.9.0".to_owned()), update_strategy: None, metadata: Vec::new(), install: Some(InstallMetadata { scope: InstallScope::User, payload_path: None, desktop_entry_path: None, icon_path: None, }), }; let mut events: Vec = Vec::new(); let mut reporter = |event: &OperationEvent| events.push(event.clone()); let result = execute_updates_with_reporter( std::slice::from_ref(&app), install_home.path(), &mut reporter, ) .unwrap(); assert_eq!(result.failed_count(), 1); assert!(events.iter().any(|event| { matches!( event, OperationEvent::StageChanged { stage: OperationStage::ResolveQuery, .. } ) })); assert!(events.iter().any(|event| { matches!( event, OperationEvent::Failed { stage: OperationStage::ResolveQuery, .. } ) })); }