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