feat: finalize search UX and release hardening

This commit is contained in:
stoorps 2026-03-21 16:53:33 +00:00
parent c63b2917da
commit 34f9543a78
Signed by: stoorps
SSH key fingerprint: SHA256:AZlPfu9hTu042EGtZElmDQoy+KvMOeShLDan/fYLoNI
44 changed files with 4983 additions and 94 deletions

View file

@ -0,0 +1,95 @@
use std::fs;
use aim_core::integration::install::{InstallRequest, PayloadInstallError, execute_install};
use aim_core::platform::DesktopHelpers;
use tempfile::tempdir;
const VALID_FIXTURE_SHA512: &str =
"ZZma4ZD+9XB4GGTHCNZu8I92OY02YrEvIG89ZtRNi99W8SZKwWkmGZz/QyNBxqAt0XeiKtcR80/dMnKlwpcIWw==";
#[test]
fn install_succeeds_with_valid_trusted_checksum() {
let root = tempdir().unwrap();
let staged_path = write_staged_payload(
root.path(),
b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
);
let final_payload_path = root.path().join("payloads/bat.AppImage");
let outcome = execute_install(&InstallRequest {
staged_payload_path: &staged_path,
final_payload_path: &final_payload_path,
trusted_checksum: Some(VALID_FIXTURE_SHA512),
desktop: None,
helpers: DesktopHelpers::default(),
})
.unwrap();
assert_eq!(outcome.final_payload_path, final_payload_path);
assert!(outcome.final_payload_path.exists());
}
#[test]
fn install_succeeds_without_trusted_checksum() {
let root = tempdir().unwrap();
let staged_path = write_staged_payload(root.path(), b"\x7fELFAppImage");
let final_payload_path = root.path().join("payloads/bat.AppImage");
let outcome = execute_install(&InstallRequest {
staged_payload_path: &staged_path,
final_payload_path: &final_payload_path,
trusted_checksum: None,
desktop: None,
helpers: DesktopHelpers::default(),
})
.unwrap();
assert!(outcome.final_payload_path.exists());
}
#[test]
fn install_fails_before_commit_when_trusted_checksum_mismatches() {
let root = tempdir().unwrap();
let staged_path = write_staged_payload(root.path(), b"\x7fELFAppImage");
let final_payload_path = root.path().join("payloads/bat.AppImage");
let error = execute_install(&InstallRequest {
staged_payload_path: &staged_path,
final_payload_path: &final_payload_path,
trusted_checksum: Some(VALID_FIXTURE_SHA512),
desktop: None,
helpers: DesktopHelpers::default(),
})
.unwrap_err();
assert!(matches!(error, PayloadInstallError::ChecksumMismatch));
assert!(!final_payload_path.exists());
assert!(!staged_path.exists());
}
#[test]
fn malformed_trusted_checksum_fails_before_commit() {
let root = tempdir().unwrap();
let staged_path = write_staged_payload(root.path(), b"\x7fELFAppImage");
let final_payload_path = root.path().join("payloads/bat.AppImage");
let error = execute_install(&InstallRequest {
staged_payload_path: &staged_path,
final_payload_path: &final_payload_path,
trusted_checksum: Some("not-base64"),
desktop: None,
helpers: DesktopHelpers::default(),
})
.unwrap_err();
assert!(matches!(error, PayloadInstallError::InvalidTrustedChecksum));
assert!(!final_payload_path.exists());
assert!(!staged_path.exists());
}
fn write_staged_payload(root: &std::path::Path, bytes: &[u8]) -> std::path::PathBuf {
let staged_path = root.join("staging/bat.download");
fs::create_dir_all(staged_path.parent().unwrap()).unwrap();
fs::write(&staged_path, bytes).unwrap();
staged_path
}

View file

