diff --git a/dotnet/src/Devolutions.Pinget.Cli/ImportInstallRequestFactory.cs b/dotnet/src/Devolutions.Pinget.Cli/ImportInstallRequestFactory.cs new file mode 100644 index 0000000..e8b4730 --- /dev/null +++ b/dotnet/src/Devolutions.Pinget.Cli/ImportInstallRequestFactory.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using Devolutions.Pinget.Core; + +namespace Devolutions.Pinget.Cli; + +internal static class ImportInstallRequestFactory +{ + internal static InstallRequest Create( + PackageQuery query, + JsonElement package, + bool acceptPackageAgreements, + bool noUpgrade) => + new() + { + Query = query with + { + Channel = GetString(package, "Channel") ?? query.Channel, + InstallScope = GetString(package, "Scope") ?? query.InstallScope, + }, + Mode = InstallerMode.SilentWithProgress, + Custom = GetString(package, "InitialCustomSwitches"), + Override = GetString(package, "InitialOverrideArguments"), + AcceptPackageAgreements = acceptPackageAgreements, + NoUpgrade = noUpgrade, + }; + + private static string? GetString(JsonElement element, string propertyName) => + element.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.String + ? value.GetString() + : null; +} diff --git a/dotnet/src/Devolutions.Pinget.Cli/Program.cs b/dotnet/src/Devolutions.Pinget.Cli/Program.cs index 3527dad..2e11ee4 100644 --- a/dotnet/src/Devolutions.Pinget.Cli/Program.cs +++ b/dotnet/src/Devolutions.Pinget.Cli/Program.cs @@ -1165,7 +1165,7 @@ try { Console.Write($"Installing {pkgId}..."); - var result = repo.Install(CreateInstallRequest( + var result = repo.Install(ImportInstallRequestFactory.Create( new PackageQuery { Id = pkgId, @@ -1173,20 +1173,8 @@ Exact = true, Version = pkgVersion, }, - null, - InstallerMode.SilentWithProgress, - null, - null, - null, - null, - false, - false, + pkg, acceptPackageAgreements, - false, - null, - false, - false, - null, noUpgrade)); if (result.NoOp) { diff --git a/dotnet/src/Devolutions.Pinget.Core.Tests/ImportInstallRequestTests.cs b/dotnet/src/Devolutions.Pinget.Core.Tests/ImportInstallRequestTests.cs new file mode 100644 index 0000000..4251a11 --- /dev/null +++ b/dotnet/src/Devolutions.Pinget.Core.Tests/ImportInstallRequestTests.cs @@ -0,0 +1,89 @@ +using System.Text.Json; +using Devolutions.Pinget.Cli; +using Devolutions.Pinget.Core; +using Xunit; + +namespace Devolutions.Pinget.Core.Tests; + +public class ImportInstallRequestTests +{ + [Fact] + public void ImportCustomSwitchesReachInstallerDispatch() + { + using var document = JsonDocument.Parse("""{"InitialCustomSwitches":"/custom-from-import"}"""); + var request = ImportInstallRequestFactory.Create( + new PackageQuery { Id = "Test.Package", Exact = true }, + document.RootElement, + acceptPackageAgreements: false, + noUpgrade: false); + + var args = InstallerDispatch.BuildArguments( + "exe", + request, + new Manifest { Id = "Test.Package", Name = "Test", Version = "1.0" }, + new Installer { InstallerType = "exe" }); + + Assert.Contains("/custom-from-import", args); + } + + [Fact] + public void ImportOverrideArgumentsReachInstallerDispatch() + { + using var document = JsonDocument.Parse("""{"InitialOverrideArguments":"/override-from-import /quiet"}"""); + var request = ImportInstallRequestFactory.Create( + new PackageQuery { Id = "Test.Package", Exact = true }, + document.RootElement, + acceptPackageAgreements: false, + noUpgrade: false); + + var args = InstallerDispatch.BuildArguments( + "exe", + request, + new Manifest { Id = "Test.Package", Name = "Test", Version = "1.0" }, + new Installer { InstallerType = "exe" }); + + Assert.Equal(["/override-from-import", "/quiet"], args); + } + + [Fact] + public void ImportScopeAndChannelReachPackageSelection() + { + using var document = JsonDocument.Parse("""{"Scope":"machine","Channel":"preview"}"""); + var request = ImportInstallRequestFactory.Create( + new PackageQuery { Id = "Test.Package", Exact = true }, + document.RootElement, + acceptPackageAgreements: false, + noUpgrade: false); + + Assert.Equal("machine", request.Query.InstallScope); + Assert.Equal("preview", request.Query.Channel); + } + + [Fact] + public void ImportWithoutOptionsKeepsExistingInstallRequestDefaults() + { + using var document = JsonDocument.Parse("""{"PackageIdentifier":"Test.Package"}"""); + var request = ImportInstallRequestFactory.Create( + new PackageQuery { Id = "Test.Package", Exact = true }, + document.RootElement, + acceptPackageAgreements: false, + noUpgrade: false); + + Assert.Equal(InstallerMode.SilentWithProgress, request.Mode); + Assert.Null(request.Custom); + Assert.Null(request.Override); + Assert.False(request.AcceptPackageAgreements); + Assert.False(request.NoUpgrade); + + var manifest = new Manifest { Id = "Test.Package", Name = "Test", Version = "1.0" }; + var installer = new Installer { InstallerType = "exe" }; + var importedArgs = InstallerDispatch.BuildArguments("exe", request, manifest, installer); + var defaultArgs = InstallerDispatch.BuildArguments( + "exe", + new InstallRequest { Query = request.Query, Mode = InstallerMode.SilentWithProgress }, + manifest, + installer); + + Assert.Equal(defaultArgs, importedArgs); + } +} diff --git a/rust/crates/pinget-cli/src/main.rs b/rust/crates/pinget-cli/src/main.rs index fabde27..0531452 100644 --- a/rust/crates/pinget-cli/src/main.rs +++ b/rust/crates/pinget-cli/src/main.rs @@ -2564,8 +2564,9 @@ fn do_import( exact: true, ..Default::default() }; + let request = create_import_install_request(query, package, accept_package_agreements, no_upgrade); - match repository.search(&query) { + match repository.search(&request.query) { Ok(result) if !result.matches.is_empty() => { let m = &result.matches[0]; if dry_run { @@ -2582,9 +2583,6 @@ fn do_import( m.version.as_deref().unwrap_or("?"), source_name ); - let mut request = InstallRequest::new(query.clone()); - request.accept_package_agreements = accept_package_agreements; - request.no_upgrade = no_upgrade; match repository.install_request(&request) { Ok(r) if r.no_op => { println!(" NO-OP"); @@ -2628,6 +2626,34 @@ fn do_import( Ok(()) } +fn create_import_install_request( + query: PackageQuery, + package: &serde_json::Value, + accept_package_agreements: bool, + no_upgrade: bool, +) -> InstallRequest { + let mut request = InstallRequest::new(query); + request.query.channel = package + .get("Channel") + .and_then(serde_json::Value::as_str) + .map(str::to_owned); + request.query.install_scope = package + .get("Scope") + .and_then(serde_json::Value::as_str) + .map(str::to_owned); + request.custom = package + .get("InitialCustomSwitches") + .and_then(serde_json::Value::as_str) + .map(str::to_owned); + request.override_args = package + .get("InitialOverrideArguments") + .and_then(serde_json::Value::as_str) + .map(str::to_owned); + request.accept_package_agreements = accept_package_agreements; + request.no_upgrade = no_upgrade; + request +} + fn can_ignore_unavailable_import_failure(error: &anyhow::Error) -> bool { let message = error.to_string().to_ascii_lowercase(); message.contains("no package matched") || message.contains("no applicable installer found") @@ -2667,6 +2693,40 @@ mod tests { assert!(!parse_boolean_setting_value("0").expect("bool")); } + #[test] + fn import_install_request_preserves_custom_switches_and_override_arguments() { + let package = serde_json::json!({ + "InitialCustomSwitches": "/custom-switch", + "InitialOverrideArguments": "/override-arguments", + "Channel": "preview", + "Scope": "machine" + }); + let request = create_import_install_request(PackageQuery::default(), &package, true, true); + + assert_eq!(request.custom.as_deref(), Some("/custom-switch")); + assert_eq!(request.override_args.as_deref(), Some("/override-arguments")); + assert_eq!(request.query.channel.as_deref(), Some("preview")); + assert_eq!(request.query.install_scope.as_deref(), Some("machine")); + assert!(request.accept_package_agreements); + assert!(request.no_upgrade); + } + + #[test] + fn import_install_request_without_options_keeps_default_install_behavior() { + let request = create_import_install_request( + PackageQuery::default(), + &serde_json::json!({ "PackageIdentifier": "Test.Package" }), + false, + false, + ); + + assert_eq!(request.mode, InstallerMode::SilentWithProgress); + assert!(request.custom.is_none()); + assert!(request.override_args.is_none()); + assert!(!request.accept_package_agreements); + assert!(!request.no_upgrade); + } + #[test] fn serialize_json_pretty_preserves_pascal_case_list_fields() { let response = ListResponse {