winget-cli

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

Sources.cpp (48664B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "TestCommon.h"
      5 #include "TestHooks.h"
      6 #include "TestSettings.h"
      7 #include "TestSource.h"
      8 
      9 #include <AppInstallerDateTime.h>
     10 #include <AppInstallerRuntime.h>
     11 #include <AppInstallerStrings.h>
     12 #include <AppInstallerErrors.h>
     13 #include <winget/Settings.h>
     14 
     15 using namespace TestCommon;
     16 using namespace AppInstaller;
     17 using namespace AppInstaller::Runtime;
     18 using namespace AppInstaller::Repository;
     19 using namespace AppInstaller::Settings;
     20 using namespace AppInstaller::Utility;
     21 
     22 // Duplicating here because a change to these values in the product *REALLY* needs to be thought through.
     23 using namespace std::string_literals;
     24 using namespace std::string_view_literals;
     25 
     26 constexpr size_t c_DefaultSourceCount = 2;
     27 
     28 constexpr std::string_view s_SourcesYaml_Sources = "Sources"sv;
     29 constexpr std::string_view s_SourcesYaml_Source_Name = "Name"sv;
     30 constexpr std::string_view s_SourcesYaml_Source_Type = "Type"sv;
     31 constexpr std::string_view s_SourcesYaml_Source_Arg = "Arg"sv;
     32 constexpr std::string_view s_SourcesYaml_Source_Data = "Data"sv;
     33 constexpr std::string_view s_SourcesYaml_Source_TrustLevel = "TrustLevel"sv;
     34 constexpr std::string_view s_SourcesYaml_Source_Explicit = "Explicit"sv;
     35 constexpr std::string_view s_SourcesYaml_Source_LastUpdate = "LastUpdate"sv;
     36 
     37 constexpr std::string_view s_EmptySources = R"(
     38 Sources:
     39 )"sv;
     40 
     41 constexpr std::string_view s_DefaultSourcesTombstoned = R"(
     42 Sources:
     43   - Name: winget
     44     Type: ""
     45     Arg: ""
     46     Data: ""
     47     IsTombstone: true
     48   - Name: msstore
     49     Type: ""
     50     Arg: ""
     51     Data: ""
     52     IsTombstone: true
     53 )"sv;
     54 
     55 constexpr std::string_view s_SingleSource = R"(
     56 Sources:
     57   - Name: testName
     58     Type: testType
     59     Arg: testArg
     60     Data: testData
     61     IsTombstone: false
     62 )"sv;
     63 
     64 constexpr std::string_view s_SingleSourceMetadata = R"(
     65 Sources:
     66   - Name: testName
     67     LastUpdate: 100
     68 )"sv;
     69 
     70 constexpr std::string_view s_SingleSourceMetadataUpdate = R"(
     71 Sources:
     72   - Name: testName
     73     LastUpdate: 101
     74 )"sv;
     75 
     76 constexpr std::string_view s_DoubleSource = R"(
     77 Sources:
     78   - Name: testName
     79     Type: testType
     80     Arg: testArg
     81     Data: testData
     82     IsTombstone: false
     83   - Name: testName2
     84     Type: testType
     85     Arg: testArg2
     86     Data: testData2
     87     IsTombstone: false
     88 )"sv;
     89 
     90 constexpr std::string_view s_DoubleSourceMetadata = R"(
     91 Sources:
     92   - Name: testName
     93     LastUpdate: 100
     94   - Name: testName2
     95     LastUpdate: 200
     96 )"sv;
     97 
     98 constexpr std::string_view s_ThreeSources = R"(
     99 Sources:
    100   - Name: testName
    101     Type: testType
    102     Arg: testArg
    103     Data: testData
    104     IsTombstone: false
    105   - Name: testName2
    106     Type: testType2
    107     Arg: testArg2
    108     Data: testData2
    109     IsTombstone: false
    110   - Name: testName3
    111     Type: testType3
    112     Arg: testArg3
    113     Data: testData3
    114     IsTombstone: false
    115   - Name: winget
    116     Type: ""
    117     Arg: ""
    118     Data: ""
    119     IsTombstone: true
    120   - Name: msstore
    121     Type: ""
    122     Arg: ""
    123     Data: ""
    124     IsTombstone: true
    125 )"sv;
    126 
    127 constexpr std::string_view s_ThreeSourcesMetadata = R"(
    128 Sources:
    129   - Name: testName
    130     LastUpdate: 0
    131   - Name: testName2
    132     LastUpdate: 1
    133   - Name: testName3
    134     LastUpdate: 2
    135 )"sv;
    136 
    137 constexpr std::string_view s_SingleSource_MissingArg = R"(
    138 Sources:
    139   - Name: testName
    140     Type: testType
    141     Data: testData
    142     IsTombstone: false
    143 )"sv;
    144 
    145 constexpr std::string_view s_TwoSource_AggregateSourceTest = R"(
    146 Sources:
    147   - Name: winget
    148     Type: testType
    149     Arg: testArg
    150     Data: testData
    151     IsTombstone: false
    152   - Name: msstore
    153     Type: testType
    154     Arg: testArg
    155     Data: testData
    156     IsTombstone: false
    157 )"sv;
    158 
    159 constexpr std::string_view s_DefaultSourceAsUserSource = R"(
    160 Sources:
    161   - Name: not-winget
    162     Type: Microsoft.PreIndexed.Package
    163     Arg: https://cdn.winget.microsoft.com/cache
    164     Data: Microsoft.Winget.Source_8wekyb3d8bbwe
    165     IsTombstone: false
    166 )"sv;
    167 
    168 constexpr std::string_view s_UserSourceNamedLikeDefault = R"(
    169 Sources:
    170   - Name: winget
    171     Type: testType
    172     Arg: testArg
    173     Data: testData
    174     IsTombstone: false
    175 )"sv;
    176 
    177 constexpr std::string_view s_SingleSource_TrustLevels_Explicit= R"(
    178 Sources:
    179   - Name: testName
    180     Type: testType
    181     Arg: testArg
    182     Data: testData
    183     IsTombstone: false
    184     TrustLevel: 3
    185     Explicit: true
    186 )"sv;
    187 
    188 namespace
    189 {
    190     // Helper to create a simple source.
    191     struct SourcesTestSource : public TestSource
    192     {
    193         SourcesTestSource() = default;
    194         SourcesTestSource(const SourceDetails& details)
    195         {
    196             Details = details;
    197         }
    198 
    199         static std::shared_ptr<ISource> Create(const SourceDetails& details)
    200         {
    201             // using return std::make_shared<TestSource>(details); will crash the x86 test during destruction.
    202             return std::shared_ptr<ISource>(new SourcesTestSource(details));
    203         }
    204 
    205         SearchResult Search(const SearchRequest&) const override
    206         {
    207             SearchResult result;
    208             PackageMatchFilter testMatchFilter1{ PackageMatchField::Id, MatchType::Exact, "test" };
    209             PackageMatchFilter testMatchFilter2{ PackageMatchField::Name, MatchType::Exact, "test" };
    210             PackageMatchFilter testMatchFilter3{ PackageMatchField::Id, MatchType::CaseInsensitive, "test" };
    211             result.Matches.emplace_back(nullptr, testMatchFilter1);
    212             result.Matches.emplace_back(nullptr, testMatchFilter2);
    213             result.Matches.emplace_back(nullptr, testMatchFilter3);
    214             return result;
    215         }
    216     };
    217 
    218     // Failing source for use with s_TwoSource_AggregateSourceTest
    219     struct FailingSourcesTestSource : public TestSource
    220     {
    221         static constexpr HRESULT FailingHR = 0xBADDAD0D;
    222 
    223         FailingSourcesTestSource() = default;
    224         FailingSourcesTestSource(const SourceDetails& details)
    225         {
    226             Details = details;
    227         }
    228 
    229         static std::shared_ptr<ISource> CreateFailWinget(const SourceDetails& details)
    230         {
    231             if (details.Name == "winget")
    232             {
    233                 THROW_HR(FailingHR);
    234             }
    235 
    236             return std::shared_ptr<ISource>(new FailingSourcesTestSource(details));
    237         }
    238 
    239         static std::shared_ptr<ISource> CreateFailAll(const SourceDetails&)
    240         {
    241             THROW_HR(FailingHR);
    242         }
    243     };
    244 
    245     void RequireDefaultSourcesAt(const std::vector<SourceDetails>& sources, size_t index)
    246     {
    247         REQUIRE(sources.size() >= index + c_DefaultSourceCount);
    248 
    249         for (size_t i = index; i < sources.size(); ++i)
    250         {
    251             INFO(i);
    252             REQUIRE(sources[i].Origin == SourceOrigin::Default);
    253         }
    254     }
    255 }
    256 
    257 
    258 TEST_CASE("RepoSources_UserSettingDoesNotExist", "[sources]")
    259 {
    260     RemoveSetting(Stream::UserSources);
    261 
    262     std::vector<SourceDetails> sources = GetSources();
    263     REQUIRE(sources.size() == c_DefaultSourceCount);
    264     RequireDefaultSourcesAt(sources, 0);
    265 }
    266 
    267 TEST_CASE("RepoSources_EmptySourcesList", "[sources]")
    268 {
    269     SetSetting(Stream::UserSources, s_EmptySources);
    270 
    271     std::vector<SourceDetails> sources = GetSources();
    272     REQUIRE(sources.size() == c_DefaultSourceCount);
    273     RequireDefaultSourcesAt(sources, 0);
    274 }
    275 
    276 TEST_CASE("RepoSources_DefaultSourcesTombstoned", "[sources]")
    277 {
    278     SetSetting(Stream::UserSources, s_DefaultSourcesTombstoned);
    279 
    280     std::vector<SourceDetails> sources = GetSources();
    281     REQUIRE(sources.empty());
    282 }
    283 
    284 TEST_CASE("RepoSources_SingleSource", "[sources]")
    285 {
    286     SetSetting(Stream::UserSources, s_SingleSource);
    287     RemoveSetting(Stream::SourcesMetadata);
    288 
    289     std::vector<SourceDetails> sources = GetSources();
    290     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    291 
    292     REQUIRE(sources[0].Name == "testName");
    293     REQUIRE(sources[0].Type == "testType");
    294     REQUIRE(sources[0].Arg == "testArg");
    295     REQUIRE(sources[0].Data == "testData");
    296     REQUIRE(sources[0].Origin == SourceOrigin::User);
    297     REQUIRE(sources[0].LastUpdateTime == ConvertUnixEpochToSystemClock(0));
    298 
    299     RequireDefaultSourcesAt(sources, 1);
    300 }
    301 
    302 TEST_CASE("RepoSources_SingleSource_TrustLevel_Explicit", "[sources]")
    303 {
    304     SetSetting(Stream::UserSources, s_SingleSource_TrustLevels_Explicit);
    305     RemoveSetting(Stream::SourcesMetadata);
    306 
    307     std::vector<SourceDetails> sources = GetSources();
    308     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    309 
    310     REQUIRE(sources[0].Name == "testName");
    311     REQUIRE(sources[0].Type == "testType");
    312     REQUIRE(sources[0].Arg == "testArg");
    313     REQUIRE(sources[0].Data == "testData");
    314     REQUIRE(sources[0].Origin == SourceOrigin::User);
    315     REQUIRE(sources[0].Explicit == true);
    316     REQUIRE(WI_IsFlagSet(sources[0].TrustLevel, SourceTrustLevel::Trusted));
    317     REQUIRE(WI_IsFlagSet(sources[0].TrustLevel, SourceTrustLevel::StoreOrigin));
    318     REQUIRE(sources[0].LastUpdateTime == ConvertUnixEpochToSystemClock(0));
    319 
    320     RequireDefaultSourcesAt(sources, 1);
    321 }
    322 
    323 TEST_CASE("RepoSources_ThreeSources", "[sources]")
    324 {
    325     SetSetting(Stream::UserSources, s_ThreeSources);
    326     SetSetting(Stream::SourcesMetadata, s_ThreeSourcesMetadata);
    327 
    328     std::vector<SourceDetails> sources = GetSources();
    329     REQUIRE(sources.size() == 3);
    330 
    331     const char* suffix[3] = { "", "2", "3" };
    332 
    333     for (size_t i = 0; i < 3; ++i)
    334     {
    335         INFO("Source #" << i);
    336         REQUIRE(sources[i].Name == "testName"s + suffix[i]);
    337         REQUIRE(sources[i].Type == "testType"s + suffix[i]);
    338         REQUIRE(sources[i].Arg == "testArg"s + suffix[i]);
    339         REQUIRE(sources[i].Data == "testData"s + suffix[i]);
    340         REQUIRE(sources[i].LastUpdateTime == ConvertUnixEpochToSystemClock(i));
    341         REQUIRE(sources[i].Origin == SourceOrigin::User);
    342     }
    343 }
    344 
    345 TEST_CASE("RepoSources_InvalidYAML", "[sources]")
    346 {
    347     SetSetting(Stream::UserSources, "Name: Value : BAD");
    348 
    349     REQUIRE_NOTHROW(GetSources());
    350 }
    351 
    352 TEST_CASE("RepoSources_MissingField", "[sources]")
    353 {
    354     SetSetting(Stream::UserSources, s_SingleSource_MissingArg);
    355 
    356     REQUIRE_NOTHROW(GetSources());
    357 }
    358 
    359 TEST_CASE("RepoSources_AddSource", "[sources]")
    360 {
    361     SetSetting(Stream::UserSources, s_EmptySources);
    362     TestHook_ClearSourceFactoryOverrides();
    363 
    364     SourceDetails details;
    365     details.Name = "thisIsTheName";
    366     details.Type = "thisIsTheType";
    367     details.Arg = "thisIsTheArg";
    368     details.Data = "thisIsTheData";
    369     details.TrustLevel = Repository::SourceTrustLevel::None;
    370     details.Explicit = false;
    371 
    372     bool addCalledOnFactory = false;
    373     TestSourceFactory factory{ SourcesTestSource::Create };
    374     factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; };
    375     TestHook_SetSourceFactoryOverride(details.Type, factory);
    376 
    377     ProgressCallback progress;
    378     AddSource(details, progress);
    379 
    380     REQUIRE(addCalledOnFactory);
    381 
    382     std::vector<SourceDetails> sources = GetSources();
    383     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    384 
    385     REQUIRE(sources[0].Name == details.Name);
    386     REQUIRE(sources[0].Type == details.Type);
    387     REQUIRE(sources[0].Arg == details.Arg);
    388     REQUIRE(sources[0].Data == details.Data);
    389     REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0));
    390     REQUIRE(sources[0].Origin == SourceOrigin::User);
    391     REQUIRE(sources[0].TrustLevel == details.TrustLevel);
    392     REQUIRE(sources[0].Explicit == details.Explicit);
    393 
    394     RequireDefaultSourcesAt(sources, 1);
    395 }
    396 
    397 TEST_CASE("RepoSources_AddMultipleSources", "[sources]")
    398 {
    399     SetSetting(Stream::UserSources, s_EmptySources);
    400 
    401     SourceDetails details;
    402     details.Name = "thisIsTheName";
    403     details.Type = "thisIsTheType";
    404     details.Arg = "thisIsTheArg";
    405     details.Data = "thisIsTheData";
    406 
    407     const char* suffix[2] = { "", "2" };
    408 
    409     TestSourceFactory factory1{ SourcesTestSource::Create };
    410     factory1.OnAdd = [&](SourceDetails& sd) { sd.Data = details.Data; };
    411     TestHook_SetSourceFactoryOverride(details.Type, factory1);
    412 
    413     ProgressCallback progress;
    414     AddSource(details, progress);
    415 
    416     std::vector<SourceDetails> sources = GetSources();
    417     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    418 
    419     REQUIRE(sources[0].Name == details.Name);
    420     REQUIRE(sources[0].Type == details.Type);
    421     REQUIRE(sources[0].Arg == details.Arg);
    422     REQUIRE(sources[0].Data == details.Data);
    423     REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0));
    424     REQUIRE(sources[0].Origin == SourceOrigin::User);
    425 
    426     RequireDefaultSourcesAt(sources, 1);
    427 
    428     SourceDetails details2;
    429     details2.Name = details.Name + suffix[1];
    430     details2.Type = details.Type + suffix[1];
    431     details2.Arg = details.Arg + suffix[1];
    432     details2.Data = details.Data + suffix[1];
    433     TestSourceFactory factory2{ SourcesTestSource::Create };
    434     factory2.OnAdd = [&](SourceDetails& sd) { sd.Data = details2.Data; };
    435     TestHook_SetSourceFactoryOverride(details2.Type, factory2);
    436 
    437     AddSource(details2, progress);
    438 
    439     sources = GetSources();
    440     REQUIRE(sources.size() == c_DefaultSourceCount + 2);
    441 
    442     for (size_t i = 0; i < 2; ++i)
    443     {
    444         INFO("Source #" << i);
    445         REQUIRE(sources[i].Name == details.Name + suffix[i]);
    446         REQUIRE(sources[i].Type == details.Type + suffix[i]);
    447         REQUIRE(sources[i].Arg == details.Arg + suffix[i]);
    448         REQUIRE(sources[i].Data == details.Data + suffix[i]);
    449         REQUIRE(sources[i].LastUpdateTime != ConvertUnixEpochToSystemClock(0));
    450         REQUIRE(sources[i].Origin == SourceOrigin::User);
    451     }
    452 
    453     RequireDefaultSourcesAt(sources, 2);
    454 }
    455 
    456 TEST_CASE("RepoSources_UpdateSource", "[sources]")
    457 {
    458     using namespace std::chrono_literals;
    459 
    460     SetSetting(Stream::UserSources, s_EmptySources);
    461     TestHook_ClearSourceFactoryOverrides();
    462 
    463     SourceDetails details;
    464     details.Name = "thisIsTheName";
    465     details.Type = "thisIsTheType";
    466     details.Arg = "thisIsTheArg";
    467     details.Data = "thisIsTheData";
    468 
    469     bool addCalledOnFactory = false;
    470     TestSourceFactory factory{ SourcesTestSource::Create };
    471     factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; };
    472     TestHook_SetSourceFactoryOverride(details.Type, factory);
    473 
    474     ProgressCallback progress;
    475     AddSource(details, progress);
    476 
    477     REQUIRE(addCalledOnFactory);
    478 
    479     std::vector<SourceDetails> sources = GetSources();
    480     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    481 
    482     REQUIRE(sources[0].Name == details.Name);
    483     REQUIRE(sources[0].Type == details.Type);
    484     REQUIRE(sources[0].Arg == details.Arg);
    485     REQUIRE(sources[0].Data == details.Data);
    486     REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0));
    487     REQUIRE(sources[0].Origin == SourceOrigin::User);
    488 
    489     RequireDefaultSourcesAt(sources, 1);
    490 
    491     // Reset for a call to update
    492     bool updateCalledOnFactory = false;
    493     auto now = std::chrono::system_clock::now();
    494     factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; };
    495 
    496     UpdateSource(details.Name, progress);
    497 
    498     REQUIRE(updateCalledOnFactory);
    499 
    500     sources = GetSources();
    501     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    502 
    503     REQUIRE(sources[0].Name == details.Name);
    504     REQUIRE(sources[0].Type == details.Type);
    505     REQUIRE(sources[0].Arg == details.Arg);
    506     REQUIRE(sources[0].Data == details.Data);
    507     REQUIRE((now - sources[0].LastUpdateTime) < 1s);
    508 }
    509 
    510 TEST_CASE("RepoSources_UpdateSourceRetries", "[sources]")
    511 {
    512     using namespace std::chrono_literals;
    513 
    514     SetSetting(Stream::UserSources, s_EmptySources);
    515     TestHook_ClearSourceFactoryOverrides();
    516 
    517     SourceDetails details;
    518     details.Name = "thisIsTheName";
    519     details.Type = "thisIsTheType";
    520     details.Arg = "thisIsTheArg";
    521     details.Data = "thisIsTheData";
    522 
    523     TestSourceFactory factory{ SourcesTestSource::Create };
    524     factory.OnAdd = [&](SourceDetails& sd) { sd.Data = details.Data; };
    525     TestHook_SetSourceFactoryOverride(details.Type, factory);
    526 
    527     ProgressCallback progress;
    528     AddSource(details, progress);
    529 
    530     // Reset for a call to update
    531     bool updateShouldThrow = false;
    532     bool updateCalledOnFactoryAgain = false;
    533     factory.OnUpdate = [&](const SourceDetails&)
    534     {
    535         if (updateShouldThrow)
    536         {
    537             updateShouldThrow = false;
    538             THROW_HR(E_ACCESSDENIED);
    539         }
    540         updateCalledOnFactoryAgain = true;
    541     };
    542 
    543     UpdateSource(details.Name, progress);
    544 
    545     REQUIRE(updateCalledOnFactoryAgain);
    546 }
    547 
    548 TEST_CASE("RepoSources_RemoveSource", "[sources]")
    549 {
    550     SetSetting(Stream::UserSources, s_EmptySources);
    551     TestHook_ClearSourceFactoryOverrides();
    552 
    553     SourceDetails details;
    554     details.Name = "thisIsTheName";
    555     details.Type = "thisIsTheType";
    556     details.Arg = "thisIsTheArg";
    557     details.Data = "thisIsTheData";
    558 
    559     bool removeCalledOnFactory = false;
    560     TestSourceFactory factory{ SourcesTestSource::Create };
    561     factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; };
    562     TestHook_SetSourceFactoryOverride(details.Type, factory);
    563 
    564     ProgressCallback progress;
    565     AddSource(details, progress);
    566 
    567     std::vector<SourceDetails> sources = GetSources();
    568     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    569 
    570     RemoveSource(details.Name, progress);
    571 
    572     REQUIRE(removeCalledOnFactory);
    573 
    574     sources = GetSources();
    575     REQUIRE(sources.size() == c_DefaultSourceCount);
    576 }
    577 
    578 TEST_CASE("RepoSources_RemoveDefaultSource", "[sources]")
    579 {
    580     SetSetting(Stream::UserSources, s_EmptySources);
    581     TestHook_ClearSourceFactoryOverrides();
    582 
    583     std::vector<SourceDetails> sources = GetSources();
    584     REQUIRE(sources.size() == c_DefaultSourceCount);
    585     REQUIRE(sources[0].Origin == SourceOrigin::Default);
    586 
    587     bool removeCalledOnFactory = false;
    588     TestSourceFactory factory{ SourcesTestSource::Create };
    589     factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; };
    590     TestHook_SetSourceFactoryOverride(sources[0].Type, factory);
    591 
    592     ProgressCallback progress;
    593 
    594     RemoveSource(sources[0].Name, progress);
    595 
    596     REQUIRE(removeCalledOnFactory);
    597 
    598     sources = GetSources();
    599     REQUIRE(sources.size() == c_DefaultSourceCount - 1);
    600 }
    601 
    602 TEST_CASE("RepoSources_UpdateOnOpen", "[sources]")
    603 {
    604     using namespace std::chrono_literals;
    605 
    606     TestHook_ClearSourceFactoryOverrides();
    607 
    608     std::string name = "testName";
    609     std::string type = "testType";
    610     std::string arg = "testArg";
    611     std::string data = "testData";
    612 
    613     bool updateCalledOnFactory = false;
    614     TestSourceFactory factory{ SourcesTestSource::Create };
    615     factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; };
    616     factory.ShouldUpdateBeforeOpenResult = true;
    617     TestHook_SetSourceFactoryOverride(type, factory);
    618 
    619     SetSetting(Stream::UserSources, s_SingleSource);
    620 
    621     ProgressCallback progress;
    622     auto source = OpenSource(name, progress);
    623 
    624     REQUIRE(updateCalledOnFactory);
    625 
    626     std::vector<SourceDetails> sources = GetSources();
    627     REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    628 
    629     REQUIRE(sources[0].Name == name);
    630     REQUIRE(sources[0].Type == type);
    631     REQUIRE(sources[0].Arg == arg);
    632     REQUIRE(sources[0].Data == data);
    633     REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0));
    634 }
    635 
    636 TEST_CASE("RepoSources_DropSourceByName", "[sources]")
    637 {
    638     SetSetting(Stream::UserSources, s_ThreeSources);
    639     SetSetting(Stream::SourcesMetadata, s_ThreeSourcesMetadata);
    640 
    641     std::vector<SourceDetails> sources = GetSources();
    642     REQUIRE(sources.size() == 3);
    643 
    644     DropSource("testName");
    645 
    646     sources = GetSources();
    647     REQUIRE(sources.size() == 2);
    648 
    649     const char* suffix[2] = { "2", "3" };
    650 
    651     for (size_t i = 0; i < 2; ++i)
    652     {
    653         INFO("Source #" << i);
    654         REQUIRE(sources[i].Name == "testName"s + suffix[i]);
    655         REQUIRE(sources[i].Type == "testType"s + suffix[i]);
    656         REQUIRE(sources[i].Arg == "testArg"s + suffix[i]);
    657         REQUIRE(sources[i].Data == "testData"s + suffix[i]);
    658         REQUIRE(sources[i].LastUpdateTime == ConvertUnixEpochToSystemClock(i + 1));
    659         REQUIRE(sources[i].Origin == SourceOrigin::User);
    660     }
    661 }
    662 
    663 TEST_CASE("RepoSources_DropAllSources", "[sources]")
    664 {
    665     SetSetting(Stream::UserSources, s_ThreeSources);
    666 
    667     std::vector<SourceDetails> sources = GetSources();
    668     REQUIRE(sources.size() == 3);
    669 
    670     DropSource({});
    671 
    672     sources = GetSources();
    673     REQUIRE(sources.size() == c_DefaultSourceCount);
    674     REQUIRE(sources[0].Origin == SourceOrigin::Default);
    675 }
    676 
    677 TEST_CASE("RepoSources_SearchAcrossMultipleSources", "[sources]")
    678 {
    679     TestHook_ClearSourceFactoryOverrides();
    680     TestSourceFactory factory{ SourcesTestSource::Create };
    681     TestHook_SetSourceFactoryOverride("testType", factory);
    682 
    683     SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest);
    684 
    685     ProgressCallback progress;
    686     auto source = OpenSource("", progress);
    687 
    688     SearchRequest request;
    689     auto result = source.Search(request);
    690     REQUIRE(result.Matches.size() == 6);
    691     REQUIRE_FALSE(result.Truncated);
    692     // matches are sorted in expected order
    693     REQUIRE((result.Matches[0].MatchCriteria.Type == MatchType::Exact && result.Matches[0].MatchCriteria.Field == PackageMatchField::Id));
    694     REQUIRE((result.Matches[1].MatchCriteria.Type == MatchType::Exact && result.Matches[1].MatchCriteria.Field == PackageMatchField::Id));
    695     REQUIRE((result.Matches[2].MatchCriteria.Type == MatchType::Exact && result.Matches[2].MatchCriteria.Field == PackageMatchField::Name));
    696     REQUIRE((result.Matches[3].MatchCriteria.Type == MatchType::Exact && result.Matches[3].MatchCriteria.Field == PackageMatchField::Name));
    697     REQUIRE((result.Matches[4].MatchCriteria.Type == MatchType::CaseInsensitive && result.Matches[4].MatchCriteria.Field == PackageMatchField::Id));
    698     REQUIRE((result.Matches[5].MatchCriteria.Type == MatchType::CaseInsensitive && result.Matches[5].MatchCriteria.Field == PackageMatchField::Id));
    699 
    700     // when truncate required
    701     request.MaximumResults = 3;
    702     result = source.Search(request);
    703     REQUIRE(result.Matches.size() == 3);
    704     REQUIRE(result.Truncated);
    705     // matches are sorted in expected order
    706     REQUIRE((result.Matches[0].MatchCriteria.Type == MatchType::Exact && result.Matches[0].MatchCriteria.Field == PackageMatchField::Id));
    707     REQUIRE((result.Matches[1].MatchCriteria.Type == MatchType::Exact && result.Matches[1].MatchCriteria.Field == PackageMatchField::Id));
    708     REQUIRE((result.Matches[2].MatchCriteria.Type == MatchType::Exact && result.Matches[2].MatchCriteria.Field == PackageMatchField::Name));
    709 }
    710 
    711 TEST_CASE("RepoSources_GroupPolicy_DefaultSource", "[sources][groupPolicy]")
    712 {
    713     WHEN("Default source is disabled")
    714     {
    715         GroupPolicyTestOverride policies;
    716         policies.SetState(TogglePolicy::Policy::DefaultSource, PolicyState::Disabled);
    717 
    718         SECTION("Get source")
    719         {
    720             // Listing the sources should not return the default.
    721             SetSetting(Stream::UserSources, s_EmptySources);
    722 
    723             auto sources = GetSources();
    724             REQUIRE(sources.size() == c_DefaultSourceCount - 1);
    725         }
    726         SECTION("Add default source")
    727         {
    728             // We should not be able to add the default source manually.
    729             SetSetting(Stream::UserSources, s_EmptySources);
    730 
    731             ProgressCallback progress;
    732             SourceDetails details;
    733             details.Name = "winget";
    734             details.Type = "Microsoft.PreIndexed.Package";
    735             details.Arg = "https://cdn.winget.microsoft.com/cache";
    736             REQUIRE_POLICY_EXCEPTION(
    737                 AddSource(details, progress),
    738                 TogglePolicy::Policy::DefaultSource);
    739         }
    740         SECTION("Ignore default source from user")
    741         {
    742             // We should ignore any existing user source that is the same as the default.
    743             SetSetting(Stream::UserSources, s_DefaultSourceAsUserSource);
    744 
    745             auto sources = GetSources();
    746             REQUIRE(sources.size() == c_DefaultSourceCount - 1);
    747         }
    748         SECTION("Add same-name source from user")
    749         {
    750             // We should allow adding sources with the same name as the default but
    751             // pointing somewhere else.
    752             SetSetting(Stream::UserSources, s_EmptySources);
    753             TestHook_ClearSourceFactoryOverrides();
    754 
    755             SourceDetails details;
    756             details.Name = "winget";
    757             details.Type = "someType";
    758             details.Arg = "notWingetRealArg";
    759             details.Data = "someData";
    760 
    761             bool addCalledOnFactory = false;
    762             TestSourceFactory factory{ SourcesTestSource::Create };
    763             factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; };
    764             TestHook_SetSourceFactoryOverride(details.Type, factory);
    765 
    766             ProgressCallback progress;
    767             AddSource(details, progress);
    768 
    769             REQUIRE(addCalledOnFactory);
    770 
    771             auto sources = GetSources();
    772             REQUIRE(sources.size() == c_DefaultSourceCount);
    773 
    774             REQUIRE(sources[0].Name == details.Name);
    775             REQUIRE(sources[0].Type == details.Type);
    776             REQUIRE(sources[0].Arg == details.Arg);
    777             REQUIRE(sources[0].Data == details.Data);
    778             REQUIRE(sources[0].Origin == SourceOrigin::User);
    779         }
    780         SECTION("Allow same name source from user")
    781         {
    782             // We should respect existing user sources with the same name.
    783             // We should allow adding sources with the same name as the default but
    784             // pointing somewhere else.
    785             SetSetting(Stream::UserSources, s_UserSourceNamedLikeDefault);
    786 
    787             auto sources = GetSources();
    788             REQUIRE(sources.size() == c_DefaultSourceCount);
    789 
    790             REQUIRE(sources[0].Name == "winget");
    791             REQUIRE(sources[0].Type == "testType");
    792             REQUIRE(sources[0].Arg == "testArg");
    793             REQUIRE(sources[0].Data == "testData");
    794             REQUIRE(sources[0].Origin == SourceOrigin::User);
    795         }
    796     }
    797 
    798     WHEN("Default source is enabled")
    799     {
    800         GroupPolicyTestOverride policies;
    801         policies.SetState(TogglePolicy::Policy::DefaultSource, PolicyState::Enabled);
    802 
    803         SECTION("Remove source is blocked")
    804         {
    805             // We should not be able to remove the default source.
    806             SetSetting(Stream::UserSources, s_EmptySources);
    807 
    808             ProgressCallback progress;
    809             REQUIRE_POLICY_EXCEPTION(
    810                 RemoveSource("winget", progress),
    811                 TogglePolicy::Policy::DefaultSource);
    812         }
    813         SECTION("Tombstone is overridden")
    814         {
    815             // We should ignore if the default source was already deleted.
    816             SetSetting(Stream::UserSources, s_DefaultSourcesTombstoned);
    817 
    818             auto sources = GetSources();
    819             REQUIRE(sources.size() == 1);
    820             REQUIRE(sources[0].Name == "winget");
    821             REQUIRE(sources[0].Origin == SourceOrigin::Default);
    822         }
    823         SECTION("Same name source is overridden")
    824         {
    825             // We should ignore existing user sources with the same name as the default.
    826             SetSetting(Stream::UserSources, s_UserSourceNamedLikeDefault);
    827 
    828             auto sources = GetSources();
    829             REQUIRE(sources.size() == c_DefaultSourceCount);
    830 
    831             REQUIRE(sources[1].Name == "winget");
    832             REQUIRE(sources[1].Arg == "https://cdn.winget.microsoft.com/cache");
    833             REQUIRE(sources[1].Origin == SourceOrigin::Default);
    834         }
    835     }
    836 }
    837 
    838 TEST_CASE("RepoSources_GroupPolicy_AdditionalSources", "[sources][groupPolicy]")
    839 {
    840     WHEN("Additional sources are enabled")
    841     {
    842         GroupPolicyTestOverride policies;
    843         policies.SetState(TogglePolicy::Policy::AdditionalSources, PolicyState::Enabled);
    844 
    845         SECTION("Additional sources are listed")
    846         {
    847             // Getting the current sources should list the additional sources.
    848             std::vector<SourceFromPolicy> policySources;
    849             const std::string suffix[3] = { "", "2", "3" };
    850             for (size_t i = 0; i < 3; ++i)
    851             {
    852                 SourceFromPolicy source;
    853                 source.Name = "name" + suffix[i];
    854                 source.Type = "type" + suffix[i];
    855                 source.Arg = "arg" + suffix[i];
    856                 source.Data = "data" + suffix[i];
    857                 source.Identifier = "id" + suffix[i];
    858                 policySources.emplace_back(std::move(source));
    859             }
    860 
    861             policies.SetValue<ValuePolicy::AdditionalSources>(policySources);
    862             SetSetting(Stream::UserSources, s_EmptySources);
    863 
    864             auto sources = GetSources();
    865 
    866             // The source list includes the default source
    867             REQUIRE(sources.size() == policySources.size() + c_DefaultSourceCount);
    868             REQUIRE(sources.back().Origin == SourceOrigin::Default);
    869 
    870             for (size_t i = 0; i < policySources.size(); ++i)
    871             {
    872                 REQUIRE(sources[i].Name == policySources[i].Name);
    873                 REQUIRE(sources[i].Type == policySources[i].Type);
    874                 REQUIRE(sources[i].Arg == policySources[i].Arg);
    875                 REQUIRE(sources[i].Data == policySources[i].Data);
    876                 REQUIRE(sources[i].Identifier == policySources[i].Identifier);
    877                 REQUIRE(sources[i].Origin == SourceOrigin::GroupPolicy);
    878             }
    879         }
    880         SECTION("Same-name user source is overridden")
    881         {
    882             // User sources with the same name as an additional source are ignored.
    883             SourceFromPolicy policySource;
    884             policySource.Name = "testName";
    885             policySource.Type = "notTestType";
    886             policySource.Arg = "notTestArg";
    887             policySource.Data = "notTestData";
    888             policySource.Identifier = "notTestId";
    889 
    890             policies.SetValue<ValuePolicy::AdditionalSources>({ policySource });
    891             SetSetting(Stream::UserSources, s_SingleSource);
    892 
    893             auto sources = GetSources();
    894 
    895             // The source list includes the default source
    896             REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    897             REQUIRE(sources[1].Origin == SourceOrigin::Default);
    898 
    899             REQUIRE(sources[0].Name == policySource.Name);
    900             REQUIRE(sources[0].Type == policySource.Type);
    901             REQUIRE(sources[0].Arg == policySource.Arg);
    902             REQUIRE(sources[0].Data == policySource.Data);
    903             REQUIRE(sources[0].Identifier == policySource.Identifier);
    904             REQUIRE(sources[0].Origin == SourceOrigin::GroupPolicy);
    905         }
    906         SECTION("Cannot remove additional source")
    907         {
    908             // An additional source cannot be removed.
    909             SourceFromPolicy policySource;
    910             policySource.Name = "name";
    911             policySource.Type = "type";
    912             policySource.Arg = "arg";
    913             policySource.Data = "data";
    914             policySource.Identifier = "id";
    915 
    916             bool removeCalledOnFactory = false;
    917             TestSourceFactory factory{ SourcesTestSource::Create };
    918             factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; };
    919             TestHook_SetSourceFactoryOverride(policySource.Type, factory);
    920 
    921             policies.SetValue<ValuePolicy::AdditionalSources>({ policySource });
    922             SetSetting(Stream::UserSources, s_EmptySources);
    923 
    924             ProgressCallback progress;
    925             REQUIRE_POLICY_EXCEPTION(
    926                 RemoveSource(policySource.Name, progress),
    927                 TogglePolicy::Policy::AdditionalSources);
    928             REQUIRE_FALSE(removeCalledOnFactory);
    929         }
    930         SECTION("Additional source overrides default")
    931         {
    932             // An additional source with the same name as a default overrides it.
    933             SourceFromPolicy policySource;
    934             policySource.Name = "winget";
    935             policySource.Type = "notDefaultType";
    936             policySource.Arg = "notDefaultArg";
    937             policySource.Data = "notDefaultData";
    938             policySource.Identifier = "notDefaultId";
    939 
    940             policies.SetValue<ValuePolicy::AdditionalSources>({ policySource });
    941             SetSetting(Stream::UserSources, s_EmptySources);
    942 
    943             auto sources = GetSources();
    944 
    945             REQUIRE(sources.size() == c_DefaultSourceCount);
    946             REQUIRE(sources[0].Name == policySource.Name);
    947             REQUIRE(sources[0].Type == policySource.Type);
    948             REQUIRE(sources[0].Arg == policySource.Arg);
    949             REQUIRE(sources[0].Data == policySource.Data);
    950             REQUIRE(sources[0].Identifier == policySource.Identifier);
    951             REQUIRE(sources[0].Origin == SourceOrigin::GroupPolicy);
    952         }
    953     }
    954 }
    955 
    956 TEST_CASE("RepoSources_GroupPolicy_AllowedSources", "[sources][groupPolicy]")
    957 {
    958     WHEN("Allowed sources are enabled")
    959     {
    960         GroupPolicyTestOverride policies;
    961         policies.SetState(TogglePolicy::Policy::AllowedSources, PolicyState::Enabled);
    962 
    963         SECTION("Add allowed source")
    964         {
    965             // We should be able to add sources in the allow list.
    966             SourceFromPolicy policySource;
    967             policySource.Name = "testName";
    968             policySource.Type = "testType";
    969             policySource.Arg = "testArg";
    970             policySource.Data = "testData";
    971             policySource.Identifier = "testId";
    972 
    973             policies.SetValue<ValuePolicy::AllowedSources>({ policySource });
    974             SetSetting(Stream::UserSources, s_EmptySources);
    975             TestHook_ClearSourceFactoryOverrides();
    976 
    977             bool addCalledOnFactory = false;
    978             TestSourceFactory factory{ SourcesTestSource::Create };
    979             factory.OnAdd = [&](SourceDetails& sd)
    980             {
    981                 addCalledOnFactory = true;
    982                 sd.Data = policySource.Data;
    983                 sd.Identifier = policySource.Identifier;
    984             };
    985             TestHook_SetSourceFactoryOverride(policySource.Type, factory);
    986 
    987             ProgressCallback progress;
    988             SourceDetails details;
    989             details.Name = policySource.Name;
    990             details.Type = policySource.Type;
    991             details.Arg = policySource.Arg;
    992             AddSource(details, progress);
    993 
    994             REQUIRE(addCalledOnFactory);
    995 
    996             // The source list includes the default source
    997             auto sources = GetSources();
    998             REQUIRE(sources.size() == c_DefaultSourceCount + 1);
    999             REQUIRE(sources[1].Origin == SourceOrigin::Default);
   1000 
   1001             REQUIRE(sources[0].Name == policySource.Name);
   1002             REQUIRE(sources[0].Type == policySource.Type);
   1003             REQUIRE(sources[0].Arg == policySource.Arg);
   1004             REQUIRE(sources[0].Data == policySource.Data);
   1005             REQUIRE(sources[0].Identifier == policySource.Identifier);
   1006             REQUIRE(sources[0].Origin == SourceOrigin::User);
   1007         }
   1008         SECTION("Cannot add non-allowed source")
   1009         {
   1010             // We should not be allowed to add anything not matching the allow list.
   1011             SourceFromPolicy policySource;
   1012             policySource.Name = "testName";
   1013             policySource.Type = "testType";
   1014             policySource.Arg = "testArg";
   1015             policySource.Data = "testData";
   1016             policySource.Identifier = "testId";
   1017 
   1018             policies.SetValue<ValuePolicy::AllowedSources>({ policySource });
   1019             SetSetting(Stream::UserSources, s_EmptySources);
   1020 
   1021             ProgressCallback progress;
   1022             SourceDetails details;
   1023             details.Name = "notAllowed";
   1024             details.Type = "type";
   1025             details.Arg = "arg";
   1026 
   1027             bool addCalledOnFactory = false;
   1028             TestSourceFactory factory{ SourcesTestSource::Create };
   1029             factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; };
   1030             TestHook_SetSourceFactoryOverride(details.Type, factory);
   1031 
   1032             REQUIRE_POLICY_EXCEPTION(
   1033                 AddSource(details, progress),
   1034                 TogglePolicy::Policy::AllowedSources);
   1035             REQUIRE_FALSE(addCalledOnFactory);
   1036         }
   1037     }
   1038 
   1039     WHEN("Allowed sources are disabled")
   1040     {
   1041         GroupPolicyTestOverride policies;
   1042         policies.SetState(TogglePolicy::Policy::AllowedSources, PolicyState::Disabled);
   1043 
   1044         SECTION("Cannot add any source")
   1045         {
   1046             SetSetting(Stream::UserSources, s_EmptySources);
   1047 
   1048             ProgressCallback progress;
   1049             SourceDetails details;
   1050             details.Name = "name";
   1051             details.Type = "type";
   1052             details.Arg = "arg";
   1053 
   1054             bool addCalledOnFactory = false;
   1055             TestSourceFactory factory{ SourcesTestSource::Create };
   1056             factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; };
   1057             TestHook_SetSourceFactoryOverride(details.Type, factory);
   1058 
   1059             REQUIRE_POLICY_EXCEPTION(
   1060                 AddSource(details, progress),
   1061                 TogglePolicy::Policy::AllowedSources);
   1062             REQUIRE_FALSE(addCalledOnFactory);
   1063 
   1064             auto sources = GetSources();
   1065             REQUIRE(sources.size() == c_DefaultSourceCount);
   1066             REQUIRE(sources[0].Origin == SourceOrigin::Default);
   1067         }
   1068         SECTION("Existing sources are ignored")
   1069         {
   1070             SetSetting(Stream::UserSources, s_SingleSource);
   1071 
   1072             auto sources = GetSources();
   1073             REQUIRE(sources.size() == c_DefaultSourceCount);
   1074             REQUIRE(sources[0].Origin == SourceOrigin::Default);
   1075         }
   1076     }
   1077 }
   1078 
   1079 TEST_CASE("RepoSources_OpenMultipleWithSingleFailure", "[sources]")
   1080 {
   1081     TestHook_ClearSourceFactoryOverrides();
   1082     TestSourceFactory factory{ FailingSourcesTestSource::CreateFailWinget };
   1083     TestHook_SetSourceFactoryOverride("testType", factory);
   1084 
   1085     SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest);
   1086 
   1087     ProgressCallback progress;
   1088     auto result = OpenSource("", progress);
   1089 
   1090     REQUIRE(result);
   1091 
   1092     SearchResult searchResult = result.Search({});
   1093 
   1094     REQUIRE(searchResult.Failures.size() == 1);
   1095 
   1096     HRESULT openFailure = S_OK;
   1097     try
   1098     {
   1099         std::rethrow_exception(searchResult.Failures[0].Exception);
   1100     }
   1101     catch (const wil::ResultException& re)
   1102     {
   1103         openFailure = re.GetErrorCode();
   1104     }
   1105     catch (...) {}
   1106 
   1107     REQUIRE(openFailure == FailingSourcesTestSource::FailingHR);
   1108 }
   1109 
   1110 TEST_CASE("RepoSources_OpenMultipleWithTotalFailure", "[sources]")
   1111 {
   1112     TestHook_ClearSourceFactoryOverrides();
   1113     TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll };
   1114     TestHook_SetSourceFactoryOverride("testType", factory);
   1115 
   1116     SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest);
   1117 
   1118     ProgressCallback progress;
   1119     REQUIRE_THROWS_HR(OpenSource("", progress), APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES);
   1120 }
   1121 
   1122 TEST_CASE("RepoSources_UpdateSettingsDuringAction_SourcesUpdate", "[sources]")
   1123 {
   1124     SetSetting(Stream::UserSources, s_SingleSource);
   1125     SetSetting(Stream::SourcesMetadata, s_SingleSourceMetadata);
   1126 
   1127     std::string userSourcesUpdate{ s_DoubleSource };
   1128     std::string sourcesMetadataUpdate{ s_DoubleSourceMetadata };
   1129 
   1130     std::string singleSourceName = "testName";
   1131     std::string doubleSourceName = "testName2";
   1132 
   1133     std::string unusedSourceName = "unusedName";
   1134     std::string unusedSourceArg = "unusedArg";
   1135     std::string testSourceType = "testType";
   1136 
   1137     TestHook_ClearSourceFactoryOverrides();
   1138     TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll };
   1139     auto settingsUpdate = [&](const AppInstaller::Repository::SourceDetails&) 
   1140     {
   1141         SetSetting(Stream::UserSources, userSourcesUpdate);
   1142         SetSetting(Stream::SourcesMetadata, sourcesMetadataUpdate);
   1143     };
   1144     factory.OnAdd = settingsUpdate;
   1145     factory.OnUpdate = settingsUpdate;
   1146     factory.OnRemove = settingsUpdate;
   1147     TestHook_SetSourceFactoryOverride(testSourceType, factory);
   1148 
   1149     ProgressCallback progress;
   1150 
   1151     SECTION("Add")
   1152     {
   1153         SourceDetails addedSource;
   1154         addedSource.Name = unusedSourceName;
   1155         addedSource.Type = testSourceType;
   1156         addedSource.Arg = unusedSourceArg;
   1157         AddSource(addedSource, progress);
   1158 
   1159         auto sources = GetSources();
   1160         REQUIRE(sources.size() == 3 + c_DefaultSourceCount);
   1161 
   1162         REQUIRE(sources[0].Name == singleSourceName);
   1163         REQUIRE(sources[1].Name == doubleSourceName);
   1164         REQUIRE(sources[2].Name == addedSource.Name);
   1165     }
   1166     SECTION("Add conflicting")
   1167     {
   1168         SourceDetails addedSource;
   1169         addedSource.Name = doubleSourceName;
   1170         addedSource.Type = testSourceType;
   1171         addedSource.Arg = unusedSourceArg;
   1172         REQUIRE_THROWS_HR(AddSource(addedSource, progress), APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS);
   1173     }
   1174     SECTION("Update")
   1175     {
   1176         UpdateSource(singleSourceName, progress);
   1177 
   1178         auto sources = GetSources();
   1179         REQUIRE(sources.size() == 2 + c_DefaultSourceCount);
   1180 
   1181         REQUIRE(sources[0].Name == singleSourceName);
   1182         REQUIRE(sources[1].Name == doubleSourceName);
   1183     }
   1184     SECTION("Remove")
   1185     {
   1186         RemoveSource(singleSourceName, progress);
   1187 
   1188         auto sources = GetSources();
   1189         REQUIRE(sources.size() == 1 + c_DefaultSourceCount);
   1190 
   1191         REQUIRE(sources[0].Name == doubleSourceName);
   1192     }
   1193     SECTION("Remove already removed")
   1194     {
   1195         userSourcesUpdate = s_EmptySources;
   1196         sourcesMetadataUpdate = s_EmptySources;
   1197 
   1198         RemoveSource(singleSourceName, progress);
   1199 
   1200         auto sources = GetSources();
   1201         REQUIRE(sources.size() == c_DefaultSourceCount);
   1202     }
   1203 }
   1204 
   1205 TEST_CASE("RepoSources_UpdateSettingsDuringAction_MetadataUpdate", "[sources]")
   1206 {
   1207     SetSetting(Stream::UserSources, s_SingleSource);
   1208     SetSetting(Stream::SourcesMetadata, s_SingleSourceMetadata);
   1209 
   1210     std::string sourcesMetadataUpdate{ s_SingleSourceMetadataUpdate };
   1211     int64_t updateTime = 101;
   1212 
   1213     std::string singleSourceName = "testName";
   1214     std::string doubleSourceName = "testName2";
   1215 
   1216     std::string unusedSourceName = "unusedName";
   1217     std::string unusedSourceArg = "unusedArg";
   1218     std::string testSourceType = "testType";
   1219 
   1220     TestHook_ClearSourceFactoryOverrides();
   1221     TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll };
   1222     auto settingsUpdate = [&](const AppInstaller::Repository::SourceDetails&)
   1223     {
   1224         SetSetting(Stream::SourcesMetadata, sourcesMetadataUpdate);
   1225     };
   1226     factory.OnAdd = settingsUpdate;
   1227     factory.OnUpdate = settingsUpdate;
   1228     factory.OnRemove = settingsUpdate;
   1229     TestHook_SetSourceFactoryOverride(testSourceType, factory);
   1230 
   1231     ProgressCallback progress;
   1232 
   1233     SECTION("Add")
   1234     {
   1235         SourceDetails addedSource;
   1236         addedSource.Name = unusedSourceName;
   1237         addedSource.Type = testSourceType;
   1238         addedSource.Arg = unusedSourceArg;
   1239         AddSource(addedSource, progress);
   1240 
   1241         auto sources = GetSources();
   1242         REQUIRE(sources.size() == 2 + c_DefaultSourceCount);
   1243 
   1244         REQUIRE(sources[0].Name == singleSourceName);
   1245         REQUIRE(ConvertSystemClockToUnixEpoch(sources[0].LastUpdateTime) == updateTime);
   1246         REQUIRE(sources[1].Name == addedSource.Name);
   1247     }
   1248     SECTION("Update")
   1249     {
   1250         UpdateSource(singleSourceName, progress);
   1251 
   1252         auto sources = GetSources();
   1253         REQUIRE(sources.size() == 1 + c_DefaultSourceCount);
   1254 
   1255         REQUIRE(sources[0].Name == singleSourceName);
   1256         REQUIRE(ConvertSystemClockToUnixEpoch(sources[0].LastUpdateTime) > updateTime);
   1257     }
   1258     SECTION("Remove")
   1259     {
   1260         RemoveSource(singleSourceName, progress);
   1261 
   1262         auto sources = GetSources();
   1263         REQUIRE(sources.size() == c_DefaultSourceCount);
   1264     }
   1265 }
   1266 
   1267 TEST_CASE("RepoSources_RestoringWellKnownSource", "[sources]")
   1268 {
   1269     TestHook_ClearSourceFactoryOverrides();
   1270     RemoveSetting(Stream::UserSources);
   1271 
   1272     Source storeSource{ WellKnownSource::MicrosoftStore };
   1273     SourceDetails details = storeSource.GetDetails();
   1274     REQUIRE(!details.CertificatePinningConfiguration.IsEmpty());
   1275 
   1276     TestSourceFactory factory{ SourcesTestSource::Create };
   1277     TestHook_SetSourceFactoryOverride(details.Type, factory);
   1278 
   1279     ProgressCallback progress;
   1280 
   1281     REQUIRE(storeSource.Remove(progress));
   1282 
   1283     Source storeAfterRemove{ details.Name };
   1284     REQUIRE(!storeAfterRemove);
   1285 
   1286     SECTION("with well known name")
   1287     {
   1288         Source addStoreBack{ details.Name, details.Arg, details.Type, Repository::SourceTrustLevel::None, false };
   1289         REQUIRE(addStoreBack.Add(progress));
   1290 
   1291         Source storeAfterAdd{ details.Name };
   1292         REQUIRE(storeAfterAdd);
   1293         REQUIRE(!storeAfterAdd.GetDetails().CertificatePinningConfiguration.IsEmpty());
   1294     }
   1295 
   1296     SECTION("with different name")
   1297     {
   1298         std::string newName = details.Name + "_new";
   1299         Source addStoreBack{ newName, details.Arg, details.Type, Repository::SourceTrustLevel::None, false };
   1300         REQUIRE(addStoreBack.Add(progress));
   1301 
   1302         Source storeAfterAdd{ newName };
   1303         REQUIRE(storeAfterAdd);
   1304         REQUIRE(storeAfterAdd.GetDetails().CertificatePinningConfiguration.IsEmpty());
   1305     }
   1306 }
   1307 
   1308 TEST_CASE("RepoSources_GroupPolicy_BypassCertificatePinningForMicrosoftStore", "[sources][groupPolicy]")
   1309 {
   1310     TestHook_ClearSourceFactoryOverrides();
   1311 
   1312     SECTION("Not configured")
   1313     {
   1314         GroupPolicyTestOverride policies;
   1315         policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::NotConfigured);
   1316         Source source(WellKnownSource::MicrosoftStore);
   1317         REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty());
   1318     }
   1319 
   1320     SECTION("Enabled")
   1321     {
   1322         GroupPolicyTestOverride policies;
   1323         policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Enabled);
   1324         Source source(WellKnownSource::MicrosoftStore);
   1325         REQUIRE(source.GetDetails().CertificatePinningConfiguration.IsEmpty());
   1326     }
   1327 
   1328     SECTION("Disabled")
   1329     {
   1330         GroupPolicyTestOverride policies;
   1331         policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Disabled);
   1332         Source source(WellKnownSource::MicrosoftStore);
   1333         REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty());
   1334     }
   1335 }
   1336 
   1337 TEST_CASE("RepoSources_BuiltInDesktopFrameworkSourceAlwaysCreatable", "[sources]")
   1338 {
   1339     Source source(WellKnownSource::DesktopFrameworks);
   1340     REQUIRE(source);
   1341 }
   1342 
   1343 TEST_CASE("RepoSources_MicrosoftStore_CertificatePinningLifetimeCheck", "[sources]")
   1344 {
   1345     TestHook_ClearSourceFactoryOverrides();
   1346 
   1347     GroupPolicyTestOverride policies;
   1348     policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Disabled);
   1349     Source source(WellKnownSource::MicrosoftStore);
   1350     REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty());
   1351 
   1352     // The configuration's remaining lifetime is the *maximum* of the remaining lifetimes of the individual chains.
   1353     // A chain's remaining lifetime is the *minimum* of the remaining lifetimes of the individual certificates.
   1354     // A certificate's remaining lifetime is a value between 0.0 and 1.0 that is the ratio of remaining valid time to total valid time.
   1355 
   1356     // The goal of this test is to warn when the pinning configuration may be in danger of expiration; either via certificate validity or
   1357     // more likely by renewals causing the pinning to reject the new, correct certificates. It operates in percentage lifetime to normalize
   1358     // the values across the chain.
   1359     INFO("If this test has failed, the pinning certificates may be nearing expiration and should be investigated.");
   1360     double lifetimePercentage = source.GetDetails().CertificatePinningConfiguration.GetRemainingLifetimePercentage();
   1361     REQUIRE(lifetimePercentage > 0.25);
   1362 }