Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider installer applicability in IsUpdateAvailable COM api #5228

Merged
merged 9 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,8 @@ public static void VerifyPortablePackage(
isAddedToPath = currentPathValue.Contains(portablePathValue);
}

if (shouldExist)
{
RunAICLICommand("uninstall", $"--product-code {productCode} --force");
}
// Always clean up as best effort.
RunAICLICommand("uninstall", $"--product-code {productCode} --force");

Assert.AreEqual(shouldExist, exeExists, $"Expected portable exe path: {exePath}");
Assert.AreEqual(shouldExist && !installDirectoryAddedToPath, symlinkExists, $"Expected portable symlink path: {symlinkPath}");
Expand Down
7 changes: 5 additions & 2 deletions src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="UninstallInterop.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -277,7 +277,10 @@ public async Task UninstallPortableModifiedSymlink()
Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist");

// Remove modified symlink as to not interfere with other tests
modifiedSymlinkInfo.Delete();
modifiedSymlinkInfo.Delete();

// Uninstall again to clean up.
await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions());
}

/// <summary>
Expand Down
80 changes: 72 additions & 8 deletions src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="UpgradeInterop.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand All @@ -8,7 +8,8 @@ namespace AppInstallerCLIE2ETests.Interop
{
using System;
using System.Collections.Generic;
using System.IO;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AppInstallerCLIE2ETests.Helpers;
using Microsoft.Management.Deployment;
Expand Down Expand Up @@ -78,7 +79,7 @@ public async Task UpgradePortable()

// Find package again, but this time it should detect the installed version
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0");
Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion?.Version);

// Configure upgrade options
var upgradeOptions = this.TestFactory.CreateInstallOptions();
Expand All @@ -90,7 +91,7 @@ public async Task UpgradePortable()

// Find package again, but this time it should detect the upgraded installed version
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "2.0.0.0");
Assert.AreEqual("2.0.0.0", searchResult.CatalogPackage.InstalledVersion?.Version);
TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true);
}

Expand Down Expand Up @@ -137,7 +138,7 @@ public async Task UpgradePortableARPMismatch()

// Find package again, it should have not been upgraded
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0");
Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version);
TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true);
}

Expand Down Expand Up @@ -185,7 +186,7 @@ public async Task UpgradePortableForcedOverride()

// Find package again, but this time it should detect the upgraded installed version
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "2.0.0.0");
Assert.AreEqual("2.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version);
TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true);
}

Expand Down Expand Up @@ -216,7 +217,7 @@ public async Task UpgradePortableUninstallPrevious()

// Find package again, but this time it should detect the installed version
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0");
Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version);

// Configure upgrade options
var upgradeOptions = this.TestFactory.CreateInstallOptions();
Expand All @@ -228,8 +229,71 @@ public async Task UpgradePortableUninstallPrevious()

// Find package again, but this time it should detect the upgraded installed version
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "3.0.0.0");
Assert.AreEqual("3.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version);
TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true);
}

/// <summary>
/// Tests IsUpdateAvailable.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Test]
public async Task TestIsUpdateAvailable_ApplicableTrue()
{
// Find and install the test package. Install the version 1.0.0.0.
var installDir = TestCommon.GetRandomTestDir();
var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller");
var installOptions = this.TestFactory.CreateInstallOptions();
installOptions.PreferredInstallLocation = installDir;
installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0");
var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions);
Assert.AreEqual(InstallResultStatus.Ok, installResult.Status);

// Find package again, but this time it should detect the installed version.
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller");

// The installed version is 1.0.0.0.
Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version);

// IsUpdateAvailable is true.
Assert.True(searchResult.CatalogPackage.IsUpdateAvailable);

// Uninstall to clean up.
var uninstallOptions = this.TestFactory.CreateUninstallOptions();
var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions);
}

/// <summary>
/// Tests applicability check is performed for IsUpdateAvailable api.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Test]
public async Task TestIsUpdateAvailable_ApplicableFalse()
{
// Find and install the test package. Install the version 1.0.0.0.
var installDir = TestCommon.GetRandomTestDir();
var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestUpgradeApplicability");
var installOptions = this.TestFactory.CreateInstallOptions();
installOptions.PreferredInstallLocation = installDir;
installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0");
var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions);
Assert.AreEqual(InstallResultStatus.Ok, installResult.Status);

