commit 20183b77b032f5825c280c264bcbdd8059c55622 parent 4a97e2c1b67c3f9e9dc38d672a61049e2c65fb79 Author: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 21 Feb 2020 18:00:33 -0800 Integrate search functionality to install flow. Add show and search command. (#40) * Add search functionality to install flow. Add show and search command. * Added tests * PR comments * PR comments * Also report ApplicationMatchField Diffstat:
28 files changed, 1001 insertions(+), 401 deletions(-)
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -170,7 +170,8 @@ <ItemGroup> <ClInclude Include="Command.h" /> <ClInclude Include="Commands\Common.h" /> - <ClInclude Include="Commands\DescribeCommand.h" /> + <ClInclude Include="Commands\SearchCommand.h" /> + <ClInclude Include="Commands\ShowCommand.h" /> <ClInclude Include="Commands\InstallCommand.h" /> <ClInclude Include="Commands\RootCommand.h" /> <ClInclude Include="Commands\SourceCommand.h" /> @@ -180,16 +181,20 @@ <ClInclude Include="Public\AppInstallerCLICore.h" /> <ClInclude Include="Search\Search.h" /> <ClInclude Include="Workflows\Common.h" /> + <ClInclude Include="Workflows\SearchFlow.h" /> <ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" /> <ClInclude Include="Workflows\InstallerHandlerBase.h" /> <ClInclude Include="Workflows\InstallFlow.h" /> <ClInclude Include="Workflows\ManifestComparator.h" /> <ClInclude Include="Workflows\MsixInstallerHandler.h" /> + <ClInclude Include="Workflows\ShowFlow.h" /> + <ClInclude Include="Workflows\WorkflowBase.h" /> <ClInclude Include="Workflows\WorkflowReporter.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="Command.cpp" /> - <ClCompile Include="Commands\DescribeCommand.cpp" /> + <ClCompile Include="Commands\SearchCommand.cpp" /> + <ClCompile Include="Commands\ShowCommand.cpp" /> <ClCompile Include="Commands\InstallCommand.cpp" /> <ClCompile Include="Commands\RootCommand.cpp" /> <ClCompile Include="Commands\SourceCommand.cpp" /> @@ -197,11 +202,14 @@ <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> + <ClCompile Include="Workflows\SearchFlow.cpp" /> <ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" /> <ClCompile Include="Workflows\InstallerHandlerBase.cpp" /> <ClCompile Include="Workflows\InstallFlow.cpp" /> <ClCompile Include="Workflows\ManifestComparator.cpp" /> <ClCompile Include="Workflows\MsixInstallerHandler.cpp" /> + <ClCompile Include="Workflows\ShowFlow.cpp" /> + <ClCompile Include="Workflows\WorkflowBase.cpp" /> <ClCompile Include="Workflows\WorkflowReporter.cpp" /> </ItemGroup> <ItemGroup> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -36,9 +36,6 @@ <ClInclude Include="Commands\InstallCommand.h"> <Filter>Commands</Filter> </ClInclude> - <ClInclude Include="Commands\DescribeCommand.h"> - <Filter>Commands</Filter> - </ClInclude> <ClInclude Include="Command.h"> <Filter>Header Files</Filter> </ClInclude> @@ -75,9 +72,24 @@ <ClInclude Include="Workflows\ShellExecuteInstallerHandler.h"> <Filter>Workflows</Filter> </ClInclude> + <ClInclude Include="Workflows\WorkflowBase.h"> + <Filter>Workflows</Filter> + </ClInclude> <ClInclude Include="Commands\SourceCommand.h"> <Filter>Commands</Filter> </ClInclude> + <ClInclude Include="Commands\ShowCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Commands\SearchCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Workflows\SearchFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> + <ClInclude Include="Workflows\ShowFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -89,18 +101,12 @@ <ClCompile Include="Commands\InstallCommand.cpp"> <Filter>Commands</Filter> </ClCompile> - <ClCompile Include="Commands\DescribeCommand.cpp"> - <Filter>Commands</Filter> - </ClCompile> <ClCompile Include="Command.cpp"> <Filter>Source Files</Filter> </ClCompile> <ClCompile Include="Core.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="Workflows\InstallFlow.cpp"> - <Filter>Workflows</Filter> - </ClCompile> <ClCompile Include="Workflows\ManifestComparator.cpp"> <Filter>Workflows</Filter> </ClCompile> @@ -116,9 +122,27 @@ <ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp"> <Filter>Workflows</Filter> </ClCompile> + <ClCompile Include="Workflows\WorkflowBase.cpp"> + <Filter>Workflows</Filter> + </ClCompile> + <ClCompile Include="Commands\ShowCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Commands\SearchCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Workflows\SearchFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> <ClCompile Include="Commands\SourceCommand.cpp"> <Filter>Commands</Filter> </ClCompile> + <ClCompile Include="Workflows\ShowFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> + <ClCompile Include="Workflows\InstallFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Commands/Common.h b/src/AppInstallerCLICore/Commands/Common.h @@ -16,4 +16,16 @@ namespace AppInstaller::CLI static constexpr std::string_view ARG_LOG = "log"sv; static constexpr std::string_view ARG_OVERRIDE = "override"sv; static constexpr std::string_view ARG_INSTALLLOCATION = "installlocation"sv; + static constexpr std::string_view ARG_QUERY = "query"sv; + static constexpr std::string_view ARG_ID = "id"sv; + static constexpr std::string_view ARG_NAME = "name"sv; + static constexpr std::string_view ARG_MONIKER = "moniker"sv; + static constexpr std::string_view ARG_TAG = "tag"sv; + static constexpr std::string_view ARG_COMMAND = "command"sv; + static constexpr std::string_view ARG_SOURCE = "source"sv; + static constexpr std::string_view ARG_COUNT = "count"sv; + static constexpr std::string_view ARG_EXACT = "exact"sv; + static constexpr std::string_view ARG_VERSION = "version"sv; + static constexpr std::string_view ARG_CHANNEL = "channel"sv; + static constexpr std::string_view ARG_LISTVERSIONS = "listversions"sv; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Commands/DescribeCommand.cpp b/src/AppInstallerCLICore/Commands/DescribeCommand.cpp @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Common.h" -#include "DescribeCommand.h" -#include "Localization.h" - -namespace AppInstaller::CLI -{ - std::vector<Argument> DescribeCommand::GetArguments() const - { - return { - Argument{ ARG_APPLICATION, LOCME("The name of the application to install"), ArgumentType::Positional, true }, - Argument{ ARG_MANIFEST, LOCME("If specified, output the full manifest file"), ArgumentType::Flag }, - }; - } - - std::string DescribeCommand::ShortDescription() const - { - return LOCME("Describes the given application"); - } - - std::vector<std::string> DescribeCommand::GetLongDescription() const - { - return { - LOCME("Describes the given application"), - }; - } -} diff --git a/src/AppInstallerCLICore/Commands/DescribeCommand.h b/src/AppInstallerCLICore/Commands/DescribeCommand.h @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct DescribeCommand final : public Command - { - DescribeCommand() : Command("describe") {} - - virtual std::vector<Argument> GetArguments() const override; - - virtual std::string ShortDescription() const override; - virtual std::vector<std::string> GetLongDescription() const override; - }; -} diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -15,7 +15,7 @@ namespace AppInstaller::CLI std::vector<Argument> InstallCommand::GetArguments() const { return { - Argument{ ARG_APPLICATION, LOCME("The name of the application to install"), ArgumentType::Positional, false }, + Argument{ ARG_QUERY, LOCME("The name of the application to install"), ArgumentType::Positional, false }, Argument{ ARG_MANIFEST, LOCME("The path to the manifest of the application to install"), ArgumentType::Standard, false }, Argument{ ARG_INTERACTIVE, LOCME("The application installation is interactive. User input is needed."), ArgumentType::Flag, false }, Argument{ ARG_SILENT, LOCME("The application installation is silent."), ArgumentType::Flag, false }, @@ -39,34 +39,23 @@ namespace AppInstaller::CLI void InstallCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const { - if (inv.Contains(ARG_MANIFEST)) - { - std::string manifest = *(inv.GetArg(ARG_MANIFEST)); - Manifest::Manifest packageManifest = Manifest::Manifest::CreateFromPath(manifest); - - Logging::Telemetry().LogManifestFields(packageManifest.Name, packageManifest.Version); + InstallFlow appInstall(inv, out, in); - InstallFlow packageInstall(packageManifest, inv, out, in); - packageInstall.Install(); - } - else - { - out << "Not supported!" << std::endl; - } + appInstall.Execute(); } void InstallCommand::ValidateArguments(Invocation& inv) const { Command::ValidateArguments(inv); - if (!inv.Contains(ARG_APPLICATION) && !inv.Contains(ARG_MANIFEST)) + if (!inv.Contains(ARG_QUERY) && !inv.Contains(ARG_MANIFEST)) { - throw CommandException(LOCME("Required argument not provided"), ARG_APPLICATION); + throw CommandException(LOCME("Required argument not provided"), ARG_QUERY); } if (inv.Contains(ARG_SILENT) && inv.Contains(ARG_INTERACTIVE)) { - throw CommandException(LOCME("More than one install behavior argument provided"), ARG_APPLICATION); + throw CommandException(LOCME("More than one install behavior argument provided"), ARG_QUERY); } } } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -5,8 +5,9 @@ #include "Localization.h" #include "InstallCommand.h" -#include "DescribeCommand.h" +#include "ShowCommand.h" #include "SourceCommand.h" +#include "SearchCommand.h" namespace AppInstaller::CLI { @@ -14,8 +15,9 @@ namespace AppInstaller::CLI { return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ std::make_unique<InstallCommand>(), - std::make_unique<DescribeCommand>(), + std::make_unique<ShowCommand>(), std::make_unique<SourceCommand>(), + std::make_unique<SearchCommand>(), }); } diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Common.h" +#include "SearchCommand.h" +#include "Localization.h" +#include "Workflows/SearchFlow.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::Workflow; + + std::vector<Argument> SearchCommand::GetArguments() const + { + return { + Argument{ ARG_QUERY, LOCME("The query used to search for an app"), ArgumentType::Positional, true }, + Argument{ ARG_ID, LOCME("If specified, filter the results by id"), ArgumentType::Standard }, + Argument{ ARG_NAME, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ ARG_MONIKER, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ ARG_TAG, LOCME("If specified, filter the results by tag"), ArgumentType::Standard }, + Argument{ ARG_COMMAND, LOCME("If specified, filter the results by command"), ArgumentType::Standard }, + Argument{ ARG_SOURCE, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ ARG_COUNT, LOCME("If specified, find app and show only up to specified number of results."), ArgumentType::Standard }, + Argument{ ARG_EXACT, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + }; + } + + std::string SearchCommand::ShortDescription() const + { + return LOCME("Find and show basic info of apps"); + } + + std::vector<std::string> SearchCommand::GetLongDescription() const + { + return { + LOCME("Find and show basic info of apps"), + }; + } + + void SearchCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + { + SearchFlow appSearch(inv, out, in); + + appSearch.Execute(); + } +} diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.h b/src/AppInstallerCLICore/Commands/SearchCommand.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct SearchCommand final : public Command + { + SearchCommand() : Command("search") {} + + virtual std::vector<Argument> GetArguments() const override; + + virtual std::string ShortDescription() const override; + virtual std::vector<std::string> GetLongDescription() const override; + + protected: + void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Common.h" +#include "ShowCommand.h" +#include "Localization.h" +#include "Workflows\ShowFlow.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::Workflow; + + std::vector<Argument> ShowCommand::GetArguments() const + { + return { + Argument{ ARG_QUERY, LOCME("The query used to search for an app"), ArgumentType::Positional, true }, + Argument{ ARG_ID, LOCME("The id of the application to show info"), ArgumentType::Standard }, + Argument{ ARG_NAME, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ ARG_MONIKER, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ ARG_VERSION, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, + Argument{ ARG_CHANNEL, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, + Argument{ ARG_SOURCE, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ ARG_EXACT, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ ARG_LISTVERSIONS, LOCME("If specified, only show available versions of the app"), ArgumentType::Flag }, + }; + } + + std::string ShowCommand::ShortDescription() const + { + return LOCME("Shows info of the given application"); + } + + std::vector<std::string> ShowCommand::GetLongDescription() const + { + return { + LOCME("Shows info of the given application"), + }; + } + + void ShowCommand::ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const + { + ShowFlow appShowInfo(inv, out, in); + + appShowInfo.Execute(); + } +} diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.h b/src/AppInstallerCLICore/Commands/ShowCommand.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ShowCommand final : public Command + { + ShowCommand() : Command("show") {} + + virtual std::vector<Argument> GetArguments() const override; + + virtual std::string ShortDescription() const override; + virtual std::vector<std::string> GetLongDescription() const override; + + protected: + void ExecuteInternal(Invocation& inv, std::ostream& out, std::istream& in) const override; + }; +} diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" +#include "Commands/Common.h" #include "InstallFlow.h" #include "ManifestComparator.h" #include "ShellExecuteInstallerHandler.h" @@ -12,11 +13,34 @@ using namespace winrt::Windows::Management::Deployment; using namespace AppInstaller::Utility; using namespace AppInstaller::Manifest; -namespace AppInstaller::Workflow { +namespace AppInstaller::Workflow +{ + void InstallFlow::Execute() + { + if (m_argsRef.Contains(CLI::ARG_MANIFEST)) + { + m_manifest = Manifest::Manifest::CreateFromPath(*(m_argsRef.GetArg(CLI::ARG_MANIFEST))); + InstallInternal(); + } + else + { + WorkflowBase::IndexSearch(); - void InstallFlow::Install() + if (WorkflowBase::EnsureOneMatchFromSearchResult()) + { + GetManifest(); + InstallInternal(); + } + } + } + + void InstallFlow::InstallInternal() { - ProcessManifest(); + Logging::Telemetry().LogManifestFields(m_manifest.Name, m_manifest.Version); + + // Select Installer + ManifestComparator manifestComparator(m_manifest, m_reporter); + m_selectedInstaller = manifestComparator.GetPreferredInstaller(m_argsRef); auto installerHandler = GetInstallerHandler(); @@ -24,22 +48,18 @@ namespace AppInstaller::Workflow { installerHandler->Install(); } - void InstallFlow::ProcessManifest() + void InstallFlow::GetManifest() { - ManifestComparator manifestComparator(m_packageManifest, m_reporter); + auto app = m_searchResult.Matches.at(0).Application.get(); - m_selectedLocalization = manifestComparator.GetPreferredLocalization(std::locale("")); + AICLI_LOG(CLI, Info, << "Found one app. App id: " << app->GetId() << " App name: " << app->GetName()); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Found app: " + app->GetName()); - m_reporter.ShowPackageInfo( - m_packageManifest.Name, - m_packageManifest.Version, - m_packageManifest.Author, - m_selectedLocalization.Description, - m_selectedLocalization.Homepage, - m_selectedLocalization.LicenseUrl + // Todo: handle failure if necessary after real search is in place + m_manifest = app->GetManifest( + m_argsRef.Contains(CLI::ARG_VERSION) ? *m_argsRef.GetArg(CLI::ARG_VERSION) : "", + m_argsRef.Contains(CLI::ARG_CHANNEL) ? *m_argsRef.GetArg(CLI::ARG_CHANNEL) : "" ); - - m_selectedInstaller = manifestComparator.GetPreferredInstaller(std::locale("")); } std::unique_ptr<InstallerHandlerBase> InstallFlow::GetInstallerHandler() diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -3,28 +3,29 @@ #pragma once #include "Common.h" +#include "WorkflowBase.h" #include "Invocation.h" #include "InstallerHandlerBase.h" #include "WorkflowReporter.h" namespace AppInstaller::Workflow { - class InstallFlow + class InstallFlow : public WorkflowBase { public: - InstallFlow(AppInstaller::Manifest::Manifest manifest, const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - m_packageManifest(manifest), m_reporter(outStream, inStream), m_argsRef(args) {} + InstallFlow(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + WorkflowBase(args, outStream, inStream) {} - void Install(); + // Execute will perform a query against index and do app install if a target app is found. + // If a manifest is given with /manifest, use the manifest and no index search is performed. + void Execute(); protected: - AppInstaller::Manifest::Manifest m_packageManifest; + AppInstaller::Manifest::Manifest m_manifest; AppInstaller::Manifest::ManifestInstaller m_selectedInstaller; - AppInstaller::Manifest::ManifestLocalization m_selectedLocalization; - WorkflowReporter m_reporter; - const AppInstaller::CLI::Invocation& m_argsRef; - virtual void ProcessManifest(); + void GetManifest(); + void InstallInternal(); // Creates corresponding InstallerHandler according to InstallerType virtual std::unique_ptr<InstallerHandlerBase> GetInstallerHandler(); diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -38,9 +38,9 @@ namespace AppInstaller::Workflow return true; } - ManifestInstaller ManifestComparator::GetPreferredInstaller(const std::locale& preferredLocale) + ManifestInstaller ManifestComparator::GetPreferredInstaller(const AppInstaller::CLI::Invocation&) { - AICLI_LOG(CLI, Info, << "Starting installer selection. Preferred locale: " << preferredLocale.name()); + AICLI_LOG(CLI, Info, << "Starting installer selection."); // Sorting the list of availlable installers according to rules defined in InstallerComparator. std::sort(m_manifestRef.Installers.begin(), m_manifestRef.Installers.end(), InstallerComparator()); @@ -64,9 +64,9 @@ namespace AppInstaller::Workflow return selectedInstaller; } - ManifestLocalization ManifestComparator::GetPreferredLocalization(const std::locale& preferredLocale) + ManifestLocalization ManifestComparator::GetPreferredLocalization(const AppInstaller::CLI::Invocation&) { - AICLI_LOG(CLI, Info, << "Starting localization selection. Preferred locale: " << preferredLocale.name()); + AICLI_LOG(CLI, Info, << "Starting localization selection."); ManifestLocalization selectedLocalization; diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once +#include "Invocation.h" namespace AppInstaller::Workflow { @@ -27,8 +28,8 @@ namespace AppInstaller::Workflow public: ManifestComparator(AppInstaller::Manifest::Manifest& manifest, WorkflowReporter& reporter) : m_manifestRef(manifest), m_reporterRef(reporter) {} - AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const std::locale& preferredLocale); - AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const std::locale& preferredLocale); + AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const AppInstaller::CLI::Invocation& args); + AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const AppInstaller::CLI::Invocation& args); private: AppInstaller::Manifest::Manifest& m_manifestRef; diff --git a/src/AppInstallerCLICore/Workflows/SearchFlow.cpp b/src/AppInstallerCLICore/Workflows/SearchFlow.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "SearchFlow.h" + +using namespace AppInstaller::Repository; + +namespace AppInstaller::Workflow +{ + void SearchFlow::Execute() + { + WorkflowBase::IndexSearch(); + + ProcessSearchResult(); + } + + void SearchFlow::ProcessSearchResult() + { + if (m_searchResult.Matches.size() == 0) + { + AICLI_LOG(CLI, Info, << "No app found matching input criteria"); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "No app found matching input criteria."); + } + else + { + WorkflowBase::ReportSearchResult(); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/SearchFlow.h b/src/AppInstallerCLICore/Workflows/SearchFlow.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include "Invocation.h" +#include "WorkflowBase.h" + +namespace AppInstaller::Workflow +{ + class SearchFlow : public WorkflowBase + { + public: + SearchFlow(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + WorkflowBase(args, outStream, inStream) {} + + void Execute();; + + protected: + + void ProcessSearchResult(); + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "Commands/Common.h" +#include "ShowFlow.h" +#include "ManifestComparator.h" + +using namespace AppInstaller::Repository; + +namespace AppInstaller::Workflow +{ + void ShowFlow::Execute() + { + WorkflowBase::IndexSearch(); + + if (WorkflowBase::EnsureOneMatchFromSearchResult()) + { + if (m_argsRef.Contains(CLI::ARG_LISTVERSIONS)) + { + ShowAppVersion(); + } + else + { + ShowAppInfo(); + } + } + } + + void ShowFlow::ShowAppInfo() + { + auto app = m_searchResult.Matches.at(0).Application.get(); + + auto manifest = app->GetManifest( + m_argsRef.Contains(CLI::ARG_VERSION) ? *m_argsRef.GetArg(CLI::ARG_VERSION) : "", + m_argsRef.Contains(CLI::ARG_CHANNEL) ? *m_argsRef.GetArg(CLI::ARG_CHANNEL) : "" + ); + + ManifestComparator manifestComparator(manifest, m_reporter); + auto selectedLocalization = manifestComparator.GetPreferredLocalization(m_argsRef); + auto selectedInstaller = manifestComparator.GetPreferredInstaller(m_argsRef); + + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Id: " + manifest.Id); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Name: " + manifest.Name); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Version: " + manifest.Version); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Author: " + manifest.Author); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "AppMoniker: " + manifest.AppMoniker); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Description: " + selectedLocalization.Description); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Homepage: " + selectedLocalization.Homepage); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "License: " + selectedLocalization.LicenseUrl); + + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Installer info:" + manifest.Id); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "--Installer Language: " + selectedInstaller.Language); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "--Installer SHA256: " + Utility::SHA256::ConvertToString(selectedInstaller.Sha256)); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "--Installer Download Url: " + selectedInstaller.Url); + } + + void ShowFlow::ShowAppVersion() + { + auto app = m_searchResult.Matches.at(0).Application.get(); + + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Id: " + app->GetId()); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Name: " + app->GetName()); + + for (auto& version : app->GetVersions()) + { + m_reporter.ShowMsg(WorkflowReporter::Level::Info, " Version: " + version.first + ", Channel: " + version.second); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.h b/src/AppInstallerCLICore/Workflows/ShowFlow.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include "Invocation.h" +#include "WorkflowBase.h" + +namespace AppInstaller::Workflow +{ + class ShowFlow : public WorkflowBase + { + public: + ShowFlow(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + WorkflowBase(args, outStream, inStream) {} + + void Execute();; + + protected: + + void ShowAppInfo(); + void ShowAppVersion(); + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "Commands/Common.h" +#include "WorkflowBase.h" +#include "Public/AppInstallerRepositorySearch.h" +#include "Public/AppInstallerRepositorySource.h" + +using namespace AppInstaller::Repository; + +namespace AppInstaller::Workflow +{ + void WorkflowBase::OpenIndexSource() + { + if (m_argsRef.Contains(CLI::ARG_SOURCE)) + { + m_source = OpenSource(*m_argsRef.GetArg(CLI::ARG_SOURCE)); + } + else + { + m_source = OpenSource(); + } + } + + void WorkflowBase::IndexSearch() + { + OpenIndexSource(); + + // Construct query + MatchType matchType = MatchType::Fuzzy; + if (m_argsRef.Contains(CLI::ARG_EXACT)) + { + matchType = MatchType::Exact; + } + + SearchRequest searchRequest; + if (m_argsRef.Contains(CLI::ARG_QUERY)) + { + searchRequest.Query.emplace(RequestMatch(matchType, *m_argsRef.GetArg(CLI::ARG_QUERY))); + } + + if (m_argsRef.Contains(CLI::ARG_ID)) + { + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Id, matchType, *m_argsRef.GetArg(CLI::ARG_ID))); + } + + if (m_argsRef.Contains(CLI::ARG_NAME)) + { + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Name, matchType, *m_argsRef.GetArg(CLI::ARG_NAME))); + } + + if (m_argsRef.Contains(CLI::ARG_MONIKER)) + { + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Moniker, matchType, *m_argsRef.GetArg(CLI::ARG_MONIKER))); + } + + if (m_argsRef.Contains(CLI::ARG_TAG)) + { + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Tag, matchType, *m_argsRef.GetArg(CLI::ARG_TAG))); + } + + if (m_argsRef.Contains(CLI::ARG_COMMAND)) + { + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Command, matchType, *m_argsRef.GetArg(CLI::ARG_COMMAND))); + } + + if (m_argsRef.Contains(CLI::ARG_COUNT)) + { + searchRequest.MaximumResults = std::stoi(*m_argsRef.GetArg(CLI::ARG_COUNT)); + } + + m_searchResult = m_source->Search(searchRequest); + } + + bool WorkflowBase::EnsureOneMatchFromSearchResult() + { + if (m_searchResult.Matches.size() == 0) + { + AICLI_LOG(CLI, Info, << "No app found matching input criteria"); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "No app found matching input criteria."); + return false; + } + + if (m_searchResult.Matches.size() > 1) + { + AICLI_LOG(CLI, Info, << "Multiple apps found matching input criteria"); + m_reporter.ShowMsg(WorkflowReporter::Level::Info, "Multiple apps found matching input criteria. Please refine the input."); + ReportSearchResult(); + return false; + } + + return true; + } + + void WorkflowBase::ReportSearchResult() + { + for (auto& match : m_searchResult.Matches) + { + auto app = match.Application.get(); + auto allVersions = app->GetVersions(); + + // Todo: Assume versions are sorted when returned so we'll use the first one as the latest version + // Need to call sort if the above is not the case. + std::string msg = app->GetId() + ", " + app->GetName() + ", " + allVersions.at(0).first; + + if (match.MatchCriteria.Field != ApplicationMatchField::Id && match.MatchCriteria.Field != ApplicationMatchField::Name) + { + msg += ", [" + ApplicationMatchFieldToString(match.MatchCriteria.Field) + ": " + match.MatchCriteria.Value + "]"; + } + + m_reporter.ShowMsg(WorkflowReporter::Level::Info, msg); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include "Invocation.h" +#include "WorkflowReporter.h" +#include "Public/AppInstallerRepositorySearch.h" +#include "Public/AppInstallerRepositorySource.h" + +namespace AppInstaller::Workflow +{ + class WorkflowBase + { + protected: + WorkflowBase(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + m_reporter(outStream, inStream), m_argsRef(args) {} + + WorkflowReporter m_reporter; + const AppInstaller::CLI::Invocation& m_argsRef; + + virtual void OpenIndexSource(); + + void IndexSearch(); + + bool EnsureOneMatchFromSearchResult(); + + void ReportSearchResult(); + + std::unique_ptr<AppInstaller::Repository::ISource> m_source; + AppInstaller::Repository::SearchResult m_searchResult; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp b/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp @@ -6,23 +6,6 @@ namespace AppInstaller::Workflow { - void WorkflowReporter::ShowPackageInfo( - const std::string& name, - const std::string& version, - const std::string& author, - const std::string& description, - const std::string& homepage, - const std::string& licenseUrl - ) - { - out << "Name: " << name << std::endl; - out << "Version: " << version << std::endl; - out << "Author: " << author << std::endl; - out << "Description: " << description << std::endl; - out << "Homepage: " << homepage << std::endl; - out << "License: " << licenseUrl << std::endl; - } - bool WorkflowReporter::PromptForBoolResponse(Level level, const std::string& msg) { UNREFERENCED_PARAMETER(level); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.h b/src/AppInstallerCLICore/Workflows/WorkflowReporter.h @@ -3,7 +3,9 @@ #pragma once +#include "Manifest/Manifest.h" #include "AppInstallerDownloader.h" +#include "Public/AppInstallerRepositorySearch.h" namespace AppInstaller::Workflow { @@ -55,14 +57,6 @@ namespace AppInstaller::Workflow WorkflowReporter(std::ostream& outStream, std::istream& inStream) : out(outStream), in(inStream), m_progressBar(outStream), m_spinner(outStream) {}; - void ShowPackageInfo( - const std::string& name, - const std::string& version, - const std::string& author, - const std::string& description, - const std::string& homepage, - const std::string& licenseUrl); - bool PromptForBoolResponse(Level level, const std::string& msg); void ShowMsg(Level level, const std::string& msg); diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -152,7 +152,7 @@ </ItemGroup> <ItemGroup> <ClCompile Include="Downloader.cpp" /> - <ClCompile Include="InstallFlow.cpp" /> + <ClCompile Include="WorkFlow.cpp" /> <ClCompile Include="LanguageUtilities.cpp" /> <ClCompile Include="main.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -50,9 +50,6 @@ <ClCompile Include="Downloader.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="InstallFlow.cpp"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="LanguageUtilities.cpp"> <Filter>Source Files</Filter> </ClCompile> @@ -62,6 +59,9 @@ <ClCompile Include="Sources.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="WorkFlow.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="Synchronization.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp @@ -1,265 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "Commands/Common.h" -#include "AppInstallerLogging.h" -#include "Manifest/Manifest.h" -#include "AppInstallerDownloader.h" -#include "AppInstallerStrings.h" -#include "Workflows/InstallFlow.h" -#include "Workflows/ShellExecuteInstallerHandler.h" -#include "Workflows/MsixInstallerHandler.h" - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Management::Deployment; -using namespace TestCommon; -using namespace AppInstaller::Workflow; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; - -class MsixInstallerHandlerTest : public MsixInstallerHandler -{ -public: - MsixInstallerHandlerTest( - const ManifestInstaller& manifestInstaller, - const AppInstaller::CLI::Invocation& args, - WorkflowReporter& reporter) : MsixInstallerHandler(manifestInstaller, args, reporter) {}; - -protected: - - std::future<void> ExecuteInstallerAsync(const Uri& uri) override - { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestMsixInstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - - file << AppInstaller::Utility::ConvertToUTF8(uri.ToString()); - - file.close(); - - co_return; - } -}; - -class ShellExecuteInstallerHandlerTest : public ShellExecuteInstallerHandler -{ -public: - ShellExecuteInstallerHandlerTest( - const ManifestInstaller& manifestInstaller, - const AppInstaller::CLI::Invocation& args, - WorkflowReporter& reporter) : ShellExecuteInstallerHandler(manifestInstaller, args, reporter) {}; - - void Download() override - { - this->m_downloadedInstaller = TestDataFile("AppInstallerTestExeInstaller.exe"); - } - - void RenameDownloadedInstaller() override {}; - - std::string TestInstallerArgs() - { - Download(); - return ShellExecuteInstallerHandler::GetInstallerArgs(); - } -}; - -class InstallFlowTest : public InstallFlow -{ -public: - InstallFlowTest(Manifest manifest, const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : - InstallFlow(manifest, args, outStream, inStream) {} - -protected: - std::unique_ptr<InstallerHandlerBase> GetInstallerHandler() override - { - switch (m_selectedInstaller.InstallerType) - { - case ManifestInstaller::InstallerTypeEnum::Exe: - return std::make_unique<ShellExecuteInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); - case ManifestInstaller::InstallerTypeEnum::Msix: - return std::make_unique<MsixInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } -}; - -TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yml")); - - std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); - testFlow.Install(); - 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("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml")); - - std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); - REQUIRE_THROWS_WITH(testFlow.Install(), Catch::Contains("No installer with applicable architecture found.")); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow]") -{ - TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); - - // Todo: point to files from our repo when the repo goes public - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml")); - - std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); - testFlow.Install(); - INFO(installOutput.str()); - - // Verify Installer is called and a local file is used as package Uri. - 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("file://") != std::string::npos); -} - -TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow]") -{ - TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); - - // Todo: point to files from our repo when the repo goes public - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml")); - - std::ostringstream installOutput; - AppInstaller::CLI::Invocation inv{ {""} }; - InstallFlowTest testFlow(manifest, inv, installOutput, std::cin); - testFlow.Install(); - INFO(installOutput.str()); - - // Verify Installer is called and a http address is used as package Uri. - 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("https://") != std::string::npos); -} - -TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") -{ - std::ostringstream installOutput; - WorkflowReporter reporter(installOutput, std::cin); - - { - // Default Msi type with no args passed in, no switches specified in manifest - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs.find("/passive") != std::string::npos); - REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); - } - - { - // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs.find("/quiet") != std::string::npos); - REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); - REQUIRE(installerArgs.find("TARGETDIR=\"MyDir\"") != std::string::npos); - } - - { - // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest - } - - { - // Default Inno type with no args passed in, no switches specified in manifest - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs.find("/SILENT") != std::string::npos); - REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); - } - - { - // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); - REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); - REQUIRE(installerArgs.find("/DIR=\"MyDir\"") != std::string::npos); - } - - { - // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest - } - - { - // Override switch specified. The whole arg passed to installer is overrided. - auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); - AppInstaller::CLI::Invocation inv{ {""} }; - inv.AddArg(AppInstaller::CLI::ARG_SILENT); - inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); - inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); - inv.AddArg(AppInstaller::CLI::ARG_OVERRIDE, "/OverrideEverything"); - ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); - std::string installerArgs = testhandler.TestInstallerArgs(); - REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch - } -}- \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -0,0 +1,428 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "Commands/Common.h" +#include "AppInstallerLogging.h" +#include "Manifest/Manifest.h" +#include "AppInstallerDownloader.h" +#include "AppInstallerStrings.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/ShowFlow.h" +#include "Workflows/ShellExecuteInstallerHandler.h" +#include "Workflows/MsixInstallerHandler.h" +#include "Public/AppInstallerRepositorySource.h" +#include "Public/AppInstallerRepositorySearch.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Management::Deployment; +using namespace TestCommon; +using namespace AppInstaller::Workflow; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; + +class MsixInstallerHandlerTest : public MsixInstallerHandler +{ +public: + MsixInstallerHandlerTest( + const ManifestInstaller& manifestInstaller, + const AppInstaller::CLI::Invocation& args, + WorkflowReporter& reporter) : MsixInstallerHandler(manifestInstaller, args, reporter) {}; + +protected: + + std::future<void> ExecuteInstallerAsync(const Uri& uri) override + { + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestMsixInstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + + file << AppInstaller::Utility::ConvertToUTF8(uri.ToString()); + + file.close(); + + co_return; + } +}; + +class ShellExecuteInstallerHandlerTest : public ShellExecuteInstallerHandler +{ +public: + ShellExecuteInstallerHandlerTest( + const ManifestInstaller& manifestInstaller, + const AppInstaller::CLI::Invocation& args, + WorkflowReporter& reporter) : ShellExecuteInstallerHandler(manifestInstaller, args, reporter) {}; + + void Download() override + { + this->m_downloadedInstaller = TestDataFile("AppInstallerTestExeInstaller.exe"); + } + + void RenameDownloadedInstaller() override {}; + + std::string TestInstallerArgs() + { + Download(); + return ShellExecuteInstallerHandler::GetInstallerArgs(); + } +}; + +struct TestSource : public ISource +{ + struct TestApplication : public IApplication + { + TestApplication(const Manifest manifest) : m_manifest(manifest) {} + + Manifest GetManifest(std::string_view, std::string_view) const override + { + return m_manifest; + } + + std::string GetId() const override + { + return m_manifest.Id; + } + + std::string GetName() const override + { + return m_manifest.Name; + } + + std::vector<std::pair<std::string, std::string>> GetVersions() const override + { + std::vector<std::pair<std::string, std::string>> result; + result.emplace_back(std::make_pair(m_manifest.Version, m_manifest.Channel)); + return result; + } + + Manifest m_manifest; + }; + + SearchResult Search(const SearchRequest& request) const override + { + SearchResult result; + if (request.Query.has_value()) + { + if (request.Query.value().Value == "TestQueryReturnOne") + { + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yml")); + result.Matches.emplace_back( + ResultMatch( + std::make_unique<TestApplication>(manifest), + ApplicationMatchFilter(ApplicationMatchField::Id, MatchType::Exact, "TestQueryReturnOne"))); + } + else if (request.Query.value().Value == "TestQueryReturnTwo") + { + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yml")); + result.Matches.emplace_back( + ResultMatch( + std::make_unique<TestApplication>(manifest), + ApplicationMatchFilter(ApplicationMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); + + auto manifest2 = Manifest::CreateFromPath(TestDataFile("GoodManifest.yml")); + result.Matches.emplace_back( + ResultMatch( + std::make_unique<TestApplication>(manifest2), + ApplicationMatchFilter(ApplicationMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); + } + } + return result; + } + + virtual const SourceDetails& GetDetails() const override { THROW_HR(E_NOTIMPL); } +}; + +class InstallFlowTest : public InstallFlow +{ +public: + InstallFlowTest(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + InstallFlow(args, outStream, inStream) {} + +protected: + std::unique_ptr<InstallerHandlerBase> GetInstallerHandler() override + { + switch (m_selectedInstaller.InstallerType) + { + case ManifestInstaller::InstallerTypeEnum::Exe: + return std::make_unique<ShellExecuteInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); + case ManifestInstaller::InstallerTypeEnum::Msix: + return std::make_unique<MsixInstallerHandlerTest>(m_selectedInstaller, m_argsRef, m_reporter); + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void OpenIndexSource() override + { + m_source = std::make_unique<TestSource>(); + } +}; + +class ShowFlowTest : public ShowFlow +{ +public: + ShowFlowTest(const AppInstaller::CLI::Invocation& args, std::ostream& outStream, std::istream& inStream) : + ShowFlow(args, outStream, inStream) {} + +protected: + + void OpenIndexSource() override + { + m_source = std::make_unique<TestSource>(); + } +}; + +TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_Exe.yml").GetPath().u8string()); + InstallFlowTest testFlow(inv, installOutput, std::cin); + testFlow.Execute(); + 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("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml").GetPath().u8string()); + InstallFlowTest testFlow(inv, installOutput, std::cin); + REQUIRE_THROWS_WITH(testFlow.Execute(), Catch::Contains("No installer with applicable architecture found.")); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow]") +{ + TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); + + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + // Todo: point to files from our repo when the repo goes public + inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml").GetPath().u8string()); + InstallFlowTest testFlow(inv, installOutput, std::cin); + testFlow.Execute(); + INFO(installOutput.str()); + + // Verify Installer is called and a local file is used as package Uri. + 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("file://") != std::string::npos); +} + +TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow]") +{ + TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); + + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + // Todo: point to files from our repo when the repo goes public + inv.AddArg(AppInstaller::CLI::ARG_MANIFEST, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml").GetPath().u8string()); + InstallFlowTest testFlow(inv, installOutput, std::cin); + testFlow.Execute(); + INFO(installOutput.str()); + + // Verify Installer is called and a http address is used as package Uri. + 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("https://") != std::string::npos); +} + +TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") +{ + std::ostringstream installOutput; + WorkflowReporter reporter(installOutput, std::cin); + + { + // Default Msi type with no args passed in, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/passive") != std::string::npos); + REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); + } + + { + // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/quiet") != std::string::npos); + REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); + REQUIRE(installerArgs.find("TARGETDIR=\"MyDir\"") != std::string::npos); + } + + { + // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + } + + { + // Default Inno type with no args passed in, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/SILENT") != std::string::npos); + REQUIRE(installerArgs.find("AppInstallerTestExeInstaller.exe.log") != std::string::npos); + } + + { + // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); + REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); + REQUIRE(installerArgs.find("/DIR=\"MyDir\"") != std::string::npos); + } + + { + // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + } + + { + // Override switch specified. The whole arg passed to installer is overrided. + auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_SILENT); + inv.AddArg(AppInstaller::CLI::ARG_LOG, "MyLog.log"); + inv.AddArg(AppInstaller::CLI::ARG_INSTALLLOCATION, "MyDir"); + inv.AddArg(AppInstaller::CLI::ARG_OVERRIDE, "/OverrideEverything"); + ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), inv, reporter); + std::string installerArgs = testhandler.TestInstallerArgs(); + REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch + } +} + +TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnOne"); + InstallFlowTest testFlow(inv, installOutput, std::cin); + testFlow.Execute(); + 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("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow]") +{ + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnZero"); + InstallFlowTest testFlow(inv, installOutput, std::cin); + testFlow.Execute(); + INFO(installOutput.str()); + + // Verify proper message is printed + REQUIRE(installOutput.str().find("No app found matching input criteria.") != std::string::npos); +} + +TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow]") +{ + std::ostringstream installOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnTwo"); + InstallFlowTest testFlow(inv, installOutput, std::cin); + testFlow.Execute(); + INFO(installOutput.str()); + + // Verify proper message is printed + REQUIRE(installOutput.str().find("Multiple apps found matching input criteria. Please refine the input.") != std::string::npos); +} + +TEST_CASE("InstallFlow_SearchAndShowAppInfo", "[ShowFlow]") +{ + std::ostringstream showOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnOne"); + ShowFlowTest testFlow(inv, showOutput, std::cin); + testFlow.Execute(); + INFO(showOutput.str()); + + // Verify AppInfo is printed + REQUIRE(showOutput.str().find("Id: AppInstallerCliTest.TestInstaller") != std::string::npos); + REQUIRE(showOutput.str().find("Name: AppInstaller Test Installer") != std::string::npos); + REQUIRE(showOutput.str().find("Version: 1.0.0.0") != std::string::npos); + REQUIRE(showOutput.str().find("--Installer Download Url: https://ThisIsNotUsed") != std::string::npos); +} + +TEST_CASE("InstallFlow_SearchAndShowAppVersion", "[ShowFlow]") +{ + std::ostringstream showOutput; + AppInstaller::CLI::Invocation inv{ {""} }; + inv.AddArg(AppInstaller::CLI::ARG_QUERY, "TestQueryReturnOne"); + inv.AddArg(AppInstaller::CLI::ARG_LISTVERSIONS); + ShowFlowTest testFlow(inv, showOutput, std::cin); + testFlow.Execute(); + INFO(showOutput.str()); + + // Verify App version is printed + REQUIRE(showOutput.str().find("Version: 1.0.0.0") != std::string::npos); + // No manifest info is printed + REQUIRE(showOutput.str().find("--Installer Download Url: https://ThisIsNotUsed") == std::string::npos); +}+ \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h @@ -100,4 +100,23 @@ namespace AppInstaller::Repository // The full set of results from the search. std::vector<ResultMatch> Matches; }; + + inline std::string ApplicationMatchFieldToString(ApplicationMatchField matchField) + { + switch (matchField) + { + case ApplicationMatchField::Command: + return "Command"; + case ApplicationMatchField::Id: + return "Id"; + case ApplicationMatchField::Moniker: + return "Moniker"; + case ApplicationMatchField::Name: + return "Name"; + case ApplicationMatchField::Tag: + return "Tag"; + } + + return "UnknownMatchField"; + } }