@ -0,0 +1,180 @@
use std::fs;
use std::io::{self, Cursor, Read};
use std::time::Duration;
use aim_core::app::add::{
InstallAppError, download_to_staged_path_with_retries,
stream_payload_to_staged_file_with_reporter,
};
use aim_core::app::progress::{NoopReporter, OperationEvent};
use aim_core::integration::install::{InstallRequest, execute_install};
use aim_core::platform::DesktopHelpers;
use aim_core::source::github::HttpClientPolicy;
use tempfile::tempdir;
#[test]
fn payload_streaming_writes_staged_file_and_reports_progress() {
let root = tempdir().unwrap();
let staged_path = root.path().join("staging/bat.download");
let bytes = b"\x7fELFAppImage";
let mut reader = Cursor::new(bytes.as_slice());
let mut events = Vec::new();
let mut reporter = |event: &OperationEvent| events.push(event.clone());
let written = stream_payload_to_staged_file_with_reporter(
&mut reader,
Some(bytes.len() as u64),
&staged_path,
&mut reporter,
)
.unwrap();
assert_eq!(written, bytes.len() as u64);
assert_eq!(
fs::metadata(&staged_path).unwrap().len(),
bytes.len() as u64
);
assert!(events.iter().any(|event| {
matches!(
event,
OperationEvent::Progress {
current,
total: Some(total)
} if *current == bytes.len() as u64 && *total == bytes.len() as u64
)
}));
}
#[test]
fn install_commits_from_staged_payload_path() {
let root = tempdir().unwrap();
let staged_path = root.path().join("staging/bat.download");
let final_payload_path = root.path().join("payloads/bat.AppImage");
fs::create_dir_all(staged_path.parent().unwrap()).unwrap();
fs::write(&staged_path, b"\x7fELFAppImage").unwrap();
let outcome = execute_install(&InstallRequest {
staged_payload_path: &staged_path,
final_payload_path: &final_payload_path,
trusted_checksum: None,
desktop: None,
helpers: DesktopHelpers::default(),
})
.unwrap();
assert_eq!(outcome.final_payload_path, final_payload_path);
assert!(outcome.final_payload_path.exists());
assert!(!staged_path.exists());
}
#[test]
fn failed_streaming_download_removes_partial_staged_payload() {
let root = tempdir().unwrap();
let staged_path = root.path().join("staging/bat.download");
let mut reader = FailingReader::new(b"\x7fELFpartial".to_vec(), 4);
let mut reporter = NoopReporter;
let result = stream_payload_to_staged_file_with_reporter(
&mut reader,
Some(12),
&staged_path,
&mut reporter,
);
assert!(result.is_err());
assert!(!staged_path.exists());
}
#[test]
fn retry_policy_retries_transient_failures_before_success() {
let root = tempdir().unwrap();
let staged_path = root.path().join("staging/bat.download");
let bytes = b"\x7fELFAppImage";
let mut attempts = 0;
let written = download_to_staged_path_with_retries(
&staged_path,
&mut NoopReporter,
HttpClientPolicy {
timeout: Duration::from_secs(30),
max_retries: 3,
},
|| {
attempts += 1;
if attempts == 1 {
return Err(InstallAppError::DownloadIo(io::Error::other(
"transient failure",
)));
}
Ok((
Box::new(Cursor::new(bytes.to_vec())) as Box<dyn Read>,
Some(bytes.len() as u64),
))
},
)
.unwrap();
assert_eq!(attempts, 2);
assert_eq!(written, bytes.len() as u64);
assert!(staged_path.exists());
}
#[test]
fn retry_exhaustion_returns_error_and_cleans_staged_payload() {
let root = tempdir().unwrap();
let staged_path = root.path().join("staging/bat.download");
let mut attempts = 0;
let result = download_to_staged_path_with_retries(
&staged_path,
&mut NoopReporter,
HttpClientPolicy {
timeout: Duration::from_secs(30),
max_retries: 2,
},
|| {
attempts += 1;
Ok((
Box::new(FailingReader::new(b"\x7fELFpartial".to_vec(), 4)) as Box<dyn Read>,
Some(12),
))
},
);
assert!(result.is_err());
assert_eq!(attempts, 2);
assert!(!staged_path.exists());
}
struct FailingReader {
bytes: Vec<u8>,
chunk_size: usize,
position: usize,
}
impl FailingReader {
fn new(bytes: Vec<u8>, chunk_size: usize) -> Self {
Self {
bytes,
chunk_size,
position: 0,
}
}
}
impl Read for FailingReader {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
if self.position >= self.chunk_size {
return Err(io::Error::other("fixture read failure"));
}
let remaining = self.chunk_size - self.position;
let to_read = remaining
.min(buffer.len())
.min(self.bytes.len() - self.position);
buffer[..to_read].copy_from_slice(&self.bytes[self.position..self.position + to_read]);
self.position += to_read;
Ok(to_read)
}
}