// Find package again, but this time it should detect the installed version.
searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestUpgradeApplicability");

// The installed version is 1.0.0.0.
Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version);

// There is version 2.0.0.0 in the package available versions.
Assert.NotNull(First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "2.0.0.0"));

// IsUpdateAvailable is false due to applicability check. Only arm64 in version 2.0.0.0.
Assert.False(searchResult.CatalogPackage.IsUpdateAvailable);

// Uninstall to clean up.
var uninstallOptions = this.TestFactory.CreateUninstallOptions();
var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions);
}

// Cannot use foreach or Linq for out-of-process IVector
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Id: AppInstallerTest.TestUpgradeApplicability
Name: TestUpgradeApplicability
Version: 1.0.0.0
Publisher: AppInstallerTest
License: Test
Installers:
- Arch: x86
Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe
Sha256: <EXEHASH>
InstallerType: exe
ProductCode: '{bfb0f666-99d5-433d-8a2e-32f31d4f8e48}'
Switches:
Custom: '/ProductID {bfb0f666-99d5-433d-8a2e-32f31d4f8e48} /DisplayName TestUpgradeApplicability'
SilentWithProgress: /exeswp
Silent: /exesilent
Interactive: /exeinteractive
Language: /exeenus
Log: /LogFile <LOGPATH>
InstallLocation: /InstallDir <INSTALLPATH>
ManifestVersion: 0.1.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Id: AppInstallerTest.TestUpgradeApplicability
Name: TestUpgradeApplicability
Version: 2.0.0.0
Publisher: AppInstallerTest
License: Test
Installers:
- Arch: arm64
Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe
Sha256: <EXEHASH>
InstallerType: exe
ProductCode: '{bfb0f666-99d5-433d-8a2e-32f31d4f8e48}'
Switches:
Custom: '/ProductID {bfb0f666-99d5-433d-8a2e-32f31d4f8e48} /DisplayName TestUpgradeApplicability'
SilentWithProgress: /exeswp
Silent: /exesilent
Interactive: /exeinteractive
Language: /exeenus
Log: /LogFile <LOGPATH>
InstallLocation: /InstallDir <INSTALLPATH>
ManifestVersion: 0.1.0
97 changes: 97 additions & 0 deletions src/AppInstallerCLITests/RestInterface_1_0.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,54 @@ namespace
})delimiter");
}

utility::string_t GetManifestsResponse_MultipleVersions()
{
return _XPLATSTR(
R"delimiter({
"Data": {
"PackageIdentifier": "Foo.Bar",
"Versions": [
{
"PackageVersion": "5.0.0",
"DefaultLocale": {
"PackageLocale": "en-us",
"Publisher": "Foo",
"PackageName": "Bar",
"License": "Foo bar license",
"ShortDescription": "Foo bar description"
},
"Installers": [
{
"Architecture": "x64",
"InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6",
"InstallerType": "exe",
"InstallerUrl": "https://installer.example.com/foobar.exe"
}
]
},
{
"PackageVersion": "6.0.0",
"DefaultLocale": {
"PackageLocale": "en-us",
"Publisher": "Foo",
"PackageName": "Bar",
"License": "Foo bar license",
"ShortDescription": "Foo bar description"
},
"Installers": [
{
"Architecture": "x64",
"InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6",
"InstallerType": "exe",
"InstallerUrl": "https://installer.example.com/foobar.exe"
}
]
}
]
}
})delimiter");
}

struct GoodManifest_AllFields
{
utility::string_t GetSampleManifest_AllFields()
Expand Down Expand Up @@ -440,6 +488,22 @@ TEST_CASE("Search_Optimized_ManifestResponse", "[RestSource][Interface_1_0]")
REQUIRE(manifest.Installers[0].Url == "https://installer.example.com/foobar.exe");
}

