winget-cli

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

SQLiteIndex.cpp (162789B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "TestCommon.h"
      5 #include <winget/SQLiteWrapper.h>
      6 #include <PackageDependenciesValidation.h>
      7 #include <ArpVersionValidation.h>
      8 #include <Microsoft/SQLiteIndex.h>
      9 #include <winget/Manifest.h>
     10 #include <AppInstallerStrings.h>
     11 #include <winget/SQLiteMetadataTable.h>
     12 #include <winget/PackageVersionDataManifest.h>
     13 
     14 #include <Microsoft/Schema/1_0/IdTable.h>
     15 #include <Microsoft/Schema/1_0/NameTable.h>
     16 #include <Microsoft/Schema/1_0/MonikerTable.h>
     17 #include <Microsoft/Schema/1_0/VersionTable.h>
     18 #include <Microsoft/Schema/1_0/ChannelTable.h>
     19 #include <Microsoft/Schema/1_0/PathPartTable.h>
     20 #include <Microsoft/Schema/1_0/ManifestTable.h>
     21 #include <Microsoft/Schema/1_0/TagsTable.h>
     22 #include <Microsoft/Schema/1_0/CommandsTable.h>
     23 #include <Microsoft/Schema/1_0/SearchResultsTable.h>
     24 #include <Microsoft/Schema/1_4/DependenciesTable.h>
     25 #include <Microsoft/Schema/2_0/Interface.h>
     26 #include <Microsoft/Schema/2_0/PackageUpdateTrackingTable.h>
     27 
     28 using namespace std::string_literals;
     29 using namespace std::string_view_literals;
     30 using namespace TestCommon;
     31 using namespace AppInstaller::Manifest;
     32 using namespace AppInstaller::Repository;
     33 using namespace AppInstaller::Repository::Microsoft;
     34 using namespace AppInstaller::SQLite;
     35 using namespace AppInstaller::Utility;
     36 
     37 using UtilityVersion = AppInstaller::Utility::Version;
     38 using SQLiteVersion = AppInstaller::SQLite::Version;
     39 
     40 SQLiteIndex CreateTestIndex(const std::string& filePath, std::optional<SQLiteVersion> version = {})
     41 {
     42     // If no specific version requested, then use generator to run against the last 3 versions.
     43     if (!version)
     44     {
     45         SQLiteVersion latestVersion{ 2, 0 };
     46         SQLiteVersion versionMinus1 = SQLiteVersion{ 1, 7 };
     47         SQLiteVersion versionMinus2 = SQLiteVersion{ 1, 6 };
     48 
     49         version = GENERATE_COPY(SQLiteVersion{ versionMinus2 }, SQLiteVersion{ versionMinus1 }, SQLiteVersion{ latestVersion });
     50     }
     51 
     52     return SQLiteIndex::CreateNew(filePath, version.value());
     53 }
     54 
     55 SQLiteVersion TestPrepareForRead(SQLiteIndex& index)
     56 {
     57     SQLiteVersion latestVersion{ 2, 0 };
     58     SQLiteVersion versionMinus1 = SQLiteVersion{ 1, 7 };
     59     SQLiteVersion versionMinus2 = SQLiteVersion{ 1, 6 };
     60 
     61     index.PrepareForPackaging();
     62 
     63     if (index.GetVersion() == versionMinus2)
     64     {
     65         // Degenerate case where we don't need to do anything
     66     }
     67     else if (index.GetVersion() == versionMinus1)
     68     {
     69         SQLiteVersion version = GENERATE_COPY(SQLiteVersion{ versionMinus2 }, SQLiteVersion{ versionMinus1 });
     70 
     71         if (version != versionMinus1)
     72         {
     73             index.ForceVersion(version);
     74             return version;
     75         }
     76     }
     77     else if (index.GetVersion() == latestVersion)
     78     {
     79         // This crosses major versions, so leave it at 2.0 always
     80     }
     81 
     82     return index.GetVersion();
     83 }
     84 
     85 std::string GetPathFromManifest(Manifest& manifest)
     86 {
     87     auto publisher = manifest.Id;
     88     AppInstaller::Utility::FindAndReplace(publisher, ".", "/");
     89     
     90     return AppInstaller::Utility::ToLower(publisher).append("/").append(manifest.Version);
     91 }
     92 
     93 void CreateFakeManifest(Manifest& manifest, string_t publisher, string_t version = "1.0.0")
     94 {
     95     manifest.Installers.push_back({});
     96     manifest.Id = publisher.append(".").append("Id");
     97     manifest.DefaultLocalization.Add<Localization::PackageName>(publisher.append(" Name"));
     98     manifest.Moniker = "testmoniker";
     99     manifest.Version = version;
    100     manifest.Channel = "test";
    101     manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
    102     manifest.Installers[0].Commands = { "test1", "test2" };
    103 }
    104 
    105 SQLiteIndex SimpleTestSetup(const std::string& filePath, Manifest& manifest, std::optional<SQLiteVersion> version = {})
    106 {
    107     SQLiteIndex index = CreateTestIndex(filePath, version);
    108 
    109     string_t publisher = "Test";
    110     CreateFakeManifest(manifest, publisher);
    111 
    112     auto relativePath = GetPathFromManifest(manifest);
    113 
    114     index.AddManifest(manifest, relativePath);
    115 
    116     return index;
    117 }
    118 
    119 struct IndexFields
    120 {
    121     IndexFields(
    122         std::string id,
    123         std::string name,
    124         std::string moniker,
    125         std::string version,
    126         std::string channel,
    127         std::vector<NormalizedString> tags,
    128         std::vector<NormalizedString> commands,
    129         std::string path
    130     ) :
    131         Id(std::move(id)),
    132         Name(std::move(name)),
    133         Moniker(std::move(moniker)),
    134         Version(std::move(version)),
    135         Channel(std::move(channel)),
    136         Tags(std::move(tags)),
    137         Commands(std::move(commands)),
    138         Path(std::move(path))
    139     {}
    140 
    141     IndexFields(
    142         std::string id,
    143         std::string name,
    144         std::string moniker,
    145         std::string version,
    146         std::string channel,
    147         std::vector<NormalizedString> tags,
    148         std::vector<NormalizedString> commands,
    149         std::string path,
    150         std::vector<NormalizedString> packageFamilyNames,
    151         std::vector<NormalizedString> productCodes
    152     ) :
    153         Id(std::move(id)),
    154         Name(std::move(name)),
    155         Moniker(std::move(moniker)),
    156         Version(std::move(version)),
    157         Channel(std::move(channel)),
    158         Tags(std::move(tags)),
    159         Commands(std::move(commands)),
    160         Path(std::move(path)),
    161         PackageFamilyNames(std::move(packageFamilyNames)),
    162         ProductCodes(std::move(productCodes))
    163     {}
    164 
    165     IndexFields(
    166         std::string id,
    167         std::string name,
    168         std::string publisher,
    169         std::string moniker,
    170         std::string version,
    171         std::string channel,
    172         std::vector<NormalizedString> tags,
    173         std::vector<NormalizedString> commands,
    174         std::string path,
    175         std::vector<NormalizedString> packageFamilyNames,
    176         std::vector<NormalizedString> productCodes
    177     ) :
    178         Id(std::move(id)),
    179         Name(std::move(name)),
    180         Publisher(std::move(publisher)),
    181         Moniker(std::move(moniker)),
    182         Version(std::move(version)),
    183         Channel(std::move(channel)),
    184         Tags(std::move(tags)),
    185         Commands(std::move(commands)),
    186         Path(std::move(path)),
    187         PackageFamilyNames(std::move(packageFamilyNames)),
    188         ProductCodes(std::move(productCodes))
    189     {}
    190 
    191     IndexFields(
    192         std::string id,
    193         std::string name,
    194         std::string publisher,
    195         std::string moniker,
    196         std::string version,
    197         std::string channel,
    198         std::vector<NormalizedString> tags,
    199         std::vector<NormalizedString> commands,
    200         std::string path,
    201         std::vector<NormalizedString> packageFamilyNames,
    202         std::vector<NormalizedString> productCodes,
    203         std::string arpName,
    204         std::string arpPublisher
    205     ) :
    206         Id(std::move(id)),
    207         Name(std::move(name)),
    208         Publisher(std::move(publisher)),
    209         Moniker(std::move(moniker)),
    210         Version(std::move(version)),
    211         Channel(std::move(channel)),
    212         Tags(std::move(tags)),
    213         Commands(std::move(commands)),
    214         Path(std::move(path)),
    215         PackageFamilyNames(std::move(packageFamilyNames)),
    216         ProductCodes(std::move(productCodes)),
    217         ArpName(std::move(arpName)),
    218         ArpPublisher(std::move(arpPublisher))
    219     {}
    220 
    221     std::string Id;
    222     std::string Name;
    223     std::string Publisher;
    224     std::string Moniker;
    225     std::string Version;
    226     std::string Channel;
    227     std::vector<NormalizedString> Tags;
    228     std::vector<NormalizedString> Commands;
    229     std::string Path;
    230     std::vector<NormalizedString> PackageFamilyNames;
    231     std::vector<NormalizedString> ProductCodes;
    232     std::string ArpName;
    233     std::string ArpPublisher;
    234 };
    235 
    236 SQLiteIndex SearchTestSetup(const std::string& filePath, std::initializer_list<IndexFields> data = {}, std::optional<SQLiteVersion> version = {})
    237 {
    238     SQLiteIndex index = CreateTestIndex(filePath, version);
    239 
    240     Manifest manifest;
    241 
    242     auto addFunc = [&](const IndexFields& d)
    243     {
    244         manifest.Id = d.Id;
    245         manifest.DefaultLocalization.Add<Localization::PackageName>(d.Name);
    246         manifest.DefaultLocalization.Add<Localization::Publisher>(d.Publisher);
    247         manifest.Moniker = d.Moniker;
    248         manifest.Version = d.Version;
    249         manifest.DefaultLocalization.Add<Localization::Tags>(d.Tags);
    250 
    251         manifest.Installers.resize(std::max(d.PackageFamilyNames.size(), d.ProductCodes.size()));
    252 
    253         if (manifest.Installers.size() == 0)
    254         {
    255             manifest.Installers.push_back({});
    256         }
    257 
    258         manifest.Channel = d.Channel;
    259         manifest.Installers[0].Commands = d.Commands;
    260 
    261         for (size_t i = 0; i < d.PackageFamilyNames.size(); ++i)
    262         {
    263             manifest.Installers[i].PackageFamilyName = d.PackageFamilyNames[i];
    264         }
    265 
    266         for (size_t i = 0; i < d.ProductCodes.size(); ++i)
    267         {
    268             manifest.Installers[i].ProductCode = d.ProductCodes[i];
    269         }
    270 
    271         if (!d.ArpName.empty() || !d.ArpPublisher.empty())
    272         {
    273             manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
    274             manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName = d.ArpName;
    275             manifest.Installers[0].AppsAndFeaturesEntries[0].Publisher = d.ArpPublisher;
    276         }
    277 
    278         index.AddManifest(manifest, d.Path);
    279     };
    280 
    281     for (const auto& d : data)
    282     {
    283         addFunc(d);
    284     }
    285 
    286     return index;
    287 }
    288 
    289 bool ArePackageFamilyNameAndProductCodeSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion)
    290 {
    291     UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion);
    292     return (index.GetVersion() >= SQLiteVersion{ 1, 1 } && testVersion >= SQLiteVersion{ 1, 1 });
    293 }
    294 
    295 bool AreNormalizedNameAndPublisherSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion)
    296 {
    297     UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion);
    298     return (index.GetVersion() >= SQLiteVersion{ 1, 2 } && testVersion >= SQLiteVersion{ 1, 2 });
    299 }
    300 
    301 bool IsManifestMetadataSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion)
    302 {
    303     UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion);
    304     return (index.GetVersion() >= SQLiteVersion{ 1, 1 } && testVersion >= SQLiteVersion{ 1, 1 });
    305 }
    306 
    307 bool AreManifestHashesSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion)
    308 {
    309     UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion);
    310     return (index.GetVersion() >= SQLiteVersion{ 1, 3 } && testVersion >= SQLiteVersion{ 1, 3 } && index.GetVersion() < SQLiteVersion{ 2, 0 });
    311 }
    312 
    313 bool AreArpVersionsSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion)
    314 {
    315     UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion);
    316     return (index.GetVersion() >= SQLiteVersion{ 1, 5 } && testVersion >= SQLiteVersion{ 1, 5 });
    317 }
    318 
    319 bool AreArpVersionsNullable(const SQLiteIndex& index)
    320 {
    321     UNSCOPED_INFO("Index " << index.GetVersion());
    322     return (index.GetVersion() >= SQLiteVersion{ 2, 0 });
    323 }
    324 
    325 bool IsMapDataFoldingSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion)
    326 {
    327     UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion);
    328     return (index.GetVersion() >= SQLiteVersion{ 1, 7 } && testVersion >= SQLiteVersion{ 1, 7 });
    329 }
    330 
    331 bool IsMapDataFolded(const SQLiteIndex& index)
    332 {
    333     UNSCOPED_INFO("Index " << index.GetVersion());
    334     return (index.GetVersion() >= SQLiteVersion{ 1, 7 });
    335 }
    336 
    337 bool AreVersionKeysSupported(const SQLiteIndex& index)
    338 {
    339     UNSCOPED_INFO("Index " << index.GetVersion());
    340     return (index.GetVersion() < SQLiteVersion{ 2, 0 });
    341 }
    342 
    343 bool AreChannelsSupported(const SQLiteIndex& index)
    344 {
    345     UNSCOPED_INFO("Index " << index.GetVersion());
    346     return (index.GetVersion() < SQLiteVersion{ 2, 0 });
    347 }
    348 
    349 bool AreManifestPathsSupported(const SQLiteIndex& index)
    350 {
    351     UNSCOPED_INFO("Index " << index.GetVersion());
    352     return (index.GetVersion() < SQLiteVersion{ 2, 0 });
    353 }
    354 
    355 std::string GetPropertyStringByKey(const SQLiteIndex& index, rowid_t primaryId, PackageVersionProperty property)
    356 {
    357     auto result = index.GetPropertyByPrimaryId(primaryId, property);
    358     REQUIRE(result);
    359     return result.value();
    360 }
    361 
    362 std::string GetPropertyStringByKey(const SQLiteIndex& index, rowid_t id, PackageVersionProperty property, std::string_view version, std::string_view channel)
    363 {
    364     if (AreVersionKeysSupported(index))
    365     {
    366         auto manifestId = index.GetManifestIdByKey(id, version, channel);
    367         REQUIRE(manifestId);
    368         auto result = index.GetPropertyByPrimaryId(manifestId.value(), property);
    369         REQUIRE(result);
    370         return result.value();
    371     }
    372     else
    373     {
    374         return GetPropertyStringByKey(index, id, property);
    375     }
    376 }
    377 
    378 std::string GetPropertyStringById(const SQLiteIndex& index, rowid_t id, PackageVersionProperty property)
    379 {
    380     if (AreVersionKeysSupported(index))
    381     {
    382         auto versions = index.GetVersionKeysById(id);
    383         REQUIRE(!versions.empty());
    384         return GetPropertyStringByKey(index, versions[0].ManifestId, property);
    385     }
    386     else
    387     {
    388         return GetPropertyStringByKey(index, id, property);
    389     }
    390 }
    391 
    392 std::string GetIdStringById(const SQLiteIndex& index, rowid_t id)
    393 {
    394     return GetPropertyStringById(index, id, PackageVersionProperty::Id);
    395 }
    396 
    397 std::string GetNameStringById(const SQLiteIndex& index, rowid_t id)
    398 {
    399     return GetPropertyStringById(index, id, PackageVersionProperty::Name);
    400 }
    401 
    402 std::string GetPathStringByKey(const SQLiteIndex& index, rowid_t id, std::string_view version, std::string_view channel)
    403 {
    404     return GetPropertyStringByKey(index, id, PackageVersionProperty::RelativePath, version, channel);
    405 }
    406 
    407 TEST_CASE("SQLiteIndexCreateLatestAndReopen", "[sqliteindex]")
    408 {
    409     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    410     INFO("Using temporary file named: " << tempFile.GetPath());
    411 
    412     SQLiteVersion versionCreated;
    413 
    414     // Create the index
    415     {
    416         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, SQLiteVersion::Latest());
    417         versionCreated = index.GetVersion();
    418     }
    419 
    420     // Reopen the index for read only
    421     {
    422         INFO("Trying with Read");
    423         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read);
    424         SQLiteVersion versionRead = index.GetVersion();
    425         REQUIRE(versionRead == versionCreated);
    426     }
    427 
    428     // Reopen the index for read/write
    429     {
    430         INFO("Trying with ReadWrite");
    431         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
    432         SQLiteVersion versionRead = index.GetVersion();
    433         REQUIRE(versionRead == versionCreated);
    434     }
    435 
    436     // Reopen the index for immutable read
    437     {
    438         INFO("Trying with Immutable");
    439         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable);
    440         SQLiteVersion versionRead = index.GetVersion();
    441         REQUIRE(versionRead == versionCreated);
    442     }
    443 }
    444 
    445 TEST_CASE("SQLiteIndexCreateAndAddManifest", "[sqliteindex]")
    446 {
    447     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    448     INFO("Using temporary file named: " << tempFile.GetPath());
    449 
    450     Manifest manifest;
    451     std::string relativePath = "test/id/1.0.0.yaml";
    452 
    453     SQLiteIndex index = SimpleTestSetup(tempFile, manifest, SQLiteVersion::Latest());
    454 }
    455 
    456 TEST_CASE("SQLiteIndexCreateAndAddManifestFile", "[sqliteindex]")
    457 {
    458     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    459     INFO("Using temporary file named: " << tempFile.GetPath());
    460 
    461     SQLiteIndex index = CreateTestIndex(tempFile);
    462 
    463     TestDataFile manifestFile{ "Manifest-Good.yaml" };
    464     std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" };
    465 
    466     index.AddManifest(manifestFile, manifestPath);
    467 }
    468 
    469 TEST_CASE("SQLiteIndexCreateAndAddManifestDuplicate", "[sqliteindex]")
    470 {
    471     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    472     INFO("Using temporary file named: " << tempFile.GetPath());
    473 
    474     Manifest manifest;
    475 
    476     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
    477     auto relativePath = GetPathFromManifest(manifest);
    478 
    479     // Attempting to add the same manifest at a different path should fail.
    480     REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
    481 
    482     // Attempting to add the same manifest with a differently cased Id at a different path should fail.
    483     manifest.Id = ToLower(manifest.Id);
    484     REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
    485 
    486     // Attempting to add a different manifest at the same path should fail.
    487     manifest.Id += "-new";
    488     REQUIRE_THROWS_HR(index.AddManifest(manifest, relativePath), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
    489 }
    490 
    491 TEST_CASE("SQLiteIndex_VersionReferencedByDependenciesClearsUnusedVersionAndKeepUsedVersion", "[sqliteindex][V1_4]")
    492 {
    493     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    494     INFO("Using temporary file named: " << tempFile.GetPath());
    495 
    496     Manifest dependencyManifest1, dependencyManifest2, manifest, isolatedManifest;
    497     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
    498 
    499     auto& publisher2 = "Test2";
    500     CreateFakeManifest(dependencyManifest2, publisher2);
    501     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
    502 
    503     auto& publisher3 = "Test3";
    504     CreateFakeManifest(manifest, publisher3);
    505     std::string dependencyOnlyVersion = "0.0.5";
    506     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
    507     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, dependencyOnlyVersion));
    508 
    509     index.AddManifest(manifest, GetPathFromManifest(manifest));
    510     
    511     // Create a new manifest that depends on v0.0.5
    512     auto& publisher4 = "Test4";
    513     CreateFakeManifest(isolatedManifest, publisher4);
    514     isolatedManifest.Version = dependencyOnlyVersion;
    515     index.AddManifest(isolatedManifest);
    516 
    517     index.RemoveManifest(isolatedManifest);
    518     // After deletion that the version(v0.0.5) must be present because it still referenced via dependencies table.
    519     {
    520         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
    521         REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, dependencyOnlyVersion).has_value());
    522     }
    523 
    524     index.RemoveManifest(manifest);
    525     // Now, that we've deleted the manifest depending on version(v0.0.5), it should be absent.
    526     {
    527         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
    528         REQUIRE(!Schema::V1_0::VersionTable::SelectIdByValue(connection, dependencyOnlyVersion).has_value());
    529     }
    530     index.RemoveManifest(dependencyManifest1);
    531     index.RemoveManifest(dependencyManifest2);
    532     
    533     // Final sanity check, nothing should be in the version table.
    534     {
    535         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
    536         REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
    537     }
    538 }
    539 
    540 TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies", "[sqliteindex][V1_4]")
    541 {
    542     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    543     INFO("Using temporary file named: " << tempFile.GetPath());
    544 
    545     Manifest dependencyManifest1, dependencyManifest2, manifest;
    546     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
    547 
    548     auto& publisher2 = "Test2";
    549     CreateFakeManifest(dependencyManifest2, publisher2);
    550     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
    551 
    552     auto& publisher3 = "Test3";
    553     CreateFakeManifest(manifest, publisher3);
    554     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
    555     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0"));
    556 
    557     index.AddManifest(manifest, GetPathFromManifest(manifest));
    558     index.UpdateManifest(manifest, GetPathFromManifest(manifest));
    559     index.RemoveManifest(manifest);
    560 }
    561 
    562 TEST_CASE("SQLiteIndex_AddManifestWithDependencies_MissingPackage", "[sqliteindex][V1_4]")
    563 {
    564     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    565     INFO("Using temporary file named: " << tempFile.GetPath());
    566 
    567     Manifest dependencyManifest1, dependencyManifest2, manifest;
    568     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
    569 
    570     // Publisher2 is not present
    571     auto& publisher2 = "Test2";
    572     CreateFakeManifest(dependencyManifest2, publisher2);
    573 
    574     auto& publisher3 = "Test3";
    575     CreateFakeManifest(manifest, publisher3);
    576     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
    577     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0"));
    578 
    579     REQUIRE_THROWS_HR(index.AddManifest(manifest, GetPathFromManifest(manifest)), APPINSTALLER_CLI_ERROR_MISSING_PACKAGE);
    580 }
    581 
    582 TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies_MissingVersion", "[sqliteindex][V1_4]")
    583 {
    584     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    585     INFO("Using temporary file named: " << tempFile.GetPath());
    586 
    587     Manifest dependencyManifest1, dependencyManifest2, manifest;
    588     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
    589 
    590     auto& publisher2 = "Test2";
    591     CreateFakeManifest(dependencyManifest2, publisher2);
    592     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
    593 
    594     auto& publisher3 = "Test3";
    595     CreateFakeManifest(manifest, publisher3);
    596     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "0.0.1"));
    597     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "0.0.2"));
    598 
    599     index.AddManifest(manifest, GetPathFromManifest(manifest));
    600     index.UpdateManifest(manifest, GetPathFromManifest(manifest));
    601     index.RemoveManifest(manifest);
    602 }
    603 
    604 TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies_EmptyManifestVersion", "[sqliteindex][V1_4]")
    605 {
    606     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    607     INFO("Using temporary file named: " << tempFile.GetPath());
    608 
    609     Manifest dependencyManifest1, dependencyManifest2, manifest;
    610     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
    611 
    612     auto& publisher2 = "Test2";
    613     CreateFakeManifest(dependencyManifest2, publisher2);
    614     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
    615 
    616     auto& publisher3 = "Test3";
    617     CreateFakeManifest(manifest, publisher3);
    618     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id));
    619     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id));
    620 
    621     index.AddManifest(manifest, GetPathFromManifest(manifest));
    622     index.UpdateManifest(manifest, GetPathFromManifest(manifest));
    623     index.RemoveManifest(manifest);
    624 }
    625 
    626 TEST_CASE("SQLiteIndex_DependenciesTable_CheckConsistency", "[sqliteindex][V1_4]")
    627 {
    628     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    629     INFO("Using temporary file named: " << tempFile.GetPath());
    630     
    631     {
    632         Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest;
    633         SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    634 
    635         constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    636         CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    637 
    638         levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    639         index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    640 
    641         constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    642         CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    643         levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    644         index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
    645 
    646         constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    647         CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    648         topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
    649     }
    650 
    651     {
    652         // Open it directly to modify the table
    653         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
    654         rowid_t nonExistentRowId = 40;
    655         rowid_t nonExistentManifest = 41;
    656         rowid_t nonExistentVersion = 42;
    657         rowid_t nonExistentPackageId = 43;
    658 
    659         Builder::StatementBuilder builder;
    660         builder.InsertInto(Schema::V1_4::DependenciesTable::TableName())
    661             .Values(nonExistentRowId, nonExistentManifest, nonExistentVersion, nonExistentPackageId);
    662         builder.Execute(connection);
    663     }
    664 
    665     {
    666         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
    667 
    668         REQUIRE(!index.CheckConsistency(true));
    669     }
    670 
    671     TempFile tempFile2{ "repolibtest_tempdb"s, ".db"s };
    672     INFO("Using temporary file named: " << tempFile2.GetPath());
    673 
    674     {
    675         SQLiteIndex index = CreateTestIndex(tempFile2, SQLiteVersion::Latest());
    676 
    677         Manifest manifest;
    678         manifest.Id = "Foo";
    679         manifest.Version = "10.0";
    680 
    681         index.AddManifest(manifest, "path");
    682 
    683         REQUIRE(index.CheckConsistency(true));
    684 
    685         // Add dependency that does not require min version
    686         Manifest manifestWithDependency1;
    687         manifestWithDependency1.Id = "Bar1";
    688         manifestWithDependency1.Version = "10.0";
    689         manifestWithDependency1.Installers.push_back({});
    690         manifestWithDependency1.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id));
    691 
    692         index.AddManifest(manifestWithDependency1, "path1");
    693 
    694         REQUIRE(index.CheckConsistency(true));
    695 
    696         // Add dependency with min version satisfied
    697         Manifest manifestWithDependency2;
    698         manifestWithDependency2.Id = "Bar2";
    699         manifestWithDependency2.Version = "10.0";
    700         manifestWithDependency2.Installers.push_back({});
    701         manifestWithDependency2.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id, "1.0"));
    702 
    703         index.AddManifest(manifestWithDependency2, "path2");
    704 
    705         REQUIRE(index.CheckConsistency(true));
    706 
    707         // Add dependency with min version not satisfied
    708         Manifest manifestWithDependency3;
    709         manifestWithDependency3.Id = "Bar3";
    710         manifestWithDependency3.Version = "10.0";
    711         manifestWithDependency3.Installers.push_back({});
    712         manifestWithDependency3.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id, "11.0"));
    713 
    714         index.AddManifest(manifestWithDependency3, "path3");
    715 
    716         REQUIRE_FALSE(index.CheckConsistency(true));
    717     }
    718 }
    719 
    720 TEST_CASE("SQLiteIndex_RemoveManifestFile_NotPresent", "[sqliteindex]")
    721 {
    722     SQLiteIndex index = CreateTestIndex(SQLITE_MEMORY_DB_CONNECTION_TARGET);
    723 
    724     TestDataFile manifestFile{ "Manifest-Good.yaml" };
    725     std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" };
    726 
    727     REQUIRE_THROWS_HR(index.RemoveManifest(manifestFile, manifestPath), E_NOT_SET);
    728 }
    729 
    730 TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]")
    731 {
    732     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    733     INFO("Using temporary file named: " << tempFile.GetPath());
    734 
    735     std::string manifest1Path = "test/id/test.id-1.0.0.yaml";
    736     Manifest manifest1;
    737     manifest1.Installers.push_back({});
    738     manifest1.Id = "test.id";
    739     manifest1.DefaultLocalization.Add<Localization::PackageName>("Test Name");
    740     manifest1.Moniker = "testmoniker";
    741     manifest1.Version = "1.0.0";
    742     manifest1.Channel = "test";
    743     manifest1.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
    744     manifest1.Installers[0].Commands = { "test1", "test2" };
    745 
    746     std::string manifest2Path = "test/woah/test.id-1.0.0.yaml";
    747     Manifest manifest2;
    748     manifest2.Installers.push_back({});
    749     manifest2.Id = "test.woah";
    750     manifest2.DefaultLocalization.Add<Localization::PackageName>("Test Name WOAH");
    751     manifest2.Moniker = "testmoniker";
    752     manifest2.Version = "1.0.0";
    753     manifest2.Channel = "test";
    754     manifest2.DefaultLocalization.Add<Localization::Tags>({});
    755     manifest2.Installers[0].Commands = { "test1", "test2", "test3" };
    756 
    757     {
    758         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
    759 
    760         index.AddManifest(manifest1, manifest1Path);
    761         index.AddManifest(manifest2, manifest2Path);
    762 
    763         // Now remove manifest1
    764         index.RemoveManifest(manifest1, manifest1Path);
    765     }
    766 
    767     {
    768         // Open it directly to directly test table state
    769         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
    770 
    771         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
    772         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
    773         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
    774         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
    775         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
    776         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
    777         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
    778         // Because manifest2 had no tags
    779         REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
    780         REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection));
    781     }
    782 
    783     {
    784         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
    785 
    786         // Now remove manifest2
    787         index.RemoveManifest(manifest2, manifest2Path);
    788     }
    789 
    790     // Open it directly to directly test table state
    791     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
    792 
    793     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
    794     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
    795     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
    796     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
    797     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
    798     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
    799     REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection));
    800     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
    801     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
    802 }
    803 
    804 TEST_CASE("SQLiteIndex_RemoveManifestWithDependencies", "[sqliteindex][V1_4]")
    805 {
    806     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    807     INFO("Using temporary file named: " << tempFile.GetPath());
    808 
    809     Manifest dependencyManifest1, dependencyManifest2, manifest;
    810     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
    811 
    812     auto& publisher2 = "Test2";
    813     CreateFakeManifest(dependencyManifest2, publisher2);
    814     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
    815 
    816     auto& publisher3 = "Test3";
    817     CreateFakeManifest(manifest, publisher3);
    818     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
    819     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0"));
    820 
    821     index.AddManifest(manifest, GetPathFromManifest(manifest));
    822 
    823     index.RemoveManifest(manifest, GetPathFromManifest(manifest));
    824 }
    825 
    826 TEST_CASE("SQLiteIndex_ValidateManifestWithDependencies", "[sqliteindex][V1_4]")
    827 {
    828     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    829     INFO("Using temporary file named: " << tempFile.GetPath());
    830 
    831     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest;
    832     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    833 
    834     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    835     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    836 
    837     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    838     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    839 
    840     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    841     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    842     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    843     index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
    844 
    845     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    846     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    847     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
    848     REQUIRE(PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest));
    849 }
    850 
    851 TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesHasLoops", "[sqliteindex][V1_4]")
    852 {
    853     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    854     INFO("Using temporary file named: " << tempFile.GetPath());
    855 
    856     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest;
    857     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    858 
    859     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    860     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    861 
    862     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    863     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    864 
    865     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    866     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    867     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    868     index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
    869 
    870     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    871     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    872     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
    873     index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest));
    874 
    875     levelThreeManifest.Installers.push_back(ManifestInstaller{});
    876     levelThreeManifest.Installers[1].Dependencies.Add(Dependency(DependencyType::Package, topLevelManifest.Id, "1.0.0"));
    877     REQUIRE_THROWS_HR(
    878         PackageDependenciesValidation::ValidateManifestDependencies(&index, levelThreeManifest),
    879         APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED);
    880 }
    881 
    882 TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesMissingNode", "[sqliteindex][V1_4]")
    883 {
    884     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    885     INFO("Using temporary file named: " << tempFile.GetPath());
    886 
    887     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest;
    888     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    889 
    890     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    891     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    892 
    893     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    894     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    895 
    896     // This node is missing, because it's not in the index.
    897     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    898     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    899     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    900 
    901     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    902     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    903     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
    904     REQUIRE_THROWS_HR(
    905         PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest),
    906         APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED);
    907 }
    908 
    909 TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesNoSuitableMinVersion", "[sqliteindex][V1_4]")
    910 {
    911     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    912     INFO("Using temporary file named: " << tempFile.GetPath());
    913 
    914     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest;
    915     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    916 
    917     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    918     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    919 
    920     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    921     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    922 
    923     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    924     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    925     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    926     index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
    927 
    928     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    929     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    930     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "2.0.0"));
    931 
    932     REQUIRE_THROWS_HR(
    933         PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest),
    934         APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED);
    935 }
    936 
    937 TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureBroken", "[sqliteindex][V1_4]")
    938 {
    939     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    940     INFO("Using temporary file named: " << tempFile.GetPath());
    941 
    942     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest;
    943     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    944 
    945     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    946     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    947 
    948     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    949     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    950 
    951     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    952     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    953     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    954     index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
    955 
    956     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    957     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    958     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
    959     index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest));
    960 
    961     REQUIRE_THROWS_HR(
    962         PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifest),
    963         APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED);
    964 }
    965 
    966 TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureNotBroken", "[sqliteindex][V1_4]")
    967 {
    968     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
    969     INFO("Using temporary file named: " << tempFile.GetPath());
    970 
    971     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest, levelThreeManifestV2;
    972     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
    973 
    974     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
    975     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
    976 
    977     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0"));
    978     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
    979 
    980     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
    981     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
    982     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
    983     index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
    984 
    985     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
    986     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
    987     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
    988     index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest));
    989 
    990     constexpr std::string_view levelThreeManifestV2Publisher = "Test";
    991     CreateFakeManifest(levelThreeManifestV2, levelThreeManifestV2Publisher, "2.0.0");
    992     index.AddManifest(levelThreeManifestV2, GetPathFromManifest(levelThreeManifestV2));
    993 
    994     REQUIRE(PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifest));
    995 }
    996 
    997 TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureBroken_NoSuitableOldManifest", "[sqliteindex][V1_4]")
    998 {
    999     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1000     INFO("Using temporary file named: " << tempFile.GetPath());
   1001 
   1002     Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest, levelThreeManifestV2;
   1003     SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest());
   1004 
   1005     constexpr std::string_view levelThreeManifestV2Publisher = "Test";
   1006     CreateFakeManifest(levelThreeManifestV2, levelThreeManifestV2Publisher, "2.0.0");
   1007     index.AddManifest(levelThreeManifestV2, GetPathFromManifest(levelThreeManifestV2));
   1008 
   1009     constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest";
   1010     CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher);
   1011 
   1012     levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "2.0.0"));
   1013     index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest));
   1014 
   1015     constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest";
   1016     CreateFakeManifest(levelOneManifest, levelOneManifestPublisher);
   1017     levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0"));
   1018     index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest));
   1019 
   1020     constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest";
   1021     CreateFakeManifest(topLevelManifest, topLevelManifestPublisher);
   1022     topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0"));
   1023     index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); 
   1024 
   1025     REQUIRE_THROWS(
   1026         PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifestV2),
   1027         APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED);
   1028 }
   1029 
   1030 TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex][V1_7]")
   1031 {
   1032     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1033     INFO("Using temporary file named: " << tempFile.GetPath());
   1034 
   1035     std::string manifest1Path = "test/id/test.id-1.0.0.yaml";
   1036     Manifest manifest1;
   1037     manifest1.Installers.push_back({});
   1038     manifest1.Id = "test.id";
   1039     manifest1.DefaultLocalization.Add<Localization::PackageName>("Test Name");
   1040     manifest1.Moniker = "testmoniker";
   1041     manifest1.Version = "1.0.0";
   1042     manifest1.Channel = "test";
   1043     manifest1.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   1044     manifest1.Installers[0].Commands = { "test1", "test2" };
   1045 
   1046     std::string manifest2Path = "test/woah/test.id-1.0.0.yaml";
   1047     Manifest manifest2;
   1048     manifest2.Installers.push_back({});
   1049     manifest2.Id = "test.woah";
   1050     manifest2.DefaultLocalization.Add<Localization::PackageName>("Test Name WOAH");
   1051     manifest2.Moniker = "testmoniker";
   1052     manifest2.Version = "1.0.0";
   1053     manifest2.Channel = "test";
   1054     manifest2.DefaultLocalization.Add<Localization::Tags>({});
   1055     manifest2.Installers[0].Commands = { "test1", "test2", "test3" };
   1056 
   1057     SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion{ 1, 7 });
   1058 
   1059     index.AddManifest(manifest1, manifest1Path);
   1060     index.AddManifest(manifest2, manifest2Path);
   1061 
   1062     // Get the second manifest's id for validating consistency
   1063     SearchRequest request;
   1064     request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest2.Id));
   1065     auto result = index.Search(request);
   1066 
   1067     REQUIRE(result.Matches.size() == 1);
   1068     auto manifest2IdRowId = result.Matches[0].first;
   1069 
   1070     auto rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {});
   1071     REQUIRE(rowId);
   1072     auto manifest2RowId = rowId.value();
   1073 
   1074     // Now remove manifest1 and prepare
   1075     index.RemoveManifest(manifest1, manifest1Path);
   1076     index.PrepareForPackaging();
   1077 
   1078     // Checking consistency will also uncover issues, but not potentially the same ones as below.
   1079     REQUIRE(index.CheckConsistency(true));
   1080 
   1081     // Repeat search to ensure consistent ids
   1082     result = index.Search(request);
   1083     REQUIRE(result.Matches.size() == 1);
   1084     REQUIRE(result.Matches[0].first == manifest2IdRowId);
   1085 
   1086     rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {});
   1087     REQUIRE(rowId);
   1088     REQUIRE(rowId.value() == manifest2RowId);
   1089 
   1090     REQUIRE(manifest2.Id == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Id));
   1091     REQUIRE(manifest2.DefaultLocalization.Get<Localization::PackageName>() == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Name));
   1092     REQUIRE(manifest2.Moniker == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Moniker));
   1093     REQUIRE(manifest2.Version == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Version));
   1094     REQUIRE(manifest2.Channel == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Channel));
   1095     REQUIRE(manifest2Path == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::RelativePath));
   1096 }
   1097 
   1098 TEST_CASE("SQLiteIndex_RemoveManifestFile", "[sqliteindex][V1_0]")
   1099 {
   1100     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1101     INFO("Using temporary file named: " << tempFile.GetPath());
   1102 
   1103     {
   1104         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
   1105 
   1106         TestDataFile manifestFile{ "Manifest-Good.yaml" };
   1107         std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" };
   1108 
   1109         index.AddManifest(manifestFile, manifestPath);
   1110 
   1111         // Now remove that manifest
   1112         index.RemoveManifest(manifestFile, manifestPath);
   1113     }
   1114 
   1115     // Open it directly to directly test table state
   1116     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1117 
   1118     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
   1119     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
   1120     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
   1121     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
   1122     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
   1123     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
   1124     REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection));
   1125     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
   1126     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1127 }
   1128 
   1129 TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_4]")
   1130 {
   1131     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1132     INFO("Using temporary file named: " << tempFile.GetPath());
   1133 
   1134     std::string manifestPath = "test/id/test.id-1.0.0.yaml";
   1135     Manifest manifest;
   1136     manifest.Installers.push_back({});
   1137     manifest.Id = "test.id";
   1138     manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name");
   1139     manifest.Moniker = "testmoniker";
   1140     manifest.Version = "1.0.0";
   1141     manifest.Channel = "test";
   1142     manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   1143     manifest.Installers[0].Commands = { "test1", "test2" };
   1144 
   1145     
   1146     {
   1147         auto version = GENERATE(SQLiteVersion{ 1, 0 }, SQLiteVersion::Latest());
   1148         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, version);
   1149 
   1150         index.AddManifest(manifest, manifestPath);
   1151     }
   1152 
   1153     {
   1154         // Open it directly to directly test table state
   1155         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1156 
   1157         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
   1158         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
   1159         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
   1160         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
   1161         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
   1162         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
   1163         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
   1164         REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection));
   1165         REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection));
   1166     }
   1167 
   1168     {
   1169         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1170 
   1171         // Update with no updates should return false
   1172         REQUIRE(!index.UpdateManifest(manifest, manifestPath));
   1173 
   1174         manifest.DefaultLocalization.Add<Localization::Description>("description2");
   1175 
   1176         // Update with no indexed updates should return false
   1177         REQUIRE(!index.UpdateManifest(manifest, manifestPath));
   1178 
   1179         // Update with indexed changes
   1180         manifest.DefaultLocalization.Add<Localization::PackageName>("Test Name2");
   1181         manifest.Moniker = "testmoniker2";
   1182         manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2", "t3" });
   1183         manifest.Installers[0].Commands = {};
   1184 
   1185         REQUIRE(index.UpdateManifest(manifest, manifestPath));
   1186     }
   1187 
   1188     {
   1189         // Open it directly to directly test table state
   1190         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1191 
   1192         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
   1193         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
   1194         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
   1195         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
   1196         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
   1197         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
   1198         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
   1199         REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection));
   1200         // The update removed all commands
   1201         REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1202     }
   1203 
   1204     {
   1205         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1206 
   1207         // Now remove manifest2
   1208         index.RemoveManifest(manifest, manifestPath);
   1209     }
   1210 
   1211     // Open it directly to directly test table state
   1212     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1213 
   1214     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
   1215     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
   1216     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
   1217     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
   1218     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
   1219     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
   1220     REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection));
   1221     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
   1222     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1223 }
   1224 
   1225 TEST_CASE("SQLiteIndex_UpdateManifestWithDependencies", "[sqliteindex][V1_4]")
   1226 {
   1227     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1228     INFO("Using temporary file named: " << tempFile.GetPath());
   1229 
   1230     Manifest dependencyManifest1, dependencyManifest2, manifest, updateManifest;
   1231     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
   1232 
   1233     auto& publisher2 = "Test2";
   1234     CreateFakeManifest(dependencyManifest2, publisher2);
   1235     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
   1236 
   1237     auto& publisher3 = "Test3";
   1238     CreateFakeManifest(manifest, publisher3);
   1239     const std::string dependencyPath3 = GetPathFromManifest(manifest);
   1240 
   1241     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
   1242     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0"));
   1243 
   1244     index.AddManifest(manifest, dependencyPath3);
   1245 
   1246     auto& publisher4 = "Test4";
   1247     CreateFakeManifest(updateManifest, publisher4);
   1248     index.AddManifest(updateManifest, GetPathFromManifest(updateManifest));
   1249     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, updateManifest.Id, "1.0.0"));
   1250 
   1251     REQUIRE(index.UpdateManifest(manifest, dependencyPath3));
   1252 }
   1253 
   1254 TEST_CASE("SQLiteIndex_UpdateManifestWithDependenciesDeleteAndAdd", "[sqliteindex][V1_4]")
   1255 {
   1256     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1257     INFO("Using temporary file named: " << tempFile.GetPath());
   1258 
   1259     Manifest dependencyManifest1, dependencyManifest2, manifest, updateManifest;
   1260     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
   1261 
   1262     auto& publisher2 = "Test2";
   1263     CreateFakeManifest(dependencyManifest2, publisher2);
   1264     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
   1265 
   1266     auto& publisher3 = "Test3";
   1267     CreateFakeManifest(manifest, publisher3);
   1268     const std::string dependencyPath3 = GetPathFromManifest(manifest);
   1269 
   1270     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
   1271     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0"));
   1272 
   1273     index.AddManifest(manifest, dependencyPath3);
   1274 
   1275     manifest.Installers[0].Dependencies.Clear();
   1276 
   1277     auto& publisher4 = "Test4";
   1278     CreateFakeManifest(updateManifest, publisher4);
   1279     index.AddManifest(updateManifest, GetPathFromManifest(updateManifest));
   1280     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, updateManifest.Id, "1.0.0"));
   1281 
   1282     REQUIRE(index.UpdateManifest(manifest, dependencyPath3));
   1283 }
   1284 
   1285 TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]")
   1286 {
   1287     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1288     INFO("Using temporary file named: " << tempFile.GetPath());
   1289 
   1290     std::string manifestPath = "test/id/test.id-1.0.0.yaml";
   1291     Manifest manifest;
   1292     manifest.Installers.push_back({});
   1293     manifest.Id = "test.id";
   1294     manifest.DefaultLocalization.Add<Localization::PackageName>("Test Name");
   1295     manifest.Moniker = "testmoniker";
   1296     manifest.Version = "1.0.0";
   1297     manifest.Channel = "test";
   1298     manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   1299     manifest.Installers[0].Commands = { "test1", "test2" };
   1300 
   1301     {
   1302         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
   1303 
   1304         index.AddManifest(manifest, manifestPath);
   1305     }
   1306 
   1307     {
   1308         // Open it directly to directly test table state
   1309         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1310 
   1311         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
   1312         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
   1313         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
   1314         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
   1315         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
   1316         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
   1317         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
   1318         REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection));
   1319         REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection));
   1320     }
   1321 
   1322     {
   1323         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1324 
   1325         manifestPath = "test/newid/test.newid-1.0.0.yaml";
   1326 
   1327         // Update with path update should indicate change
   1328         REQUIRE(index.UpdateManifest(manifest, manifestPath));
   1329     }
   1330 
   1331     {
   1332         // Open it directly to directly test table state
   1333         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1334 
   1335         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
   1336         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
   1337         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
   1338         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
   1339         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
   1340         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
   1341         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
   1342         REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection));
   1343         REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection));
   1344     }
   1345 
   1346     {
   1347         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1348 
   1349         // Now remove manifest, with unknown path
   1350         index.RemoveManifest(manifest, "");
   1351     }
   1352 
   1353     // Open it directly to directly test table state
   1354     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1355 
   1356     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
   1357     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
   1358     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
   1359     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
   1360     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
   1361     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
   1362     REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection));
   1363     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
   1364     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1365 }
   1366 
   1367 TEST_CASE("SQLiteIndex_UpdateManifest_Pathless", "[sqliteindex][V1_0]")
   1368 {
   1369     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1370     INFO("Using temporary file named: " << tempFile.GetPath());
   1371 
   1372     Manifest manifest;
   1373     manifest.Installers.push_back({});
   1374     manifest.Id = "test.id";
   1375     manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name");
   1376     manifest.Moniker = "testmoniker";
   1377     manifest.Version = "1.0.0";
   1378     manifest.Channel = "test";
   1379     manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   1380     manifest.Installers[0].Commands = { "test1", "test2" };
   1381 
   1382     {
   1383         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
   1384 
   1385         index.AddManifest(manifest);
   1386     }
   1387 
   1388     {
   1389         // Open it directly to directly test table state
   1390         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1391 
   1392         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
   1393         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
   1394         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
   1395         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
   1396         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
   1397         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
   1398         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
   1399         REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection));
   1400         REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection));
   1401     }
   1402 
   1403     {
   1404         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1405 
   1406         // Update with no updates should return false
   1407         REQUIRE(!index.UpdateManifest(manifest));
   1408 
   1409         manifest.DefaultLocalization.Add<Localization::Description>("description2");
   1410 
   1411         // Update with no indexed updates should return false
   1412         REQUIRE(!index.UpdateManifest(manifest));
   1413 
   1414         // Update with indexed changes
   1415         manifest.DefaultLocalization.Add<Localization::PackageName>("Test Name2");
   1416         manifest.Moniker = "testmoniker2";
   1417         manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2", "t3" });
   1418         manifest.Installers[0].Commands = {};
   1419 
   1420         REQUIRE(index.UpdateManifest(manifest));
   1421     }
   1422 
   1423     {
   1424         // Open it directly to directly test table state
   1425         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1426 
   1427         REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection));
   1428         REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection));
   1429         REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection));
   1430         REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection));
   1431         REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection));
   1432         REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection));
   1433         REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection));
   1434         REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection));
   1435         // The update removed all commands
   1436         REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1437     }
   1438 
   1439     {
   1440         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1441 
   1442         // Now remove manifest2
   1443         index.RemoveManifest(manifest);
   1444     }
   1445 
   1446     // Open it directly to directly test table state
   1447     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1448 
   1449     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
   1450     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
   1451     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
   1452     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
   1453     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
   1454     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
   1455     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
   1456     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1457 }
   1458 
   1459 TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]")
   1460 {
   1461     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1462     INFO("Using temporary file named: " << tempFile.GetPath());
   1463 
   1464     std::string manifestPath = "test/id/test.id-1.0.0.yaml";
   1465     Manifest manifest;
   1466     manifest.Installers.push_back({});
   1467     manifest.Id = "test.id";
   1468     manifest.DefaultLocalization.Add<Localization::PackageName>("Test Name");
   1469     manifest.Moniker = "testmoniker";
   1470     manifest.Version = "1.0.0-test";
   1471     manifest.Channel = "test";
   1472     manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   1473     manifest.Installers[0].Commands = { "test1", "test2" };
   1474 
   1475     {
   1476         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
   1477 
   1478         index.AddManifest(manifest, manifestPath);
   1479     }
   1480 
   1481     {
   1482         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1483 
   1484         manifest.Id = "Test.Id";
   1485 
   1486         // Update with path update should indicate change
   1487         REQUIRE(index.UpdateManifest(manifest, manifestPath));
   1488     }
   1489 
   1490     {
   1491         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1492 
   1493         manifest.Version = "1.0.0-Test";
   1494 
   1495         // Update with path update should indicate change
   1496         REQUIRE(index.UpdateManifest(manifest, manifestPath));
   1497     }
   1498 
   1499     {
   1500         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1501 
   1502         manifest.Channel = "Test";
   1503 
   1504         // Update with path update should indicate change
   1505         REQUIRE(index.UpdateManifest(manifest, manifestPath));
   1506     }
   1507 
   1508     {
   1509         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1510 
   1511         manifest.DefaultLocalization.Add<Localization::PackageName>("test name");
   1512 
   1513         // Update with path update should indicate change
   1514         REQUIRE(index.UpdateManifest(manifest, manifestPath));
   1515     }
   1516 
   1517     {
   1518         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1519 
   1520         // Now remove manifest, with unknown path
   1521         index.RemoveManifest(manifest, "");
   1522     }
   1523 
   1524     // Open it directly to directly test table state
   1525     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1526 
   1527     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
   1528     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
   1529     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
   1530     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
   1531     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
   1532     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
   1533     REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection));
   1534     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
   1535     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1536 }
   1537 
   1538 TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]")
   1539 {
   1540     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1541     INFO("Using temporary file named: " << tempFile.GetPath());
   1542 
   1543     std::string manifest1Path = "test/id/test.id-1.0.0.yaml";
   1544     Manifest manifest1;
   1545     manifest1.Installers.push_back({});
   1546     manifest1.Id = "test.id";
   1547     manifest1.DefaultLocalization.Add<Localization::PackageName>("Test Name");
   1548     manifest1.Moniker = "testmoniker";
   1549     manifest1.Version = "1.0.0";
   1550     manifest1.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   1551     manifest1.Installers[0].Commands = { "test1", "test2" };
   1552 
   1553     std::string manifest2Path = "test/id/test.id-2.0.0.yaml";
   1554     Manifest manifest2 = manifest1;
   1555     manifest2.Id = "Test.Id";
   1556     manifest1.Version = "2.0.0";
   1557 
   1558     {
   1559         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
   1560 
   1561         index.AddManifest(manifest1, manifest1Path);
   1562 
   1563         auto results = index.Search({});
   1564         REQUIRE(results.Matches.size() == 1);
   1565         REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first));
   1566     }
   1567 
   1568     {
   1569         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1570 
   1571         index.AddManifest(manifest2, manifest2Path);
   1572 
   1573         auto results = index.Search({});
   1574         REQUIRE(results.Matches.size() == 1);
   1575         REQUIRE(manifest2.Id == GetIdStringById(index, results.Matches[0].first));
   1576     }
   1577 
   1578     {
   1579         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1580 
   1581         manifest1.Id = "TEST.ID";
   1582 
   1583         REQUIRE(index.UpdateManifest(manifest1, manifest1Path));
   1584 
   1585         auto results = index.Search({});
   1586         REQUIRE(results.Matches.size() == 1);
   1587         REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first));
   1588     }
   1589 
   1590     {
   1591         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1592 
   1593         index.RemoveManifest(manifest1, manifest1Path);
   1594 
   1595         auto results = index.Search({});
   1596         REQUIRE(results.Matches.size() == 1);
   1597         REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first));
   1598     }
   1599 
   1600     {
   1601         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   1602 
   1603         index.RemoveManifest(manifest2, manifest2Path);
   1604 
   1605         auto results = index.Search({});
   1606         REQUIRE(results.Matches.empty());
   1607     }
   1608 
   1609     // Open it directly to directly test table state
   1610     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1611 
   1612     REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection));
   1613     REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection));
   1614     REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection));
   1615     REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection));
   1616     REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection));
   1617     REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection));
   1618     REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection));
   1619     REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection));
   1620     REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection));
   1621 }
   1622 
   1623 TEST_CASE("PathPartTable_EnsurePathExists_Negative_Paths", "[sqliteindex][V1_0]")
   1624 {
   1625     // Open it directly to directly test pathpart table
   1626     Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create);
   1627 
   1628     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"()", false), E_INVALIDARG);
   1629     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(\)", false), E_INVALIDARG);
   1630     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(/)", false), E_INVALIDARG);
   1631     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:)", false), E_INVALIDARG);
   1632     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:\\)", false), E_INVALIDARG);
   1633     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:\temp\path\file.txt)", false), E_INVALIDARG);
   1634     REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(\temp\path\file.txt)", false), E_INVALIDARG);
   1635 }
   1636 
   1637 TEST_CASE("PathPartTable_EnsurePathExists", "[sqliteindex][V1_0]")
   1638 {
   1639     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1640     INFO("Using temporary file named: " << tempFile.GetPath());
   1641 
   1642     // Create the index
   1643     {
   1644         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 });
   1645         SQLiteVersion versionCreated = index.GetVersion();
   1646         REQUIRE(versionCreated == SQLiteVersion{ 1, 0 });
   1647     }
   1648 
   1649     // Open it directly to directly test pathpart table
   1650     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   1651 
   1652     // attempt to find path that doesn't exist
   1653     auto result0 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", false);
   1654     REQUIRE(!std::get<0>(result0));
   1655 
   1656     // add path
   1657     auto result1 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", true);
   1658     REQUIRE(std::get<0>(result1));
   1659 
   1660     // Second time trying to create should return false and same id
   1661     auto result2 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", true);
   1662     REQUIRE(!std::get<0>(result2));
   1663     REQUIRE(std::get<1>(result1) == std::get<1>(result2));
   1664 
   1665     // Trying to find but not create should return true because it exists
   1666     auto result3 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", false);
   1667     REQUIRE(std::get<0>(result3));
   1668     REQUIRE(std::get<1>(result1) == std::get<1>(result3));
   1669 
   1670     // attempt to find a different file
   1671     auto result4 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d.txt)", false);
   1672     REQUIRE(!std::get<0>(result4));
   1673 
   1674     // add a different file
   1675     auto result5 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d.txt)", true);
   1676     REQUIRE(std::get<0>(result5));
   1677     REQUIRE(std::get<1>(result1) != std::get<1>(result5));
   1678 
   1679     // add the same file but deeper
   1680     auto result6 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d\c.txt)", true);
   1681     REQUIRE(std::get<0>(result6));
   1682     REQUIRE(std::get<1>(result1) != std::get<1>(result6));
   1683 
   1684     // get the deeper file with extra separators
   1685     auto result7 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\\b\d\\c.txt)", true);
   1686     REQUIRE(!std::get<0>(result7));
   1687     REQUIRE(std::get<1>(result6) == std::get<1>(result7));
   1688 }
   1689 
   1690 TEST_CASE("SQLiteIndex_PrepareForPackaging", "[sqliteindex]")
   1691 {
   1692     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1693     INFO("Using temporary file named: " << tempFile.GetPath());
   1694 
   1695     SQLiteIndex index = CreateTestIndex(tempFile);
   1696 
   1697     TestDataFile manifestFile{ "Manifest-Good.yaml" };
   1698     std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" };
   1699 
   1700     index.AddManifest(manifestFile, manifestPath);
   1701 
   1702     index.PrepareForPackaging();
   1703 }
   1704 
   1705 TEST_CASE("SQLiteIndex_Search_IdExactMatch", "[sqliteindex]")
   1706 {
   1707     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1708     INFO("Using temporary file named: " << tempFile.GetPath());
   1709 
   1710     Manifest manifest;
   1711     std::string relativePath = "test/id/1.0.0.yaml";
   1712     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1713 
   1714     TestPrepareForRead(index);
   1715 
   1716     SearchRequest request;
   1717     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1718 
   1719     auto results = index.Search(request);
   1720     REQUIRE(results.Matches.size() == 1);
   1721     REQUIRE(results.Matches[0].second.Field == PackageMatchField::Id);
   1722     REQUIRE(results.Matches[0].second.Type == MatchType::Exact);
   1723     REQUIRE(results.Matches[0].second.Value == manifest.Id);
   1724 }
   1725 
   1726 TEST_CASE("SQLiteIndex_Search_MultipleMatch", "[sqliteindex]")
   1727 {
   1728     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1729     INFO("Using temporary file named: " << tempFile.GetPath());
   1730 
   1731     Manifest manifest;
   1732     std::string relativePath = "test/id/1.0.0.yaml";
   1733     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1734 
   1735     manifest.Version = "2.0.0";
   1736     index.AddManifest(manifest, relativePath + "2");
   1737 
   1738     TestPrepareForRead(index);
   1739 
   1740     SearchRequest request;
   1741     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1742 
   1743     auto results = index.Search(request);
   1744     REQUIRE(results.Matches.size() == 1);
   1745 
   1746     if (AreVersionKeysSupported(index))
   1747     {
   1748         auto result = index.GetVersionKeysById(results.Matches[0].first);
   1749         REQUIRE(result.size() == 2);
   1750     }
   1751     else
   1752     {
   1753         REQUIRE_THROWS_HR(index.GetVersionKeysById(results.Matches[0].first), E_NOT_VALID_STATE);
   1754     }
   1755 }
   1756 
   1757 TEST_CASE("SQLiteIndex_Search_NoMatch", "[sqliteindex]")
   1758 {
   1759     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1760     INFO("Using temporary file named: " << tempFile.GetPath());
   1761 
   1762     Manifest manifest;
   1763     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1764 
   1765     TestPrepareForRead(index);
   1766 
   1767     SearchRequest request;
   1768     request.Query = RequestMatch(MatchType::Exact, "THIS DOES NOT MATCH ANYTHING!");
   1769 
   1770     auto results = index.Search(request);
   1771     REQUIRE(results.Matches.size() == 0);
   1772 }
   1773 
   1774 TEST_CASE("SQLiteIndex_IdString", "[sqliteindex]")
   1775 {
   1776     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1777     INFO("Using temporary file named: " << tempFile.GetPath());
   1778 
   1779     Manifest manifest;
   1780     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1781 
   1782     TestPrepareForRead(index);
   1783 
   1784     SearchRequest request;
   1785     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1786 
   1787     auto results = index.Search(request);
   1788     REQUIRE(results.Matches.size() == 1);
   1789 
   1790     auto result = GetIdStringById(index, results.Matches[0].first);
   1791     REQUIRE(result == manifest.Id);
   1792 }
   1793 
   1794 TEST_CASE("SQLiteIndex_NameString", "[sqliteindex]")
   1795 {
   1796     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1797     INFO("Using temporary file named: " << tempFile.GetPath());
   1798 
   1799     Manifest manifest;
   1800     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1801 
   1802     TestPrepareForRead(index);
   1803 
   1804     SearchRequest request;
   1805     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1806 
   1807     auto results = index.Search(request);
   1808     REQUIRE(results.Matches.size() == 1);
   1809 
   1810     auto result = GetNameStringById(index, results.Matches[0].first);
   1811     REQUIRE(result == manifest.DefaultLocalization.Get<Localization::PackageName>());
   1812 }
   1813 
   1814 TEST_CASE("SQLiteIndex_PathString", "[sqliteindex]")
   1815 {
   1816     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1817     INFO("Using temporary file named: " << tempFile.GetPath());
   1818 
   1819     Manifest manifest;
   1820     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1821     auto relativePath = GetPathFromManifest(manifest);
   1822 
   1823     TestPrepareForRead(index);
   1824 
   1825     if (!AreManifestPathsSupported(index))
   1826     {
   1827         return;
   1828     }
   1829 
   1830     SearchRequest request;
   1831     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1832 
   1833     auto results = index.Search(request);
   1834     REQUIRE(results.Matches.size() == 1);
   1835 
   1836     auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel);
   1837     REQUIRE(specificResult == relativePath);
   1838 
   1839     auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel);
   1840     REQUIRE(latestResult == relativePath);
   1841 }
   1842 
   1843 TEST_CASE("SQLiteIndex_PathlessString", "[sqliteindex]")
   1844 {
   1845     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1846     INFO("Using temporary file named: " << tempFile.GetPath());
   1847 
   1848     Manifest manifest;
   1849     std::string relativePath;
   1850 
   1851     SQLiteIndex index = CreateTestIndex(tempFile);
   1852     CreateFakeManifest(manifest, "Test");
   1853     index.AddManifest(manifest);
   1854 
   1855     TestPrepareForRead(index);
   1856 
   1857     if (!AreManifestPathsSupported(index))
   1858     {
   1859         return;
   1860     }
   1861 
   1862     SearchRequest request;
   1863     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1864 
   1865     auto results = index.Search(request);
   1866     REQUIRE(results.Matches.size() == 1);
   1867 
   1868     auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel);
   1869     REQUIRE(specificResult == relativePath);
   1870 
   1871     auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel);
   1872     REQUIRE(latestResult == relativePath);
   1873 }
   1874 
   1875 TEST_CASE("SQLiteIndex_Versions", "[sqliteindex]")
   1876 {
   1877     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1878     INFO("Using temporary file named: " << tempFile.GetPath());
   1879 
   1880     Manifest manifest;
   1881     std::string relativePath = "test/id/1.0.0.yaml";
   1882     SQLiteIndex index = SimpleTestSetup(tempFile, manifest);
   1883 
   1884     TestPrepareForRead(index);
   1885 
   1886     SearchRequest request;
   1887     request.Query = RequestMatch(MatchType::Exact, manifest.Id);
   1888 
   1889     auto results = index.Search(request);
   1890     REQUIRE(results.Matches.size() == 1);
   1891 
   1892     if (AreVersionKeysSupported(index))
   1893     {
   1894         auto result = index.GetVersionKeysById(results.Matches[0].first);
   1895         REQUIRE(result.size() == 1);
   1896         REQUIRE(result[0].VersionAndChannel.GetVersion().ToString() == manifest.Version);
   1897         REQUIRE(result[0].VersionAndChannel.GetChannel().ToString() == manifest.Channel);
   1898     }
   1899     else
   1900     {
   1901         REQUIRE_THROWS_HR(index.GetVersionKeysById(results.Matches[0].first), E_NOT_VALID_STATE);
   1902     }
   1903 }
   1904 
   1905 TEST_CASE("SQLiteIndex_Search_VersionSorting", "[sqliteindex]")
   1906 {
   1907     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1908     INFO("Using temporary file named: " << tempFile.GetPath());
   1909 
   1910     std::vector<VersionAndChannel> sortedList =
   1911     {
   1912         { UtilityVersion("15.0.0"), Channel("") },
   1913         { UtilityVersion("14.0.0"), Channel("") },
   1914         { UtilityVersion("13.2.0"), Channel("") },
   1915         { UtilityVersion("13.2.0-bugfix"), Channel("") },
   1916         { UtilityVersion("13.0.0"), Channel("") },
   1917         { UtilityVersion("16.0.0"), Channel("alpha") },
   1918         { UtilityVersion("15.8.0"), Channel("alpha") },
   1919         { UtilityVersion("15.1.0"), Channel("beta") },
   1920     };
   1921 
   1922     SQLiteIndex index = SearchTestSetup(tempFile, {
   1923         { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" },
   1924         { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" },
   1925         { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" },
   1926         { "Id", "Name", "Moniker", "13.2.0", "", {}, { "Command" }, "Path4" },
   1927         { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" },
   1928         { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" },
   1929         { "Id", "Name", "Moniker", "13.2.0-bugfix", "", { "foo" }, { "com3" }, "Path7" },
   1930         { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" },
   1931         });
   1932 
   1933     TestPrepareForRead(index);
   1934 
   1935     if (!AreChannelsSupported(index))
   1936     {
   1937         return;
   1938     }
   1939 
   1940     SearchRequest request;
   1941     request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id");
   1942 
   1943     auto results = index.Search(request);
   1944     REQUIRE(results.Matches.size() == 1);
   1945 
   1946     auto result = index.GetVersionKeysById(results.Matches[0].first);
   1947     REQUIRE(result.size() == sortedList.size());
   1948 
   1949     for (size_t i = 0; i < result.size(); ++i)
   1950     {
   1951         const VersionAndChannel& sortedVAC = sortedList[i];
   1952         const VersionAndChannel& resultVAC = result[i].VersionAndChannel;
   1953 
   1954         INFO(i);
   1955         REQUIRE(sortedVAC.GetVersion().ToString() == resultVAC.GetVersion().ToString());
   1956         REQUIRE(sortedVAC.GetChannel().ToString() == resultVAC.GetChannel().ToString());
   1957     }
   1958 }
   1959 
   1960 TEST_CASE("SQLiteIndex_PathString_VersionSorting", "[sqliteindex]")
   1961 {
   1962     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   1963     INFO("Using temporary file named: " << tempFile.GetPath());
   1964 
   1965     std::vector<VersionAndChannel> sortedList =
   1966     {
   1967         { UtilityVersion("15.0.0"), Channel("") },
   1968         { UtilityVersion("14.0.0"), Channel("") },
   1969         { UtilityVersion("13.2.0"), Channel("") },
   1970         { UtilityVersion("13.2.0-bugfix"), Channel("") },
   1971         { UtilityVersion("13.0.0"), Channel("") },
   1972         { UtilityVersion("16.0.0"), Channel("alpha") },
   1973         { UtilityVersion("15.8.0"), Channel("alpha") },
   1974         { UtilityVersion("15.1.0"), Channel("beta") },
   1975     };
   1976 
   1977     SQLiteIndex index = SearchTestSetup(tempFile, {
   1978         { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" },
   1979         { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" },
   1980         { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" },
   1981         { "Id", "Name", "Moniker", "13.2.0", "", {}, { "Command" }, "Path4" },
   1982         { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" },
   1983         { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" },
   1984         { "Id", "Name", "Moniker", "13.2.0-bugfix", "", { "foo" }, { "com3" }, "Path7" },
   1985         { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" },
   1986         });
   1987 
   1988     TestPrepareForRead(index);
   1989 
   1990     if (!AreChannelsSupported(index))
   1991     {
   1992         return;
   1993     }
   1994 
   1995     SearchRequest request;
   1996     request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id");
   1997 
   1998     auto results = index.Search(request);
   1999     REQUIRE(results.Matches.size() == 1);
   2000 
   2001     auto result = GetPathStringByKey(index, results.Matches[0].first, "", "");
   2002     REQUIRE(result == "Path3");
   2003 
   2004     result = GetPathStringByKey(index, results.Matches[0].first, "", "alpha");
   2005     REQUIRE(result == "Path2");
   2006 
   2007     result = GetPathStringByKey(index, results.Matches[0].first, "", "beta");
   2008     REQUIRE(result == "Path5");
   2009 
   2010     auto nonResult = index.GetManifestIdByKey(results.Matches[0].first, "", "gamma");
   2011     REQUIRE(!nonResult.has_value());
   2012 }
   2013 
   2014 TEST_CASE("SQLiteIndex_PathString_CaseInsensitive", "[sqliteindex]")
   2015 {
   2016     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2017     INFO("Using temporary file named: " << tempFile.GetPath());
   2018 
   2019     SQLiteIndex index = SearchTestSetup(tempFile, {
   2020         { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" },
   2021         { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" },
   2022         { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" },
   2023         { "Id", "Name", "Moniker", "13.2.0-BUGFIX", "", {}, { "Command" }, "Path4" },
   2024         { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" },
   2025         { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" },
   2026         { "Id", "Name", "Moniker", "13.2.0-bugfix", "beta", { "foo" }, { "com3" }, "Path7" },
   2027         { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" },
   2028         });
   2029 
   2030     TestPrepareForRead(index);
   2031 
   2032     if (!AreChannelsSupported(index))
   2033     {
   2034         return;
   2035     }
   2036 
   2037     SearchRequest request;
   2038     request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id");
   2039 
   2040     auto results = index.Search(request);
   2041     REQUIRE(results.Matches.size() == 1);
   2042 
   2043     auto result = index.GetManifestIdByKey(results.Matches[0].first, "", "Alpha");
   2044     REQUIRE(result.has_value());
   2045 
   2046     result = index.GetManifestIdByKey(results.Matches[0].first, "13.2.0-BugFix", "");
   2047     REQUIRE(result.has_value());
   2048 
   2049     result = index.GetManifestIdByKey(results.Matches[0].first, "13.2.0-BugFix", "BETA");
   2050     REQUIRE(result.has_value());
   2051 }
   2052 
   2053 TEST_CASE("SQLiteIndex_SearchResultsTableSearches", "[sqliteindex][V1_0]")
   2054 {
   2055     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2056     INFO("Using temporary file named: " << tempFile.GetPath());
   2057 
   2058     Manifest manifest;
   2059     {
   2060         (void)SimpleTestSetup(tempFile, manifest, SQLiteVersion{ 1, 0 });
   2061     }
   2062 
   2063     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
   2064     Schema::V1_0::SearchResultsTable search(connection);
   2065 
   2066     std::string value = "test";
   2067 
   2068     // Perform every type of field and match search
   2069     PackageMatchFilter filter(PackageMatchField::Id, MatchType::Exact, value);
   2070 
   2071     for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Tag, PackageMatchField::Command })
   2072     {
   2073         filter.Field = field;
   2074 
   2075         for (auto match : { MatchType::Exact, MatchType::Fuzzy, MatchType::FuzzySubstring, MatchType::Substring, MatchType::Wildcard })
   2076         {
   2077             filter.Type = match;
   2078             search.SearchOnField(filter);
   2079         }
   2080     }
   2081 }
   2082 
   2083 TEST_CASE("SQLiteIndex_Search_EmptySearch", "[sqliteindex]")
   2084 {
   2085     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2086     INFO("Using temporary file named: " << tempFile.GetPath());
   2087 
   2088     SQLiteIndex index = SearchTestSetup(tempFile,{
   2089         { "Id1", "Name", "Moniker", "Version1", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2090         { "Id1", "Name", "Moniker", "Version2", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2091         { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2092         { "Id3", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path4" },
   2093         });
   2094 
   2095     TestPrepareForRead(index);
   2096 
   2097     SearchRequest request;
   2098 
   2099     auto results = index.Search(request);
   2100     REQUIRE(results.Matches.size() == 3);
   2101 }
   2102 
   2103 TEST_CASE("SQLiteIndex_Search_Exact", "[sqliteindex]")
   2104 {
   2105     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2106     INFO("Using temporary file named: " << tempFile.GetPath());
   2107 
   2108     SQLiteIndex index = SearchTestSetup(tempFile, {
   2109         { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2110         { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2111         });
   2112 
   2113     TestPrepareForRead(index);
   2114 
   2115     SearchRequest request;
   2116     request.Query = RequestMatch(MatchType::Exact, "Id");
   2117 
   2118     auto results = index.Search(request);
   2119     REQUIRE(results.Matches.size() == 1);
   2120 }
   2121 
   2122 TEST_CASE("SQLiteIndex_Search_Substring", "[sqliteindex]")
   2123 {
   2124     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2125     INFO("Using temporary file named: " << tempFile.GetPath());
   2126 
   2127     SQLiteIndex index = SearchTestSetup(tempFile, {
   2128         { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2129         { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2130         });
   2131 
   2132     TestPrepareForRead(index);
   2133 
   2134     SearchRequest request;
   2135     request.Query = RequestMatch(MatchType::Substring, "Id");
   2136 
   2137     auto results = index.Search(request);
   2138     REQUIRE(results.Matches.size() == 2);
   2139 }
   2140 
   2141 TEST_CASE("SQLiteIndex_Search_ExactBeforeSubstring", "[sqliteindex]")
   2142 {
   2143     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2144     INFO("Using temporary file named: " << tempFile.GetPath());
   2145 
   2146     SQLiteIndex index = SearchTestSetup(tempFile, {
   2147         { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2148         { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2149         });
   2150 
   2151     TestPrepareForRead(index);
   2152 
   2153     SearchRequest request;
   2154     request.Query = RequestMatch(MatchType::Substring, "Id");
   2155 
   2156     auto results = index.Search(request);
   2157     REQUIRE(results.Matches.size() == 2);
   2158 
   2159     REQUIRE(GetIdStringById(index, results.Matches[0].first) == "Id");
   2160     REQUIRE(GetIdStringById(index, results.Matches[1].first) == "Id2");
   2161 }
   2162 
   2163 TEST_CASE("SQLiteIndex_Search_SingleFilter", "[sqliteindex]")
   2164 {
   2165     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2166     INFO("Using temporary file named: " << tempFile.GetPath());
   2167 
   2168     SQLiteIndex index = SearchTestSetup(tempFile, {
   2169         { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2170         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2171         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2172         });
   2173 
   2174     TestPrepareForRead(index);
   2175 
   2176     SearchRequest request;
   2177     request.Filters.emplace_back(PackageMatchField::Name, MatchType::Substring, "a");
   2178 
   2179     auto results = index.Search(request);
   2180     REQUIRE(results.Matches.size() == 2);
   2181 
   2182     request.Filters[0].Value = "e";
   2183 
   2184     results = index.Search(request);
   2185     REQUIRE(results.Matches.size() == 1);
   2186 }
   2187 
   2188 TEST_CASE("SQLiteIndex_Search_Multimatch", "[sqliteindex]")
   2189 {
   2190     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2191     INFO("Using temporary file named: " << tempFile.GetPath());
   2192 
   2193     SQLiteIndex index = SearchTestSetup(tempFile, {
   2194         { "Id1", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2195         { "Id1", "Name1", "Moniker", "Version1", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2196         { "Id2", "Name", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path3" },
   2197         { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path4" },
   2198         { "Id3", "Name", "Moniker", "Version1", "", { "Tag" }, { "Command" }, "Path5" },
   2199         { "Id3", "Name", "Moniker", "Version2", "", { "Tag" }, { "Command" }, "Path6" },
   2200         { "Id3", "Name", "Moniker", "Version3", "", { "Tag" }, { "Command" }, "Path7" },
   2201         });
   2202 
   2203     TestPrepareForRead(index);
   2204 
   2205     SearchRequest request;
   2206     // An empty string should match all substrings
   2207     request.Query = RequestMatch(MatchType::Substring, "");
   2208 
   2209     auto results = index.Search(request);
   2210     REQUIRE(results.Matches.size() == 3);
   2211 }
   2212 
   2213 TEST_CASE("SQLiteIndex_Search_QueryAndFilter", "[sqliteindex]")
   2214 {
   2215     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2216     INFO("Using temporary file named: " << tempFile.GetPath());
   2217 
   2218     SQLiteIndex index = SearchTestSetup(tempFile, {
   2219         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2220         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2221         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2222         });
   2223 
   2224     TestPrepareForRead(index);
   2225 
   2226     SearchRequest request;
   2227     request.Query = RequestMatch(MatchType::Substring, "Id");
   2228     request.Filters.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na");
   2229 
   2230     auto results = index.Search(request);
   2231     REQUIRE(results.Matches.size() == 1);
   2232 
   2233     auto result = GetIdStringById(index, results.Matches[0].first);
   2234     REQUIRE(result == "Id2");
   2235 }
   2236 
   2237 TEST_CASE("SQLiteIndex_Search_QueryAndMultipleFilters", "[sqliteindex]")
   2238 {
   2239     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2240     INFO("Using temporary file named: " << tempFile.GetPath());
   2241 
   2242     SQLiteIndex index = SearchTestSetup(tempFile, {
   2243         { "Id1", "Name", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path1" },
   2244         { "Id1", "Name1", "Moniker", "Version1", "Channel", { "floor" }, { "com3" }, "Path2" },
   2245         { "Id2", "Name", "Moniker", "Version", "", {}, { "Command" }, "Path3" },
   2246         { "Id2", "Name", "Moniker", "Version", "Channel", {}, { "Command" }, "Path4" },
   2247         { "Id3", "Tagit", "Moniker", "Version1", "", { "foo" }, { "com3" }, "Path5" },
   2248         { "Id3", "Tagit", "Moniker", "Version2", "", { "foo" }, { "com3" }, "Path6" },
   2249         { "Id3", "Tagit", "new", "Version3", "", { "foo" }, { "com3" }, "Path7" },
   2250         });
   2251 
   2252     TestPrepareForRead(index);
   2253 
   2254     SearchRequest request;
   2255     request.Query = RequestMatch(MatchType::Substring, "tag");
   2256     request.Filters.emplace_back(PackageMatchField::Command, MatchType::Exact, "com3");
   2257     request.Filters.emplace_back(PackageMatchField::Tag, MatchType::Substring, "foo");
   2258     request.Filters.emplace_back(PackageMatchField::Moniker, MatchType::Substring, "new");
   2259 
   2260     auto results = index.Search(request);
   2261     REQUIRE(results.Matches.size() == 1);
   2262 
   2263     auto result = GetIdStringById(index, results.Matches[0].first);
   2264     REQUIRE(result == "Id3");
   2265 }
   2266 
   2267 TEST_CASE("SQLiteIndex_Search_SimpleICULike", "[sqliteindex]")
   2268 {
   2269     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2270     INFO("Using temporary file named: " << tempFile.GetPath());
   2271 
   2272     // Insert decomposed character: [upper] A + umlaut
   2273     SQLiteIndex index = SearchTestSetup(tempFile, {
   2274         { u8"\x41\x308wesomeApp", "HasUmlaut", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path1" },
   2275         { u8"AwesomeApp", "Nope", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path2" },
   2276         });
   2277 
   2278     TestPrepareForRead(index);
   2279 
   2280     SearchRequest request;
   2281     // Search for anything containing: [lower] a + umlaut
   2282     request.Filters.emplace_back(PackageMatchField::Id, MatchType::Substring, u8"\xE4");
   2283 
   2284     auto results = index.Search(request);
   2285     REQUIRE(results.Matches.size() == 1);
   2286 
   2287     auto result = GetNameStringById(index, results.Matches[0].first);
   2288     REQUIRE(result == "HasUmlaut");
   2289 }
   2290 
   2291 TEST_CASE("SQLiteIndex_Search_MaximumResults_Equal", "[sqliteindex]")
   2292 {
   2293     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2294     INFO("Using temporary file named: " << tempFile.GetPath());
   2295 
   2296     SQLiteIndex index = SearchTestSetup(tempFile, {
   2297         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2298         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2299         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2300         });
   2301 
   2302     TestPrepareForRead(index);
   2303 
   2304     SearchRequest request;
   2305     request.MaximumResults = 3;
   2306 
   2307     auto results = index.Search(request);
   2308     REQUIRE(results.Matches.size() == 3);
   2309     REQUIRE(!results.Truncated);
   2310 }
   2311 
   2312 TEST_CASE("SQLiteIndex_Search_MaximumResults_Less", "[sqliteindex]")
   2313 {
   2314     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2315     INFO("Using temporary file named: " << tempFile.GetPath());
   2316 
   2317     SQLiteIndex index = SearchTestSetup(tempFile, {
   2318         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2319         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2320         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2321         });
   2322 
   2323     TestPrepareForRead(index);
   2324 
   2325     SearchRequest request;
   2326     request.MaximumResults = 2;
   2327 
   2328     auto results = index.Search(request);
   2329     REQUIRE(results.Matches.size() == 2);
   2330     REQUIRE(results.Truncated);
   2331 }
   2332 
   2333 TEST_CASE("SQLiteIndex_Search_MaximumResults_Greater", "[sqliteindex]")
   2334 {
   2335     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2336     INFO("Using temporary file named: " << tempFile.GetPath());
   2337 
   2338     SQLiteIndex index = SearchTestSetup(tempFile, {
   2339         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2340         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2341         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2342         });
   2343 
   2344     TestPrepareForRead(index);
   2345 
   2346     SearchRequest request;
   2347     request.MaximumResults = 4;
   2348 
   2349     auto results = index.Search(request);
   2350     REQUIRE(results.Matches.size() == 3);
   2351     REQUIRE(!results.Truncated);
   2352 }
   2353 
   2354 TEST_CASE("SQLiteIndex_Search_QueryAndInclusion", "[sqliteindex]")
   2355 {
   2356     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2357     INFO("Using temporary file named: " << tempFile.GetPath());
   2358 
   2359     SQLiteIndex index = SearchTestSetup(tempFile, {
   2360         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2361         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2362         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2363         });
   2364 
   2365     TestPrepareForRead(index);
   2366 
   2367     SearchRequest request;
   2368     request.Query = RequestMatch(MatchType::CaseInsensitive, "id3");
   2369     request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na");
   2370 
   2371     auto results = index.Search(request);
   2372     REQUIRE(results.Matches.size() == 3);
   2373 }
   2374 
   2375 TEST_CASE("SQLiteIndex_Search_InclusionOnly", "[sqliteindex]")
   2376 {
   2377     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2378     INFO("Using temporary file named: " << tempFile.GetPath());
   2379 
   2380     SQLiteIndex index = SearchTestSetup(tempFile, {
   2381         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2382         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2383         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2384         });
   2385 
   2386     TestPrepareForRead(index);
   2387 
   2388     SearchRequest request;
   2389     request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na");
   2390 
   2391     auto results = index.Search(request);
   2392     REQUIRE(results.Matches.size() == 2);
   2393 }
   2394 
   2395 TEST_CASE("SQLiteIndex_Search_InclusionAndFilter", "[sqliteindex]")
   2396 {
   2397     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2398     INFO("Using temporary file named: " << tempFile.GetPath());
   2399 
   2400     SQLiteIndex index = SearchTestSetup(tempFile, {
   2401         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2402         { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2403         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2404         });
   2405 
   2406     TestPrepareForRead(index);
   2407 
   2408     SearchRequest request;
   2409     request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na");
   2410     request.Filters.emplace_back(PackageMatchField::Name, MatchType::CaseInsensitive, "name");
   2411 
   2412     auto results = index.Search(request);
   2413     REQUIRE(results.Matches.size() == 1);
   2414 
   2415     auto result = GetIdStringById(index, results.Matches[0].first);
   2416     REQUIRE(result == "Nope");
   2417 }
   2418 
   2419 TEST_CASE("SQLiteIndex_Search_QueryInclusionAndFilter", "[sqliteindex]")
   2420 {
   2421     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2422     INFO("Using temporary file named: " << tempFile.GetPath());
   2423 
   2424     SQLiteIndex index = SearchTestSetup(tempFile, {
   2425         { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2426         { "Id2", "Na", "monicka", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" },
   2427         { "Id3", "No", "moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2428         });
   2429 
   2430     TestPrepareForRead(index);
   2431 
   2432     SearchRequest request;
   2433     request.Query = RequestMatch(MatchType::Substring, "id3");
   2434     request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "na");
   2435     request.Filters.emplace_back(PackageMatchField::Moniker, MatchType::CaseInsensitive, "MONIKER");
   2436 
   2437     auto results = index.Search(request);
   2438     REQUIRE(results.Matches.size() == 2);
   2439 }
   2440 
   2441 TEST_CASE("SQLiteIndex_Search_CaseInsensitive", "[sqliteindex]")
   2442 {
   2443     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2444     INFO("Using temporary file named: " << tempFile.GetPath());
   2445 
   2446     SQLiteIndex index = SearchTestSetup(tempFile, {
   2447         { "Nope", "id3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2448         { "Id2", "Na", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2" },
   2449         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2450         });
   2451 
   2452     TestPrepareForRead(index);
   2453 
   2454     SearchRequest request;
   2455     request.Query = RequestMatch(MatchType::CaseInsensitive, "id3");
   2456 
   2457     auto results = index.Search(request);
   2458     REQUIRE(results.Matches.size() == 3);
   2459 }
   2460 
   2461 TEST_CASE("SQLiteIndex_Search_StartsWith", "[sqliteindex]")
   2462 {
   2463     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2464     INFO("Using temporary file named: " << tempFile.GetPath());
   2465 
   2466     SQLiteIndex index = SearchTestSetup(tempFile, {
   2467         { "NopeId", "id3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" },
   2468         { "Id2", "Na", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2" },
   2469         { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" },
   2470         });
   2471 
   2472     TestPrepareForRead(index);
   2473 
   2474     SearchRequest request;
   2475     request.Inclusions.push_back(PackageMatchFilter(PackageMatchField::Id, MatchType::StartsWith, "id"));
   2476 
   2477     auto results = index.Search(request);
   2478     REQUIRE(results.Matches.size() == 2);
   2479 }
   2480 
   2481 TEST_CASE("SQLiteIndex_Search_Query_PackageFamilyNameSubstring", "[sqliteindex]")
   2482 {
   2483     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2484     INFO("Using temporary file named: " << tempFile.GetPath());
   2485 
   2486     SQLiteIndex index = SearchTestSetup(tempFile, {
   2487         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2488         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2489         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2490         });
   2491 
   2492     SQLiteVersion testVersion = TestPrepareForRead(index);
   2493 
   2494     SearchRequest request;
   2495     request.Query = RequestMatch(MatchType::Substring, "PFN");
   2496 
   2497     auto results = index.Search(request);
   2498     REQUIRE(results.Matches.size() == 0);
   2499 }
   2500 
   2501 TEST_CASE("SQLiteIndex_Search_Query_ProductCodeSubstring", "[sqliteindex]")
   2502 {
   2503     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2504     INFO("Using temporary file named: " << tempFile.GetPath());
   2505 
   2506     SQLiteIndex index = SearchTestSetup(tempFile, {
   2507         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2508         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2509         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2510         });
   2511 
   2512     SQLiteVersion testVersion = TestPrepareForRead(index);
   2513 
   2514     SearchRequest request;
   2515     request.Query = RequestMatch(MatchType::Substring, "PC");
   2516 
   2517     auto results = index.Search(request);
   2518     REQUIRE(results.Matches.size() == 0);
   2519 }
   2520 
   2521 TEST_CASE("SQLiteIndex_Search_Query_PackageFamilyNameMatch", "[sqliteindex]")
   2522 {
   2523     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2524     INFO("Using temporary file named: " << tempFile.GetPath());
   2525 
   2526     SQLiteIndex index = SearchTestSetup(tempFile, {
   2527         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2528         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2529         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2530         });
   2531 
   2532     SQLiteVersion testVersion = TestPrepareForRead(index);
   2533 
   2534     SearchRequest request;
   2535     request.Query = RequestMatch(MatchType::Substring, "pfn1");
   2536 
   2537     auto results = index.Search(request);
   2538 
   2539     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2540     {
   2541         REQUIRE(results.Matches.size() == 1);
   2542     }
   2543     else
   2544     {
   2545         REQUIRE(results.Matches.size() == 0);
   2546     }
   2547 }
   2548 
   2549 TEST_CASE("SQLiteIndex_Search_Query_ProductCodeMatch", "[sqliteindex]")
   2550 {
   2551     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2552     INFO("Using temporary file named: " << tempFile.GetPath());
   2553 
   2554     SQLiteIndex index = SearchTestSetup(tempFile, {
   2555         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2556         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2557         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2558         });
   2559 
   2560     SQLiteVersion testVersion = TestPrepareForRead(index);
   2561 
   2562     SearchRequest request;
   2563     request.Query = RequestMatch(MatchType::Substring, "pc2");
   2564 
   2565     auto results = index.Search(request);
   2566 
   2567     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2568     {
   2569         REQUIRE(results.Matches.size() == 1);
   2570     }
   2571     else
   2572     {
   2573         REQUIRE(results.Matches.size() == 0);
   2574     }
   2575 }
   2576 
   2577 TEST_CASE("SQLiteIndex_Search_PackageFamilyNameSubstring", "[sqliteindex]")
   2578 {
   2579     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2580     INFO("Using temporary file named: " << tempFile.GetPath());
   2581 
   2582     SQLiteIndex index = SearchTestSetup(tempFile, {
   2583         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2584         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2585         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2586         });
   2587 
   2588     SQLiteVersion testVersion = TestPrepareForRead(index);
   2589 
   2590     SearchRequest request;
   2591     request.Inclusions.emplace_back(PackageMatchField::PackageFamilyName, MatchType::Substring, "PFN");
   2592 
   2593     auto results = index.Search(request);
   2594 
   2595     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2596     {
   2597         REQUIRE(results.Matches.size() == 3);
   2598     }
   2599     else
   2600     {
   2601         REQUIRE(results.Matches.size() == 0);
   2602     }
   2603 }
   2604 
   2605 TEST_CASE("SQLiteIndex_Search_ProductCodeSubstring", "[sqliteindex]")
   2606 {
   2607     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2608     INFO("Using temporary file named: " << tempFile.GetPath());
   2609 
   2610     SQLiteIndex index = SearchTestSetup(tempFile, {
   2611         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2612         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2613         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2614         });
   2615 
   2616     SQLiteVersion testVersion = TestPrepareForRead(index);
   2617 
   2618     SearchRequest request;
   2619     request.Inclusions.emplace_back(PackageMatchField::ProductCode, MatchType::Substring, "PC");
   2620 
   2621     auto results = index.Search(request);
   2622 
   2623     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2624     {
   2625         REQUIRE(results.Matches.size() == 3);
   2626     }
   2627     else
   2628     {
   2629         REQUIRE(results.Matches.size() == 0);
   2630     }
   2631 }
   2632 
   2633 TEST_CASE("SQLiteIndex_Search_PackageFamilyNameMatch", "[sqliteindex]")
   2634 {
   2635     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2636     INFO("Using temporary file named: " << tempFile.GetPath());
   2637 
   2638     SQLiteIndex index = SearchTestSetup(tempFile, {
   2639         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2640         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2641         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2642         });
   2643 
   2644     SQLiteVersion testVersion = TestPrepareForRead(index);
   2645 
   2646     SearchRequest request;
   2647     request.Inclusions.emplace_back(PackageMatchField::PackageFamilyName, MatchType::Exact, "pfn1");
   2648 
   2649     auto results = index.Search(request);
   2650 
   2651     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2652     {
   2653         REQUIRE(results.Matches.size() == 1);
   2654     }
   2655     else
   2656     {
   2657         REQUIRE(results.Matches.size() == 0);
   2658     }
   2659 }
   2660 
   2661 TEST_CASE("SQLiteIndex_Search_ProductCodeMatch", "[sqliteindex]")
   2662 {
   2663     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2664     INFO("Using temporary file named: " << tempFile.GetPath());
   2665 
   2666     SQLiteIndex index = SearchTestSetup(tempFile, {
   2667         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   2668         { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   2669         { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   2670         });
   2671 
   2672     SQLiteVersion testVersion = TestPrepareForRead(index);
   2673 
   2674     SearchRequest request;
   2675     request.Inclusions.emplace_back(PackageMatchField::ProductCode, MatchType::Exact, "pc2");
   2676 
   2677     auto results = index.Search(request);
   2678 
   2679     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2680     {
   2681         REQUIRE(results.Matches.size() == 1);
   2682     }
   2683     else
   2684     {
   2685         REQUIRE(results.Matches.size() == 0);
   2686     }
   2687 }
   2688 
   2689 TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]")
   2690 {
   2691     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2692     INFO("Using temporary file named: " << tempFile.GetPath());
   2693 
   2694     std::string manifest1Path = "test/id/test.id-1.0.0.yaml";
   2695     Manifest manifest1;
   2696     manifest1.Installers.push_back({});
   2697     manifest1.Id = "test.id";
   2698     manifest1.DefaultLocalization.Add<Localization::PackageName>("Test Name");
   2699     manifest1.Moniker = "testmoniker";
   2700     manifest1.Version = "1.0.0";
   2701     manifest1.Channel = "test";
   2702     manifest1.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   2703     manifest1.Installers[0].Commands = { "test1", "test2" };
   2704 
   2705     std::string manifest2Path = "test/woah/test.id-1.0.0.yaml";
   2706     Manifest manifest2;
   2707     manifest2.Installers.push_back({});
   2708     manifest2.Id = "test.woah";
   2709     manifest2.DefaultLocalization.Add<Localization::PackageName>("Test Name WOAH");
   2710     manifest2.Moniker = "testmoniker";
   2711     manifest2.Version = "1.0.0";
   2712     manifest2.Channel = "test";
   2713     manifest2.DefaultLocalization.Add<Localization::Tags>({});
   2714     manifest2.Installers[0].Commands = { "test1", "test2", "test3" };
   2715 
   2716     rowid_t manifestRowId = 0;
   2717 
   2718     {
   2719         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 1 });
   2720 
   2721         index.AddManifest(manifest1, manifest1Path);
   2722         index.AddManifest(manifest2, manifest2Path);
   2723 
   2724         // Get the first manifest's id for removal
   2725         SearchRequest request;
   2726         request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest1.Id));
   2727         auto result = index.Search(request);
   2728 
   2729         REQUIRE(result.Matches.size() == 1);
   2730         manifestRowId = result.Matches[0].first;
   2731     }
   2732 
   2733     {
   2734         // Open it directly to modify the table
   2735         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   2736 
   2737         Builder::StatementBuilder builder;
   2738         builder.DeleteFrom(Schema::V1_0::IdTable::TableName()).Where(RowIDName).Equals(manifestRowId);
   2739         builder.Execute(connection);
   2740     }
   2741 
   2742     {
   2743         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   2744 
   2745         REQUIRE(!index.CheckConsistency(true));
   2746     }
   2747 }
   2748 
   2749 TEST_CASE("SQLiteIndex_GetMultiProperty_PackageFamilyName", "[sqliteindex]")
   2750 {
   2751     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2752     INFO("Using temporary file named: " << tempFile.GetPath());
   2753 
   2754     SQLiteIndex index = SearchTestSetup(tempFile, {
   2755         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1", "PFN2" }, {} },
   2756         });
   2757 
   2758     SQLiteVersion testVersion = TestPrepareForRead(index);
   2759 
   2760     SearchRequest request;
   2761 
   2762     auto results = index.Search(request);
   2763     REQUIRE(results.Matches.size() == 1);
   2764 
   2765     auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::PackageFamilyName);
   2766 
   2767     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2768     {
   2769         REQUIRE(props.size() == 2);
   2770         REQUIRE(std::find(props.begin(), props.end(), FoldCase("PFN1"sv)) != props.end());
   2771         REQUIRE(std::find(props.begin(), props.end(), FoldCase("PFN2"sv)) != props.end());
   2772     }
   2773     else
   2774     {
   2775         REQUIRE(props.empty());
   2776     }
   2777 }
   2778 
   2779 TEST_CASE("SQLiteIndex_GetMultiProperty_ProductCode", "[sqliteindex]")
   2780 {
   2781     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2782     INFO("Using temporary file named: " << tempFile.GetPath());
   2783 
   2784     SQLiteIndex index = SearchTestSetup(tempFile, {
   2785         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2786         });
   2787 
   2788     SQLiteVersion testVersion = TestPrepareForRead(index);
   2789 
   2790     SearchRequest request;
   2791 
   2792     auto results = index.Search(request);
   2793     REQUIRE(results.Matches.size() == 1);
   2794 
   2795     auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::ProductCode);
   2796 
   2797     if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion))
   2798     {
   2799         REQUIRE(props.size() == 2);
   2800         REQUIRE(std::find(props.begin(), props.end(), FoldCase("PC1"sv)) != props.end());
   2801         REQUIRE(std::find(props.begin(), props.end(), FoldCase("PC2"sv)) != props.end());
   2802     }
   2803     else
   2804     {
   2805         REQUIRE(props.empty());
   2806     }
   2807 }
   2808 
   2809 TEST_CASE("SQLiteIndex_GetMultiProperty_Tag", "[sqliteindex]")
   2810 {
   2811     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2812     INFO("Using temporary file named: " << tempFile.GetPath());
   2813 
   2814     SQLiteIndex index = SearchTestSetup(tempFile, {
   2815         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag1", "Tag2" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2816         });
   2817 
   2818     SQLiteVersion testVersion = TestPrepareForRead(index);
   2819 
   2820     SearchRequest request;
   2821 
   2822     auto results = index.Search(request);
   2823     REQUIRE(results.Matches.size() == 1);
   2824 
   2825     auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::Tag);
   2826 
   2827     REQUIRE(props.size() == 2);
   2828     REQUIRE(std::find(props.begin(), props.end(), "Tag1") != props.end());
   2829     REQUIRE(std::find(props.begin(), props.end(), "Tag2") != props.end());
   2830 }
   2831 
   2832 TEST_CASE("SQLiteIndex_GetMultiProperty_Command", "[sqliteindex]")
   2833 {
   2834     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2835     INFO("Using temporary file named: " << tempFile.GetPath());
   2836 
   2837     SQLiteIndex index = SearchTestSetup(tempFile, {
   2838         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag1", "Tag2" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2839         });
   2840 
   2841     SQLiteVersion testVersion = TestPrepareForRead(index);
   2842 
   2843     SearchRequest request;
   2844 
   2845     auto results = index.Search(request);
   2846     REQUIRE(results.Matches.size() == 1);
   2847 
   2848     auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::Command);
   2849 
   2850     REQUIRE(props.size() == 1);
   2851     REQUIRE(props[0] == "Command");
   2852 }
   2853 
   2854 TEST_CASE("SQLiteIndex_ManifestMetadata", "[sqliteindex][V1_7]")
   2855 {
   2856     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2857     INFO("Using temporary file named: " << tempFile.GetPath());
   2858 
   2859     SQLiteIndex index = SearchTestSetup(tempFile, {
   2860         { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2861         { "Id2", "Name2", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", { "PFN1", "PFN2" }, {} },
   2862         }, SQLiteVersion{ 1, 7 });
   2863 
   2864     SQLiteVersion testVersion = TestPrepareForRead(index);
   2865 
   2866     SearchRequest request;
   2867 
   2868     auto results = index.Search(request);
   2869     REQUIRE(results.Matches.size() == 2);
   2870 
   2871     for (const auto [id, match] : results.Matches)
   2872     {
   2873         REQUIRE(index.GetMetadataByManifestId(id).empty());
   2874     }
   2875 
   2876     auto manifestId1 = results.Matches[0].first;
   2877     auto manifestId2 = results.Matches[1].first;
   2878 
   2879     std::string metadataValue = "data about data";
   2880 
   2881     index.SetMetadataByManifestId(manifestId1, PackageVersionMetadata::InstalledType, metadataValue);
   2882 
   2883     if (IsManifestMetadataSupported(index, testVersion))
   2884     {
   2885         auto metadataResult = index.GetMetadataByManifestId(manifestId1);
   2886         REQUIRE(metadataResult.size() == 1);
   2887         REQUIRE(metadataResult[0].first == PackageVersionMetadata::InstalledType);
   2888         REQUIRE(metadataResult[0].second == metadataValue);
   2889     }
   2890     else
   2891     {
   2892         REQUIRE(index.GetMetadataByManifestId(manifestId1).empty());
   2893     }
   2894 
   2895     REQUIRE(index.GetMetadataByManifestId(manifestId2).empty());
   2896 }
   2897 
   2898 TEST_CASE("SQLiteIndex_NormNameAndPublisher_Exact", "[sqliteindex]")
   2899 {
   2900     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2901     INFO("Using temporary file named: " << tempFile.GetPath());
   2902 
   2903     std::string testName = "Name";
   2904     std::string testPublisher = "Publisher";
   2905 
   2906     SQLiteIndex index = SearchTestSetup(tempFile, {
   2907         { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2908         });
   2909 
   2910     SQLiteVersion testVersion = TestPrepareForRead(index);
   2911 
   2912     SearchRequest request;
   2913     request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName, testPublisher));
   2914 
   2915     auto results = index.Search(request);
   2916 
   2917     if (AreNormalizedNameAndPublisherSupported(index, testVersion))
   2918     {
   2919         REQUIRE(results.Matches.size() == 1);
   2920     }
   2921     else
   2922     {
   2923         REQUIRE(results.Matches.empty());
   2924     }
   2925 }
   2926 
   2927 TEST_CASE("SQLiteIndex_NormNameAndPublisher_Simple", "[sqliteindex]")
   2928 {
   2929     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2930     INFO("Using temporary file named: " << tempFile.GetPath());
   2931 
   2932     std::string testName = "Name";
   2933     std::string testPublisher = "Publisher";
   2934 
   2935     SQLiteIndex index = SearchTestSetup(tempFile, {
   2936         { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2937         });
   2938 
   2939     SQLiteVersion testVersion = TestPrepareForRead(index);
   2940 
   2941     SearchRequest request;
   2942     request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName + " 1.0", testPublisher + " Corporation"));
   2943 
   2944     auto results = index.Search(request);
   2945 
   2946     if (AreNormalizedNameAndPublisherSupported(index, testVersion))
   2947     {
   2948         REQUIRE(results.Matches.size() == 1);
   2949     }
   2950     else
   2951     {
   2952         REQUIRE(results.Matches.empty());
   2953     }
   2954 }
   2955 
   2956 TEST_CASE("SQLiteIndex_NormNameAndPublisher_Complex", "[sqliteindex]")
   2957 {
   2958     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2959     INFO("Using temporary file named: " << tempFile.GetPath());
   2960 
   2961     std::string testName = "Name";
   2962     std::string testPublisher = "Publisher";
   2963 
   2964     SQLiteIndex index = SearchTestSetup(tempFile, {
   2965         { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } },
   2966         { "Id2", testName, "Different Publisher", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", {}, { "PC1", "PC2" } },
   2967         });
   2968 
   2969     SQLiteVersion testVersion = TestPrepareForRead(index);
   2970 
   2971     SearchRequest request;
   2972     request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName + " 1.0", testPublisher));
   2973 
   2974     auto results = index.Search(request);
   2975 
   2976     if (AreNormalizedNameAndPublisherSupported(index, testVersion))
   2977     {
   2978         REQUIRE(results.Matches.size() == 1);
   2979     }
   2980     else
   2981     {
   2982         REQUIRE(results.Matches.empty());
   2983     }
   2984 }
   2985 
   2986 TEST_CASE("SQLiteIndex_NormNameAndPublisher_AppsAndFeatures", "[sqliteindex]")
   2987 {
   2988     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   2989     INFO("Using temporary file named: " << tempFile.GetPath());
   2990 
   2991     std::string testName = "Name";
   2992     std::string testPublisher = "Publisher";
   2993     std::string arpTestName = "Other Thing";
   2994     std::string arpTestPublisher = "Big Company Name";
   2995 
   2996     SQLiteIndex index = SearchTestSetup(tempFile, {
   2997         { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" }, arpTestName, arpTestPublisher },
   2998         });
   2999 
   3000     SQLiteVersion testVersion = TestPrepareForRead(index);
   3001 
   3002     SearchRequest request;
   3003     request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, arpTestName, arpTestPublisher));
   3004 
   3005     auto results = index.Search(request);
   3006 
   3007     if (AreNormalizedNameAndPublisherSupported(index, testVersion))
   3008     {
   3009         REQUIRE(results.Matches.size() == 1);
   3010     }
   3011     else
   3012     {
   3013         REQUIRE(results.Matches.empty());
   3014     }
   3015 }
   3016 
   3017 TEST_CASE("SQLiteIndex_ManifestHash_Present", "[sqliteindex]")
   3018 {
   3019     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3020     INFO("Using temporary file named: " << tempFile.GetPath());
   3021 
   3022     uint8_t data[4] = { 1, 2, 3, 4 };
   3023     SHA256::HashBuffer hash = SHA256::ComputeHash(data, sizeof(data));
   3024 
   3025     SQLiteIndex index = CreateTestIndex(tempFile);
   3026 
   3027     Manifest manifest;
   3028     manifest.Id = "Foo";
   3029     manifest.Version = "Bar";
   3030     manifest.StreamSha256 = hash;
   3031     index.AddManifest(manifest, "path");
   3032 
   3033     SQLiteVersion testVersion = TestPrepareForRead(index);
   3034 
   3035     auto results = index.Search({});
   3036     REQUIRE(results.Matches.size() == 1);
   3037 
   3038     auto hashResult = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ManifestSHA256Hash);
   3039 
   3040     // Regardless of what hash, it should still be a SHA256 hash
   3041     REQUIRE(hashResult);
   3042     auto hashResultBytes = SHA256::ConvertToBytes(hashResult.value());
   3043     REQUIRE(hash.size() == hashResultBytes.size());
   3044 
   3045     if (AreManifestHashesSupported(index, testVersion))
   3046     {
   3047         REQUIRE(std::equal(hash.begin(), hash.end(), hashResultBytes.begin()));
   3048     }
   3049 }
   3050 
   3051 TEST_CASE("SQLiteIndex_ManifestHash_Missing", "[sqliteindex]")
   3052 {
   3053     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3054     INFO("Using temporary file named: " << tempFile.GetPath());
   3055 
   3056     SQLiteIndex index = CreateTestIndex(tempFile);
   3057 
   3058     Manifest manifest;
   3059     manifest.Id = "Foo";
   3060     manifest.Version = "Bar";
   3061     index.AddManifest(manifest, "path");
   3062 
   3063     SQLiteVersion testVersion = TestPrepareForRead(index);
   3064 
   3065     auto results = index.Search({});
   3066     REQUIRE(results.Matches.size() == 1);
   3067 
   3068     auto hashResult = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ManifestSHA256Hash);
   3069 
   3070     if (AreManifestHashesSupported(index, testVersion))
   3071     {
   3072         REQUIRE(!hashResult);
   3073     }
   3074 }
   3075 
   3076 TEST_CASE("SQLiteIndex_ManifestArpVersion_Present_Add", "[sqliteindex]")
   3077 {
   3078     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3079     INFO("Using temporary file named: " << tempFile.GetPath());
   3080 
   3081     SQLiteIndex index = CreateTestIndex(tempFile);
   3082 
   3083     Manifest manifest;
   3084     manifest.Id = "Foo";
   3085     manifest.Version = "Bar";
   3086     manifest.Installers.push_back({});
   3087     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3088     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3089     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3090     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3091     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3092 
   3093     index.AddManifest(manifest, "path");
   3094 
   3095     SQLiteVersion testVersion = TestPrepareForRead(index);
   3096 
   3097     auto results = index.Search({});
   3098     REQUIRE(results.Matches.size() == 1);
   3099 
   3100     auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion);
   3101     auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion);
   3102 
   3103     if (AreArpVersionsSupported(index, testVersion))
   3104     {
   3105         REQUIRE(arpMin);
   3106         REQUIRE(UtilityVersion(arpMin.value()) == UtilityVersion(manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion));
   3107         REQUIRE(arpMax);
   3108         REQUIRE(UtilityVersion(arpMax.value()) == UtilityVersion(manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion));
   3109     }
   3110     else
   3111     {
   3112         REQUIRE_FALSE(arpMin);
   3113         REQUIRE_FALSE(arpMax);
   3114     }
   3115 }
   3116 
   3117 TEST_CASE("SQLiteIndex_ManifestArpVersion_Present_AddThenUpdate", "[sqliteindex]")
   3118 {
   3119     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3120     INFO("Using temporary file named: " << tempFile.GetPath());
   3121 
   3122     SQLiteIndex index = CreateTestIndex(tempFile);
   3123 
   3124     Manifest manifest;
   3125     manifest.Id = "Foo";
   3126     manifest.Version = "Bar";
   3127     manifest.Installers.push_back({});
   3128     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3129     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3130     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3131     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3132     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3133 
   3134     index.AddManifest(manifest, "path");
   3135 
   3136     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.1";
   3137 
   3138     index.UpdateManifest(manifest, "path");
   3139 
   3140     SQLiteVersion testVersion = TestPrepareForRead(index);
   3141 
   3142     auto results = index.Search({});
   3143     REQUIRE(results.Matches.size() == 1);
   3144 
   3145     auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion);
   3146     auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion);
   3147 
   3148     if (AreArpVersionsSupported(index, testVersion))
   3149     {
   3150         REQUIRE(arpMin);
   3151         REQUIRE(arpMin.value() == "1.1");
   3152         REQUIRE(arpMax);
   3153         REQUIRE(arpMax.value() == "1.1");
   3154     }
   3155     else
   3156     {
   3157         REQUIRE_FALSE(arpMin);
   3158         REQUIRE_FALSE(arpMax);
   3159     }
   3160 }
   3161 
   3162 TEST_CASE("SQLiteIndex_ManifestArpVersion_Empty", "[sqliteindex]")
   3163 {
   3164     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3165     INFO("Using temporary file named: " << tempFile.GetPath());
   3166 
   3167     SQLiteIndex index = CreateTestIndex(tempFile);
   3168 
   3169     Manifest manifest;
   3170     manifest.Id = "Foo";
   3171     manifest.Version = "Bar";
   3172     index.AddManifest(manifest, "path");
   3173 
   3174     SQLiteVersion testVersion = TestPrepareForRead(index);
   3175 
   3176     auto results = index.Search({});
   3177     REQUIRE(results.Matches.size() == 1);
   3178 
   3179     auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion);
   3180     auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion);
   3181 
   3182     if (AreArpVersionsSupported(index, testVersion) && !AreArpVersionsNullable(index))
   3183     {
   3184         REQUIRE(arpMin);
   3185         REQUIRE(arpMin.value() == "");
   3186         REQUIRE(arpMax);
   3187         REQUIRE(arpMax.value() == "");
   3188     }
   3189     else
   3190     {
   3191         REQUIRE_FALSE(arpMin);
   3192         REQUIRE_FALSE(arpMax);
   3193     }
   3194 }
   3195 
   3196 TEST_CASE("SQLiteIndex_RemoveManifestArpVersionKeepUsedDeleteUnused", "[sqliteindex]")
   3197 {
   3198     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3199     INFO("Using temporary file named: " << tempFile.GetPath());
   3200 
   3201     SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest());
   3202 
   3203     Manifest manifest;
   3204     manifest.Id = "Foo";
   3205     manifest.Version = "10.0";
   3206     manifest.Installers.push_back({});
   3207     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3208     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3209     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3210     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3211     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3212 
   3213     index.AddManifest(manifest, "path");
   3214 
   3215     Manifest manifest2;
   3216     manifest2.Id = "Foo2";
   3217     manifest2.Version = "1.0";
   3218     manifest2.Installers.push_back({});
   3219     manifest2.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3220     manifest2.Installers[0].AppsAndFeaturesEntries.push_back({});
   3221     manifest2.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "10.0";
   3222 
   3223     index.AddManifest(manifest2, "path2");
   3224 
   3225     // Before removing, "10.0", "1.0" and "1.1" should all exist.
   3226     {
   3227         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
   3228         REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "10.0").has_value());
   3229         REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.0").has_value());
   3230         REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.1").has_value());
   3231     }
   3232 
   3233     index.RemoveManifest(manifest);
   3234 
   3235     // After removing the first manifest, "10.0" and "1.0" should still stay, "1.1" should be removed.
   3236     {
   3237         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly);
   3238         REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "10.0").has_value());
   3239         REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.0").has_value());
   3240         REQUIRE_FALSE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.1").has_value());
   3241     }
   3242 }
   3243 
   3244 TEST_CASE("SQLiteIndex_ManifestArpVersionConflict_AddThrows", "[sqliteindex]")
   3245 {
   3246     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3247     INFO("Using temporary file named: " << tempFile.GetPath());
   3248 
   3249     SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest());
   3250 
   3251     Manifest manifest;
   3252     manifest.Id = "Foo";
   3253     manifest.Version = "10.0";
   3254     manifest.DefaultLocalization.Add<Localization::PackageName>("ArpVersionCheckConsistencyTest");
   3255     manifest.Moniker = "testmoniker";
   3256     manifest.Installers.push_back({});
   3257     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3258     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3259     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3260     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3261     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3262 
   3263     index.AddManifest(manifest, "path");
   3264 
   3265     REQUIRE(index.CheckConsistency(true));
   3266 
   3267     // Add a conflicting one
   3268     manifest.Version = "10.1";
   3269 
   3270     REQUIRE_THROWS_HR(index.AddManifest(manifest, "path2"), APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED);
   3271 }
   3272 
   3273 TEST_CASE("SQLiteIndex_ManifestArpVersionConflict_UpdateThrows", "[sqliteindex]")
   3274 {
   3275     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3276     INFO("Using temporary file named: " << tempFile.GetPath());
   3277 
   3278     SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest());
   3279 
   3280     Manifest manifest;
   3281     manifest.Id = "Foo";
   3282     manifest.Version = "10.0";
   3283     manifest.DefaultLocalization.Add<Localization::PackageName>("ArpVersionCheckConsistencyTest");
   3284     manifest.Moniker = "testmoniker";
   3285     manifest.Installers.push_back({});
   3286     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3287     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3288     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3289     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3290     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3291 
   3292     index.AddManifest(manifest, "path");
   3293     REQUIRE(index.CheckConsistency(true));
   3294 
   3295     // Add another version
   3296     manifest.Version = "10.1";
   3297     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "2.0";
   3298     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "2.1";
   3299 
   3300     index.AddManifest(manifest, "path2");
   3301     REQUIRE(index.CheckConsistency(true));
   3302 
   3303     // Update to a conflict
   3304     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3305     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "2.1";
   3306 
   3307     REQUIRE_THROWS_HR(index.UpdateManifest(manifest, "path2"), APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED);
   3308 }
   3309 
   3310 TEST_CASE("SQLiteIndex_ManifestArpVersion_ValidateManifestAgainstIndex", "[sqliteindex]")
   3311 {
   3312     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3313     INFO("Using temporary file named: " << tempFile.GetPath());
   3314 
   3315     SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest());
   3316 
   3317     Manifest manifest;
   3318     manifest.Id = "Foo";
   3319     manifest.Version = "10.0";
   3320     manifest.Installers.push_back({});
   3321     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3322     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3323     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3324     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3325     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3326 
   3327     index.AddManifest(manifest, "path");
   3328 
   3329     // Updating same version should not result in failure.
   3330     REQUIRE_NOTHROW(ValidateManifestArpVersion(&index, manifest));
   3331 
   3332     // Add different version should result in failure.
   3333     manifest.Version = "10.1";
   3334     REQUIRE_THROWS(ValidateManifestArpVersion(&index, manifest));
   3335 }
   3336 
   3337 TEST_CASE("SQLiteIndex_CheckConsistency_FindEmbeddedNull", "[sqliteindex]")
   3338 {
   3339     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3340     INFO("Using temporary file named: " << tempFile.GetPath());
   3341 
   3342     SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest());
   3343 
   3344     Manifest manifest;
   3345     manifest.Id = "Foo";
   3346     manifest.Version = "10.0";
   3347     manifest.Installers.push_back({});
   3348     manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3349     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3350     manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0";
   3351     manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3352     manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1";
   3353 
   3354     index.AddManifest(manifest, "path");
   3355 
   3356     // Inject a null character using SQL without binding since we block it
   3357     Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   3358     Statement update = Statement::Create(connection, "Update versions set version = '10.0'||char(0)||'After Null' where version = '10.0'");
   3359     update.Execute();
   3360 
   3361     REQUIRE(!index.CheckConsistency(true));
   3362 }
   3363 
   3364 TEST_CASE("SQLiteIndex_MapDataFolding_Tags", "[sqliteindex][mapdatafolding]")
   3365 {
   3366     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3367     INFO("Using temporary file named: " << tempFile.GetPath());
   3368 
   3369     std::string tag1 = "Tag1";
   3370     std::string tag2 = "Tag2";
   3371 
   3372     SQLiteIndex index = SearchTestSetup(tempFile, {
   3373         { "Id", "Name", "Publisher", "Moniker", "Version1", "", { tag1 }, { "Command" }, "Path1", {}, { "PC1" } },
   3374         { "Id", "Name", "Publisher", "Moniker", "Version2", "", { tag2 }, { "Command" }, "Path2", {}, { "PC2" } },
   3375         });
   3376 
   3377     SQLiteVersion testVersion = TestPrepareForRead(index);
   3378 
   3379     SearchRequest request1;
   3380     request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Tag, MatchType::Exact, tag1));
   3381     auto results1 = index.Search(request1);
   3382 
   3383     SearchRequest request2;
   3384     request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Tag, MatchType::Exact, tag2));
   3385     auto results2 = index.Search(request2);
   3386 
   3387     REQUIRE(results1.Matches.size() == 1);
   3388     REQUIRE(results2.Matches.size() == 1);
   3389     REQUIRE(results1.Matches[0].first == results2.Matches[0].first);
   3390 }
   3391 
   3392 TEST_CASE("SQLiteIndex_MapDataFolding_PFNs", "[sqliteindex][mapdatafolding]")
   3393 {
   3394     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3395     INFO("Using temporary file named: " << tempFile.GetPath());
   3396 
   3397     std::string pfn1 = "PFN1";
   3398     std::string pfn2 = "PFN2";
   3399 
   3400     SQLiteIndex index = SearchTestSetup(tempFile, {
   3401         { "Id", "Name", "Publisher", "Moniker", "Version1", "", { }, { "Command" }, "Path1", { pfn1 }, { } },
   3402         { "Id", "Name", "Publisher", "Moniker", "Version2", "", { }, { "Command" }, "Path2", { pfn2 }, { } },
   3403         });
   3404 
   3405     SQLiteVersion testVersion = TestPrepareForRead(index);
   3406 
   3407     if (!AreChannelsSupported(index))
   3408     {
   3409         return;
   3410     }
   3411 
   3412     SearchRequest request1;
   3413     request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pfn1));
   3414     auto results1 = index.Search(request1);
   3415 
   3416     SearchRequest request2;
   3417     request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pfn2));
   3418     auto results2 = index.Search(request2);
   3419 
   3420     REQUIRE(results1.Matches.size() == 1);
   3421     REQUIRE(results2.Matches.size() == 1);
   3422     REQUIRE(results1.Matches[0].first == results2.Matches[0].first);
   3423 
   3424     auto versionKeys = index.GetVersionKeysById(results1.Matches[0].first);
   3425     REQUIRE(versionKeys.size() == 2);
   3426 
   3427     auto manifestId1 = versionKeys[0].ManifestId;
   3428     auto manifestId2 = versionKeys[1].ManifestId;
   3429 
   3430     auto pfnValues1 = index.GetMultiPropertyByPrimaryId(manifestId1, PackageVersionMultiProperty::PackageFamilyName);
   3431     auto pfnValues2 = index.GetMultiPropertyByPrimaryId(manifestId2, PackageVersionMultiProperty::PackageFamilyName);
   3432 
   3433     if (IsMapDataFoldingSupported(index, testVersion))
   3434     {
   3435         REQUIRE(pfnValues1.size() == 2);
   3436         REQUIRE(pfnValues2.size() == 2);
   3437         REQUIRE(pfnValues1[0] != pfnValues1[1]);
   3438     }
   3439     else if (IsMapDataFolded(index))
   3440     {
   3441         if (manifestId1 > manifestId2)
   3442         {
   3443             REQUIRE(pfnValues1.size() == 2);
   3444             REQUIRE(pfnValues2.size() == 0);
   3445             REQUIRE(pfnValues1[0] != pfnValues1[1]);
   3446         }
   3447         else
   3448         {
   3449             REQUIRE(pfnValues1.size() == 0);
   3450             REQUIRE(pfnValues2.size() == 2);
   3451             REQUIRE(pfnValues2[0] != pfnValues2[1]);
   3452         }
   3453     }
   3454     else
   3455     {
   3456         REQUIRE(pfnValues1.size() == 1);
   3457         REQUIRE(pfnValues2.size() == 1);
   3458         REQUIRE(pfnValues1[0] != pfnValues2[0]);
   3459     }
   3460 }
   3461 
   3462 TEST_CASE("SQLiteIndex_MapDataFolding_ProductCodes", "[sqliteindex][mapdatafolding]")
   3463 {
   3464     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3465     INFO("Using temporary file named: " << tempFile.GetPath());
   3466 
   3467     std::string pc1 = "PC1";
   3468     std::string pc2 = "PC2";
   3469 
   3470     SQLiteIndex index = SearchTestSetup(tempFile, {
   3471         { "Id", "Name", "Publisher", "Moniker", "Version1", "", { }, { "Command" }, "Path1", { }, { pc1 } },
   3472         { "Id", "Name", "Publisher", "Moniker", "Version2", "", { }, { "Command" }, "Path2", { }, { pc2 } },
   3473         });
   3474 
   3475     SQLiteVersion testVersion = TestPrepareForRead(index);
   3476 
   3477     if (!AreChannelsSupported(index))
   3478     {
   3479         return;
   3480     }
   3481 
   3482     SearchRequest request1;
   3483     request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pc1));
   3484     auto results1 = index.Search(request1);
   3485 
   3486     SearchRequest request2;
   3487     request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pc2));
   3488     auto results2 = index.Search(request2);
   3489 
   3490     REQUIRE(results1.Matches.size() == 1);
   3491     REQUIRE(results2.Matches.size() == 1);
   3492     REQUIRE(results1.Matches[0].first == results2.Matches[0].first);
   3493 
   3494     auto versionKeys = index.GetVersionKeysById(results1.Matches[0].first);
   3495     REQUIRE(versionKeys.size() == 2);
   3496 
   3497     auto manifestId1 = versionKeys[0].ManifestId;
   3498     auto manifestId2 = versionKeys[1].ManifestId;
   3499 
   3500     auto pcValues1 = index.GetMultiPropertyByPrimaryId(manifestId1, PackageVersionMultiProperty::ProductCode);
   3501     auto pcValues2 = index.GetMultiPropertyByPrimaryId(manifestId2, PackageVersionMultiProperty::ProductCode);
   3502 
   3503     REQUIRE(pcValues1.size() == 1);
   3504     REQUIRE(pcValues2.size() == 1);
   3505     REQUIRE(pcValues1[0] != pcValues2[0]);
   3506 }
   3507 
   3508 TEST_CASE("SQLiteIndex_FilePath_Memory", "[sqliteindex]")
   3509 {
   3510     SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET);
   3511     auto contextData = index.GetContextData();
   3512     REQUIRE(!contextData.Contains(Schema::Property::DatabaseFilePath));
   3513 }
   3514 
   3515 TEST_CASE("SQLiteIndex_FilePath_Create", "[sqliteindex]")
   3516 {
   3517     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3518 
   3519     SQLiteIndex index = SQLiteIndex::CreateNew(tempFile);
   3520     auto contextData = index.GetContextData();
   3521     REQUIRE(contextData.Contains(Schema::Property::DatabaseFilePath));
   3522     REQUIRE(contextData.Get<Schema::Property::DatabaseFilePath>() == tempFile.GetPath());
   3523 }
   3524 
   3525 TEST_CASE("SQLiteIndex_FilePath_Open", "[sqliteindex]")
   3526 {
   3527     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3528 
   3529     {
   3530         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile);
   3531     }
   3532 
   3533     SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read);
   3534     auto contextData = index.GetContextData();
   3535     REQUIRE(contextData.Contains(Schema::Property::DatabaseFilePath));
   3536     REQUIRE(contextData.Get<Schema::Property::DatabaseFilePath>() == tempFile.GetPath());
   3537 }
   3538 
   3539 TEST_CASE("SQLiteIndex_MigrateTo_Unsupported", "[sqliteindex][V1_7]")
   3540 {
   3541     SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLiteVersion{ 1, 6 });
   3542     REQUIRE(!index.MigrateTo(SQLiteVersion{ 1, 7 }));
   3543 }
   3544 
   3545 TEST_CASE("SQLiteIndex_MigrateTo_Empty", "[sqliteindex][V2_0]")
   3546 {
   3547     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3548     INFO("Using temporary file named: " << tempFile.GetPath());
   3549 
   3550     {
   3551         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, SQLiteVersion{ 1, 7 });
   3552         REQUIRE(index.MigrateTo(SQLiteVersion{ 2, 0 }));
   3553         REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 });
   3554     }
   3555 
   3556     {
   3557         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read);
   3558         REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 });
   3559     }
   3560 }
   3561 
   3562 TEST_CASE("SQLiteIndex_MigrateTo_Data", "[sqliteindex][V2_0]")
   3563 {
   3564     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3565     INFO("Using temporary file named: " << tempFile.GetPath());
   3566 
   3567     std::string packageId1 = "Id1";
   3568     std::string packageId2 = "Id2";
   3569     std::string packageId3 = "Id3";
   3570 
   3571     SQLiteIndex index = SearchTestSetup(tempFile, {
   3572         { packageId1, "Name1", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } },
   3573         { packageId2, "Name2", "Moniker", "Version", "", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } },
   3574         { packageId3, "Name3", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } },
   3575         });
   3576 
   3577     auto preMigrationVersion = index.GetVersion();
   3578 
   3579     if (preMigrationVersion == SQLiteVersion{ 1, 7 })
   3580     {
   3581         REQUIRE(index.MigrateTo(SQLiteVersion{ 2, 0 }));
   3582         REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 });
   3583 
   3584         Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite);
   3585         auto updateData = Schema::V2_0::PackageUpdateTrackingTable::GetUpdatesSince(connection, 0);
   3586 
   3587         REQUIRE(updateData.size() == 3);
   3588         REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId1; }) == 1);
   3589         REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId2; }) == 1);
   3590         REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId3; }) == 1);
   3591     }
   3592     else
   3593     {
   3594         REQUIRE(!index.MigrateTo(SQLiteVersion{ 2, 0 }));
   3595         REQUIRE(index.GetVersion() == preMigrationVersion);
   3596     }
   3597 }
   3598 
   3599 TEST_CASE("SQLiteIndex_Property_IntermediateFilePath", "[sqliteindex]")
   3600 {
   3601     SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET);
   3602     std::filesystem::path intermediateFilePath = "A:\\Path";
   3603     index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediateFilePath.u8string());
   3604 
   3605     auto contextData = index.GetContextData();
   3606     REQUIRE(contextData.Contains(Schema::Property::IntermediateFileOutputPath));
   3607     REQUIRE(contextData.Get<Schema::Property::IntermediateFileOutputPath>() == intermediateFilePath);
   3608 }
   3609 
   3610 struct ManifestAndPath
   3611 {
   3612     Manifest Manifest;
   3613     std::string Path;
   3614 };
   3615 
   3616 void CreateFakeManifestAndPath(
   3617     ManifestAndPath& manifestAndPath,
   3618     const string_t& publisher,
   3619     std::string_view version = "1.0.0",
   3620     std::optional<std::string_view> arpMinVersion = {},
   3621     std::optional<std::string_view> arpMaxVersion = {})
   3622 {
   3623     CreateFakeManifest(manifestAndPath.Manifest, publisher, version);
   3624     manifestAndPath.Path = ConvertToUTF8(CreateNewGuidNameWString());
   3625     manifestAndPath.Manifest.StreamSha256 = SHA256::ComputeHash(manifestAndPath.Path);
   3626 
   3627     if (arpMinVersion)
   3628     {
   3629         manifestAndPath.Manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3630         manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3631         manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.back().DisplayVersion = arpMinVersion.value();
   3632     }
   3633 
   3634     if (arpMaxVersion)
   3635     {
   3636         manifestAndPath.Manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe;
   3637         manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.push_back({});
   3638         manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.back().DisplayVersion = arpMaxVersion.value();
   3639     }
   3640 }
   3641 
   3642 std::filesystem::path GetOnlyChild(const std::filesystem::path& parent)
   3643 {
   3644     auto parentDirectoryIterator = std::filesystem::directory_iterator{ parent };
   3645     std::filesystem::path result = parentDirectoryIterator->path();
   3646     REQUIRE(++parentDirectoryIterator == std::filesystem::directory_iterator{});
   3647     return result;
   3648 }
   3649 
   3650 void CheckIntermediates(const std::filesystem::path& baseDirectory, const std::vector<std::vector<ManifestAndPath>>& expectedIntermediatesData, std::chrono::seconds sleep = 1s)
   3651 {
   3652     std::filesystem::path intermediatesDirectory = baseDirectory / "packages";
   3653 
   3654     size_t intermediatePackageCount = std::count_if(std::filesystem::directory_iterator{ intermediatesDirectory }, std::filesystem::directory_iterator{}, [](const auto&){ return true; });
   3655     REQUIRE(intermediatePackageCount == expectedIntermediatesData.size());
   3656 
   3657     for (const auto& versions : expectedIntermediatesData)
   3658     {
   3659         REQUIRE(!versions.empty());
   3660         INFO(versions[0].Manifest.Id);
   3661         std::filesystem::path packageDirectory = intermediatesDirectory / ConvertToUTF16(versions[0].Manifest.Id);
   3662 
   3663         REQUIRE(std::filesystem::exists(packageDirectory));
   3664         std::filesystem::path hashDirectory = GetOnlyChild(packageDirectory);
   3665 
   3666         std::filesystem::path versionDataFile = GetOnlyChild(hashDirectory);
   3667         std::ifstream versionDataStream{ versionDataFile, std::ios_base::in | std::ios_base::binary };
   3668         auto versionDataBytes = ReadEntireStreamAsByteArray(versionDataStream);
   3669 
   3670         PackageVersionDataManifest versionDataManifest;
   3671         versionDataManifest.Deserialize(PackageVersionDataManifest::CreateDecompressor().Decompress(versionDataBytes));
   3672 
   3673         const auto& versionDataVersions = versionDataManifest.Versions();
   3674         REQUIRE(versionDataVersions.size() == versions.size());
   3675 
   3676         for (const auto& manifestAndPath : versions)
   3677         {
   3678             const auto& versionDataItr = std::find_if(versionDataVersions.begin(), versionDataVersions.end(), [&](const auto& v) { return v.Version == manifestAndPath.Manifest.Version; });
   3679             REQUIRE(versionDataItr != versionDataVersions.end());
   3680             const auto& versionData = *versionDataItr;
   3681 
   3682             REQUIRE(manifestAndPath.Path == versionData.ManifestRelativePath);
   3683             REQUIRE(SHA256::ConvertToString(manifestAndPath.Manifest.StreamSha256) == versionData.ManifestHash);
   3684 
   3685             auto versionRange = manifestAndPath.Manifest.GetArpVersionRange();
   3686             if (!versionRange.IsEmpty())
   3687             {
   3688                 REQUIRE(versionData.ArpMinVersion);
   3689                 REQUIRE(versionRange.GetMinVersion() == versionData.ArpMinVersion.value());
   3690                 REQUIRE(versionData.ArpMaxVersion);
   3691                 REQUIRE(versionRange.GetMaxVersion() == versionData.ArpMaxVersion.value());
   3692             }
   3693         }
   3694     }
   3695 
   3696     // This is needed to force the timestamp to roll over to a new value for the next call to this function.
   3697     // An alternate solution would be to hook the timestamp function and control the values it returns
   3698     // so that we can advance/halt time arbitrarily.
   3699     std::this_thread::sleep_for(sleep);
   3700 }
   3701 
   3702 void PrepareAndCheckIntermediates(const std::filesystem::path& baseFile, const std::filesystem::path& preparedFile, const std::vector<std::vector<ManifestAndPath>>& expectedIntermediatesData, std::chrono::seconds sleep = 1s)
   3703 {
   3704     TempDirectory intermediatesDirectory{ "v2_0_intermediates" };
   3705     INFO("Intermediates directory: " << intermediatesDirectory.GetPath());
   3706 
   3707     std::filesystem::copy_file(baseFile, preparedFile, std::filesystem::copy_options::overwrite_existing);
   3708 
   3709     SQLiteIndex index = SQLiteIndex::Open(preparedFile.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite);
   3710     index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory);
   3711     index.PrepareForPackaging();
   3712 
   3713     CheckIntermediates(intermediatesDirectory, expectedIntermediatesData, sleep);
   3714 }
   3715 
   3716 TEST_CASE("SQLiteIndex_V2_0_UsageFlow_Simple", "[sqliteindex][V2_0]")
   3717 {
   3718     TempFile baseFile{ "v2_0_index_tempdb"s, ".db"s };
   3719     TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s };
   3720     INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]");
   3721 
   3722     // Create empty index
   3723     std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 2, 0 });
   3724 
   3725     std::string publisher = "Publisher";
   3726     ManifestAndPath manifest1;
   3727     CreateFakeManifestAndPath(manifest1, publisher, "1.0");
   3728 
   3729     {
   3730         // Open existing file to add a manifest
   3731         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3732         index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, "");
   3733         index.AddManifest(manifest1.Manifest, manifest1.Path);
   3734     }
   3735 
   3736     PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }, 0s);
   3737 }
   3738 
   3739 TEST_CASE("SQLiteIndex_V2_0_UsageFlow_Complex", "[sqliteindex][V2_0]")
   3740 {
   3741     TempFile baseFile{ "v2_0_index_tempdb"s, ".db"s };
   3742     TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s };
   3743     INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]");
   3744 
   3745     // Create empty index
   3746     std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 2, 0 });
   3747 
   3748     // Open existing file to add a new package
   3749     std::string Publisher1 = "Publisher1";
   3750     ManifestAndPath manifest1;
   3751     CreateFakeManifestAndPath(manifest1, Publisher1, "1.0");
   3752 
   3753     {
   3754         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3755         index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, "");
   3756         index.AddManifest(manifest1.Manifest, manifest1.Path);
   3757     }
   3758 
   3759     PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } });
   3760 
   3761     // Open existing file to add another new package
   3762     ManifestAndPath manifest2;
   3763     CreateFakeManifestAndPath(manifest2, "Publisher2", "1.0");
   3764 
   3765     {
   3766         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3767         index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, "");
   3768         index.AddManifest(manifest2.Manifest, manifest2.Path);
   3769     }
   3770 
   3771     PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 } });
   3772 
   3773     // Open existing file to add a new version of existing package
   3774     ManifestAndPath manifest3;
   3775     CreateFakeManifestAndPath(manifest3, Publisher1, "2.0");
   3776 
   3777     {
   3778         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3779         index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, "");
   3780         index.AddManifest(manifest3.Manifest, manifest3.Path);
   3781     }
   3782 
   3783     PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1, manifest3 } });
   3784 
   3785     // Open existing file to add a new version of existing package and update an existing version
   3786     manifest2.Manifest.StreamSha256 = SHA256::ComputeHash(manifest2.Manifest.Id);
   3787 
   3788     ManifestAndPath manifest4;
   3789     CreateFakeManifestAndPath(manifest4, Publisher1, "3.0");
   3790 
   3791     {
   3792         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3793         index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, "");
   3794         index.UpdateManifest(manifest2.Manifest, manifest2.Path);
   3795         index.AddManifest(manifest4.Manifest, manifest4.Path);
   3796     }
   3797 
   3798     PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3, manifest4 } }, 0s);
   3799 }
   3800 
   3801 void MigratePrepareAndCheckIntermediates(const std::filesystem::path& baseFile, const std::filesystem::path& preparedFile, const std::vector<std::vector<ManifestAndPath>>& expectedIntermediatesData)
   3802 {
   3803     TempDirectory intermediatesDirectory{ "v2_0_intermediates" };
   3804     INFO("Intermediates directory: " << intermediatesDirectory.GetPath());
   3805 
   3806     std::filesystem::copy_file(baseFile, preparedFile, std::filesystem::copy_options::overwrite_existing);
   3807 
   3808     SQLiteIndex index = SQLiteIndex::Open(preparedFile.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite);
   3809     index.MigrateTo({ 2, 0 });
   3810     index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory);
   3811     index.PrepareForPackaging();
   3812 
   3813     CheckIntermediates(intermediatesDirectory, expectedIntermediatesData, 0s);
   3814 }
   3815 
   3816 TEST_CASE("SQLiteIndex_V2_0_UsageFlow_ComplexMigration", "[sqliteindex][V2_0]")
   3817 {
   3818     TempFile baseFile{ "v1_7_index_tempdb"s, ".db"s };
   3819     TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s };
   3820     INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]");
   3821 
   3822     // Create empty index
   3823     std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 1, 7 });
   3824 
   3825     // Open existing file to add a new package
   3826     std::string Publisher1 = "Publisher1";
   3827     ManifestAndPath manifest1;
   3828     CreateFakeManifestAndPath(manifest1, Publisher1, "1.0");
   3829 
   3830     {
   3831         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3832         index.AddManifest(manifest1.Manifest, manifest1.Path);
   3833     }
   3834 
   3835     MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } });
   3836 
   3837     // Open existing file to add another new package
   3838     ManifestAndPath manifest2;
   3839     CreateFakeManifestAndPath(manifest2, "Publisher2", "1.0");
   3840 
   3841     {
   3842         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3843         index.AddManifest(manifest2.Manifest, manifest2.Path);
   3844     }
   3845 
   3846     MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { {  manifest2 }, { manifest1 } });
   3847 
   3848     // Open existing file to add a new version of existing package
   3849     ManifestAndPath manifest3;
   3850     CreateFakeManifestAndPath(manifest3, Publisher1, "2.0");
   3851 
   3852     {
   3853         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3854         index.AddManifest(manifest3.Manifest, manifest3.Path);
   3855     }
   3856 
   3857     MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { {  manifest2 }, { manifest1, manifest3 } });
   3858 
   3859     // Open existing file to add a new version of existing package and update an existing version
   3860     manifest2.Manifest.StreamSha256 = SHA256::ComputeHash(manifest2.Manifest.Id);
   3861 
   3862     ManifestAndPath manifest4;
   3863     CreateFakeManifestAndPath(manifest4, Publisher1, "3.0");
   3864 
   3865     {
   3866         SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3867         index.UpdateManifest(manifest2.Manifest, manifest2.Path);
   3868         index.AddManifest(manifest4.Manifest, manifest4.Path);
   3869     }
   3870 
   3871     MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3, manifest4 } });
   3872 }
   3873 
   3874 TEST_CASE("SQLiteIndex_DependencyWithCaseMismatch", "[sqliteindex][V1_4]")
   3875 {
   3876     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3877     INFO("Using temporary file named: " << tempFile.GetPath());
   3878 
   3879     Manifest dependencyManifest1, dependencyManifest2, manifest;
   3880     SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest());
   3881 
   3882     // Must contain some upper case
   3883     auto& publisher2 = "Test2";
   3884     CreateFakeManifest(dependencyManifest2, publisher2);
   3885     index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2));
   3886 
   3887     auto& publisher3 = "Test3";
   3888     CreateFakeManifest(manifest, publisher3);
   3889     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0"));
   3890     manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, ToLower(dependencyManifest2.Id), "1.0.0"));
   3891 
   3892     index.AddManifest(manifest, GetPathFromManifest(manifest));
   3893 }
   3894 
   3895 TEST_CASE("SQLiteIndex_AddOrUpdateManifest", "[sqliteindex]")
   3896 {
   3897     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3898     INFO("Using temporary file named: " << tempFile.GetPath());
   3899 
   3900     std::string manifestPath = "test/id/test.id-1.0.0.yaml";
   3901     Manifest manifest;
   3902     manifest.Installers.push_back({});
   3903     manifest.Id = "test.id";
   3904     manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name");
   3905     manifest.Moniker = "testmoniker";
   3906     manifest.Version = "1.0.0";
   3907     manifest.Channel = "test";
   3908     manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2" });
   3909     manifest.Installers[0].Commands = { "test1", "test2" };
   3910 
   3911     {
   3912         auto version = GENERATE(SQLiteVersion{ 1, 0 }, SQLiteVersion::Latest());
   3913         SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, version);
   3914 
   3915         REQUIRE(index.AddOrUpdateManifest(manifest, manifestPath));
   3916     }
   3917 
   3918     {
   3919         SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite);
   3920 
   3921         // Update should return false
   3922         REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath));
   3923 
   3924         manifest.DefaultLocalization.Add<Localization::Description>("description2");
   3925 
   3926         // Update should return false
   3927         REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath));
   3928 
   3929         // Update with indexed changes should still return false
   3930         manifest.DefaultLocalization.Add<Localization::PackageName>("Test Name2");
   3931         manifest.Moniker = "testmoniker2";
   3932         manifest.DefaultLocalization.Add<Localization::Tags>({ "t1", "t2", "t3" });
   3933         manifest.Installers[0].Commands = {};
   3934 
   3935         REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath));
   3936     }
   3937 }
   3938 
   3939 TEST_CASE("SQLiteIndex_VersionStringPreserved", "[sqliteindex]")
   3940 {
   3941     TempFile tempFile{ "repolibtest_tempdb"s, ".db"s };
   3942     INFO("Using temporary file named: " << tempFile.GetPath());
   3943 
   3944     Manifest manifest;
   3945     SQLiteIndex index = CreateTestIndex(tempFile);
   3946 
   3947     string_t publisher = "Test";
   3948     std::string version = GENERATE("1.0", "1.10");
   3949 
   3950     CreateFakeManifest(manifest, publisher, version);
   3951     index.AddManifest(manifest, GetPathFromManifest(manifest));
   3952 
   3953     TempDirectory intermediatesDirectory{ "v2_0_intermediates" };
   3954     INFO("Intermediates directory: " << intermediatesDirectory.GetPath());
   3955 
   3956     index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory);
   3957     index.PrepareForPackaging();
   3958 
   3959     auto results = index.Search({});
   3960     REQUIRE(results.Matches.size() == 1);
   3961 
   3962     std::string extractedVersion = GetPropertyStringById(index, results.Matches[0].first, PackageVersionProperty::Version);
   3963 
   3964     REQUIRE(extractedVersion == version);
   3965 }