View file

@ -1,5 +1,8 @@
use aim_core::app::query::resolve_query;
use aim_core::source::github::{FixtureGitHubTransport, discover_github_candidates_with};
use aim_core::source::github::{
FixtureGitHubTransport, discover_github_candidates_with, http_client_policy,
};
use std::time::Duration;
#[test]
fn discovery_reports_appimage_assets_and_latest_linux_yml() {
@ -31,3 +34,11 @@ fn discovery_marks_explicit_older_release_against_latest_fixture_release() {
assert_eq!(discovery.releases[0].tag, "v0.0.12");
assert!(discovery.requested_is_older_release);
}
#[test]
fn github_http_policy_uses_explicit_timeout_and_retry_defaults() {
let policy = http_client_policy();
assert_eq!(policy.timeout, Duration::from_secs(30));
assert_eq!(policy.max_retries, 3);
}

View file

@ -13,13 +13,15 @@ fn integration_failure_removes_new_payload_and_generated_files() {
fs::create_dir(&staging_root).unwrap();
fs::create_dir(&payload_root).unwrap();
fs::write(&blocking_path, "blocker").unwrap();
let staged_path = staging_root.join("bat.download");
fs::write(&staged_path, b"\x7fELFAppImage").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,
staged_payload_path: &staged_path,
final_payload_path: &final_payload_path,
artifact_bytes: b"\x7fELFAppImage",
trusted_checksum: None,
desktop: Some(DesktopIntegrationRequest {
desktop_entry_path: &desktop_entry_path,
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",

View file

@ -8,21 +8,27 @@ use std::fs;
use std::os::unix::fs::PermissionsExt;
use tempfile::tempdir;
fn write_staged_payload(root: &std::path::Path, name: &str, bytes: &[u8]) -> std::path::PathBuf {
let staged_path = root.join("staging").join(format!("{name}.download"));
fs::create_dir_all(staged_path.parent().unwrap()).unwrap();
fs::write(&staged_path, bytes).unwrap();
staged_path
}
#[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 staged_path = write_staged_payload(root.path(), "bat", b"\x7fELFAppImage");
let outcome = execute_install(&InstallRequest {
staging_root: &staging_root,
staged_payload_path: &staged_path,
final_payload_path: &payload_root.join("bat.AppImage"),
artifact_bytes: b"\x7fELFAppImage",
trusted_checksum: None,
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",
@ -40,16 +46,19 @@ fn install_writes_desktop_entry_and_reports_refresh_warning_only() {
#[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 staged_path = write_staged_payload(
root.path(),
"bat",
b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
);
let update_helper = helper_root.join("update-desktop-database");
let icon_helper = helper_root.join("gtk-update-icon-cache");
@ -70,9 +79,9 @@ fn install_executes_refresh_helpers_when_available() {
fs::create_dir_all(&icon_root).unwrap();
let outcome = execute_install(&InstallRequest {
staging_root: &staging_root,
staged_payload_path: &staged_path,
final_payload_path: &payload_root.join("bat.AppImage"),
artifact_bytes: b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
trusted_checksum: None,
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",
@ -97,20 +106,23 @@ fn install_executes_refresh_helpers_when_available() {
#[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 staged_path = write_staged_payload(
root.path(),
"bat",
b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
);
let outcome = execute_install(&InstallRequest {
staging_root: &staging_root,
staged_payload_path: &staged_path,
final_payload_path: &payload_root.join("bat.AppImage"),
artifact_bytes: b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82",
trusted_checksum: None,
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",

View file

@ -11,9 +11,10 @@ fn payload_commit_moves_staged_appimage_into_final_location() {
fs::create_dir(&staging_root).unwrap();
fs::create_dir(&payload_root).unwrap();
let staged_path = staging_root.join("bat.download");
fs::write(&staged_path, b"\x7fELFAppImage").unwrap();
let final_payload_path = payload_root.join("bat.AppImage");
let outcome =
stage_and_commit_payload(&staging_root, &final_payload_path, b"\x7fELFAppImage").unwrap();
let outcome = stage_and_commit_payload(&staged_path, &final_payload_path).unwrap();
assert_eq!(
outcome

View file

@ -101,3 +101,84 @@ fn registry_round_trips_install_metadata() {
Some("/tmp/install-home/.local/share/icons/hicolor/256x256/apps/t3code.png")
);
}
#[test]
fn registry_save_is_atomic_and_cleans_up_temp_file() {
let dir = tempdir().unwrap();
let registry_path = dir.path().join("registry.toml");
let store = RegistryStore::new(registry_path.clone());
store
.save(&aim_core::registry::model::Registry {
version: 1,
apps: vec![aim_core::domain::app::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,
}],
})
.unwrap();
assert!(registry_path.exists());
assert!(!dir.path().join("registry.toml.tmp").exists());
}
#[test]
fn registry_exclusive_lock_rejects_second_mutator() {
let dir = tempdir().unwrap();
let store = RegistryStore::new(dir.path().join("registry.toml"));
let _guard = store.lock_exclusive().unwrap();
let error = store.lock_exclusive().unwrap_err();
assert!(matches!(
error,
aim_core::registry::store::RegistryStoreError::LockUnavailable
));
}
#[test]
fn registry_mutate_exclusive_reloads_and_writes_latest_state() {
let dir = tempdir().unwrap();
let store = RegistryStore::new(dir.path().join("registry.toml"));
store
.save(&aim_core::registry::model::Registry {
version: 1,
apps: vec![aim_core::domain::app::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,
}],
})
.unwrap();
store
.mutate_exclusive(|registry| {
registry.apps.push(aim_core::domain::app::AppRecord {
stable_id: "t3code".to_owned(),
display_name: "T3 Code".to_owned(),
source_input: None,
source: None,
installed_version: None,
update_strategy: None,
metadata: Vec::new(),
install: None,
});
})
.unwrap();
let loaded = store.load().unwrap();
assert_eq!(loaded.apps.len(), 2);
assert_eq!(loaded.apps[0].stable_id, "bat");
assert_eq!(loaded.apps[1].stable_id, "t3code");
}

View file

@ -0,0 +1,212 @@
use aim_core::app::search::{
GitHubSearchProvider, SearchProvider, SearchProviderError, build_search_results_with,
};
use aim_core::domain::app::AppRecord;
use aim_core::domain::search::{SearchInstallStatus, SearchQuery};
use aim_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceKind, SourceRef};
use aim_core::source::github::{FixtureGitHubTransport, search_github_repositories_with};
#[test]
fn github_fixtures_return_normalized_remote_hits() {
let query = SearchQuery::new("bat");
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &[], &[&provider]).unwrap();
assert_eq!(query.remote_limit, 10);
assert!(results.installed_matches.is_empty());
assert!(results.warnings.is_empty());
assert_eq!(results.remote_hits.len(), 3);
let first = &results.remote_hits[0];
assert_eq!(first.provider_id, "github");
assert_eq!(first.display_name, "sharkdp/bat");
assert_eq!(
first.description.as_deref(),
Some("A cat(1) clone with wings.")
);
assert_eq!(first.source_locator, "https://github.com/sharkdp/bat");
assert_eq!(first.install_query, "sharkdp/bat");
assert_eq!(first.canonical_locator, "sharkdp/bat");
assert_eq!(first.version.as_deref(), Some("1.0.0"));
assert_eq!(first.install_status, SearchInstallStatus::Available);
}
#[test]
fn github_search_respects_limit_and_fixture_order() {
let query = SearchQuery::with_remote_limit("bat", 2);
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &[], &[&provider]).unwrap();
let locators = results
.remote_hits
.iter()
.map(|hit| hit.canonical_locator.as_str())
.collect::<Vec<_>>();
assert_eq!(locators, vec!["sharkdp/bat", "astatine/bat"]);
}
#[test]
fn github_search_ranks_full_name_matches_above_description_only_matches() {
let query = SearchQuery::new("pingdotgg");
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &[], &[&provider]).unwrap();
let locators = results
.remote_hits
.iter()
.map(|hit| hit.canonical_locator.as_str())
.collect::<Vec<_>>();
assert_eq!(locators[0], "pingdotgg/t3code");
assert_eq!(locators, vec!["pingdotgg/t3code"]);
}
#[test]
fn github_search_backfills_description_matches_after_name_matches() {
let query = SearchQuery::with_remote_limit("pingdotgg", 3);
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &[], &[&provider]).unwrap();
let locators = results
.remote_hits
.iter()
.map(|hit| hit.canonical_locator.as_str())
.collect::<Vec<_>>();
assert_eq!(locators, vec!["pingdotgg/t3code"]);
}
#[test]
fn github_search_only_returns_repositories_with_appimage_release_assets() {
let query = SearchQuery::new("pingdotgg");
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &[], &[&provider]).unwrap();
assert!(
results
.remote_hits
.iter()
.all(|hit| hit.canonical_locator == "pingdotgg/t3code")
);
}
#[test]
fn github_name_only_search_excludes_description_only_matches() {
let hits =
search_github_repositories_with("pingdotgg in:name", 10, &FixtureGitHubTransport).unwrap();
let locators = hits
.iter()
.map(|hit| hit.full_name.as_str())
.collect::<Vec<_>>();
assert_eq!(locators, vec!["pingdotgg/t3code"]);
}
#[test]
fn app_search_results_can_carry_local_matches_and_warnings() {
let query = SearchQuery::new("bat");
let installed = vec![AppRecord {
stable_id: "bat".to_owned(),
display_name: "Bat".to_owned(),
source_input: None,
source: None,
installed_version: Some("1.0.0".to_owned()),
update_strategy: None,
metadata: Vec::new(),
install: None,
}];
let provider = FailingProvider;
let results = build_search_results_with(&query, &installed, &[&provider]).unwrap();
assert!(results.remote_hits.is_empty());
assert_eq!(results.installed_matches.len(), 1);
assert_eq!(results.installed_matches[0].stable_id, "bat");
assert_eq!(results.installed_matches[0].display_name, "Bat");
assert_eq!(results.warnings.len(), 1);
assert_eq!(results.warnings[0].provider_id.as_deref(), Some("github"));
}
#[test]
fn github_search_marks_matching_current_install_as_installed() {
let query = SearchQuery::new("bat");
let installed = vec![installed_github_app("sharkdp/bat", "1.0.0")];
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &installed, &[&provider]).unwrap();
let bat = results
.remote_hits
.iter()
.find(|hit| hit.install_query == "sharkdp/bat")
.unwrap();
assert_eq!(
bat.install_status,
SearchInstallStatus::Installed {
installed_version: Some("1.0.0".to_owned()),
}
);
}
#[test]
fn github_search_marks_older_install_as_update_available() {
let query = SearchQuery::new("pingdotgg");
let installed = vec![installed_github_app("pingdotgg/t3code", "0.0.11")];
let provider = GitHubSearchProvider::new(&FixtureGitHubTransport);
let results = build_search_results_with(&query, &installed, &[&provider]).unwrap();
let t3code = results
.remote_hits
.iter()
.find(|hit| hit.install_query == "pingdotgg/t3code")
.unwrap();
assert_eq!(t3code.version.as_deref(), Some("0.0.12"));
assert_eq!(
t3code.install_status,
SearchInstallStatus::UpdateAvailable {
installed_version: Some("0.0.11".to_owned()),
latest_version: Some("0.0.12".to_owned()),
}
);
}
fn installed_github_app(locator: &str, installed_version: &str) -> AppRecord {
AppRecord {
stable_id: locator.replace('/', "-"),
display_name: locator.split('/').next_back().unwrap().to_owned(),
source_input: Some(locator.to_owned()),
source: Some(SourceRef {
kind: SourceKind::GitHub,
locator: locator.to_owned(),
input_kind: SourceInputKind::RepoShorthand,
normalized_kind: NormalizedSourceKind::GitHubRepository,
canonical_locator: Some(locator.to_owned()),
requested_tag: None,
requested_asset_name: None,
tracks_latest: true,
}),
installed_version: Some(installed_version.to_owned()),
update_strategy: None,
metadata: Vec::new(),
install: None,
}
}
struct FailingProvider;
impl SearchProvider for FailingProvider {
fn search(
&self,
_query: &SearchQuery,
) -> Result<Vec<aim_core::domain::search::SearchResult>, SearchProviderError> {
Err(SearchProviderError::new("github", "fixture rate limit"))
}
}