TEST_CASE("Search_Optimized_ManifestResponse_MultipleVersions", "[RestSource][Interface_1_0]")
{
HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) };
AppInstaller::Repository::SearchRequest request;
PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo.Bar" };
request.Filters.emplace_back(std::move(filter));
Interface v1{ TestRestUriString, std::move(helper) };
Schema::IRestClient::SearchResult result = v1.Search(request);
REQUIRE(result.Matches.size() == 1);
REQUIRE(result.Matches[0].Versions.size() == 2);
REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0");
REQUIRE(result.Matches[0].Versions[0].Manifest);
REQUIRE(result.Matches[0].Versions[1].VersionAndChannel.GetVersion().ToString() == "6.0.0");
REQUIRE(result.Matches[0].Versions[1].Manifest);
}

TEST_CASE("Search_Optimized_NoResponse_NotFoundCode", "[RestSource][Interface_1_0]")
{
HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) };
Expand Down Expand Up @@ -481,6 +545,18 @@ TEST_CASE("GetManifests_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]")
REQUIRE(manifests.size() == 0);
}

TEST_CASE("GetManifests_GoodResponse_MultipleVersions", "[RestSource][Interface_1_0]")
{
HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) };
Interface v1{ TestRestUriString, std::move(helper) };

// GetManifests
std::vector<Manifest> manifests = v1.GetManifests("Foo.Bar");
REQUIRE(manifests.size() == 2);
REQUIRE(manifests[0].Version == "5.0.0");
REQUIRE(manifests[1].Version == "6.0.0");
}

TEST_CASE("GetManifests_BadResponse_SuccessCode", "[RestSource][Interface_1_0]")
{
utility::string_t badManifest = _XPLATSTR(
Expand Down Expand Up @@ -546,3 +622,24 @@ TEST_CASE("GetManifests_GoodResponse_UnknownInstaller", "[RestSource][Interface_
REQUIRE(manifest.Installers.at(0).BaseInstallerType == InstallerTypeEnum::Unknown);
REQUIRE(manifest.Installers.at(0).ProductId.empty());
}

TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionFound", "[RestSource][Interface_1_0]")
{
HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) };
Interface v1{ TestRestUriString, std::move(helper) };

// GetManifests
std::optional<Manifest> manifest = v1.GetManifestByVersion("Foo.Bar", "5.0.0", "");
REQUIRE(manifest.has_value());
REQUIRE(manifest->Version == "5.0.0");
}

TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionNotFound", "[RestSource][Interface_1_0]")
{
HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) };
Interface v1{ TestRestUriString, std::move(helper) };

// GetManifests
std::optional<Manifest> manifest = v1.GetManifestByVersion("Foo.Bar", "7.0.0", "");
REQUIRE_FALSE(manifest.has_value());
}
6 changes: 3 additions & 3 deletions src/AppInstallerCommonCore/Manifest/YamlParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,8 +528,8 @@ namespace AppInstaller::Manifest::YamlParser

YamlManifestInfo manifestInfo;
YAML::Document doc = YAML::LoadDocument(file.path());
manifestInfo.Root = std::move(doc).GetRoot();
manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader();
manifestInfo.Root = std::move(doc).GetRoot();
manifestInfo.FileName = file.path().filename().u8string();
docList.emplace_back(std::move(manifestInfo));
}
Expand All @@ -538,8 +538,8 @@ namespace AppInstaller::Manifest::YamlParser
{
YamlManifestInfo manifestInfo;
YAML::Document doc = YAML::LoadDocument(inputPath, manifestInfo.StreamSha256);
manifestInfo.Root = std::move(doc).GetRoot();
manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader();
manifestInfo.Root = std::move(doc).GetRoot();
manifestInfo.FileName = inputPath.filename().u8string();
docList.emplace_back(std::move(manifestInfo));
}
Expand All @@ -563,8 +563,8 @@ namespace AppInstaller::Manifest::YamlParser
{
YamlManifestInfo manifestInfo;
YAML::Document doc = YAML::LoadDocument(input);
manifestInfo.Root = std::move(doc).GetRoot();
manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader();
manifestInfo.Root = std::move(doc).GetRoot();
docList.emplace_back(std::move(manifestInfo));
}
catch (const std::exception& e)
Expand Down
Loading
Loading