ImportFlow.cpp (13037B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestHooks.h" 5 #include "WorkflowCommon.h" 6 #include <Commands/ImportCommand.h> 7 #include <winget/Settings.h> 8 #include <Workflows/ImportExportFlow.h> 9 #include <Workflows/ShellExecuteInstallerHandler.h> 10 11 using namespace TestCommon; 12 using namespace AppInstaller::CLI; 13 using namespace AppInstaller::Repository; 14 using namespace AppInstaller::Utility::literals; 15 16 void OverrideForImportSource(TestContext& context, bool useTestCompositeSource = false) 17 { 18 auto testCompositeSource = CreateTestSource({ 19 TSR::TestInstaller_Exe, 20 TSR::TestInstaller_Exe_Dependencies, 21 TSR::TestInstaller_Exe_LicenseAgreement, 22 TSR::TestInstaller_Exe_NothingInstalled, 23 TSR::TestInstaller_Msix, 24 TSR::TestInstaller_Msix_WFDependency, 25 }); 26 27 context.Override({ "OpenPredefinedSource", [=](TestContext& context) 28 { 29 auto installedSource = useTestCompositeSource ? testCompositeSource : std::make_shared<TestSource>(); 30 context.Add<Execution::Data::Source>(Source{ installedSource }); 31 } }); 32 33 context.Override({ Workflow::OpenSourcesForImport, [=](TestContext& context) 34 { 35 context.Add<Execution::Data::Sources>(std::vector<Source>{ Source{ testCompositeSource } }); 36 } }); 37 } 38 39 TEST_CASE("ImportFlow_Successful", "[ImportFlow][workflow]") 40 { 41 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 42 TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); 43 44 std::ostringstream importOutput; 45 TestContext context{ importOutput, std::cin }; 46 auto previousThreadGlobals = context.SetForCurrentThread(); 47 OverrideForImportSource(context); 48 OverrideForMSIX(context); 49 OverrideForShellExecute(context); 50 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good.json").GetPath().string()); 51 52 ImportCommand importCommand({}); 53 importCommand.Execute(context); 54 INFO(importOutput.str()); 55 56 // Verify all packages were installed 57 REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); 58 REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); 59 } 60 61 TEST_CASE("ImportFlow_PackageAlreadyInstalled", "[ImportFlow][workflow]") 62 { 63 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 64 65 std::ostringstream importOutput; 66 TestContext context{ importOutput, std::cin }; 67 auto previousThreadGlobals = context.SetForCurrentThread(); 68 OverrideForImportSource(context, true); 69 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); 70 71 ImportCommand importCommand({}); 72 importCommand.Execute(context); 73 INFO(importOutput.str()); 74 75 // Exe should not have been installed again 76 REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); 77 REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageAlreadyInstalled("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); 78 } 79 80 TEST_CASE("ImportFlow_IgnoreVersions", "[ImportFlow][workflow]") 81 { 82 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 83 84 std::ostringstream importOutput; 85 TestContext context{ importOutput, std::cin }; 86 auto previousThreadGlobals = context.SetForCurrentThread(); 87 OverrideForImportSource(context); 88 OverrideForShellExecute(context); 89 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); 90 context.Args.AddArg(Execution::Args::Type::IgnoreVersions); 91 92 ImportCommand importCommand({}); 93 importCommand.Execute(context); 94 INFO(importOutput.str()); 95 96 // Specified version is already installed. It should have been updated since we ignored the version. 97 REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); 98 } 99 100 TEST_CASE("ImportFlow_MissingSource", "[ImportFlow][workflow]") 101 { 102 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 103 104 std::ostringstream importOutput; 105 TestContext context{ importOutput, std::cin }; 106 auto previousThreadGlobals = context.SetForCurrentThread(); 107 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownSource.json").GetPath().string()); 108 109 ImportCommand importCommand({}); 110 importCommand.Execute(context); 111 INFO(importOutput.str()); 112 113 // Installer should not be called 114 REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); 115 REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportSourceNotInstalled("TestSource"_liv)).get()) != std::string::npos); 116 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); 117 } 118 119 TEST_CASE("ImportFlow_MissingPackage", "[ImportFlow][workflow]") 120 { 121 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 122 123 std::ostringstream importOutput; 124 TestContext context{ importOutput, std::cin }; 125 auto previousThreadGlobals = context.SetForCurrentThread(); 126 OverrideForImportSource(context); 127 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackage.json").GetPath().string()); 128 129 ImportCommand importCommand({}); 130 importCommand.Execute(context); 131 INFO(importOutput.str()); 132 133 // Installer should not be called 134 REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); 135 REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); 136 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); 137 } 138 139 TEST_CASE("ImportFlow_IgnoreMissingPackage", "[ImportFlow][workflow]") 140 { 141 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 142 143 std::ostringstream importOutput; 144 TestContext context{ importOutput, std::cin }; 145 auto previousThreadGlobals = context.SetForCurrentThread(); 146 OverrideForImportSource(context); 147 OverrideForShellExecute(context); 148 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackage.json").GetPath().string()); 149 context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); 150 151 ImportCommand importCommand({}); 152 importCommand.Execute(context); 153 INFO(importOutput.str()); 154 155 // Verify installer was called for the package that was available. 156 REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); 157 REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); 158 } 159 160 TEST_CASE("ImportFlow_MissingVersion", "[ImportFlow][workflow]") 161 { 162 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 163 164 std::ostringstream importOutput; 165 TestContext context{ importOutput, std::cin }; 166 auto previousThreadGlobals = context.SetForCurrentThread(); 167 OverrideForImportSource(context); 168 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackageVersion.json").GetPath().string()); 169 170 ImportCommand importCommand({}); 171 importCommand.Execute(context); 172 INFO(importOutput.str()); 173 174 // Installer should not be called 175 REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); 176 REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQuerySearchFailed("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); 177 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); 178 } 179 180 TEST_CASE("ImportFlow_MalformedJsonFile", "[ImportFlow][workflow]") 181 { 182 std::ostringstream importOutput; 183 TestContext context{ importOutput, std::cin }; 184 auto previousThreadGlobals = context.SetForCurrentThread(); 185 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Malformed.json").GetPath().string()); 186 187 ImportCommand importCommand({}); 188 importCommand.Execute(context); 189 INFO(importOutput.str()); 190 191 // Command should have failed 192 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); 193 } 194 195 TEST_CASE("ImportFlow_InvalidJsonFile", "[ImportFlow][workflow]") 196 { 197 std::ostringstream importOutput; 198 TestContext context{ importOutput, std::cin }; 199 auto previousThreadGlobals = context.SetForCurrentThread(); 200 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Invalid.json").GetPath().string()); 201 202 ImportCommand importCommand({}); 203 importCommand.Execute(context); 204 INFO(importOutput.str()); 205 206 // Command should have failed 207 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); 208 } 209 210 TEST_CASE("ImportFlow_MachineScope", "[ImportFlow][workflow]") 211 { 212 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 213 214 std::ostringstream importOutput; 215 TestContext context{ importOutput, std::cin }; 216 auto previousThreadGlobals = context.SetForCurrentThread(); 217 OverrideForImportSource(context); 218 OverrideForShellExecute(context); 219 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-MachineScope.json").GetPath().string()); 220 221 ImportCommand importCommand({}); 222 importCommand.Execute(context); 223 INFO(importOutput.str()); 224 225 // Verify all packages were installed 226 REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); 227 std::ifstream installResultFile(exeInstallResultPath.GetPath()); 228 REQUIRE(installResultFile.is_open()); 229 std::string installResultStr; 230 std::getline(installResultFile, installResultStr); 231 REQUIRE(installResultStr.find("/scope=machine") != std::string::npos); 232 } 233 234 TEST_CASE("ImportFlow_Dependencies", "[ImportFlow][workflow][dependencies]") 235 { 236 std::ostringstream importOutput; 237 TestContext context{ importOutput, std::cin }; 238 auto previousThreadGlobals = context.SetForCurrentThread(); 239 OverrideForImportSource(context); 240 OverrideForMSIX(context); 241 OverrideForShellExecute(context); 242 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-Dependencies.json").GetPath().string()); 243 244 ImportCommand importCommand({}); 245 importCommand.Execute(context); 246 INFO(importOutput.str()); 247 248 // Verify dependencies for all packages are informed 249 REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportCommandReportDependencies).get()) != std::string::npos); 250 REQUIRE(importOutput.str().find("PreviewIIS") != std::string::npos); 251 REQUIRE(importOutput.str().find("Preview VC Runtime") != std::string::npos); 252 REQUIRE(importOutput.str().find("Hyper-V") != std::string::npos); 253 } 254 255 TEST_CASE("ImportFlow_LicenseAgreement", "[ImportFlow][workflow]") 256 { 257 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 258 259 std::ostringstream importOutput; 260 TestContext context{ importOutput, std::cin }; 261 auto previousThreadGlobals = context.SetForCurrentThread(); 262 OverrideForImportSource(context); 263 OverrideForShellExecute(context); 264 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithLicenseAgreement.json").GetPath().string()); 265 context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); 266 267 ImportCommand importCommand({}); 268 importCommand.Execute(context); 269 INFO(importOutput.str()); 270 271 // Verify agreements are shown 272 REQUIRE(importOutput.str().find("Agreement for EXE") != std::string::npos); 273 REQUIRE(importOutput.str().find("This is the agreement for the EXE") != std::string::npos); 274 275 // Verify all packages were installed 276 REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); 277 } 278 279 TEST_CASE("ImportFlow_LicenseAgreement_NotAccepted", "[ImportFlow][workflow]") 280 { 281 // Say "No" at the agreements prompt 282 std::istringstream importInput{ "n" }; 283 284 std::ostringstream importOutput; 285 TestContext context{ importOutput, importInput }; 286 auto previousThreadGlobals = context.SetForCurrentThread(); 287 OverrideForImportSource(context); 288 context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithLicenseAgreement.json").GetPath().string()); 289 290 ImportCommand importCommand({}); 291 importCommand.Execute(context); 292 INFO(importOutput.str()); 293 294 // Verify agreements are shown 295 REQUIRE(importOutput.str().find("Agreement for EXE") != std::string::npos); 296 REQUIRE(importOutput.str().find("This is the agreement for the EXE") != std::string::npos); 297 298 // Command should have failed 299 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); 300 }