// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" #include "WorkflowCommon.h" #include "TestHooks.h" #include #include #include #include #include using namespace TestCommon; using namespace AppInstaller::CLI; using namespace AppInstaller::CLI::Execution; using namespace AppInstaller::Settings; using namespace AppInstaller::Utility::literals; TEST_CASE("UpdateFlow_UpdateWithManifest", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Exe.yaml").GetPath().u8string()); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called and parameters are passed in. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); std::ifstream updateResultFile(updateResultPath.GetPath()); REQUIRE(updateResultFile.is_open()); std::string updateResultStr; std::getline(updateResultFile, updateResultStr); REQUIRE(updateResultStr.find("/update") != std::string::npos); REQUIRE(updateResultStr.find("/silentwithprogress") != std::string::npos); } TEST_CASE("UpdateFlow_UpdateWithManifestMSStore", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestMSStoreUpdated.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); OverrideForMSStore(context, true); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore.yaml").GetPath().u8string()); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called and parameters are passed in. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); std::ifstream updateResultFile(updateResultPath.GetPath()); REQUIRE(updateResultFile.is_open()); std::string updateResultStr; std::getline(updateResultFile, updateResultStr); REQUIRE(updateResultStr.find("9WZDNCRFJ364") != std::string::npos); } TEST_CASE("UpdateFlow_UpdateWithManifestAppNotInstalled", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({})); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml").GetPath().u8string()); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::NoInstalledPackageFound).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); } TEST_CASE("UpdateFlow_UpdateWithManifestVersionAlreadyInstalled", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } TEST_CASE("UpdateFlow_UpdateExe", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); context.Args.AddArg(Execution::Args::Type::Silent); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called and parameters are passed in. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); std::ifstream updateResultFile(updateResultPath.GetPath()); REQUIRE(updateResultFile.is_open()); std::string updateResultStr; std::getline(updateResultFile, updateResultStr); REQUIRE(updateResultStr.find("/update") != std::string::npos); REQUIRE(updateResultStr.find("/silence") != std::string::npos); REQUIRE(updateResultStr.find("/ver3.0.0.0") != std::string::npos); } TEST_CASE("UpdateFlow_UpdateZip_Exe", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Zip })); OverrideForShellExecute(context); OverrideForExtractInstallerFromArchive(context); OverrideForVerifyAndSetNestedInstaller(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Zip.Query); context.Args.AddArg(Execution::Args::Type::Silent); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called and parameters are passed in. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); std::ifstream updateResultFile(updateResultPath.GetPath()); REQUIRE(updateResultFile.is_open()); std::string updateResultStr; std::getline(updateResultFile, updateResultStr); REQUIRE(updateResultStr.find("/custom") != std::string::npos); REQUIRE(updateResultStr.find("/silence") != std::string::npos); REQUIRE(updateResultStr.find("/ver2.0.0.0") != std::string::npos); } TEST_CASE("UpdateFlow_UpdatePortable", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); OverrideForPortableInstallFlow(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } TEST_CASE("UpdateFlow_Portable_SymlinkCreationFail", "[UpdateFlow][workflow]") { // Update portable with symlink creation failure verify that it succeeds. TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto PreviousThreadGlobals = context.SetForCurrentThread(); bool overrideCreateSymlinkStatus = false; AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(&overrideCreateSymlinkStatus); OverridePortableInstaller(context); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); const auto& targetDirectory = tempDirectory.GetPath(); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); context.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; REQUIRE(std::filesystem::exists(portableTargetPath)); REQUIRE(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); // Perform uninstall std::ostringstream uninstallOutput; TestContext uninstallContext{ uninstallOutput, std::cin }; auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); OverrideForCompositeInstalledSource(uninstallContext, CreateTestSource({ TSR::TestInstaller_Portable })); uninstallContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); UninstallCommand uninstall({}); uninstall.Execute(uninstallContext); INFO(uninstallOutput.str()); REQUIRE_FALSE(std::filesystem::exists(portableTargetPath)); } TEST_CASE("UpdateFlow_UpdateExeWithUnsupportedArgs", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); TestCommon::TempDirectory tempDirectory("TempDirectory", false); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnsupportedArguments })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnsupportedArguments.Query); context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); UpgradeCommand update({}); context.SetExecutingCommand(&update); update.Execute(context); INFO(updateOutput.str()); // Verify unsupported arguments error message is shown REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); REQUIRE(updateOutput.str().find("-l,--location") != std::string::npos); } TEST_CASE("UpdateFlow_UnknownVersion", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); TestCommon::TempDirectory tempDirectory("TempDirectory", false); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnknownVersion })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnknownVersion.Query); context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); UpgradeCommand update({}); context.SetExecutingCommand(&update); update.Execute(context); INFO(updateOutput.str()); // Verify help message is shown the user to use --include-unknown REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionExplanation).get()) != std::string::npos); } TEST_CASE("UpdateFlow_UnknownVersion_IncludeUnknownArg", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); TestCommon::TempDirectory tempDirectory("TempDirectory", false); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnknownVersion })); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnknownVersion.Query); context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); context.Args.AddArg(Execution::Args::Type::IncludeUnknown); UpgradeCommand update({}); context.SetExecutingCommand(&update); update.Execute(context); INFO(updateOutput.str()); REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } TEST_CASE("UpdateFlow_NoArgs_UnknownVersion", "[UpdateFlow][workflow]") { std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); UpgradeCommand update({}); context.SetExecutingCommand(&update); update.Execute(context); INFO(updateOutput.str()); // Verify --include-unknown help text is displayed if update is executed with no args and an unknown version package is available for upgrade. REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); } TEST_CASE("UpdateFlow_IncludeUnknown", "[UpdateFlow][workflow]") { std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); context.Args.AddArg(Execution::Args::Type::IncludeUnknown); UpgradeCommand update({}); context.SetExecutingCommand(&update); update.Execute(context); INFO(updateOutput.str()); // Verify unknown version package is displayed available for upgrade. REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) == std::string::npos); REQUIRE(updateOutput.str().find("unknown") != std::string::npos); } TEST_CASE("UpdateFlow_UpdatePortableWithManifest", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); OverrideForPortableInstallFlow(context); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Portable.yaml").GetPath().u8string()); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpdateMsix", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestMsixInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Msix })); OverrideForMSIX(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpdateMSStore", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestMSStoreUpdated.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); OverrideForMSStore(context, true); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_MSStore.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); std::ifstream updateResultFile(updateResultPath.GetPath()); REQUIRE(updateResultFile.is_open()); std::string updateResultStr; std::getline(updateResultFile, updateResultStr); REQUIRE(updateResultStr.find("9WZDNCRFJ364") != std::string::npos); } TEST_CASE("UpdateFlow_UpdateExeLatestAlreadyInstalled", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LatestInstalled })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LatestInstalled.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicable", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicableSpecificVersion", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } TEST_CASE("UpdateFlow_UpdateExeWithDifferentInstalledType", "[UpdateFlow][workflow]") { // Tests installer applicability when installed type is different but listed in the manifest TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_DifferentInstallerType })); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_DifferentInstallerType.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is called. REQUIRE(context.GetTerminationHR() == S_OK); REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpdateExeSpecificVersionNotFound", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); context.Args.AddArg(Execution::Args::Type::Version, "1.2.3.4"sv); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::GetManifestResultVersionNotFound("1.2.3.4"_liv)).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); } TEST_CASE("UpdateFlow_UpdateExeSpecificVersionNotApplicable", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); // This must be 2.0.0.0 since the version would not be an upgrade otherwise context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); OverrideForShellExecute(context); OverrideForMSIX(context); OverrideForMSStore(context, true); OverrideForPortableInstall(context); context.Args.AddArg(Execution::Args::Type::All); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify that --include-unknown help message is displayed. REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") == std::string::npos); // Verify installers are called. REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpdateAll_IncludeUnknown", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); OverrideForShellExecute(context); OverrideForMSIX(context); OverrideForMSStore(context, true); OverrideForPortableInstall(context); context.Args.AddArg(Execution::Args::Type::All); context.Args.AddArg(Execution::Args::Type::IncludeUnknown); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify that --include-unknown help message is NOT displayed and unknown version package is upgraded. REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) == std::string::npos); REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") != std::string::npos); // Verify installers are called. REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpgradeWithDuplicateUpgradeItemsFound", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems })); // Installer should only be run once since the 2 upgrade items are same. OverrideForShellExecute(context, 1); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems.Query); context.Args.AddArg(Execution::Args::Type::All); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify installers are called. REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); } TEST_CASE("UpdateFlow_Dependencies", "[UpdateFlow][workflow][dependencies]") { std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_Dependencies })); OverrideForShellExecute(context); OverrideEnableWindowsFeaturesDependencies(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_Dependencies.Query);; UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); std::string updateResultStr = updateOutput.str(); // Verify dependencies are informed REQUIRE(updateResultStr.find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); REQUIRE(updateResultStr.find("PreviewIIS") != std::string::npos); REQUIRE(updateResultStr.find("Preview VC Runtime") != std::string::npos); } TEST_CASE("UpdateFlow_LicenseAgreement", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LicenseAgreement })); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LicenseAgreement.Query); context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify agreements are shown REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); // Verify Installer is called. REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } TEST_CASE("UpdateFlow_LicenseAgreement_NotAccepted", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); // Say "No" at the agreements prompt std::istringstream updateInput{ "n" }; std::ostringstream updateOutput; TestContext context{ updateOutput, updateInput }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LicenseAgreement })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LicenseAgreement.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify agreements are shown REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); // Verify Installer is not called. REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); REQUIRE_FALSE(std::filesystem::exists(updateResultPath.GetPath())); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); } TEST_CASE("UpdateFlow_All_LicenseAgreement", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UpgradeUsesAgreements, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix_UpgradeUsesAgreements, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); OverrideForShellExecute(context); OverrideForMSIX(context); OverrideForMSStore(context, true); OverrideForPortableInstall(context); context.Args.AddArg(Execution::Args::Type::All); context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify agreements are shown REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); REQUIRE(updateOutput.str().find("Agreement for MSIX") != std::string::npos); REQUIRE(updateOutput.str().find("This is the agreement for the MSIX") != std::string::npos); // Verify installers are called. REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); } TEST_CASE("UpdateFlow_All_LicenseAgreement_NotAccepted", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); // Say "No" at the agreements prompt std::istringstream updateInput{ "n" }; std::ostringstream updateOutput; TestContext context{ updateOutput, updateInput }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UpgradeUsesAgreements, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix_UpgradeUsesAgreements, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); context.Args.AddArg(Execution::Args::Type::All); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify agreements are shown REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); REQUIRE(updateOutput.str().find("Agreement for MSIX") != std::string::npos); REQUIRE(updateOutput.str().find("This is the agreement for the MSIX") != std::string::npos); // Verify installers are not called. REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); REQUIRE_FALSE(std::filesystem::exists(updateExeResultPath.GetPath())); REQUIRE_FALSE(std::filesystem::exists(updateMsixResultPath.GetPath())); REQUIRE_FALSE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); } TEST_CASE("UpdateFlow_RequireExplicit", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); // Msix package has an update that requires explicit upgrade. // Exe, Portable, MSStore, Zip are also listed with an available upgrade. OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Exe_UnknownVersion, TSR::TestInstaller_Msix_UpgradeRequiresExplicit, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, TSR::TestInstaller_Zip, })); SECTION("List available upgrades") { UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // The package that requires explicit upgrade is listed below the header for pinned packages REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeInstaller") != std::string::npos); auto pinnedPackagesHeaderPosition = updateOutput.str().find(Resource::LocString(Resource::String::UpgradeAvailableForPinned)); auto pinnedPackageLinePosition = updateOutput.str().find("AppInstallerCliTest.TestMsixInstaller"); REQUIRE(pinnedPackagesHeaderPosition != std::string::npos); REQUIRE(pinnedPackageLinePosition != std::string::npos); REQUIRE(pinnedPackagesHeaderPosition < pinnedPackageLinePosition); REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) == std::string::npos); } SECTION("Upgrade all except pinned") { context.Args.AddArg(Args::Type::All); OverrideForMSStore(context, true); OverrideForPortableInstall(context); OverrideForShellExecute(context); OverrideForExtractInstallerFromArchive(context); OverrideForVerifyAndSetNestedInstaller(context); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); auto s = updateOutput.str(); // Verify message is printed for skipped package REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) != std::string::npos); // Verify package is not installed, but all others are REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); REQUIRE(!std::filesystem::exists(updateMsixResultPath.GetPath())); } SECTION("Upgrade explicitly") { context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); OverrideForMSIX(context); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); } // Command should always succeed REQUIRE(context.GetTerminationHR() == S_OK); } TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable", "[UpdateFlow][workflow]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); context.Args.AddArg(Execution::Args::Type::Silent); InstallCommand install({}); install.Execute(context); INFO(installOutput.str()); // Verify Installer is called and parameters are passed in. REQUIRE(std::filesystem::exists(installResultPath.GetPath())); std::ifstream installResultFile(installResultPath.GetPath()); REQUIRE(installResultFile.is_open()); std::string installResultStr; std::getline(installResultFile, installResultStr); REQUIRE(installResultStr.find("/update") != std::string::npos); REQUIRE(installResultStr.find("/ver3.0.0.0") != std::string::npos); } TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable_WithNoUpgrade", "[UpdateFlow][workflow]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); context.Args.AddArg(Execution::Args::Type::NoUpgrade); InstallCommand install({}); install.Execute(context); INFO(installOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAlreadyInstalled).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED); } TEST_CASE("InstallFlow_FoundInstalledAndUpgradeNotAvailable", "[UpdateFlow][workflow]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LatestInstalled })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LatestInstalled.Query); InstallCommand install({}); install.Execute(context); INFO(installOutput.str()); // Verify Installer is not called. REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); } TEST_CASE("UpdateFlow_UpdateAll_ForwardArgs", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix, TSR::TestInstaller_MSStore, TSR::TestInstaller_Portable, })); OverrideForShellExecute(context); OverrideForMSIX(context); OverrideForMSStore(context, true); OverrideForPortableInstall(context); context.Args.AddArg(Execution::Args::Type::All); context.Args.AddArg(Execution::Args::Type::Silent); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify installers are called with the silent flags REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); std::ifstream updateExeResultFile(updateExeResultPath.GetPath()); std::string updateExeResultStr; std::getline(updateExeResultFile, updateExeResultStr); REQUIRE(updateExeResultStr.find("/silence") != std::string::npos); REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpdateMultiple", "[UpdateFlow][workflow][MultiQuery]") { TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); TestCommon::TempFile msixUpdateResultPath("TestMsixInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); OverrideForShellExecute(context); OverrideForMSIX(context); context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); // Verify Installers are called called. REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); } TEST_CASE("UpdateFlow_UpdateMultiple_NotAllFound", "[UpdateFlow][workflow][MultiQuery]") { TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); SECTION("Ignore unavailable") { OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE(!context.IsTerminated()); REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); } SECTION("Don't ignore unavailable") { UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); } } TEST_CASE("UpdateFlow_UpdateWithReboot", "[UpdateFlow][workflow][reboot]") { TestCommon::TestUserSettings testSettings; std::ostringstream updateOutput; TestContext context{ updateOutput, std::cin }; auto previousThreadGlobals = context.SetForCurrentThread(); OverrideForShellExecute(context); OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_ExpectedReturnCodes })); context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_ExpectedReturnCodes.Query); context.Args.AddArg(Execution::Args::Type::AllowReboot); context.Override({ AppInstaller::CLI::Workflow::ShellExecuteInstallImpl, [&](TestContext& context) { // APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH (not treated as an installer error) context.Add(9); } }); SECTION("Reboot success") { TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(true); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE_FALSE(context.IsTerminated()); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); REQUIRE_FALSE(updateOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); } SECTION("Reboot failed") { TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); UpgradeCommand update({}); update.Execute(context); INFO(updateOutput.str()); REQUIRE_FALSE(context.IsTerminated()); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); } }