winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }