Implement per-distro installation flow
This commit is contained in:
parent
caf870d05e
commit
b9b60e9b6c
21 changed files with 1109 additions and 33 deletions
36
crates/aim-core/tests/install_failures.rs
Normal file
36
crates/aim-core/tests/install_failures.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use aim_core::integration::install::{DesktopIntegrationRequest, InstallRequest, execute_install};
|
||||
use aim_core::platform::DesktopHelpers;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn integration_failure_removes_new_payload_and_generated_files() {
|
||||
let root = tempdir().unwrap();
|
||||
let staging_root = root.path().join("staging");
|
||||
let payload_root = root.path().join("payloads");
|
||||
let blocking_path = root.path().join("not-a-directory");
|
||||
|
||||
fs::create_dir(&staging_root).unwrap();
|
||||
fs::create_dir(&payload_root).unwrap();
|
||||
fs::write(&blocking_path, "blocker").unwrap();
|
||||
|
||||
let final_payload_path = payload_root.join("bat.AppImage");
|
||||
let desktop_entry_path = blocking_path.join("aim-bat.desktop");
|
||||
let error = execute_install(&InstallRequest {
|
||||
staging_root: &staging_root,
|
||||
final_payload_path: &final_payload_path,
|
||||
artifact_bytes: b"\x7fELFAppImage",
|
||||
desktop: Some(DesktopIntegrationRequest {
|
||||
desktop_entry_path: &desktop_entry_path,
|
||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||
icon_path: None,
|
||||
icon_bytes: None,
|
||||
}),
|
||||
helpers: DesktopHelpers::default(),
|
||||
})
|
||||
.unwrap_err();
|
||||
|
||||
assert!(error.to_string().contains("desktop integration failed"));
|
||||
assert!(!final_payload_path.exists());
|
||||
assert!(!desktop_entry_path.exists());
|
||||
}
|
||||
127
crates/aim-core/tests/install_integration.rs
Normal file
127
crates/aim-core/tests/install_integration.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
use aim_core::integration::install::{DesktopIntegrationRequest, InstallRequest, execute_install};
|
||||
use aim_core::platform::DesktopHelpers;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn install_writes_desktop_entry_and_reports_refresh_warning_only() {
|
||||
let root = tempdir().unwrap();
|
||||
let staging_root = root.path().join("staging");
|
||||
let payload_root = root.path().join("payloads");
|
||||
let desktop_root = root.path().join("applications");
|
||||
|
||||
fs::create_dir(&staging_root).unwrap();
|
||||
fs::create_dir(&payload_root).unwrap();
|
||||
fs::create_dir(&desktop_root).unwrap();
|
||||
|
||||
let outcome = execute_install(&InstallRequest {
|
||||
staging_root: &staging_root,
|
||||
final_payload_path: &payload_root.join("bat.AppImage"),
|
||||
artifact_bytes: b"\x7fELFAppImage",
|
||||
desktop: Some(DesktopIntegrationRequest {
|
||||
desktop_entry_path: &desktop_root.join("aim-bat.desktop"),
|
||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||
icon_path: None,
|
||||
icon_bytes: None,
|
||||
}),
|
||||
helpers: DesktopHelpers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(outcome.desktop_entry_path.unwrap().exists());
|
||||
assert!(!outcome.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_executes_refresh_helpers_when_available() {
|
||||
let root = tempdir().unwrap();
|
||||
let staging_root = root.path().join("staging");
|
||||
let payload_root = root.path().join("payloads");
|
||||
let desktop_root = root.path().join("applications");
|
||||
let helper_root = root.path().join("helpers");
|
||||
let log_path = root.path().join("helpers.log");
|
||||
|
||||
fs::create_dir(&staging_root).unwrap();
|
||||
fs::create_dir(&payload_root).unwrap();
|
||||
fs::create_dir(&desktop_root).unwrap();
|
||||
fs::create_dir(&helper_root).unwrap();
|
||||
|
||||
let update_helper = helper_root.join("update-desktop-database");
|
||||
let icon_helper = helper_root.join("gtk-update-icon-cache");
|
||||
fs::write(
|
||||
&update_helper,
|
||||
format!("#!/bin/sh\necho desktop:$1 >> {}\n", log_path.display()),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
&icon_helper,
|
||||
format!("#!/bin/sh\necho icon:$3 >> {}\n", log_path.display()),
|
||||
)
|
||||
.unwrap();
|
||||
fs::set_permissions(&update_helper, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
fs::set_permissions(&icon_helper, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
|
||||
let icon_root = root.path().join("icons/hicolor/256x256/apps");
|
||||
fs::create_dir_all(&icon_root).unwrap();
|
||||
|
||||
let outcome = execute_install(&InstallRequest {
|
||||
staging_root: &staging_root,
|
||||
final_payload_path: &payload_root.join("bat.AppImage"),
|
||||
artifact_bytes: b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
|
||||
desktop: Some(DesktopIntegrationRequest {
|
||||
desktop_entry_path: &desktop_root.join("aim-bat.desktop"),
|
||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||
icon_path: Some(&icon_root.join("bat.png")),
|
||||
icon_bytes: None,
|
||||
}),
|
||||
helpers: DesktopHelpers {
|
||||
update_desktop_database: true,
|
||||
gtk_update_icon_cache: true,
|
||||
update_desktop_database_path: Some(update_helper),
|
||||
gtk_update_icon_cache_path: Some(icon_helper),
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(outcome.warnings.is_empty());
|
||||
let log = fs::read_to_string(&log_path).unwrap();
|
||||
assert!(log.contains("desktop:"));
|
||||
assert!(log.contains("icon:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_extracts_icon_from_appimage_payload_when_icon_path_is_requested() {
|
||||
let root = tempdir().unwrap();
|
||||
let staging_root = root.path().join("staging");
|
||||
let payload_root = root.path().join("payloads");
|
||||
let desktop_root = root.path().join("applications");
|
||||
let icon_root = root.path().join("icons/hicolor/256x256/apps");
|
||||
|
||||
fs::create_dir(&staging_root).unwrap();
|
||||
fs::create_dir(&payload_root).unwrap();
|
||||
fs::create_dir(&desktop_root).unwrap();
|
||||
fs::create_dir_all(&icon_root).unwrap();
|
||||
|
||||
let outcome = execute_install(&InstallRequest {
|
||||
staging_root: &staging_root,
|
||||
final_payload_path: &payload_root.join("bat.AppImage"),
|
||||
artifact_bytes: b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
|
||||
desktop: Some(DesktopIntegrationRequest {
|
||||
desktop_entry_path: &desktop_root.join("aim-bat.desktop"),
|
||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||
icon_path: Some(&icon_root.join("bat.png")),
|
||||
icon_bytes: None,
|
||||
}),
|
||||
helpers: DesktopHelpers::default(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let icon_path = outcome.icon_path.unwrap();
|
||||
assert!(icon_path.exists());
|
||||
assert!(
|
||||
fs::read(&icon_path)
|
||||
.unwrap()
|
||||
.starts_with(b"\x89PNG\r\n\x1a\n")
|
||||
);
|
||||
}
|
||||
32
crates/aim-core/tests/install_payload.rs
Normal file
32
crates/aim-core/tests/install_payload.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use aim_core::integration::install::stage_and_commit_payload;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn payload_commit_moves_staged_appimage_into_final_location() {
|
||||
let root = tempdir().unwrap();
|
||||
let staging_root = root.path().join("staging");
|
||||
let payload_root = root.path().join("payloads");
|
||||
fs::create_dir(&staging_root).unwrap();
|
||||
fs::create_dir(&payload_root).unwrap();
|
||||
|
||||
let final_payload_path = payload_root.join("bat.AppImage");
|
||||
let outcome =
|
||||
stage_and_commit_payload(&staging_root, &final_payload_path, b"\x7fELFAppImage").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
outcome
|
||||
.final_payload_path
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str()),
|
||||
Some("AppImage")
|
||||
);
|
||||
assert!(outcome.final_payload_path.exists());
|
||||
|
||||
let mode = fs::metadata(&outcome.final_payload_path)
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(mode & 0o111, 0o111);
|
||||
}
|
||||
49
crates/aim-core/tests/install_policy.rs
Normal file
49
crates/aim-core/tests/install_policy.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use aim_core::integration::policy::{IntegrationMode, resolve_install_policy};
|
||||
use aim_core::platform::{DistroFamily, HostCapabilities, InstallScope};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn immutable_system_request_downgrades_to_user_when_allowed() {
|
||||
let capabilities = HostCapabilities::immutable_user_only();
|
||||
let policy =
|
||||
resolve_install_policy(DistroFamily::Immutable, InstallScope::System, &capabilities)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(policy.scope, InstallScope::User);
|
||||
assert_eq!(policy.integration_mode, IntegrationMode::Degraded);
|
||||
assert!(!policy.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nix_system_request_is_denied() {
|
||||
let error = resolve_install_policy(
|
||||
DistroFamily::Nix,
|
||||
InstallScope::System,
|
||||
&HostCapabilities::default(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(error.contains("not supported on Nix hosts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_policy_uses_managed_payload_and_native_integration_roots() {
|
||||
let policy = resolve_install_policy(
|
||||
DistroFamily::Fedora,
|
||||
InstallScope::System,
|
||||
&HostCapabilities::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(policy.scope, InstallScope::System);
|
||||
assert_eq!(policy.payload_root, Path::new("/opt/aim/appimages"));
|
||||
assert_eq!(
|
||||
policy.desktop_entry_root,
|
||||
Path::new("/usr/share/applications")
|
||||
);
|
||||
assert_eq!(
|
||||
policy.icon_root,
|
||||
Path::new("/usr/share/icons/hicolor/256x256/apps")
|
||||
);
|
||||
assert_eq!(policy.integration_mode, IntegrationMode::Full);
|
||||
}
|
||||
52
crates/aim-core/tests/platform_detection.rs
Normal file
52
crates/aim-core/tests/platform_detection.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use aim_core::platform::capabilities::{probe_desktop_helpers, probe_writable_roots};
|
||||
use aim_core::platform::distro::{DistroFamily, detect_distro_family};
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn detects_fedora_family_from_os_release() {
|
||||
let distro = detect_distro_family("ID=fedora\nID_LIKE=rhel centos\n");
|
||||
assert_eq!(distro, DistroFamily::Fedora);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_immutable_family_from_variant_id() {
|
||||
let distro = detect_distro_family("ID=fedora\nVARIANT_ID=silverblue\n");
|
||||
assert_eq!(distro, DistroFamily::Immutable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probes_desktop_helpers_from_search_paths() {
|
||||
let helper_dir = tempdir().unwrap();
|
||||
let update_desktop_database = helper_dir.path().join("update-desktop-database");
|
||||
let gtk_update_icon_cache = helper_dir.path().join("gtk-update-icon-cache");
|
||||
|
||||
fs::write(&update_desktop_database, "#!/bin/sh\n").unwrap();
|
||||
fs::write(>k_update_icon_cache, "#!/bin/sh\n").unwrap();
|
||||
fs::set_permissions(&update_desktop_database, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
fs::set_permissions(>k_update_icon_cache, fs::Permissions::from_mode(0o755)).unwrap();
|
||||
|
||||
let helpers = probe_desktop_helpers(&[helper_dir.path()]);
|
||||
|
||||
assert!(helpers.update_desktop_database);
|
||||
assert!(helpers.gtk_update_icon_cache);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probes_writable_roots_from_candidate_directories() {
|
||||
let root = tempdir().unwrap();
|
||||
let payload = root.path().join("payload");
|
||||
let desktop_entries = root.path().join("applications");
|
||||
let icons = root.path().join("icons");
|
||||
|
||||
fs::create_dir(&payload).unwrap();
|
||||
fs::create_dir(&desktop_entries).unwrap();
|
||||
fs::create_dir(&icons).unwrap();
|
||||
|
||||
let writable = probe_writable_roots(&payload, &desktop_entries, &icons);
|
||||
|
||||
assert!(writable.payload);
|
||||
assert!(writable.desktop_entries);
|
||||
assert!(writable.icons);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue