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 }