diff --git a/src/AppInstallerCLITests/ARPChanges.cpp b/src/AppInstallerCLITests/ARPChanges.cpp
index ed9a56ff40..eaf0478275 100644
--- a/src/AppInstallerCLITests/ARPChanges.cpp
+++ b/src/AppInstallerCLITests/ARPChanges.cpp
@@ -52,9 +52,9 @@ struct TestTelemetry : public TelemetryTraceLogger
mutable bool WasLogSuccessfulInstallARPChangeCalled = false;
};
-struct TestContext : public Context
+struct ARPTestContext : public Context
{
- TestContext(Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Exe) :
+ ARPTestContext(Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Exe) :
Context(OStream, IStream), SourceFactory([this](const SourceDetails&) { return Source; })
{
// Put installer in to control whether arp change code cares to run
@@ -117,7 +117,7 @@ struct TestContext : public Context
AddEverythingResult("Id2", "Name2", "Publisher2", "2.0");
}
- ~TestContext()
+ ~ARPTestContext()
{
TestHook_ClearSourceFactoryOverrides();
TestHook_SetTelemetryOverride({});
@@ -227,7 +227,7 @@ struct TestHeuristicOverride
TEST_CASE("ARPChanges_MSIX_Ignored", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context(Manifest::InstallerTypeEnum::Msix);
+ ARPTestContext context(Manifest::InstallerTypeEnum::Msix);
context << SnapshotARPEntries;
@@ -241,7 +241,7 @@ TEST_CASE("ARPChanges_MSIX_Ignored", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_CheckSnapshot", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
@@ -276,7 +276,7 @@ TEST_CASE("ARPChanges_CheckSnapshot", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_NoChange_NoMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -288,7 +288,7 @@ TEST_CASE("ARPChanges_NoChange_NoMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_NoChange_SingleMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -302,7 +302,7 @@ TEST_CASE("ARPChanges_NoChange_SingleMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_NoChange_MultiMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -317,7 +317,7 @@ TEST_CASE("ARPChanges_NoChange_MultiMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_SingleChange_NoMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -331,7 +331,7 @@ TEST_CASE("ARPChanges_SingleChange_NoMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_SingleChange_SingleMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -346,7 +346,7 @@ TEST_CASE("ARPChanges_SingleChange_SingleMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -362,7 +362,7 @@ TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_MultiChange_NoMatch", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -377,7 +377,7 @@ TEST_CASE("ARPChanges_MultiChange_NoMatch", "[ARPChanges][workflow]")
TEST_CASE("ARPChanges_MultiChange_SingleMatch_NoOverlap", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -393,7 +393,7 @@ TEST_CASE("ARPChanges_MultiChange_SingleMatch_NoOverlap", "[ARPChanges][workflow
TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -409,7 +409,7 @@ TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]"
TEST_CASE("ARPChanges_MultiChange_MultiMatch_NoOverlap", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -426,7 +426,7 @@ TEST_CASE("ARPChanges_MultiChange_MultiMatch_NoOverlap", "[ARPChanges][workflow]
TEST_CASE("ARPChanges_MultiChange_MultiMatch_SingleOverlap", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
@@ -443,7 +443,7 @@ TEST_CASE("ARPChanges_MultiChange_MultiMatch_SingleOverlap", "[ARPChanges][workf
TEST_CASE("ARPChanges_MultiChange_MultiMatch_MultiOverlap", "[ARPChanges][workflow]")
{
TestHeuristicOverride heuristicOverride;
- TestContext context;
+ ARPTestContext context;
context << SnapshotARPEntries;
REQUIRE(context.Contains(Data::ARPCorrelationData));
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
index f4b9ac1484..ca1a736e72 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
@@ -185,6 +185,7 @@
+
@@ -199,12 +200,16 @@
+
+
+
+
@@ -218,6 +223,7 @@
+
@@ -228,11 +234,15 @@
+
+
+
+
@@ -252,6 +262,7 @@
+
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
index bd20cac6eb..f98d82be41 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
@@ -60,6 +60,9 @@
Header Files
+
+ Header Files
+
@@ -242,6 +245,36 @@
Source Files\Common
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
+
+ Source Files\CLI
+
diff --git a/src/AppInstallerCLITests/ExportFlow.cpp b/src/AppInstallerCLITests/ExportFlow.cpp
new file mode 100644
index 0000000000..76d687af77
--- /dev/null
+++ b/src/AppInstallerCLITests/ExportFlow.cpp
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+#include "WorkflowCommon.h"
+#include
+
+using namespace TestCommon;
+using namespace AppInstaller::CLI;
+
+TEST_CASE("ExportFlow_ExportAll", "[ExportFlow][workflow]")
+{
+ TestCommon::TempFile exportResultPath("TestExport.json");
+
+ std::ostringstream exportOutput;
+ TestContext context{ exportOutput, std::cin };
+ auto previousThreadGlobals = context.SetForCurrentThread();
+ OverrideForCompositeInstalledSource(context);
+ context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath);
+
+ ExportCommand exportCommand({});
+ exportCommand.Execute(context);
+ INFO(exportOutput.str());
+
+ // Verify contents of exported collection
+ const auto& exportedCollection = context.Get();
+ REQUIRE(exportedCollection.Sources.size() == 1);
+ REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource");
+
+ const auto& exportedPackages = exportedCollection.Sources[0].Packages;
+ REQUIRE(exportedPackages.size() == 6);
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestMsixInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestMSStoreInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestPortableInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
+}
+
+TEST_CASE("ExportFlow_ExportAll_WithVersions", "[ExportFlow][workflow]")
+{
+ TestCommon::TempFile exportResultPath("TestExport.json");
+
+ std::ostringstream exportOutput;
+ TestContext context{ exportOutput, std::cin };
+ auto previousThreadGlobals = context.SetForCurrentThread();
+ OverrideForCompositeInstalledSource(context);
+ context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath);
+ context.Args.AddArg(Execution::Args::Type::IncludeVersions);
+
+ ExportCommand exportCommand({});
+ exportCommand.Execute(context);
+ INFO(exportOutput.str());
+
+ // Verify contents of exported collection
+ const auto& exportedCollection = context.Get();
+ REQUIRE(exportedCollection.Sources.size() == 1);
+ REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource");
+
+ const auto& exportedPackages = exportedCollection.Sources[0].Packages;
+ REQUIRE(exportedPackages.size() == 6);
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestMsixInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestMSStoreInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestPortableInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
+ }));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString() == "unknown";
+ }));
+}
diff --git a/src/AppInstallerCLITests/ImportFlow.cpp b/src/AppInstallerCLITests/ImportFlow.cpp
new file mode 100644
index 0000000000..60ccb58d66
--- /dev/null
+++ b/src/AppInstallerCLITests/ImportFlow.cpp
@@ -0,0 +1,291 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+#include "WorkflowCommon.h"
+#include
+#include
+
+using namespace TestCommon;
+using namespace AppInstaller::CLI;
+using namespace AppInstaller::Repository;
+using namespace AppInstaller::Utility::literals;
+
+void OverrideForImportSource(TestContext& context, bool useTestCompositeSource = false)
+{
+ context.Override({ "OpenPredefinedSource", [=](TestContext& context)
+ {
+ auto installedSource = useTestCompositeSource ? std::make_shared() : std::make_shared();
+ context.Add(Source{ installedSource });
+ } });
+
+ context.Override({ Workflow::OpenSourcesForImport, [](TestContext& context)
+ {
+ context.Add(std::vector