winget-cli

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

CompositeSource.cpp (76406B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "TestCommon.h"
      5 #include "TestSource.h"
      6 #include "TestHooks.h"
      7 #include <CompositeSource.h>
      8 #include <Microsoft/SQLiteIndexSource.h>
      9 #include <Microsoft/PinningIndex.h>
     10 #include <PackageTrackingCatalogSourceFactory.h>
     11 #include <winget/Pin.h>
     12 #include <winget/PinningData.h>
     13 #include <winget/PackageVersionSelection.h>
     14 
     15 using namespace std::string_literals;
     16 using namespace std::string_view_literals;
     17 using namespace TestCommon;
     18 using namespace AppInstaller;
     19 using namespace AppInstaller::Pinning;
     20 using namespace AppInstaller::Repository;
     21 using namespace AppInstaller::Repository::Microsoft;
     22 using namespace AppInstaller::Utility;
     23 
     24 constexpr std::string_view s_Everything_Query = "everything"sv;
     25 
     26 // A test source that has two modes:
     27 //  1. A request that IsForEverything returns the stored result. This models the
     28 //      incoming search request to a CompositeSource.
     29 //  2. A request that is not for everything invokes TestSource::SearchFunction to
     30 //      enable verification of expectations.
     31 struct ComponentTestSource : public TestSource
     32 {
     33     ComponentTestSource() = default;
     34 
     35     ComponentTestSource(std::string_view identifier, SourceOrigin origin = SourceOrigin::Default)
     36     {
     37         Details.Identifier = identifier;
     38         Details.Origin = origin;
     39     }
     40 
     41     SearchResult Search(const SearchRequest& request) const override
     42     {
     43         if (request.Query && request.Query.value().Value == s_Everything_Query)
     44         {
     45             return Everything;
     46         }
     47         else
     48         {
     49             return TestSource::Search(request);
     50         }
     51     }
     52 
     53     SearchResult Everything;
     54 };
     55 
     56 // A helper to make matches.
     57 struct Criteria : public PackageMatchFilter
     58 {
     59     Criteria() : PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard, ""sv) {}
     60     Criteria(PackageMatchField field) : PackageMatchFilter(field, MatchType::Wildcard, ""sv) {}
     61 };
     62 
     63 Manifest::Manifest MakeDefaultManifest(std::string_view version = "1.0"sv)
     64 {
     65     Manifest::Manifest result;
     66 
     67     result.Id = "Id";
     68     result.DefaultLocalization.Add<Manifest::Localization::PackageName>("Name");
     69     result.DefaultLocalization.Add<Manifest::Localization::Publisher>("Publisher");
     70     result.Version = version;
     71     result.Installers.push_back({});
     72 
     73     return result;
     74 }
     75 
     76 struct TestPackageHelper
     77 {
     78     TestPackageHelper(bool isInstalled, std::shared_ptr<ISource> source = {}) :
     79         m_isInstalled(isInstalled), m_manifest(MakeDefaultManifest()), m_source(source) {}
     80 
     81     TestPackageHelper& WithId(const std::string& id)
     82     {
     83         m_manifest.Id = id;
     84         return *this;
     85     }
     86 
     87     TestPackageHelper& WithVersion(std::string_view version)
     88     {
     89         m_manifest.Version = version;
     90         return *this;
     91     }
     92 
     93     TestPackageHelper& WithChannel(const std::string& channel)
     94     {
     95         m_manifest.Channel = channel;
     96         return *this;
     97     }
     98 
     99     TestPackageHelper& WithDefaultName(std::string_view name)
    100     {
    101         m_manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(std::string{ name });
    102         return *this;
    103     }
    104 
    105     TestPackageHelper& WithPFN(const std::string& pfn)
    106     {
    107         m_manifest.Installers[0].PackageFamilyName = pfn;
    108         return *this;
    109     }
    110 
    111     TestPackageHelper& WithPC(const std::string& pc)
    112     {
    113         m_manifest.Installers[0].ProductCode = pc;
    114         return *this;
    115     }
    116 
    117     TestPackageHelper& HideSRS(bool value = true)
    118     {
    119         m_hideSystemReferenceStrings = value;
    120         return *this;
    121     }
    122 
    123     std::shared_ptr<TestCompositePackage> ToPackage()
    124     {
    125         if (!m_package)
    126         {
    127             if (m_isInstalled)
    128             {
    129                 m_package = TestCompositePackage::Make(m_manifest, TestCompositePackage::MetadataMap{}, std::vector<Manifest::Manifest>(), m_source);
    130             }
    131             else
    132             {
    133                 m_package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ m_manifest }, m_source, m_hideSystemReferenceStrings);
    134             }
    135         }
    136 
    137         return m_package;
    138     }
    139 
    140     operator std::shared_ptr<ICompositePackage>()
    141     {
    142         return ToPackage();
    143     }
    144 
    145     operator const Manifest::Manifest& () const
    146     {
    147         return m_manifest;
    148     }
    149 
    150 private:
    151     bool m_isInstalled;
    152     Manifest::Manifest m_manifest;
    153     std::shared_ptr<ISource> m_source;
    154     std::shared_ptr<TestCompositePackage> m_package;
    155     bool m_hideSystemReferenceStrings = false;
    156 };
    157 
    158 // A helper to create the sources used by the majority of tests in this file.
    159 struct CompositeTestSetup
    160 {
    161     CompositeTestSetup(CompositeSearchBehavior behavior = CompositeSearchBehavior::Installed) : Composite("*Tests")
    162     {
    163         Installed = std::make_shared<ComponentTestSource>("InstalledTestSource1", SourceOrigin::Predefined);
    164         Available = std::make_shared<ComponentTestSource>("AvailableTestSource1");
    165         Composite.SetInstalledSource(Source{ Installed }, behavior);
    166         Composite.AddAvailableSource(Source{ Available });
    167     }
    168 
    169     SearchResult Search(bool disableDataChecks = false)
    170     {
    171         size_t initialCountOfCallsRequiringVersionData = Available->CountOfCallsRequiringVersionData;
    172         size_t initialCountOfCallsRequiringManifestData = Available->CountOfCallsRequiringManifestData;
    173 
    174         SearchRequest request;
    175         request.Query = RequestMatch(MatchType::Exact, s_Everything_Query);
    176         auto result = Composite.Search(request);
    177 
    178         // We want to prevent calls to these functions for Search as they can require network or I/O activity.
    179         size_t countOfCallsRequiringVersionData = Available->CountOfCallsRequiringVersionData - initialCountOfCallsRequiringVersionData;
    180         size_t countOfCallsRequiringManifestData = Available->CountOfCallsRequiringManifestData - initialCountOfCallsRequiringManifestData;
    181         if (!disableDataChecks && (countOfCallsRequiringVersionData || countOfCallsRequiringManifestData))
    182         {
    183             std::ostringstream stream;
    184             stream << "Version data calls [" << countOfCallsRequiringVersionData << "] : Manifest data calls [" << countOfCallsRequiringManifestData << "]";
    185             FAIL(stream.str());
    186         }
    187 
    188         return result;
    189     }
    190 
    191     TestPackageHelper MakeInstalled(std::shared_ptr<ISource> source)
    192     {
    193         return { /* isInstalled */ true, std::move(source)};
    194     }
    195 
    196     TestPackageHelper MakeInstalled()
    197     {
    198         return MakeInstalled(Installed);
    199     }
    200 
    201     TestPackageHelper MakeAvailable(std::shared_ptr<ISource> source)
    202     {
    203         return { /* isInstalled */ false, std::move(source) };
    204     }
    205 
    206     TestPackageHelper MakeAvailable()
    207     {
    208         return MakeAvailable(Available);
    209     }
    210 
    211     std::shared_ptr<ComponentTestSource> Installed;
    212     std::shared_ptr<ComponentTestSource> Available;
    213     CompositeSource Composite;
    214 };
    215 
    216 // A helper to create the sources used by the majority of tests in this file.
    217 struct CompositeWithTrackingTestSetup : public CompositeTestSetup
    218 {
    219     CompositeWithTrackingTestSetup() : TrackingFactory([&](const SourceDetails&) { return Tracking; })
    220     {
    221         Tracking = std::make_shared<SQLiteIndexSource>(SourceDetails{}, SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET));
    222         TestHook_SetSourceFactoryOverride(std::string{ PackageTrackingCatalogSourceFactory::Type() }, TrackingFactory);
    223     }
    224 
    225     ~CompositeWithTrackingTestSetup()
    226     {
    227         TestHook_ClearSourceFactoryOverrides();
    228     }
    229 
    230     TestSourceFactory TrackingFactory;
    231     std::shared_ptr<SQLiteIndexSource> Tracking;
    232 };
    233 
    234 bool SearchRequestIncludes(const std::vector<PackageMatchFilter>& filters, PackageMatchField field, MatchType type, std::optional<std::string> value = {})
    235 {
    236     bool found = false;
    237 
    238     for (const PackageMatchFilter& filter : filters)
    239     {
    240         if (filter.Field == field && filter.Type == type &&
    241             (!value || filter.Value == value.value()))
    242         {
    243             found = true;
    244         }
    245     }
    246 
    247     return found;
    248 }
    249 
    250 void RequireSearchRequestIncludes(const std::vector<PackageMatchFilter>& filters, PackageMatchField field, MatchType type, std::optional<std::string> value = {})
    251 {
    252     REQUIRE(SearchRequestIncludes(filters, field, type, value));
    253 }
    254 
    255 TEST_CASE("CompositeSource_PackageFamilyName_NotAvailable", "[CompositeSource]")
    256 {
    257     // Pre-folded for easier ==
    258     std::string pfn = "sortof_apfn";
    259 
    260     CompositeTestSetup setup;
    261     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    262 
    263     SearchResult result = setup.Search();
    264 
    265     REQUIRE(result.Matches.size() == 1);
    266     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    267     REQUIRE(result.Matches[0].Package->GetAvailable().empty());
    268 }
    269 
    270 TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]")
    271 {
    272     std::string pfn = "sortof_apfn";
    273 
    274     CompositeTestSetup setup;
    275     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    276     setup.Available->SearchFunction = [&](const SearchRequest& request)
    277     {
    278         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    279 
    280         SearchResult result;
    281         result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    282         return result;
    283     };
    284 
    285     SearchResult result = setup.Search();
    286 
    287     REQUIRE(result.Matches.size() == 1);
    288     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    289     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    290     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    291 }
    292 
    293 TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]")
    294 {
    295     std::string pc = "thiscouldbeapc";
    296 
    297     CompositeTestSetup setup;
    298     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPC(pc), Criteria());
    299 
    300     SearchResult result = setup.Search();
    301 
    302     REQUIRE(result.Matches.size() == 1);
    303     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    304     REQUIRE(result.Matches[0].Package->GetAvailable().empty());
    305 }
    306 
    307 TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]")
    308 {
    309     std::string pc = "thiscouldbeapc";
    310 
    311     CompositeTestSetup setup;
    312     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPC(pc), Criteria());
    313     setup.Available->SearchFunction = [&](const SearchRequest& request)
    314     {
    315         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, pc);
    316 
    317         SearchResult result;
    318         result.Matches.emplace_back(setup.MakeAvailable().WithPC(pc), Criteria());
    319         return result;
    320     };
    321 
    322     SearchResult result = setup.Search();
    323 
    324     REQUIRE(result.Matches.size() == 1);
    325     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    326     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    327     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    328 }
    329 
    330 TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]")
    331 {
    332     CompositeTestSetup setup;
    333     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled(), Criteria());
    334     setup.Available->SearchFunction = [&](const SearchRequest& request)
    335     {
    336         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact);
    337 
    338         SearchResult result;
    339         result.Matches.emplace_back(setup.MakeAvailable(), Criteria());
    340         return result;
    341     };
    342 
    343     SearchResult result = setup.Search();
    344 
    345     REQUIRE(result.Matches.size() == 1);
    346     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    347     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    348     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    349 }
    350 
    351 TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]")
    352 {
    353     std::string name = "MatchingName";
    354 
    355     CompositeTestSetup setup;
    356     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria());
    357     setup.Available->SearchFunction = [&](const SearchRequest&)
    358     {
    359         SearchResult result;
    360         result.Matches.emplace_back(setup.MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher));
    361         result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(name), Criteria(PackageMatchField::PackageFamilyName));
    362         return result;
    363     };
    364 
    365     SearchResult result = setup.Search();
    366 
    367     REQUIRE(result.Matches.size() == 1);
    368     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    369     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    370     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    371     auto version = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion();
    372     REQUIRE(version->GetProperty(PackageVersionProperty::Name).get() == name);
    373     REQUIRE(!Version(version->GetProperty(PackageVersionProperty::Version)).IsUnknown());
    374 }
    375 
    376 TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource]")
    377 {
    378     CompositeTestSetup setup;
    379     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria());
    380     setup.Available->SearchFunction = [&](const SearchRequest&)
    381     {
    382         SearchResult result;
    383         result.Matches.emplace_back(setup.MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher));
    384         result.Matches.emplace_back(setup.MakeAvailable().WithId("Another diff ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher));
    385         return result;
    386     };
    387 
    388     SearchResult result = setup.Search();
    389 
    390     REQUIRE(result.Matches.size() == 1);
    391     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    392     REQUIRE(result.Matches[0].Package->GetAvailable().empty());
    393 }
    394 
    395 TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]")
    396 {
    397     std::string pfn = "sortof_apfn";
    398 
    399     CompositeTestSetup setup;
    400     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
    401     auto availablePackage = setup.MakeAvailable().WithPFN(pfn);
    402 
    403     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
    404     setup.Installed->SearchFunction = [&](const SearchRequest& request)
    405     {
    406         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    407 
    408         SearchResult result;
    409         result.Matches.emplace_back(installedPackage, Criteria());
    410         return result;
    411     };
    412 
    413     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
    414     setup.Available->SearchFunction = [&](const SearchRequest& request)
    415     {
    416         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    417 
    418         SearchResult result;
    419         result.Matches.emplace_back(availablePackage, Criteria());
    420         return result;
    421     };
    422 
    423     SearchResult result = setup.Search();
    424 
    425     REQUIRE(result.Matches.size() == 1);
    426     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    427     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    428     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    429 }
    430 
    431 TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]")
    432 {
    433     std::string pfn = "sortof_apfn";
    434 
    435     CompositeTestSetup setup;
    436     setup.Installed->SearchFunction = [&](const SearchRequest& request)
    437     {
    438         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    439 
    440         SearchResult result;
    441         result.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria(PackageMatchField::PackageFamilyName));
    442         return result;
    443     };
    444 
    445     std::shared_ptr<TestCompositePackage> availablePackage = setup.MakeAvailable().WithPFN(pfn).ToPackage();
    446     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
    447     setup.Available->SearchFunction = [&](const SearchRequest& request)
    448     {
    449         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    450 
    451         SearchResult result;
    452         result.Matches.emplace_back(availablePackage, Criteria(PackageMatchField::PackageFamilyName));
    453         return result;
    454     };
    455 
    456     SearchResult result = setup.Search();
    457 
    458     REQUIRE(result.Matches.size() == 1);
    459     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    460     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    461     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    462 }
    463 
    464 TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[CompositeSource]")
    465 {
    466     std::string pfn = "sortof_apfn";
    467 
    468     CompositeTestSetup setup;
    469     setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    470     setup.Available->SearchFunction = [&](const SearchRequest& request)
    471     {
    472         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    473 
    474         SearchResult result;
    475         result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    476         return result;
    477     };
    478 
    479     SearchResult result = setup.Search();
    480 
    481     REQUIRE(result.Matches.empty());
    482 }
    483 
    484 TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]")
    485 {
    486     std::string pfn = "sortof_apfn";
    487     MatchType originalType = MatchType::Wildcard;
    488     MatchType type = MatchType::Exact;
    489 
    490     CompositeTestSetup setup;
    491     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
    492     auto availablePackage = setup.MakeAvailable().WithPFN(pfn);
    493 
    494     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
    495 
    496     setup.Available->SearchFunction = [&](const SearchRequest& request)
    497     {
    498         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    499 
    500         SearchResult result;
    501         result.Matches.emplace_back(availablePackage, Criteria());
    502         return result;
    503     };
    504 
    505     SearchResult result = setup.Search();
    506 
    507     REQUIRE(result.Matches.size() == 1);
    508     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    509     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    510     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    511     REQUIRE(result.Matches[0].MatchCriteria.Type == originalType);
    512 
    513     // Now make the source root search find it with a better criteria
    514     setup.Installed->SearchFunction = [&](const SearchRequest& request)
    515     {
    516         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    517 
    518         SearchResult result;
    519         result.Matches.emplace_back(installedPackage, Criteria());
    520         return result;
    521     };
    522 
    523     setup.Available->Everything.Matches.emplace_back(availablePackage, PackageMatchFilter(PackageMatchField::Id, type, ""sv));
    524 
    525     result = setup.Search();
    526 
    527     REQUIRE(result.Matches.size() == 1);
    528     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    529     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    530     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    531     REQUIRE(result.Matches[0].MatchCriteria.Type == type);
    532 }
    533 
    534 TEST_CASE("CompositePackage_PropertyFromInstalled", "[CompositeSource]")
    535 {
    536     std::string id = "Special test ID";
    537 
    538     CompositeTestSetup setup;
    539     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(id), Criteria());
    540 
    541     SearchResult result = setup.Search();
    542 
    543     REQUIRE(result.Matches.size() == 1);
    544     REQUIRE(result.Matches[0].Package->GetProperty(PackageProperty::Id) == id);
    545 }
    546 
    547 TEST_CASE("CompositePackage_PropertyFromAvailable", "[CompositeSource]")
    548 {
    549     std::string id = "Special test ID";
    550     std::string pfn = "sortof_apfn";
    551 
    552     CompositeTestSetup setup;
    553     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    554     setup.Available->SearchFunction = [&](const SearchRequest&)
    555     {
    556         SearchResult result;
    557         result.Matches.emplace_back(setup.MakeAvailable().WithId(id), Criteria());
    558         return result;
    559     };
    560 
    561     SearchResult result = setup.Search();
    562 
    563     REQUIRE(result.Matches.size() == 1);
    564     REQUIRE(result.Matches[0].Package->GetProperty(PackageProperty::Id) == id);
    565 }
    566 
    567 TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSource]")
    568 {
    569     std::string pfn = "sortof_apfn";
    570     std::string channel = "Channel";
    571 
    572     CompositeTestSetup setup;
    573     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    574     setup.Available->SearchFunction = [&](const SearchRequest&)
    575     {
    576         Manifest::Manifest noChannel = MakeDefaultManifest();
    577         noChannel.Version = "1.0";
    578 
    579         Manifest::Manifest hasChannel = MakeDefaultManifest();
    580         hasChannel.Channel = channel;
    581         hasChannel.Version = "2.0";
    582 
    583         SearchResult result;
    584         result.Matches.emplace_back(TestCompositePackage::Make(std::vector<Manifest::Manifest>{ noChannel, hasChannel }, setup.Available), Criteria());
    585         REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    586         REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2);
    587         return result;
    588     };
    589 
    590     // Disable data checks as we call one of the data methods as validation in the search
    591     SearchResult result = setup.Search(true);
    592 
    593     REQUIRE(result.Matches.size() == 1);
    594     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    595     auto package = result.Matches[0].Package->GetAvailable()[0];
    596 
    597     auto versionKeys = package->GetVersionKeys();
    598     REQUIRE(versionKeys.size() == 2);
    599 
    600     auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package);
    601     auto availableVersionKeys = availableVersions->GetVersionKeys();
    602     REQUIRE(availableVersionKeys.size() == 1);
    603     REQUIRE(availableVersionKeys[0].Channel.empty());
    604 
    605     auto latestVersion = availableVersions->GetLatestVersion();
    606     REQUIRE(latestVersion);
    607     REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get().empty());
    608 }
    609 
    610 TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[CompositeSource]")
    611 {
    612     std::string pfn = "sortof_apfn";
    613     std::string channel = "Channel";
    614 
    615     CompositeTestSetup setup;
    616     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn).WithChannel(channel), Criteria());
    617     setup.Available->SearchFunction = [&](const SearchRequest&)
    618     {
    619         Manifest::Manifest noChannel = MakeDefaultManifest();
    620         noChannel.Version = "1.0";
    621 
    622         Manifest::Manifest hasChannel = MakeDefaultManifest();
    623         hasChannel.Channel = channel;
    624         hasChannel.Version = "2.0";
    625 
    626         SearchResult result;
    627         result.Matches.emplace_back(TestCompositePackage::Make(std::vector<Manifest::Manifest>{ noChannel, hasChannel }, setup.Available), Criteria());
    628         REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    629         REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2);
    630         return result;
    631     };
    632 
    633     // Disable data checks as we call one of the data methods as validation in the search
    634     SearchResult result = setup.Search(true);
    635 
    636     REQUIRE(result.Matches.size() == 1);
    637     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    638     auto package = result.Matches[0].Package->GetAvailable()[0];
    639 
    640     auto versionKeys = package->GetVersionKeys();
    641     REQUIRE(versionKeys.size() == 2);
    642 
    643     auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package);
    644     auto availableVersionKeys = availableVersions->GetVersionKeys();
    645     REQUIRE(availableVersionKeys.size() == 1);
    646     REQUIRE(availableVersionKeys[0].Channel == channel);
    647 
    648     auto latestVersion = availableVersions->GetLatestVersion();
    649     REQUIRE(latestVersion);
    650     REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get() == channel);
    651 }
    652 
    653 TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource]")
    654 {
    655     TestCommon::TestUserSettings testSettings;
    656 
    657     std::string pfn = "sortof_apfn";
    658     std::string firstName = "Name1";
    659     std::string secondName = "Name2";
    660 
    661     CompositeTestSetup setup;
    662     std::shared_ptr<ComponentTestSource> secondAvailable = std::make_shared<ComponentTestSource>();
    663     setup.Composite.AddAvailableSource(Source{ secondAvailable });
    664 
    665     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    666 
    667     setup.Available->SearchFunction = [&](const SearchRequest& request)
    668     {
    669         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    670 
    671         SearchResult result;
    672         result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(firstName), Criteria());
    673         return result;
    674     };
    675 
    676     secondAvailable->SearchFunction = [&](const SearchRequest& request)
    677     {
    678         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    679 
    680         SearchResult result;
    681         result.Matches.emplace_back(setup.MakeAvailable(secondAvailable).WithDefaultName(secondName), Criteria());
    682         return result;
    683     };
    684 
    685     SearchResult result = setup.Search();
    686 
    687     REQUIRE(result.Matches.size() == 1);
    688     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    689     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2);
    690     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == firstName);
    691     REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetProperty(PackageProperty::Name).get() == secondName);
    692 }
    693 
    694 TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSource]")
    695 {
    696     std::string pfn = "sortof_apfn";
    697     std::string firstName = "Name1";
    698     std::string secondName = "Name2";
    699 
    700     CompositeTestSetup setup;
    701     std::shared_ptr<ComponentTestSource> secondAvailable = std::make_shared<ComponentTestSource>();
    702     setup.Composite.AddAvailableSource(Source{ secondAvailable });
    703 
    704     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    705 
    706     secondAvailable->SearchFunction = [&](const SearchRequest& request)
    707     {
    708         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    709 
    710         SearchResult result;
    711         result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(secondName), Criteria());
    712         return result;
    713     };
    714 
    715     SearchResult result = setup.Search();
    716 
    717     REQUIRE(result.Matches.size() == 1);
    718     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    719     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    720     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == secondName);
    721 }
    722 
    723 TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[CompositeSource]")
    724 {
    725     std::string pfn = "sortof_apfn";
    726 
    727     CompositeTestSetup setup;
    728     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
    729 
    730     std::shared_ptr<ComponentTestSource> secondAvailable = std::make_shared<ComponentTestSource>();
    731     setup.Composite.AddAvailableSource(Source{ secondAvailable });
    732 
    733     setup.Installed->SearchFunction = [&](const SearchRequest& request)
    734     {
    735         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    736 
    737         SearchResult result;
    738         result.Matches.emplace_back(installedPackage, Criteria(PackageMatchField::PackageFamilyName));
    739         return result;
    740     };
    741 
    742     setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    743     secondAvailable->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    744 
    745     SearchResult result = setup.Search();
    746 
    747     REQUIRE(result.Matches.size() == 1);
    748     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    749     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2);
    750     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1);
    751     REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetVersionKeys().size() == 1);
    752 }
    753 
    754 TEST_CASE("CompositeSource_IsSame", "[CompositeSource]")
    755 {
    756     CompositeTestSetup setup;
    757     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria());
    758 
    759     SearchResult result1 = setup.Search();
    760     REQUIRE(result1.Matches.size() == 1);
    761 
    762     SearchResult result2 = setup.Search();
    763     REQUIRE(result2.Matches.size() == 1);
    764 
    765     REQUIRE(result1.Matches[0].Package->GetInstalled());
    766     REQUIRE(result1.Matches[0].Package->GetInstalled()->IsSame(result1.Matches[0].Package->GetInstalled().get()));
    767 }
    768 
    769 TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]")
    770 {
    771     HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_FOUND;
    772     std::string pfn = "sortof_apfn";
    773 
    774     std::shared_ptr<ComponentTestSource> AvailableSucceeds = std::make_shared<ComponentTestSource>();
    775     AvailableSucceeds->SearchFunction = [&](const SearchRequest&)
    776     {
    777         SearchResult result;
    778         result.Matches.emplace_back(TestPackageHelper{ /* isInstalled */ false }.WithPFN(pfn), Criteria());
    779         return result;
    780     };
    781 
    782     std::shared_ptr<ComponentTestSource> AvailableFails = std::make_shared<ComponentTestSource>();
    783     AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); };
    784     AvailableFails->Details.Name = "The one that fails";
    785 
    786     CompositeSource Composite("*CompositeSource_AvailableSearchFailure");
    787     Composite.AddAvailableSource(Source{ AvailableSucceeds });
    788     Composite.AddAvailableSource(Source{ AvailableFails });
    789 
    790     SearchResult result = Composite.Search({});
    791 
    792     REQUIRE(result.Matches.size() == 1);
    793     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    794 
    795     auto pfns = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName);
    796     REQUIRE(pfns.size() == 1);
    797     REQUIRE(pfns[0] == pfn);
    798 
    799     REQUIRE(result.Failures.size() == 1);
    800     REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name);
    801 
    802     HRESULT searchFailure = S_OK;
    803     try
    804     {
    805         std::rethrow_exception(result.Failures[0].Exception);
    806     }
    807     catch (const wil::ResultException& re)
    808     {
    809         searchFailure = re.GetErrorCode();
    810     }
    811     catch (...) {}
    812 
    813     REQUIRE(searchFailure == expectedHR);
    814 }
    815 
    816 TEST_CASE("CompositeSource_InstalledToAvailableCorrelationSearchFailure", "[CompositeSource]")
    817 {
    818     HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_LONG;
    819     std::string pfn = "sortof_apfn";
    820 
    821     CompositeTestSetup setup;
    822     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria());
    823     setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    824 
    825     std::shared_ptr<ComponentTestSource> AvailableFails = std::make_shared<ComponentTestSource>();
    826     AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); };
    827     AvailableFails->Details.Name = "The one that fails";
    828 
    829     setup.Composite.AddAvailableSource(Source{ AvailableFails });
    830 
    831     SearchResult result = setup.Search();
    832 
    833     REQUIRE(result.Matches.size() == 1);
    834 
    835     REQUIRE(result.Failures.size() == 1);
    836     REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name);
    837 
    838     HRESULT searchFailure = S_OK;
    839     try
    840     {
    841         std::rethrow_exception(result.Failures[0].Exception);
    842     }
    843     catch (const wil::ResultException& re)
    844     {
    845         searchFailure = re.GetErrorCode();
    846     }
    847     catch (...) {}
    848 
    849     REQUIRE(searchFailure == expectedHR);
    850 }
    851 
    852 TEST_CASE("CompositeSource_InstalledAvailableSearchFailure", "[CompositeSource]")
    853 {
    854     HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_LONG;
    855     std::string pfn = "sortof_apfn";
    856 
    857     CompositeTestSetup setup;
    858     setup.Available->SearchFunction = [&](const SearchRequest&)
    859     {
    860         SearchResult result;
    861         result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria());
    862         return result;
    863     };
    864 
    865     std::shared_ptr<ComponentTestSource> AvailableFails = std::make_shared<ComponentTestSource>();
    866     AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); };
    867     AvailableFails->Details.Name = "The one that fails";
    868 
    869     setup.Composite.AddAvailableSource(Source{ AvailableFails });
    870 
    871     setup.Composite.SetInstalledSource(Source{ setup.Installed }, CompositeSearchBehavior::AvailablePackages);
    872 
    873     SearchRequest request;
    874     request.Query = RequestMatch{ MatchType::Exact, "whatever" };
    875     SearchResult result = setup.Composite.Search(request);
    876 
    877     REQUIRE(result.Matches.size() == 1);
    878 
    879     REQUIRE(result.Failures.size() == 1);
    880     REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name);
    881 
    882     HRESULT searchFailure = S_OK;
    883     try
    884     {
    885         std::rethrow_exception(result.Failures[0].Exception);
    886     }
    887     catch (const wil::ResultException& re)
    888     {
    889         searchFailure = re.GetErrorCode();
    890     }
    891     catch (...) {}
    892 
    893     REQUIRE(searchFailure == expectedHR);
    894 }
    895 
    896 TEST_CASE("CompositeSource_TrackingPackageFound", "[CompositeSource]")
    897 {
    898     std::string availableID = "Available.ID";
    899     std::string pfn = "sortof_apfn";
    900 
    901     CompositeWithTrackingTestSetup setup;
    902     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
    903     auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query);
    904 
    905     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
    906     setup.Installed->SearchFunction = [&](const SearchRequest& request)
    907     {
    908         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    909 
    910         SearchResult result;
    911         result.Matches.emplace_back(installedPackage, Criteria());
    912         return result;
    913     };
    914 
    915     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
    916     setup.Available->SearchFunction = [&](const SearchRequest& request)
    917     {
    918         if (request.Filters.empty())
    919         {
    920             RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    921         }
    922         else
    923         {
    924             REQUIRE(request.Filters.size() == 1);
    925             RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID);
    926         }
    927 
    928         SearchResult result;
    929         result.Matches.emplace_back(availablePackage, Criteria());
    930         return result;
    931     };
    932 
    933     setup.Tracking->GetIndex().AddManifest(availablePackage);
    934 
    935     SearchResult result = setup.Search();
    936 
    937     REQUIRE(result.Matches.size() == 1);
    938     REQUIRE(result.Matches[0].Package);
    939     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    940     REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier);
    941     REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier);
    942     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
    943     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion());
    944 }
    945 
    946 TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", "[CompositeSource]")
    947 {
    948     std::string availableID = "Available.ID";
    949     std::string pfn = "sortof_apfn";
    950 
    951     CompositeWithTrackingTestSetup setup;
    952     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
    953     auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query);
    954 
    955     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
    956     setup.Installed->SearchFunction = [&](const SearchRequest& request)
    957     {
    958         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    959 
    960         SearchResult result;
    961         result.Matches.emplace_back(installedPackage, Criteria());
    962         return result;
    963     };
    964 
    965     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
    966     setup.Available->SearchFunction = [&](const SearchRequest& request)
    967     {
    968         if (request.Filters.empty())
    969         {
    970             RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
    971         }
    972         else
    973         {
    974             REQUIRE(request.Filters.size() == 1);
    975             RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID);
    976         }
    977 
    978         SearchResult result;
    979         result.Matches.emplace_back(availablePackage, Criteria());
    980         return result;
    981     };
    982 
    983     auto manifestId = setup.Tracking->GetIndex().AddManifest(availablePackage);
    984 
    985     // Add test PackageVersionMetadata to be populated to InstalledVersion metadata
    986     setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::TrackingWriteTime, "100");
    987     setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::UserIntentArchitecture, "X86");
    988     setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::UserIntentLocale, "en-US");
    989     setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InstalledArchitecture, "X86");
    990     setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InstalledLocale, "en-US");
    991     setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::PinnedState, "PinnedByManifest");
    992 
    993     SearchResult result = setup.Search();
    994 
    995     REQUIRE(result.Matches.size() == 1);
    996     REQUIRE(result.Matches[0].Package);
    997     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
    998 
    999     auto metadata = GetInstalledVersion(result.Matches[0].Package)->GetMetadata();
   1000     REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentArchitecture] == "X86");
   1001     REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentLocale] == "en-US");
   1002     REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledArchitecture] == "X86");
   1003     REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledLocale] == "en-US");
   1004     REQUIRE(metadata[Repository::PackageVersionMetadata::PinnedState] == "PinnedByManifest");
   1005 }
   1006 
   1007 TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]")
   1008 {
   1009     std::string availableID = "Available.ID";
   1010     std::string pfn = "sortof_apfn";
   1011 
   1012     CompositeWithTrackingTestSetup setup;
   1013     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
   1014     auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query);
   1015 
   1016     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
   1017     setup.Installed->SearchFunction = [&](const SearchRequest& request)
   1018     {
   1019         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
   1020 
   1021         SearchResult result;
   1022         result.Matches.emplace_back(installedPackage, Criteria());
   1023         return result;
   1024     };
   1025 
   1026     setup.Tracking->GetIndex().AddManifest(availablePackage);
   1027 
   1028     SearchResult result = setup.Search();
   1029 
   1030     REQUIRE(result.Matches.size() == 1);
   1031     REQUIRE(result.Matches[0].Package);
   1032     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
   1033     REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier);
   1034     REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier);
   1035     REQUIRE(result.Matches[0].Package->GetAvailable().empty());
   1036 }
   1037 
   1038 TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]")
   1039 {
   1040     CompositeWithTrackingTestSetup setup;
   1041 
   1042     std::string availableID = "Available.ID";
   1043     std::string pfn = "sortof_apfn";
   1044 
   1045     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
   1046     auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query);
   1047 
   1048     setup.Installed->SearchFunction = [&](const SearchRequest& request)
   1049     {
   1050         RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn);
   1051 
   1052         SearchResult result;
   1053         result.Matches.emplace_back(installedPackage, Criteria(PackageMatchField::PackageFamilyName));
   1054         return result;
   1055     };
   1056 
   1057     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
   1058     setup.Available->SearchFunction = [&](const SearchRequest& request)
   1059     {
   1060         REQUIRE(request.Filters.size() == 1);
   1061         RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID);
   1062 
   1063         SearchResult result;
   1064         result.Matches.emplace_back(availablePackage, Criteria());
   1065         return result;
   1066     };
   1067 
   1068     setup.Tracking->GetIndex().AddManifest(availablePackage);
   1069 
   1070     SearchResult result = setup.Search();
   1071 
   1072     REQUIRE(result.Matches.size() == 1);
   1073     REQUIRE(result.Matches[0].Package);
   1074     REQUIRE(GetInstalledVersion(result.Matches[0].Package));
   1075     REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier);
   1076     REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier);
   1077     REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1);
   1078     REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion());
   1079 }
   1080 
   1081 TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]")
   1082 {
   1083     std::string availableID = "Available.ID";
   1084     std::string pfn = "sortof_apfn";
   1085 
   1086     CompositeWithTrackingTestSetup setup;
   1087     auto installedPackage = setup.MakeInstalled().WithPFN(pfn);
   1088     auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query);
   1089 
   1090     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
   1091 
   1092     setup.Tracking->GetIndex().AddManifest(availablePackage);
   1093 
   1094     SearchResult result = setup.Search();
   1095 
   1096     REQUIRE(result.Matches.empty());
   1097 }
   1098 
   1099 TEST_CASE("CompositeSource_NullInstalledVersion", "[CompositeSource]")
   1100 {
   1101     CompositeTestSetup setup;
   1102     setup.Installed->Everything.Matches.emplace_back(setup.MakeAvailable(), Criteria());
   1103 
   1104     // We are mostly testing to see if a null installed version causes an AV or not
   1105     SearchResult result = setup.Search();
   1106     REQUIRE(result.Matches.size() == 0);
   1107 }
   1108 
   1109 TEST_CASE("CompositeSource_NullAvailableVersion", "[CompositeSource]")
   1110 {
   1111     CompositeTestSetup setup{ CompositeSearchBehavior::AvailablePackages };
   1112     setup.Available->Everything.Matches.emplace_back(setup.MakeInstalled(), Criteria());
   1113 
   1114     // We are mostly testing to see if a null available version causes an AV or not
   1115     REQUIRE_THROWS_HR(setup.Search(), E_UNEXPECTED);
   1116 }
   1117 
   1118 struct ExpectedResultForPinBehavior
   1119 {
   1120     ExpectedResultForPinBehavior(bool isUpdateAvailable, std::optional<std::string> latestAvailableVersion)
   1121         : IsUpdateAvailable(isUpdateAvailable), LatestAvailableVersion(latestAvailableVersion) {}
   1122     ExpectedResultForPinBehavior() {}
   1123 
   1124     bool IsUpdateAvailable = false;
   1125     std::optional<std::string> LatestAvailableVersion;
   1126 };
   1127 
   1128 struct ExpectedPackageVersionKey : public PackageVersionKey
   1129 {
   1130     ExpectedPackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel, PinType pinType) :
   1131         PackageVersionKey(sourceId, version, channel), PinnedState(pinType) {}
   1132 
   1133     PinType PinnedState;
   1134 };
   1135 
   1136 struct ExpectedResultsForPinning
   1137 {
   1138     std::map<PinBehavior, ExpectedResultForPinBehavior> ResultsForPinBehavior;
   1139     std::vector<ExpectedPackageVersionKey> AvailableVersions;
   1140 };
   1141 
   1142 void RequireExpectedResultsWithPin(std::shared_ptr<ICompositePackage> package, const ExpectedResultsForPinning& expectedResult, std::shared_ptr<IPackageVersion> packageVersion = {})
   1143 {
   1144     PinningData pinningData{ PinningData::Disposition::ReadOnly };
   1145     auto availableVersions = GetAvailableVersionsForInstalledVersion(package);
   1146 
   1147     if (!packageVersion)
   1148     {
   1149         packageVersion = GetInstalledVersion(package);
   1150     }
   1151 
   1152     for (const auto& entry : expectedResult.ResultsForPinBehavior)
   1153     {
   1154         auto pinBehavior = entry.first;
   1155         const auto& result = entry.second;
   1156 
   1157         auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, packageVersion);
   1158         auto latestAvailable = evaluator.GetLatestAvailableVersionForPins(availableVersions);
   1159 
   1160         REQUIRE(evaluator.IsUpdate(latestAvailable) == result.IsUpdateAvailable);
   1161 
   1162         if (result.LatestAvailableVersion.has_value())
   1163         {
   1164             REQUIRE(latestAvailable);
   1165             REQUIRE(latestAvailable->GetManifest().Version == result.LatestAvailableVersion.value());
   1166         }
   1167         else
   1168         {
   1169             REQUIRE(!latestAvailable);
   1170         }
   1171     }
   1172 
   1173     auto availableVersionKeys = availableVersions->GetVersionKeys();
   1174     REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size());
   1175     for (size_t i = 0; i < availableVersionKeys.size(); ++i)
   1176     {
   1177         auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, packageVersion);
   1178 
   1179         auto availableVersion = availableVersions->GetVersion(expectedResult.AvailableVersions[i]);
   1180         REQUIRE(availableVersion);
   1181         REQUIRE(availableVersionKeys[i].SourceId == expectedResult.AvailableVersions[i].SourceId);
   1182         REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i].Version);
   1183         REQUIRE(evaluator.EvaluatePinType(availableVersion) == expectedResult.AvailableVersions[i].PinnedState);
   1184     }
   1185 }
   1186 
   1187 TEST_CASE("CompositeSource_Pinning_AvailableVersionPinned", "[CompositeSource][PinFlow]")
   1188 {
   1189     // We use an installed package that has 3 available versions: v1.0.0, v1.0.1 and v1.1.0.
   1190     // Installed is v1.0.1
   1191     // We then test the 4 possible pin states (unpinned, Pinned, Blocked, Gated)
   1192     // with the 3 possible pin search behaviors (ignore, consider, include pinned)
   1193     TempFile indexFile("pinningIndex", ".db");
   1194     TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath());
   1195 
   1196     TestUserSettings userSettings;
   1197     CompositeTestSetup setup;
   1198 
   1199     auto installedPackage = setup.MakeInstalled().WithVersion("1.0.1"sv);
   1200     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
   1201 
   1202     setup.Available->SearchFunction = [&](const SearchRequest&)
   1203     {
   1204         auto manifest1 = MakeDefaultManifest("1.0.0"sv);
   1205         auto manifest2 = MakeDefaultManifest("1.0.1"sv);
   1206         auto manifest3 = MakeDefaultManifest("1.1.0"sv);
   1207         auto package = TestCompositePackage::Make(
   1208             std::vector<Manifest::Manifest>{ manifest3, manifest2, manifest1 },
   1209             setup.Available);
   1210 
   1211         SearchResult result;
   1212         result.Matches.emplace_back(package, Criteria());
   1213         return result;
   1214     };
   1215 
   1216     ExpectedResultsForPinning expectedResult;
   1217     // The result when ignoring pins is always the same
   1218     expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" };
   1219 
   1220     PinKey pinKey("Id", setup.Available->Details.Identifier);
   1221     auto pinningIndex = PinningIndex::OpenOrCreateDefault();
   1222     REQUIRE(pinningIndex);
   1223 
   1224     SECTION("Unpinned")
   1225     {
   1226         // If there are no pins, the result should not change if we consider them
   1227         expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins];
   1228         expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins];
   1229         expectedResult.AvailableVersions = {
   1230             { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown },
   1231             { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown },
   1232             { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown },
   1233         };
   1234     }
   1235     SECTION("Pinned")
   1236     {
   1237         pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey }));
   1238 
   1239         // Pinning pins are ignored with --include-pinned
   1240         expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins];
   1241 
   1242         expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} };
   1243         expectedResult.AvailableVersions = {
   1244             { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Pinning },
   1245             { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Pinning },
   1246             { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Pinning },
   1247         };
   1248     }
   1249     SECTION("Blocked")
   1250     {
   1251         pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey }));
   1252         expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} };
   1253 
   1254         // Blocking pins are not affected by --include-pinned
   1255         expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins];
   1256 
   1257         expectedResult.AvailableVersions = {
   1258             { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Blocking },
   1259             { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Blocking },
   1260             { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Blocking },
   1261         };
   1262     }
   1263     SECTION("Gated to 1.*")
   1264     {
   1265         pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv }));
   1266         expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" };
   1267 
   1268         // Gating pins are not affected by --include-pinned
   1269         expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins];
   1270 
   1271         expectedResult.AvailableVersions = {
   1272             { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown },
   1273             { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown },
   1274             { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown },
   1275         };
   1276     }
   1277     SECTION("Gated to 1.0.*")
   1278     {
   1279         pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv }));
   1280         expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ "1.0.1" };
   1281 
   1282         // Gating pins are not affected by --include-pinned
   1283         expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins];
   1284 
   1285         expectedResult.AvailableVersions = {
   1286             { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Gating },
   1287             { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown },
   1288             { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown },
   1289         };
   1290     }
   1291 
   1292     SearchResult result = setup.Search();
   1293     REQUIRE(result.Matches.size() == 1);
   1294     auto package = result.Matches[0].Package;
   1295     REQUIRE(package);
   1296 
   1297     RequireExpectedResultsWithPin(package, expectedResult);
   1298 }
   1299 
   1300 TEST_CASE("CompositeSource_Pinning_OneSourcePinned", "[CompositeSource][PinFlow]")
   1301 {
   1302     // We use an installed package that has 2 available sources.
   1303     // If one of them is pinned, we should still get the updates from the other one.
   1304     TempFile indexFile("pinningIndex", ".db");
   1305     TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath());
   1306 
   1307     TestUserSettings userSettings;
   1308     CompositeTestSetup setup;
   1309 
   1310     auto installedPackage = setup.MakeInstalled().WithVersion("1.0"sv);
   1311     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
   1312 
   1313     setup.Available->SearchFunction = [&](const SearchRequest&)
   1314     {
   1315         auto package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("2.0"sv) }, setup.Available);
   1316 
   1317         SearchResult result;
   1318         result.Matches.emplace_back(package, Criteria());
   1319         return result;
   1320     };
   1321 
   1322     std::shared_ptr<ComponentTestSource> secondAvailable = std::make_shared<ComponentTestSource>("SecondTestSource");
   1323     setup.Composite.AddAvailableSource(Source{ secondAvailable });
   1324     secondAvailable->SearchFunction = [&](const SearchRequest&)
   1325     {
   1326         auto package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("1.1"sv) }, secondAvailable);
   1327 
   1328         SearchResult result;
   1329         result.Matches.emplace_back(package, Criteria());
   1330         return result;
   1331     };
   1332 
   1333     {
   1334         PinKey pinKey("Id", setup.Available->Details.Identifier);
   1335         auto pinningIndex = PinningIndex::OpenOrCreateDefault();
   1336         REQUIRE(pinningIndex);
   1337         pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey }));
   1338     }
   1339 
   1340     ExpectedResultsForPinning expectedResult;
   1341     expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" };
   1342     expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1" };
   1343     expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" };
   1344     expectedResult.AvailableVersions = {
   1345         { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning },
   1346         { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown },
   1347     };
   1348 
   1349     SearchResult result = setup.Search();
   1350     REQUIRE(result.Matches.size() == 1);
   1351     auto package = result.Matches[0].Package;
   1352     REQUIRE(package);
   1353     RequireExpectedResultsWithPin(package, expectedResult);
   1354 }
   1355 
   1356 TEST_CASE("CompositeSource_Pinning_OneSourceGated", "[CompositeSource][PinFlow]")
   1357 {
   1358     // We use an installed package that has 2 available sources.
   1359     // If one of them has a gating pin, we should still get the updates from it
   1360     TempFile indexFile("pinningIndex", ".db");
   1361     TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath());
   1362 
   1363     TestUserSettings userSettings;
   1364     CompositeTestSetup setup;
   1365 
   1366     auto installedPackage = setup.MakeInstalled().WithVersion("1.0.1"sv);
   1367     setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria());
   1368 
   1369     setup.Available->SearchFunction = [&](const SearchRequest&)
   1370     {
   1371         auto package = TestCompositePackage::Make(
   1372             std::vector<Manifest::Manifest>{
   1373                 MakeDefaultManifest("2.0"sv),
   1374                 MakeDefaultManifest("1.2"sv),
   1375             },
   1376             setup.Available);
   1377 
   1378         SearchResult result;
   1379         result.Matches.emplace_back(package, Criteria());
   1380         return result;
   1381     };
   1382 
   1383     std::shared_ptr<ComponentTestSource> secondAvailable = std::make_shared<ComponentTestSource>("SecondTestSource");
   1384     setup.Composite.AddAvailableSource(Source{ secondAvailable });
   1385     secondAvailable->SearchFunction = [&](const SearchRequest&)
   1386     {
   1387         auto package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("1.1"sv) }, secondAvailable);
   1388 
   1389         SearchResult result;
   1390         result.Matches.emplace_back(package, Criteria());
   1391         return result;
   1392     };
   1393 
   1394     {
   1395         PinKey pinKey("Id", setup.Available->Details.Identifier);
   1396         auto pinningIndex = PinningIndex::OpenOrCreateDefault();
   1397         REQUIRE(pinningIndex);
   1398         pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv }));
   1399     }
   1400 
   1401     ExpectedResultsForPinning expectedResult;
   1402     expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" };
   1403     expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" };
   1404     expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" };
   1405     expectedResult.AvailableVersions = {
   1406         { "AvailableTestSource1", "2.0", "", Pinning::PinType::Gating },
   1407         { "AvailableTestSource1", "1.2", "", Pinning::PinType::Unknown },
   1408         { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown },
   1409     };
   1410 
   1411     SearchResult result = setup.Search();
   1412     REQUIRE(result.Matches.size() == 1);
   1413     auto package = result.Matches[0].Package;
   1414     REQUIRE(package);
   1415     RequireExpectedResultsWithPin(package, expectedResult);
   1416 }
   1417 
   1418 TEST_CASE("CompositeSource_Pinning_MultipleInstalled", "[CompositeSource][PinFlow]")
   1419 {
   1420     // Tests the case where multiple installed packages match to a single available package.
   1421     // If one of the two installed packages is pinned, when searching we should get
   1422     // two Composite packages, with only one of them pinned.
   1423     TempFile indexFile("pinningIndex", ".db");
   1424     TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath());
   1425 
   1426     TestUserSettings userSettings;
   1427     
   1428     std::string packageId = "packageId";
   1429     std::string productCode1 = "product-code1";
   1430     std::string productCode2 = "product-code2";
   1431 
   1432     CompositeTestSetup setup;
   1433 
   1434     // Installed packages differ in product code and version
   1435     auto installedPackage1 = setup.MakeInstalled().WithId(productCode1).WithPC(productCode1).WithVersion("1.1"sv);
   1436     auto installedPackage2 = setup.MakeInstalled().WithId(productCode2).WithPC(productCode2).WithVersion("1.2"sv);
   1437 
   1438     setup.Installed->SearchFunction = [&](const SearchRequest& request)
   1439     {
   1440         bool isSearchById = SearchRequestIncludes(request.Inclusions, PackageMatchField::Id, MatchType::Exact, packageId);
   1441 
   1442         SearchResult result;
   1443         if (isSearchById || SearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, productCode1))
   1444         {
   1445             result.Matches.emplace_back(installedPackage1, Criteria(request.Inclusions[0].Field));
   1446         }
   1447 
   1448         if (isSearchById || SearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, productCode2))
   1449         {
   1450             result.Matches.emplace_back(installedPackage2, Criteria(request.Inclusions[0].Field));
   1451         }
   1452 
   1453         return result;
   1454     };
   1455 
   1456     // Available package has the same ID, no product code, and different version from both the installed packages;
   1457     setup.Available->SearchFunction = [&](const SearchRequest&)
   1458     {
   1459         SearchResult result;
   1460         result.Matches.emplace_back(setup.MakeAvailable().WithId(packageId).WithVersion("2.0"sv), Criteria());
   1461         return result;
   1462     };
   1463 
   1464     // We will pin the first package only
   1465     PinKey pinKey = PinKey::GetPinKeyForInstalled(productCode1);
   1466     auto pinningIndex = PinningIndex::OpenOrCreateDefault();
   1467     REQUIRE(pinningIndex);
   1468 
   1469     // We will check the pinning status for both installed packages
   1470     ExpectedResultsForPinning expectedResult1;
   1471     ExpectedResultsForPinning expectedResult2;
   1472 
   1473     expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins]
   1474         = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" };
   1475 
   1476     // The second package is never pinned, so its result is always the same
   1477     expectedResult2.ResultsForPinBehavior[PinBehavior::IgnorePins]
   1478         = expectedResult2.ResultsForPinBehavior[PinBehavior::ConsiderPins]
   1479         = expectedResult2.ResultsForPinBehavior[PinBehavior::IncludePinned]
   1480         = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" };
   1481     expectedResult2.AvailableVersions = {
   1482         { "AvailableTestSource1", "2.0", "", Pinning::PinType::Unknown },
   1483     };
   1484 
   1485     SECTION("Unpinned")
   1486     {
   1487         // If there are no pins, the result should not change if we consider them
   1488         expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins]
   1489             = expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned]
   1490             = expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins];
   1491         expectedResult1.AvailableVersions = {
   1492             { "AvailableTestSource1", "2.0", "", Pinning::PinType::Unknown },
   1493         };
   1494     }
   1495     SECTION("Pinned")
   1496     {
   1497         pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey }));
   1498 
   1499         // Pinning pins are ignored with --include-pinned
   1500         expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins];
   1501 
   1502         expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} };
   1503         expectedResult1.AvailableVersions = {
   1504             { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning },
   1505         };
   1506     }
   1507     SECTION("Blocked")
   1508     {
   1509         pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey }));
   1510         expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} };
   1511 
   1512         // Blocking pins are not affected by --include-pinned
   1513         expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins];
   1514 
   1515         expectedResult1.AvailableVersions = {
   1516             { "AvailableTestSource1", "2.0", "", Pinning::PinType::Blocking },
   1517         };
   1518     }
   1519 
   1520     SearchRequest searchRequest;
   1521     searchRequest.Inclusions.emplace_back(PackageMatchField::Id, MatchType::Exact, packageId);
   1522     SearchResult result = setup.Composite.Search(searchRequest);
   1523 
   1524     REQUIRE(result.Matches.size() == 1);
   1525     auto installedPackage = result.Matches[0].Package->GetInstalled();
   1526     REQUIRE(installedPackage);
   1527     auto installedVersions = installedPackage->GetVersionKeys();
   1528     REQUIRE(installedVersions.size() == 2);
   1529 
   1530     // Here we assume that the order we return the packages in the installed source
   1531     // search is preserved. We'll need to change it if that stops being the case.
   1532     auto packageVersion1 = installedPackage->GetVersion(installedVersions[1]);
   1533     REQUIRE(packageVersion1);
   1534 
   1535     auto packageVersion2 = installedPackage->GetVersion(installedVersions[0]);
   1536     REQUIRE(packageVersion2);
   1537 
   1538     RequireExpectedResultsWithPin(result.Matches[0].Package, expectedResult1, packageVersion1);
   1539     RequireExpectedResultsWithPin(result.Matches[0].Package, expectedResult2, packageVersion2);
   1540 }
   1541 
   1542 TEST_CASE("CompositeSource_CorrelateToInstalledContainsManifestData", "[CompositeSource]")
   1543 {
   1544     CompositeTestSetup setup;
   1545     setup.Installed->SearchFunction = [&](const SearchRequest& request)
   1546     {
   1547         if (request.Purpose == SearchPurpose::CorrelationToInstalled)
   1548         {
   1549             bool expectedSearchFound = false;
   1550             for (const auto& inclusion : request.Inclusions)
   1551             {
   1552                 if (inclusion.Field == PackageMatchField::ProductCode && inclusion.Value == "hello")
   1553                 {
   1554                     expectedSearchFound = true;
   1555                     break;
   1556                 }
   1557             }
   1558 
   1559             REQUIRE(expectedSearchFound);
   1560         }
   1561 
   1562         SearchResult result;
   1563         return result;
   1564     };
   1565     setup.Available->SearchFunction = [&](const SearchRequest&)
   1566     {
   1567         SearchResult result;
   1568         result.Matches.emplace_back(setup.MakeAvailable().WithPC("hello"), Criteria());
   1569         return result;
   1570     };
   1571 
   1572     SearchRequest request;
   1573     request.Query = RequestMatch(MatchType::Exact, "NotForEverything");
   1574     SearchResult result = setup.Composite.Search(request);
   1575 }
   1576 
   1577 TEST_CASE("CompositeSource_Respects_FeatureFlag_ManifestMayContainAdditionalSystemReferenceStrings", "[CompositeSource]")
   1578 {
   1579     std::string id = "Special test ID";
   1580     std::string productCode1 = "product-code1";
   1581 
   1582     CompositeTestSetup setup;
   1583     bool productCodeSearched = false;
   1584     setup.Installed->SearchFunction = [&](const SearchRequest& request)
   1585         {
   1586             for (const auto& inclusion : request.Inclusions)
   1587             {
   1588                 if (inclusion.Field == PackageMatchField::ProductCode)
   1589                 {
   1590                     productCodeSearched = true;
   1591                 }
   1592             }
   1593 
   1594             return SearchResult{};
   1595         };
   1596     setup.Available->SearchFunction = [&](const SearchRequest&)
   1597         {
   1598             SearchResult result;
   1599             result.Matches.emplace_back(setup.MakeAvailable().WithId(id).WithPC(productCode1).HideSRS(), Criteria());
   1600             return result;
   1601         };
   1602 
   1603     SECTION("Feature false")
   1604     {
   1605         SearchRequest request;
   1606         request.Query = RequestMatch(MatchType::Exact, "NotForEverything");
   1607         SearchResult result = setup.Composite.Search(request);
   1608 
   1609         REQUIRE(!productCodeSearched);
   1610     }
   1611     SECTION("Feature true")
   1612     {
   1613         setup.Available->QueryFeatureFlagFunction = [](SourceFeatureFlag flag)
   1614             {
   1615                 return (flag == SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings);
   1616             };
   1617 
   1618         SearchRequest request;
   1619         request.Query = RequestMatch(MatchType::Exact, "NotForEverything");
   1620         SearchResult result = setup.Composite.Search(request);
   1621 
   1622         REQUIRE(productCodeSearched);
   1623     }
   1624 }
   1625 
   1626 TEST_CASE("CompositeSource_SxS_TwoVersions_NoAvailable", "[CompositeSource][SideBySide]")
   1627 {
   1628     std::string productCode1 = "PC1";
   1629     std::string productCode2 = "PC2";
   1630 
   1631     CompositeTestSetup setup;
   1632     auto availablePackage = setup.MakeAvailable();
   1633 
   1634     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("1.0").WithPC(productCode1), Criteria());
   1635     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("2.0").WithPC(productCode2), Criteria());
   1636 
   1637     SearchResult result = setup.Search();
   1638 
   1639     REQUIRE(result.Matches.size() == 2);
   1640 }
   1641 
   1642 TEST_CASE("CompositeSource_SxS_TwoVersions_DifferentAvailable", "[CompositeSource][SideBySide]")
   1643 {
   1644     std::string productCode1 = "PC1";
   1645     std::string productCode2 = "PC2";
   1646 
   1647     CompositeTestSetup setup;
   1648     auto availablePackage1 = setup.MakeAvailable().ToPackage();
   1649     auto availablePackage2 = setup.MakeAvailable().ToPackage();
   1650 
   1651     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("1.0").WithPC(productCode1), Criteria());
   1652     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("2.0").WithPC(productCode2), Criteria());
   1653 
   1654     setup.Available->SearchFunction = [&](const SearchRequest& request)
   1655         {
   1656             SearchResult result;
   1657 
   1658             std::string productCode;
   1659             for (const auto& item : request.Inclusions)
   1660             {
   1661                 if (item.Field == PackageMatchField::ProductCode)
   1662                 {
   1663                     productCode = item.Value;
   1664                     break;
   1665                 }
   1666             }
   1667 
   1668             if (productCode == productCode1)
   1669             {
   1670                 result.Matches.emplace_back(availablePackage1, Criteria());
   1671             }
   1672             else if (productCode == productCode1)
   1673             {
   1674                 result.Matches.emplace_back(availablePackage2, Criteria());
   1675             }
   1676 
   1677             return result;
   1678         };
   1679 
   1680     SearchResult result = setup.Search();
   1681 
   1682     REQUIRE(result.Matches.size() == 2);
   1683 }
   1684 
   1685 TEST_CASE("CompositeSource_SxS_TwoVersions_SameAvailable", "[CompositeSource][SideBySide]")
   1686 {
   1687     std::string version1 = "1.0";
   1688     std::string version2 = "2.0";
   1689     std::string productCode1 = "PC1";
   1690     std::string productCode2 = "PC2";
   1691 
   1692     CompositeTestSetup setup;
   1693     auto availablePackage = setup.MakeAvailable().ToPackage();
   1694 
   1695     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria());
   1696     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria());
   1697 
   1698     setup.Available->SearchFunction = [&](const SearchRequest&)
   1699         {
   1700             SearchResult result;
   1701             result.Matches.emplace_back(availablePackage, Criteria());
   1702             return result;
   1703         };
   1704 
   1705     SearchResult result = setup.Search();
   1706 
   1707     REQUIRE(result.Matches.size() == 1);
   1708     auto package = result.Matches[0].Package;
   1709     REQUIRE(package);
   1710     auto installedPackage = package->GetInstalled();
   1711     REQUIRE(installedPackage);
   1712     auto installedVersions = installedPackage->GetVersionKeys();
   1713     REQUIRE(installedVersions.size() == 2);
   1714     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; }));
   1715     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; }));
   1716     auto availablePackages = package->GetAvailable();
   1717     REQUIRE(availablePackages.size() == 1);
   1718     REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get()));
   1719 }
   1720 
   1721 TEST_CASE("CompositeSource_SxS_ThreeVersions_SameAvailable", "[CompositeSource][SideBySide]")
   1722 {
   1723     std::string version1 = "1.0";
   1724     std::string version2 = "2.0";
   1725     std::string version3 = "3.0";
   1726     std::string productCode1 = "PC1";
   1727     std::string productCode2 = "PC2";
   1728     std::string productCode3 = "PC3";
   1729 
   1730     CompositeTestSetup setup;
   1731     auto availablePackage = setup.MakeAvailable().ToPackage();
   1732 
   1733     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria());
   1734     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria());
   1735     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version3).WithPC(productCode3), Criteria());
   1736 
   1737     setup.Available->SearchFunction = [&](const SearchRequest&)
   1738         {
   1739             SearchResult result;
   1740             result.Matches.emplace_back(availablePackage, Criteria());
   1741             return result;
   1742         };
   1743 
   1744     SearchResult result = setup.Search();
   1745 
   1746     REQUIRE(result.Matches.size() == 1);
   1747     auto package = result.Matches[0].Package;
   1748     REQUIRE(package);
   1749     auto installedPackage = package->GetInstalled();
   1750     REQUIRE(installedPackage);
   1751     auto installedVersions = installedPackage->GetVersionKeys();
   1752     REQUIRE(installedVersions.size() == 3);
   1753     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; }));
   1754     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; }));
   1755     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version3; }));
   1756     auto availablePackages = package->GetAvailable();
   1757     REQUIRE(availablePackages.size() == 1);
   1758     REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get()));
   1759 }
   1760 
   1761 TEST_CASE("CompositeSource_SxS_TwoVersions_SameAvailable_Tracking", "[CompositeSource][SideBySide]")
   1762 {
   1763     std::string version1 = "1.0";
   1764     std::string version2 = "2.0";
   1765     std::string productCode1 = "PC1";
   1766     std::string productCode2 = "PC2";
   1767 
   1768     CompositeWithTrackingTestSetup setup;
   1769     auto installedPackage1 = setup.MakeInstalled().WithVersion(version1).WithPC(productCode1);
   1770     auto availablePackage = setup.MakeAvailable().ToPackage();
   1771 
   1772     setup.Installed->Everything.Matches.emplace_back(installedPackage1, Criteria());
   1773     setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria());
   1774     setup.Tracking->GetIndex().AddManifest(installedPackage1);
   1775 
   1776     setup.Available->SearchFunction = [&](const SearchRequest&)
   1777         {
   1778             SearchResult result;
   1779             result.Matches.emplace_back(availablePackage, Criteria());
   1780             return result;
   1781         };
   1782 
   1783     SearchResult result = setup.Search();
   1784 
   1785     REQUIRE(result.Matches.size() == 1);
   1786     auto package = result.Matches[0].Package;
   1787     REQUIRE(package);
   1788     auto installedPackage = package->GetInstalled();
   1789     REQUIRE(installedPackage);
   1790     auto installedVersions = installedPackage->GetVersionKeys();
   1791     REQUIRE(installedVersions.size() == 2);
   1792     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; }));
   1793     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; }));
   1794     auto availablePackages = package->GetAvailable();
   1795     REQUIRE(availablePackages.size() == 1);
   1796     REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get()));
   1797 }
   1798 
   1799 TEST_CASE("CompositeSource_SxS_Available_TwoVersions_SameAvailable", "[CompositeSource][SideBySide]")
   1800 {
   1801     std::string version1 = "1.0";
   1802     std::string version2 = "2.0";
   1803     std::string productCode1 = "PC1";
   1804     std::string productCode2 = "PC2";
   1805 
   1806     CompositeTestSetup setup;
   1807     auto availablePackage = setup.MakeAvailable().ToPackage();
   1808 
   1809     setup.Installed->SearchFunction = [&](const SearchRequest&)
   1810         {
   1811             SearchResult result;
   1812             result.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria());
   1813             result.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria());
   1814             return result;
   1815         };
   1816 
   1817     setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria());
   1818 
   1819     SearchResult result = setup.Search();
   1820 
   1821     REQUIRE(result.Matches.size() == 1);
   1822     auto package = result.Matches[0].Package;
   1823     REQUIRE(package);
   1824     auto installedPackage = package->GetInstalled();
   1825     REQUIRE(installedPackage);
   1826     auto installedVersions = installedPackage->GetVersionKeys();
   1827     REQUIRE(installedVersions.size() == 2);
   1828     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; }));
   1829     REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; }));
   1830     auto availablePackages = package->GetAvailable();
   1831     REQUIRE(availablePackages.size() == 1);
   1832     REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get()));
   1833 }