github source v1

This commit is contained in:
stoorps 2026-03-19 20:14:39 +00:00
parent 71f89dde9c
commit caf870d05e
Signed by: stoorps
SSH key fingerprint: SHA256:AZlPfu9hTu042EGtZElmDQoy+KvMOeShLDan/fYLoNI
50 changed files with 4139 additions and 131 deletions

View file

@ -1,3 +1,4 @@
use aim_core::adapters::github::GitHubAdapter;
use aim_core::adapters::traits::AdapterCapabilities;
#[test]
@ -5,3 +6,13 @@ fn adapter_capabilities_can_report_exact_resolution_only() {
let capabilities = AdapterCapabilities::exact_resolution_only();
assert!(!capabilities.supports_search);
}
#[test]
fn legacy_github_adapter_delegates_to_source_pipeline() {
let adapter = GitHubAdapter;
let result = adapter.normalize("sharkdp/bat").unwrap();
assert_eq!(result.normalized_kind.as_str(), "github-repository");
assert_eq!(result.canonical_locator.as_deref(), Some("sharkdp/bat"));
}

View file

@ -4,6 +4,7 @@ use aim_core::adapters::all_adapter_kinds;
fn all_expected_adapter_kinds_are_registered() {
let kinds = all_adapter_kinds();
assert!(kinds.contains(&"github"));
assert!(kinds.contains(&"gitlab"));
assert!(kinds.contains(&"direct-url"));
assert!(kinds.contains(&"zsync"));

View file

@ -0,0 +1,3 @@
zsync: 0.6.2
Filename: T3-Code-0.0.11-x86_64.AppImage
URL: https://example.test/T3-Code-0.0.11-x86_64.AppImage

View file

@ -0,0 +1,3 @@
version: 0.0.11
path: T3-Code-0.0.11-x86_64.AppImage
sha512: example-sha

View file

@ -1,5 +1,6 @@
use aim_core::app::add::build_add_plan;
use aim_core::app::add::{build_add_plan_with, materialize_app_record, prefer_latest_tracking};
use aim_core::app::query::resolve_query;
use aim_core::source::github::FixtureGitHubTransport;
#[test]
fn github_adapter_can_normalize_owner_repo_source() {
@ -10,9 +11,78 @@ fn github_adapter_can_normalize_owner_repo_source() {
#[test]
fn add_flow_builds_github_plan_from_owner_repo_query() {
let plan = build_add_plan("sharkdp/bat").unwrap();
let plan = build_add_plan_with("sharkdp/bat", &FixtureGitHubTransport).unwrap();
assert_eq!(plan.resolution.source.kind.as_str(), "github");
assert_eq!(plan.resolution.source.locator, "sharkdp/bat");
assert_eq!(plan.resolution.release.version, "latest");
assert_eq!(plan.selected_artifact.selection_reason, "metadata-guided");
}
#[test]
fn add_plan_prefers_metadata_guided_appimage_when_available() {
let plan = build_add_plan_with("pingdotgg/t3code", &FixtureGitHubTransport).unwrap();
assert_eq!(plan.selected_artifact.selection_reason, "metadata-guided");
assert_eq!(
plan.update_strategy.preferred.kind.as_str(),
"electron-builder"
);
}
#[test]
fn direct_old_release_url_requests_tracking_choice_prompt() {
let plan = build_add_plan_with(
"https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage",
&FixtureGitHubTransport,
)
.unwrap();
assert!(
plan.interactions
.iter()
.any(|item| item.key == "tracking-preference")
);
}
#[test]
fn materialized_record_preserves_source_and_strategy() {
let query = "sharkdp/bat";
let plan = build_add_plan_with(query, &FixtureGitHubTransport).unwrap();
let record = materialize_app_record(query, &plan).unwrap();
assert_eq!(record.stable_id, "sharkdp-bat");
assert_eq!(record.display_name, "bat");
assert_eq!(record.source_input.as_deref(), Some(query));
assert_eq!(record.installed_version.as_deref(), Some("1.0.0"));
assert_eq!(
record
.update_strategy
.as_ref()
.unwrap()
.preferred
.kind
.as_str(),
"electron-builder"
);
assert_eq!(record.source.as_ref().unwrap().locator, query);
}
#[test]
fn latest_tracking_choice_promotes_non_direct_update_channel() {
let plan = build_add_plan_with(
"https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage",
&FixtureGitHubTransport,
)
.unwrap();
let resolved = prefer_latest_tracking(plan);
assert!(resolved.interactions.is_empty());
assert_eq!(resolved.resolution.source.locator, "pingdotgg/t3code");
assert!(resolved.resolution.source.tracks_latest);
assert_ne!(
resolved.update_strategy.preferred.kind.as_str(),
"direct-asset-lineage"
);
}

View file

@ -0,0 +1,33 @@
use aim_core::app::query::resolve_query;
use aim_core::source::github::{FixtureGitHubTransport, discover_github_candidates_with};
#[test]
fn discovery_reports_appimage_assets_and_latest_linux_yml() {
let source = resolve_query("pingdotgg/t3code").unwrap();
let discovery = discover_github_candidates_with(&source, &FixtureGitHubTransport).unwrap();
assert!(
discovery
.assets
.iter()
.any(|asset| asset.name.ends_with(".AppImage"))
);
assert!(
discovery
.metadata_documents
.iter()
.any(|doc| doc.url.ends_with("latest-linux.yml"))
);
}
#[test]
fn discovery_marks_explicit_older_release_against_latest_fixture_release() {
let source = resolve_query(
"https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage",
)
.unwrap();
let discovery = discover_github_candidates_with(&source, &FixtureGitHubTransport).unwrap();
assert_eq!(discovery.releases[0].tag, "v0.0.12");
assert!(discovery.requested_is_older_release);
}

View file

@ -0,0 +1,11 @@
use aim_core::domain::update::ParsedMetadataKind;
use aim_core::metadata::{MetadataDocument, parse_document};
#[test]
fn unknown_document_returns_typed_warning_not_panic() {
let doc = MetadataDocument::plain_text("https://example.test/notes.txt", b"not metadata");
let result = parse_document(&doc).unwrap();
assert_eq!(result.kind, ParsedMetadataKind::Unknown);
assert!(!result.warnings.is_empty());
}

View file

@ -0,0 +1,15 @@
use aim_core::domain::update::ParsedMetadataKind;
use aim_core::metadata::{MetadataDocument, parse_document};
#[test]
fn parses_latest_linux_yml_into_download_hints() {
let raw = include_bytes!("fixtures/latest-linux.yml");
let doc = MetadataDocument::yaml("https://example.test/latest-linux.yml", raw);
let result = parse_document(&doc).unwrap();
assert_eq!(result.kind, ParsedMetadataKind::ElectronBuilder);
assert_eq!(
result.hints.primary_download.as_deref(),
Some("T3-Code-0.0.11-x86_64.AppImage")
);
}

View file

@ -0,0 +1,12 @@
use aim_core::domain::update::ParsedMetadataKind;
use aim_core::metadata::{MetadataDocument, parse_document};
#[test]
fn parses_zsync_document_into_channel_hints() {
let raw = include_bytes!("fixtures/example.zsync");
let doc = MetadataDocument::plain_text("https://example.test/app.AppImage.zsync", raw);
let result = parse_document(&doc).unwrap();
assert_eq!(result.kind, ParsedMetadataKind::Zsync);
assert!(result.hints.primary_download.is_some());
}

View file

@ -1,8 +1,27 @@
use aim_core::app::query::resolve_query;
use aim_core::domain::source::SourceKind;
use aim_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceKind};
#[test]
fn owner_repo_defaults_to_github() {
let source = resolve_query("sharkdp/bat").unwrap();
assert_eq!(source.kind, SourceKind::GitHub);
assert_eq!(source.input_kind, SourceInputKind::RepoShorthand);
assert_eq!(
source.normalized_kind,
NormalizedSourceKind::GitHubRepository
);
}
#[test]
fn classifies_github_release_asset_url() {
let source = resolve_query(
"https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage",
)
.unwrap();
assert_eq!(source.input_kind, SourceInputKind::GitHubReleaseAssetUrl);
assert_eq!(
source.normalized_kind,
NormalizedSourceKind::GitHubReleaseAsset
);
}

View file

@ -8,3 +8,46 @@ fn registry_round_trips_app_records() {
let loaded = store.load().unwrap();
assert!(loaded.apps.is_empty());
}
#[test]
fn registry_round_trips_update_strategy_and_alternates() {
let dir = tempdir().unwrap();
let store = RegistryStore::new(dir.path().join("registry.toml"));
let registry = aim_core::registry::model::Registry {
version: 1,
apps: vec![aim_core::domain::app::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(aim_core::domain::update::UpdateStrategy {
preferred: aim_core::domain::update::ChannelPreference {
kind: aim_core::domain::update::UpdateChannelKind::DirectAsset,
locator: "https://example.test/app.AppImage".to_owned(),
reason: "install-origin-match".to_owned(),
},
alternates: vec![
aim_core::domain::update::ChannelPreference {
kind: aim_core::domain::update::UpdateChannelKind::GitHubReleases,
locator: "pingdotgg/t3code".to_owned(),
reason: "heuristic-match".to_owned(),
},
aim_core::domain::update::ChannelPreference {
kind: aim_core::domain::update::UpdateChannelKind::ElectronBuilder,
locator: "https://example.test/latest-linux.yml".to_owned(),
reason: "metadata-guided".to_owned(),
},
],
}),
metadata: Vec::new(),
}],
};
store.save(&registry).unwrap();
let loaded = store.load().unwrap();
let strategy = loaded.apps[0].update_strategy.as_ref().unwrap();
assert_eq!(strategy.preferred.reason, "install-origin-match");
assert_eq!(strategy.alternates.len(), 2);
}

View file

@ -1,4 +1,4 @@
use aim_core::app::interaction::InteractionRequest;
use aim_core::app::interaction::{InteractionKind, InteractionRequest};
use aim_core::app::list::build_list_rows;
use aim_core::app::remove::resolve_registered_app;
use aim_core::domain::app::AppRecord;
@ -15,6 +15,11 @@ fn list_flow_returns_display_rows_for_registered_apps() {
let rows = build_list_rows(&[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(),
}]);
assert_eq!(rows.len(), 1);
@ -28,10 +33,20 @@ fn ambiguous_remove_matches_include_stable_ids_for_client_choice() {
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(),
},
AppRecord {
stable_id: "bat-nightly".to_owned(),
display_name: "Bat".to_owned(),
source_input: None,
source: None,
installed_version: None,
update_strategy: None,
metadata: Vec::new(),
},
];
@ -40,9 +55,12 @@ fn ambiguous_remove_matches_include_stable_ids_for_client_choice() {
assert_eq!(
error,
aim_core::app::remove::ResolveRegisteredAppError::Ambiguous {
request: InteractionRequest::SelectRegisteredApp {
query: "Bat".to_owned(),
matches: vec!["Bat (bat)".to_owned(), "Bat (bat-nightly)".to_owned()],
request: InteractionRequest {
key: "select-registered-app".to_owned(),
kind: InteractionKind::SelectRegisteredApp {
query: "Bat".to_owned(),
matches: vec!["Bat (bat)".to_owned(), "Bat (bat-nightly)".to_owned()],
},
},
}
);

View file

@ -1,5 +1,6 @@
use aim_core::app::update::build_update_plan;
use aim_core::domain::app::AppRecord;
use aim_core::domain::update::{ChannelPreference, UpdateChannelKind, UpdateStrategy};
#[test]
fn empty_registry_produces_empty_plan() {
@ -13,10 +14,48 @@ 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(),
}];
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(),
}];
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");
}