InstallerMetadataCollectionContext.cpp (62080B)
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 8 #include <winget/InstallerMetadataCollectionContext.h> 9 10 using namespace AppInstaller; 11 using namespace AppInstaller::Repository; 12 using namespace AppInstaller::Repository::Correlation; 13 using namespace AppInstaller::Repository::Metadata; 14 using namespace TestCommon; 15 16 namespace 17 { 18 // Indicates to set minimal defaults on TestInput 19 struct MinimalDefaults_t {} MinimalDefaults; 20 21 struct TestInput 22 { 23 TestInput() = default; 24 25 TestInput(MinimalDefaults_t) 26 { 27 Version = "1.0"; 28 SupportedMetadataVersion = "1.0"; 29 SubmissionIdentifier = "1"; 30 InstallerHash = "ABCD"; 31 PackageData = std::make_optional<Manifest::Manifest>(); 32 PackageData->DefaultLocalization.Locale = "en-us"; 33 PackageData->DefaultLocalization.Add<Manifest::Localization::PackageName>("Name"); 34 PackageData->DefaultLocalization.Add<Manifest::Localization::Publisher>("Publisher"); 35 } 36 37 TestInput(MinimalDefaults_t, const std::string& productVersion, const std::string& productCode, Manifest::InstallerTypeEnum installerType) : TestInput(MinimalDefaults) 38 { 39 CurrentMetadata = std::make_optional<ProductMetadata>(); 40 CurrentMetadata->SchemaVersion.Assign("1.0"); 41 CurrentMetadata->ProductVersionMin.Assign(productVersion); 42 CurrentMetadata->ProductVersionMax.Assign(productVersion); 43 auto& installerMetadata = CurrentMetadata->InstallerMetadataMap[InstallerHash.value()]; 44 installerMetadata.SubmissionIdentifier = SubmissionIdentifier.value(); 45 installerMetadata.AppsAndFeaturesEntries.push_back({}); 46 auto& entry = installerMetadata.AppsAndFeaturesEntries.back(); 47 entry.DisplayName = PackageData->DefaultLocalization.Get<Manifest::Localization::PackageName>(); 48 entry.Publisher = PackageData->DefaultLocalization.Get<Manifest::Localization::Publisher>(); 49 entry.DisplayVersion = productVersion; 50 entry.ProductCode = productCode; 51 entry.InstallerType = installerType; 52 } 53 54 std::optional<std::string> Version; 55 std::optional<std::string> SupportedMetadataVersion; 56 std::optional<ProductMetadata> CurrentMetadata; 57 std::optional<web::json::value> SubmissionData; 58 std::optional<std::string> SubmissionIdentifier; 59 std::optional<std::string> InstallerHash; 60 // Schema 1.0 only cares about DefaultLocale and Locales 61 std::optional<Manifest::Manifest> PackageData; 62 63 std::wstring ToJSON() 64 { 65 web::json::value json; 66 67 if (Version) 68 { 69 json[L"version"] = JSON::GetStringValue(Version.value()); 70 } 71 72 if (SupportedMetadataVersion) 73 { 74 json[L"supportedMetadataVersion"] = JSON::GetStringValue(SupportedMetadataVersion.value()); 75 } 76 77 if (CurrentMetadata) 78 { 79 json[L"currentMetadata"] = CurrentMetadata->ToJson(CurrentMetadata->SchemaVersion, 0); 80 } 81 82 if (SubmissionData) 83 { 84 json[L"submissionData"] = SubmissionData.value(); 85 } 86 else if (SubmissionIdentifier) 87 { 88 web::json::value submissionData; 89 submissionData[L"submissionIdentifier"] = JSON::GetStringValue(SubmissionIdentifier.value()); 90 json[L"submissionData"] = std::move(submissionData); 91 } 92 93 if (InstallerHash || PackageData) 94 { 95 web::json::value packageData; 96 97 if (InstallerHash) 98 { 99 packageData[L"installerHash"] = JSON::GetStringValue(InstallerHash.value()); 100 } 101 102 if (PackageData) 103 { 104 packageData[L"DefaultLocale"] = LocaleToJSON(PackageData->DefaultLocalization); 105 106 // TODO: Implement other locales 107 } 108 109 json[L"packageData"] = std::move(packageData); 110 } 111 112 return json.serialize(); 113 } 114 115 private: 116 web::json::value LocaleToJSON(const Manifest::ManifestLocalization& localization) const 117 { 118 web::json::value locale; 119 120 locale[L"PackageLocale"] = JSON::GetStringValue(localization.Locale); 121 122 if (localization.Contains(Manifest::Localization::PackageName)) 123 { 124 locale[L"PackageName"] = JSON::GetStringValue(localization.Get<Manifest::Localization::PackageName>()); 125 } 126 127 if (localization.Contains(Manifest::Localization::Publisher)) 128 { 129 locale[L"Publisher"] = JSON::GetStringValue(localization.Get<Manifest::Localization::Publisher>()); 130 } 131 132 // TODO: Implement any other needed fields 133 134 return locale; 135 } 136 }; 137 138 struct TestOutput 139 { 140 TestOutput(const std::string& json) : OriginalJSON(json) 141 { 142 web::json::value input = web::json::value::parse(Utility::ConvertToUTF16(json)); 143 144 auto versionString = JSON::GetRawStringValueFromJsonNode(input, L"version"); 145 if (versionString) 146 { 147 Version = std::move(versionString); 148 } 149 150 auto submissionDataValue = JSON::GetJsonValueFromNode(input, L"submissionData"); 151 if (submissionDataValue) 152 { 153 SubmissionData = submissionDataValue.value(); 154 } 155 156 auto installerHashString = JSON::GetRawStringValueFromJsonNode(input, L"installerHash"); 157 if (installerHashString) 158 { 159 InstallerHash = std::move(installerHashString); 160 } 161 162 auto statusString = JSON::GetRawStringValueFromJsonNode(input, L"status"); 163 if (statusString) 164 { 165 Status = std::move(statusString); 166 } 167 168 auto metadataValue = JSON::GetJsonValueFromNode(input, L"metadata"); 169 if (metadataValue && !metadataValue->get().is_null()) 170 { 171 Metadata = std::make_optional<ProductMetadata>(); 172 Metadata->FromJson(metadataValue->get()); 173 } 174 175 auto diagnosticsValue = JSON::GetJsonValueFromNode(input, L"diagnostics"); 176 if (diagnosticsValue) 177 { 178 auto errorHRNumber = JSON::GetRawIntValueFromJsonNode(diagnosticsValue.value(), L"errorHR"); 179 if (errorHRNumber) 180 { 181 ErrorHR = std::move(errorHRNumber); 182 } 183 184 auto errorTextString = JSON::GetRawStringValueFromJsonNode(diagnosticsValue.value(), L"errorText"); 185 if (errorTextString) 186 { 187 ErrorText = std::move(errorTextString); 188 } 189 } 190 } 191 192 std::string OriginalJSON; 193 194 std::optional<std::string> Version; 195 std::optional<web::json::value> SubmissionData; 196 std::optional<std::string> InstallerHash; 197 std::optional<std::string> Status; 198 std::optional<ProductMetadata> Metadata; 199 std::optional<HRESULT> ErrorHR; 200 std::optional<std::string> ErrorText; 201 202 bool IsError() const 203 { 204 return Status && Status.value() == "Error"; 205 } 206 207 bool IsSuccess() const 208 { 209 return Status && Status.value() == "Success"; 210 } 211 212 bool IsLowConfidence() const 213 { 214 return Status && Status.value() == "LowConfidence"; 215 } 216 217 void ValidateFieldPresence() const 218 { 219 REQUIRE(Version); 220 REQUIRE(SubmissionData); 221 REQUIRE(InstallerHash); 222 REQUIRE(Status); 223 224 REQUIRE(IsSuccess() == Metadata.has_value()); 225 226 REQUIRE(IsError() == ErrorHR.has_value()); 227 REQUIRE(IsError() == ErrorText.has_value()); 228 } 229 }; 230 231 struct TestARPCorrelationData : public ARPCorrelationData 232 { 233 ARPCorrelationResult CorrelateForNewlyInstalled(const Manifest::Manifest&, const ARPCorrelationSettings&) override 234 { 235 return CorrelateForNewlyInstalledResult; 236 } 237 238 ARPCorrelationResult CorrelateForNewlyInstalledResult; 239 }; 240 241 struct TestInstalledFilesCorrelation : public InstalledFilesCorrelation 242 { 243 Correlation::InstallationMetadata CorrelateForNewlyInstalled(const Manifest::Manifest&, const std::string&) override 244 { 245 return InstallationMetadata; 246 } 247 248 void StartFileWatcher() override {} 249 250 void StopFileWatcher() override {} 251 252 Correlation::InstallationMetadata InstallationMetadata; 253 }; 254 255 InstallerMetadataCollectionContext CreateTestContext( 256 std::unique_ptr<ARPCorrelationData>&& data, 257 std::unique_ptr<InstalledFilesCorrelation>&& installedFiles, 258 TestInput& input) 259 { 260 return { std::move(data), std::move(installedFiles), input.ToJSON() }; 261 } 262 263 InstallerMetadataCollectionContext CreateTestContext(std::unique_ptr<ARPCorrelationData>&& data, TestInput& input) 264 { 265 return { std::move(data), std::make_unique<TestInstalledFilesCorrelation>(), input.ToJSON()}; 266 } 267 268 InstallerMetadataCollectionContext CreateTestContext(TestInput& input) 269 { 270 return CreateTestContext(std::make_unique<TestARPCorrelationData>(), input); 271 } 272 273 TestOutput GetOutput(InstallerMetadataCollectionContext& context) 274 { 275 std::ostringstream strstr; 276 context.Complete(strstr); 277 278 return { strstr.str() }; 279 } 280 281 TestOutput GetOutput(TestInput& input) 282 { 283 InstallerMetadataCollectionContext context = CreateTestContext(input); 284 return GetOutput(context); 285 } 286 287 void BadInputTest(TestInput& input) 288 { 289 TestOutput output = GetOutput(input); 290 REQUIRE(output.IsError()); 291 output.ValidateFieldPresence(); 292 } 293 294 ProductMetadata MakeProductMetadata(std::string_view submissionIdentifier = "Submission 1", const std::string& installerHash = "ABCD") 295 { 296 ProductMetadata result; 297 result.SchemaVersion.Assign("1.0"); 298 result.ProductVersionMin.Assign("1.0"); 299 result.ProductVersionMax.Assign("1.0"); 300 auto& installerMetadata = result.InstallerMetadataMap[installerHash]; 301 installerMetadata.SubmissionIdentifier = submissionIdentifier; 302 installerMetadata.AppsAndFeaturesEntries.push_back({}); 303 auto& entry = installerMetadata.AppsAndFeaturesEntries.back(); 304 entry.DisplayName = "Name"; 305 entry.Publisher = "Publisher"; 306 entry.DisplayVersion = "1.0"; 307 entry.ProductCode = "{guid}"; 308 entry.InstallerType = Manifest::InstallerTypeEnum::Msi; 309 return result; 310 } 311 312 struct TestMerge 313 { 314 TestMerge() = default; 315 316 TestMerge(MinimalDefaults_t) 317 { 318 Version = "1.0"; 319 Metadatas = std::make_optional<std::vector<ProductMetadata>>(); 320 Metadatas->emplace_back(MakeProductMetadata()); 321 } 322 323 std::optional<std::string> Version; 324 std::optional<std::vector<ProductMetadata>> Metadatas; 325 326 std::wstring ToJSON() 327 { 328 web::json::value json; 329 330 if (Version) 331 { 332 json[L"version"] = JSON::GetStringValue(Version.value()); 333 } 334 335 if (Metadatas) 336 { 337 web::json::value metadatasArray; 338 339 if (Metadatas->empty()) 340 { 341 metadatasArray = web::json::value::array(); 342 } 343 else 344 { 345 size_t index = 0; 346 for (auto& value : Metadatas.value()) 347 { 348 metadatasArray[index++] = value.ToJson(value.SchemaVersion, 0); 349 } 350 } 351 352 json[L"metadatas"] = std::move(metadatasArray); 353 } 354 355 return json.serialize(); 356 } 357 }; 358 } 359 360 TEST_CASE("MetadataCollection_MinimumInput", "[metadata_collection]") 361 { 362 TestInput input(MinimalDefaults); 363 TestOutput output = GetOutput(input); 364 REQUIRE(!output.IsError()); 365 output.ValidateFieldPresence(); 366 367 REQUIRE(output.Version.value() == input.Version.value()); 368 REQUIRE(output.InstallerHash.value() == input.InstallerHash.value()); 369 } 370 371 TEST_CASE("MetadataCollection_SubmissionDataCopied", "[metadata_collection]") 372 { 373 TestInput input(MinimalDefaults); 374 375 web::json::value submissionData; 376 std::wstring testValueName = L"testValueName"; 377 std::string testValueValue = "Test value value"; 378 submissionData[L"submissionIdentifier"] = JSON::GetStringValue("Required identifier"); 379 submissionData[testValueName] = JSON::GetStringValue(testValueValue); 380 381 input.SubmissionData = submissionData; 382 383 TestOutput output = GetOutput(input); 384 REQUIRE(!output.IsError()); 385 output.ValidateFieldPresence(); 386 387 REQUIRE(output.Version.value() == input.Version.value()); 388 REQUIRE(output.InstallerHash.value() == input.InstallerHash.value()); 389 auto outputValue = JSON::GetRawStringValueFromJsonNode(output.SubmissionData.value(), testValueName); 390 REQUIRE(outputValue); 391 REQUIRE(outputValue.value() == testValueValue); 392 } 393 394 TEST_CASE("MetadataCollection_BadInput", "[metadata_collection]") 395 { 396 TestInput input(MinimalDefaults); 397 398 #define RESET_FIELD_SECTION(_field_) \ 399 SECTION("No " #_field_) \ 400 { \ 401 input._field_.reset(); \ 402 BadInputTest(input); \ 403 } 404 405 RESET_FIELD_SECTION(Version); 406 RESET_FIELD_SECTION(SupportedMetadataVersion); 407 RESET_FIELD_SECTION(SubmissionIdentifier); 408 RESET_FIELD_SECTION(InstallerHash); 409 RESET_FIELD_SECTION(PackageData); 410 411 #undef RESET_FIELD_SECTION 412 } 413 414 TEST_CASE("MetadataCollection_LowConfidence", "[metadata_collection]") 415 { 416 TestInput input(MinimalDefaults); 417 // The default test correlation object won't have a package correlation set 418 TestOutput output = GetOutput(input); 419 REQUIRE(output.IsLowConfidence()); 420 REQUIRE(!output.Metadata); 421 output.ValidateFieldPresence(); 422 } 423 424 TEST_CASE("MetadataCollection_NewPackage", "[metadata_collection]") 425 { 426 TestInput input(MinimalDefaults); 427 auto correlationData = std::make_unique<TestARPCorrelationData>(); 428 429 Manifest::Manifest manifest; 430 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Test Package Name"); 431 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>("Test Publisher"); 432 manifest.Version = "1.2.3"; 433 manifest.Installers.push_back({}); 434 manifest.Installers[0].ProductCode = "{guid}"; 435 436 IPackageVersion::Metadata metadata; 437 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); 438 metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); 439 440 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 441 442 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 443 TestOutput output = GetOutput(context); 444 445 REQUIRE(output.IsSuccess()); 446 output.ValidateFieldPresence(); 447 448 REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); 449 REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); 450 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 451 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 452 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 453 REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); 454 REQUIRE(entry.Scope.empty()); 455 REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); 456 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()); 457 REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>()); 458 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); 459 REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); 460 REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); 461 REQUIRE(output.Metadata->HistoricalMetadataList.empty()); 462 } 463 464 TEST_CASE("MetadataCollection_SameSubmission_SameInstaller", "[metadata_collection]") 465 { 466 std::string version = "1.3.5"; 467 std::string productCode = "{guid}"; 468 Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; 469 470 TestInput input(MinimalDefaults, version, productCode, installerType); 471 auto correlationData = std::make_unique<TestARPCorrelationData>(); 472 473 Manifest::Manifest manifest; 474 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Different Language Name"); 475 // Same publisher 476 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); 477 manifest.Version = version; 478 manifest.Installers.push_back({}); 479 manifest.Installers[0].ProductCode = productCode; 480 481 IPackageVersion::Metadata metadata; 482 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); 483 484 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 485 486 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 487 TestOutput output = GetOutput(context); 488 489 REQUIRE(output.IsSuccess()); 490 output.ValidateFieldPresence(); 491 492 REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); 493 REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); 494 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 495 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 496 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 497 REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); 498 REQUIRE(entry.AppsAndFeaturesEntries.size() == 2); 499 500 // One should have all values, and the other should have just a different name 501 // Base which one is which off of whether Publisher is set 502 for (const auto& featureEntry : entry.AppsAndFeaturesEntries) 503 { 504 if (featureEntry.Publisher.empty()) 505 { 506 REQUIRE(featureEntry.DisplayName == manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()); 507 REQUIRE(featureEntry.DisplayVersion.empty()); 508 REQUIRE(featureEntry.ProductCode.empty()); 509 REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Unknown); 510 } 511 else 512 { 513 REQUIRE(featureEntry.DisplayName == input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); 514 REQUIRE(featureEntry.Publisher == manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>()); 515 REQUIRE(featureEntry.DisplayVersion == manifest.Version); 516 REQUIRE(featureEntry.ProductCode == manifest.Installers[0].ProductCode); 517 REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); 518 } 519 } 520 REQUIRE(output.Metadata->HistoricalMetadataList.empty()); 521 } 522 523 TEST_CASE("MetadataCollection_SameSubmission_NewInstaller", "[metadata_collection]") 524 { 525 std::string versionPresent = "1.3.5"; 526 std::string versionIncoming = "1.3.5.1"; 527 std::string productCodePresent = "{guid}"; 528 std::string productCodeIncoming = "{guid_different}"; 529 Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; 530 531 TestInput input(MinimalDefaults, versionPresent, productCodePresent, installerType); 532 // Change the incoming hash to be new 533 input.InstallerHash = input.InstallerHash.value() + "_DIFFERENT"; 534 auto correlationData = std::make_unique<TestARPCorrelationData>(); 535 536 Manifest::Manifest manifest; 537 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Name (but different architecture)"); 538 // Same publisher 539 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); 540 manifest.Version = versionIncoming; 541 manifest.Installers.push_back({}); 542 manifest.Installers[0].ProductCode = productCodeIncoming; 543 544 IPackageVersion::Metadata metadata; 545 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); 546 547 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 548 549 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 550 TestOutput output = GetOutput(context); 551 552 REQUIRE(output.IsSuccess()); 553 output.ValidateFieldPresence(); 554 555 REQUIRE(output.Metadata->ProductVersionMin.ToString() == versionPresent); 556 REQUIRE(output.Metadata->ProductVersionMax.ToString() == versionIncoming); 557 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 2); 558 559 for (const auto& installerMetadata : output.Metadata->InstallerMetadataMap) 560 { 561 const auto& entry = installerMetadata.second; 562 563 REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); 564 REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); 565 566 const auto& featureEntry = entry.AppsAndFeaturesEntries.front(); 567 REQUIRE(featureEntry.Publisher == manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>()); 568 569 if (featureEntry.ProductCode == productCodePresent) 570 { 571 REQUIRE(featureEntry.DisplayName == input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); 572 REQUIRE(featureEntry.DisplayVersion == versionPresent); 573 REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); 574 } 575 else 576 { 577 REQUIRE(featureEntry.DisplayName == manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()); 578 REQUIRE(featureEntry.DisplayVersion == versionIncoming); 579 REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); 580 REQUIRE(featureEntry.ProductCode == productCodeIncoming); 581 } 582 } 583 584 REQUIRE(output.Metadata->HistoricalMetadataList.empty()); 585 } 586 587 TEST_CASE("MetadataCollection_NewSubmission", "[metadata_collection]") 588 { 589 std::string versionPresent = "1.3.5"; 590 std::string versionIncoming = "1.4.0"; 591 std::string productCodePresent = "{guid}"; 592 std::string productCodeIncoming = "{guid_different}"; 593 Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; 594 595 TestInput input(MinimalDefaults, versionPresent, productCodePresent, installerType); 596 input.SubmissionIdentifier = input.SubmissionIdentifier.value() + "_NEW"; 597 input.InstallerHash = input.InstallerHash.value() + "_DIFFERENT"; 598 auto correlationData = std::make_unique<TestARPCorrelationData>(); 599 600 Manifest::Manifest manifest; 601 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); 602 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); 603 manifest.Version = versionIncoming; 604 manifest.Installers.push_back({}); 605 manifest.Installers[0].ProductCode = productCodeIncoming; 606 607 IPackageVersion::Metadata metadata; 608 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); 609 610 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 611 612 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 613 TestOutput output = GetOutput(context); 614 615 REQUIRE(output.IsSuccess()); 616 output.ValidateFieldPresence(); 617 618 REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); 619 REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); 620 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 621 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 622 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 623 REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); 624 REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); 625 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()); 626 REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>()); 627 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); 628 REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); 629 REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); 630 631 REQUIRE(output.Metadata->HistoricalMetadataList.size() == 1); 632 const auto& historicalEntry = output.Metadata->HistoricalMetadataList[0]; 633 REQUIRE(historicalEntry.ProductVersionMin.ToString() == input.CurrentMetadata->ProductVersionMin.ToString()); 634 REQUIRE(historicalEntry.ProductVersionMax.ToString() == input.CurrentMetadata->ProductVersionMax.ToString()); 635 const auto& appsAndFeaturesEntry = input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries.front(); 636 REQUIRE(historicalEntry.Names.size() == 1); 637 REQUIRE(*historicalEntry.Names.begin() == appsAndFeaturesEntry.DisplayName); 638 REQUIRE(historicalEntry.ProductCodes.size() == 1); 639 REQUIRE(*historicalEntry.ProductCodes.begin() == appsAndFeaturesEntry.ProductCode); 640 REQUIRE(historicalEntry.Publishers.size() == 1); 641 REQUIRE(*historicalEntry.Publishers.begin() == appsAndFeaturesEntry.Publisher); 642 } 643 644 TEST_CASE("MetadataCollection_Merge_Empty", "[metadata_collection]") 645 { 646 TestMerge mergeData{ MinimalDefaults }; 647 mergeData.Metadatas->clear(); 648 649 REQUIRE_THROWS_HR(InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}), E_NOT_SET); 650 } 651 652 TEST_CASE("MetadataCollection_Merge_SubmissionMismatch", "[metadata_collection]") 653 { 654 TestMerge mergeData{ MinimalDefaults }; 655 mergeData.Metadatas->emplace_back(MakeProductMetadata("Submission 2")); 656 657 REQUIRE_THROWS_HR(InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}), E_NOT_VALID_STATE); 658 } 659 660 TEST_CASE("MetadataCollection_Merge_DifferentInstallers", "[metadata_collection]") 661 { 662 TestMerge mergeData{ MinimalDefaults }; 663 mergeData.Metadatas->emplace_back(MakeProductMetadata(mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.SubmissionIdentifier, "EFGH")); 664 665 std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 666 REQUIRE(!mergeResult.empty()); 667 668 ProductMetadata mergeMetadata; 669 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 670 671 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 2); 672 for (const auto& item : mergeMetadata.InstallerMetadataMap) 673 { 674 REQUIRE(item.second.AppsAndFeaturesEntries.size() == 1); 675 REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayName.empty()); 676 REQUIRE(!item.second.AppsAndFeaturesEntries[0].Publisher.empty()); 677 REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayVersion.empty()); 678 REQUIRE(!item.second.AppsAndFeaturesEntries[0].ProductCode.empty()); 679 } 680 } 681 682 TEST_CASE("MetadataCollection_Merge_SameInstaller", "[metadata_collection]") 683 { 684 TestMerge mergeData{ MinimalDefaults }; 685 mergeData.Metadatas->emplace_back(MakeProductMetadata()); 686 687 std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 688 REQUIRE(!mergeResult.empty()); 689 690 ProductMetadata mergeMetadata; 691 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 692 693 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 694 for (const auto& item : mergeMetadata.InstallerMetadataMap) 695 { 696 REQUIRE(item.second.AppsAndFeaturesEntries.size() == 1); 697 REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayName.empty()); 698 REQUIRE(!item.second.AppsAndFeaturesEntries[0].Publisher.empty()); 699 REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayVersion.empty()); 700 REQUIRE(!item.second.AppsAndFeaturesEntries[0].ProductCode.empty()); 701 } 702 } 703 704 TEST_CASE("MetadataCollection_NewPackage_1_1", "[metadata_collection]") 705 { 706 TestInput input(MinimalDefaults); 707 input.SupportedMetadataVersion = "1.1"; 708 auto correlationData = std::make_unique<TestARPCorrelationData>(); 709 710 Manifest::Manifest manifest; 711 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Test Package Name"); 712 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>("Test Publisher"); 713 manifest.Version = "1.2.3"; 714 manifest.Installers.push_back({}); 715 manifest.Installers[0].ProductCode = "{guid}"; 716 717 IPackageVersion::Metadata metadata; 718 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); 719 metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); 720 721 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 722 723 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 724 TestOutput output = GetOutput(context); 725 726 REQUIRE(output.IsSuccess()); 727 output.ValidateFieldPresence(); 728 729 REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); 730 REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); 731 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 732 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 733 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 734 REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); 735 REQUIRE(entry.Scope == metadata[PackageVersionMetadata::InstalledScope]); 736 REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); 737 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()); 738 REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>()); 739 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); 740 REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); 741 REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); 742 REQUIRE(output.Metadata->HistoricalMetadataList.empty()); 743 } 744 745 TEST_CASE("MetadataCollection_NewPackage_NoScope", "[metadata_collection]") 746 { 747 TestInput input(MinimalDefaults); 748 input.SupportedMetadataVersion = "1.1"; 749 auto correlationData = std::make_unique<TestARPCorrelationData>(); 750 751 Manifest::Manifest manifest; 752 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Test Package Name"); 753 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>("Test Publisher"); 754 manifest.Version = "1.2.3"; 755 manifest.Installers.push_back({}); 756 manifest.Installers[0].ProductCode = "{guid}"; 757 758 IPackageVersion::Metadata metadata; 759 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); 760 761 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 762 763 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 764 TestOutput output = GetOutput(context); 765 766 REQUIRE(output.IsSuccess()); 767 output.ValidateFieldPresence(); 768 769 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 770 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 771 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 772 REQUIRE(entry.Scope.empty()); 773 } 774 775 TEST_CASE("MetadataCollection_SameSubmission_SameInstaller_Scopes", "[metadata_collection]") 776 { 777 std::string version = "1.3.5"; 778 std::string productCode = "{guid}"; 779 Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; 780 std::string currentScope = GENERATE(std::string{}, 781 Manifest::ScopeToString(Manifest::ScopeEnum::Unknown), 782 Manifest::ScopeToString(Manifest::ScopeEnum::User), 783 Manifest::ScopeToString(Manifest::ScopeEnum::Machine)); 784 std::string newScope{ Manifest::ScopeToString(Manifest::ScopeEnum::User) }; 785 786 INFO(currentScope); 787 788 TestInput input(MinimalDefaults, version, productCode, installerType); 789 input.SupportedMetadataVersion = "1.1"; 790 input.CurrentMetadata->SchemaVersion = { "1.1" }; 791 input.CurrentMetadata->InstallerMetadataMap.begin()->second.Scope = currentScope; 792 auto correlationData = std::make_unique<TestARPCorrelationData>(); 793 794 Manifest::Manifest manifest; 795 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Different Language Name"); 796 // Same publisher 797 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); 798 manifest.Version = version; 799 manifest.Installers.push_back({}); 800 manifest.Installers[0].ProductCode = productCode; 801 802 IPackageVersion::Metadata metadata; 803 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); 804 metadata[PackageVersionMetadata::InstalledScope] = newScope; 805 806 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 807 808 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 809 TestOutput output = GetOutput(context); 810 811 REQUIRE(output.IsSuccess()); 812 output.ValidateFieldPresence(); 813 814 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 815 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 816 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 817 818 if (currentScope.empty()) 819 { 820 REQUIRE(entry.Scope == newScope); 821 } 822 else if (currentScope != newScope) 823 { 824 // If Unknown, should stay Unknown 825 // If different, should become Unknown 826 REQUIRE(entry.Scope == Manifest::ScopeToString(Manifest::ScopeEnum::Unknown)); 827 } 828 else 829 { 830 // If same, should not change 831 REQUIRE(entry.Scope == currentScope); 832 } 833 } 834 835 TEST_CASE("MetadataCollection_Merge_SameInstaller_Scopes", "[metadata_collection]") 836 { 837 TestMerge mergeData{ MinimalDefaults }; 838 mergeData.Metadatas->emplace_back(MakeProductMetadata()); 839 840 std::string currentScope = GENERATE(std::string{}, 841 Manifest::ScopeToString(Manifest::ScopeEnum::Unknown), 842 Manifest::ScopeToString(Manifest::ScopeEnum::User), 843 Manifest::ScopeToString(Manifest::ScopeEnum::Machine)); 844 std::string newScope{ Manifest::ScopeToString(Manifest::ScopeEnum::Machine) }; 845 846 INFO(currentScope); 847 848 mergeData.Metadatas->at(0).SchemaVersion = { "1.1" }; 849 mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.Scope = currentScope; 850 mergeData.Metadatas->at(1).SchemaVersion = { "1.1" }; 851 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.Scope = newScope; 852 853 std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 854 REQUIRE(!mergeResult.empty()); 855 856 ProductMetadata mergeMetadata; 857 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 858 859 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 860 for (const auto& item : mergeMetadata.InstallerMetadataMap) 861 { 862 if (currentScope.empty()) 863 { 864 REQUIRE(item.second.Scope == newScope); 865 } 866 else if (currentScope != newScope) 867 { 868 // If Unknown, should stay Unknown 869 // If different, should become Unknown 870 REQUIRE(item.second.Scope == Manifest::ScopeToString(Manifest::ScopeEnum::Unknown)); 871 } 872 else 873 { 874 // If same, should not change 875 REQUIRE(item.second.Scope == currentScope); 876 } 877 } 878 } 879 880 TEST_CASE("MetadataCollection_NewPackage_1_2", "[metadata_collection]") 881 { 882 TestInput input(MinimalDefaults); 883 input.SupportedMetadataVersion = "1.2"; 884 auto correlationData = std::make_unique<TestARPCorrelationData>(); 885 auto installedFilesData = std::make_unique<TestInstalledFilesCorrelation>(); 886 887 Manifest::Manifest manifest; 888 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Test Package Name"); 889 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>("Test Publisher"); 890 manifest.Version = "1.2.3"; 891 manifest.Installers.push_back({}); 892 manifest.Installers[0].ProductCode = "{guid}"; 893 894 IPackageVersion::Metadata metadata; 895 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); 896 metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); 897 898 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 899 900 Correlation::InstallationMetadata installedFiles; 901 installedFiles.InstalledFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; 902 Manifest::InstalledFile installedFile; 903 installedFile.RelativeFilePath = "test.exe"; 904 installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 905 installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; 906 installedFile.InvocationParameter = "invocation"; 907 installedFile.DisplayName = "name"; 908 installedFiles.InstalledFiles.Files.emplace_back(std::move(installedFile)); 909 Correlation::InstalledStartupLinkFile startupLink; 910 startupLink.RelativeFilePath = "TestApp.lnk"; 911 startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; 912 installedFiles.StartupLinkFiles.emplace_back(std::move(startupLink)); 913 914 installedFilesData->InstallationMetadata = std::move(installedFiles); 915 916 std::vector<AppInstaller::Repository::ExtractedIconInfo> testIcons; 917 AppInstaller::Repository::ExtractedIconInfo iconInfo; 918 iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); 919 iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 920 iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; 921 iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; 922 iconInfo.IconTheme = Manifest::IconThemeEnum::Default; 923 testIcons.emplace_back(std::move(iconInfo)); 924 925 TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ testIcons }; 926 927 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), std::move(installedFilesData), input); 928 TestOutput output = GetOutput(context); 929 930 REQUIRE(output.IsSuccess()); 931 output.ValidateFieldPresence(); 932 933 REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); 934 REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); 935 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 936 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 937 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 938 REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); 939 REQUIRE(entry.Scope == metadata[PackageVersionMetadata::InstalledScope]); 940 REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); 941 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()); 942 REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>()); 943 REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); 944 REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); 945 REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); 946 REQUIRE(entry.InstalledFiles.has_value()); 947 REQUIRE(entry.InstalledFiles->DefaultInstallLocation == "%TEMP%\\TestApp"); 948 REQUIRE(entry.InstalledFiles->Files.size() == 1); 949 REQUIRE(entry.InstalledFiles->Files[0].RelativeFilePath == "test.exe"); 950 REQUIRE(entry.InstalledFiles->Files[0].FileSha256 == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759")); 951 REQUIRE(entry.InstalledFiles->Files[0].FileType == Manifest::InstalledFileTypeEnum::Launch); 952 REQUIRE(entry.InstalledFiles->Files[0].InvocationParameter == "invocation"); 953 REQUIRE(entry.InstalledFiles->Files[0].DisplayName == "name"); 954 REQUIRE(entry.StartupLinkFiles.has_value()); 955 REQUIRE(entry.StartupLinkFiles->size() == 1); 956 REQUIRE(entry.StartupLinkFiles->at(0).RelativeFilePath == "TestApp.lnk"); 957 REQUIRE(entry.StartupLinkFiles->at(0).FileType == Manifest::InstalledFileTypeEnum::Launch); 958 REQUIRE(entry.Icons.size() == 1); 959 REQUIRE(entry.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700")); 960 REQUIRE(entry.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759")); 961 REQUIRE(entry.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Ico); 962 REQUIRE(entry.Icons[0].IconResolution == Manifest::IconResolutionEnum::Custom); 963 REQUIRE(entry.Icons[0].IconTheme == Manifest::IconThemeEnum::Default); 964 REQUIRE(output.Metadata->HistoricalMetadataList.empty()); 965 } 966 967 TEST_CASE("MetadataCollection_NewPackage_NoInstallationMetadata_NoIcons", "[metadata_collection]") 968 { 969 TestInput input(MinimalDefaults); 970 input.SupportedMetadataVersion = "1.2"; 971 auto correlationData = std::make_unique<TestARPCorrelationData>(); 972 973 Manifest::Manifest manifest; 974 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Test Package Name"); 975 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>("Test Publisher"); 976 manifest.Version = "1.2.3"; 977 manifest.Installers.push_back({}); 978 manifest.Installers[0].ProductCode = "{guid}"; 979 980 IPackageVersion::Metadata metadata; 981 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); 982 983 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 984 985 std::vector<AppInstaller::Repository::ExtractedIconInfo> testIcons; 986 TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ testIcons }; 987 988 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); 989 TestOutput output = GetOutput(context); 990 991 REQUIRE(output.IsSuccess()); 992 output.ValidateFieldPresence(); 993 994 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 995 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 996 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 997 REQUIRE(entry.Scope.empty()); 998 REQUIRE_FALSE(entry.InstalledFiles.has_value()); 999 REQUIRE_FALSE(entry.StartupLinkFiles.has_value()); 1000 REQUIRE(entry.Icons.size() == 0); 1001 } 1002 1003 TEST_CASE("MetadataCollection_SameSubmission_SameInstaller_InstallationMetadata_Icons", "[metadata_collection]") 1004 { 1005 std::string version = "1.3.5"; 1006 std::string productCode = "{guid}"; 1007 Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; 1008 1009 TestInput input(MinimalDefaults, version, productCode, installerType); 1010 input.SupportedMetadataVersion = "1.2"; 1011 input.CurrentMetadata->SchemaVersion = { "1.2" }; 1012 1013 Manifest::InstallationMetadataInfo installedFiles; 1014 installedFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; 1015 Manifest::InstalledFile installedFile; 1016 installedFile.RelativeFilePath = "test.exe"; 1017 installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 1018 installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; 1019 installedFile.InvocationParameter = "invocation"; 1020 installedFile.DisplayName = "name"; 1021 installedFiles.Files.emplace_back(std::move(installedFile)); 1022 input.CurrentMetadata->InstallerMetadataMap.begin()->second.InstalledFiles = std::move(installedFiles); 1023 1024 std::vector<InstalledStartupLinkFile> startupLinkFiles; 1025 Correlation::InstalledStartupLinkFile startupLink; 1026 startupLink.RelativeFilePath = "TestApp.lnk"; 1027 startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; 1028 startupLinkFiles.emplace_back(std::move(startupLink)); 1029 input.CurrentMetadata->InstallerMetadataMap.begin()->second.StartupLinkFiles = std::move(startupLinkFiles); 1030 1031 std::vector<AppInstaller::Repository::ExtractedIconInfo> testIcons; 1032 AppInstaller::Repository::ExtractedIconInfo iconInfo; 1033 iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); 1034 iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 1035 iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; 1036 iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; 1037 iconInfo.IconTheme = Manifest::IconThemeEnum::Default; 1038 testIcons.emplace_back(std::move(iconInfo)); 1039 input.CurrentMetadata->InstallerMetadataMap.begin()->second.Icons = std::move(testIcons); 1040 1041 auto correlationData = std::make_unique<TestARPCorrelationData>(); 1042 auto installedFilesData = std::make_unique<TestInstalledFilesCorrelation>(); 1043 1044 Manifest::Manifest manifest; 1045 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>("Different Language Name"); 1046 // Same publisher 1047 manifest.DefaultLocalization.Add<Manifest::Localization::Publisher>(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); 1048 manifest.Version = version; 1049 manifest.Installers.push_back({}); 1050 manifest.Installers[0].ProductCode = productCode; 1051 1052 IPackageVersion::Metadata metadata; 1053 metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); 1054 1055 correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared<TestPackageVersion>(manifest, metadata); 1056 1057 Correlation::InstallationMetadata newInstalledFiles; 1058 newInstalledFiles.InstalledFiles.DefaultInstallLocation = "%TEMP%\\NewTestApp"; 1059 Manifest::InstalledFile newInstalledFile; 1060 newInstalledFile.RelativeFilePath = "test.exe"; 1061 newInstalledFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 1062 newInstalledFile.FileType = Manifest::InstalledFileTypeEnum::Launch; 1063 newInstalledFile.InvocationParameter = "invocation"; 1064 newInstalledFile.DisplayName = "name"; 1065 newInstalledFiles.InstalledFiles.Files.emplace_back(std::move(newInstalledFile)); 1066 Correlation::InstalledStartupLinkFile newStartupLink; 1067 newStartupLink.RelativeFilePath = "NewTestApp.lnk"; 1068 newStartupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; 1069 newInstalledFiles.StartupLinkFiles.emplace_back(std::move(newStartupLink)); 1070 1071 installedFilesData->InstallationMetadata = std::move(newInstalledFiles); 1072 1073 std::vector<AppInstaller::Repository::ExtractedIconInfo> newTestIcons; 1074 AppInstaller::Repository::ExtractedIconInfo newIconInfo; 1075 newIconInfo.IconContent = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"); 1076 newIconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499"); 1077 newIconInfo.IconFileType = Manifest::IconFileTypeEnum::Jpeg; 1078 newIconInfo.IconResolution = Manifest::IconResolutionEnum::Square16; 1079 newIconInfo.IconTheme = Manifest::IconThemeEnum::Light; 1080 newTestIcons.emplace_back(std::move(newIconInfo)); 1081 1082 TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ newTestIcons }; 1083 1084 InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), std::move(installedFilesData), input); 1085 TestOutput output = GetOutput(context); 1086 1087 REQUIRE(output.IsSuccess()); 1088 output.ValidateFieldPresence(); 1089 1090 REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); 1091 REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); 1092 const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; 1093 1094 // Conflicting installed files entries get removed. 1095 REQUIRE(entry.InstalledFiles.has_value()); 1096 REQUIRE_FALSE(entry.InstalledFiles->HasData()); 1097 // Non duplicate startup links get added. 1098 REQUIRE(entry.StartupLinkFiles.has_value()); 1099 REQUIRE(entry.StartupLinkFiles->size() == 2); 1100 // New detected icons always take over 1101 REQUIRE(entry.Icons.size() == 1); 1102 REQUIRE(entry.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); 1103 REQUIRE(entry.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499")); 1104 REQUIRE(entry.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Jpeg); 1105 REQUIRE(entry.Icons[0].IconResolution == Manifest::IconResolutionEnum::Square16); 1106 REQUIRE(entry.Icons[0].IconTheme == Manifest::IconThemeEnum::Light); 1107 } 1108 1109 TEST_CASE("MetadataCollection_Merge_SameInstaller_InstalledFiles", "[metadata_collection]") 1110 { 1111 TestMerge mergeData{ MinimalDefaults }; 1112 mergeData.Metadatas->emplace_back(MakeProductMetadata()); 1113 1114 Manifest::InstallationMetadataInfo installedFiles; 1115 installedFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; 1116 Manifest::InstalledFile installedFile; 1117 installedFile.RelativeFilePath = "test.exe"; 1118 installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 1119 installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; 1120 installedFile.InvocationParameter = "invocation"; 1121 installedFile.DisplayName = "name"; 1122 installedFiles.Files.emplace_back(std::move(installedFile)); 1123 1124 mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; 1125 mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.InstalledFiles = installedFiles; 1126 1127 // Different default install location clears whole data 1128 Manifest::InstallationMetadataInfo newInstalledFiles = installedFiles; 1129 newInstalledFiles.DefaultInstallLocation = "%TEMP%\\NewTestApp"; 1130 mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; 1131 1132 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles; 1133 std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 1134 REQUIRE(!mergeResult.empty()); 1135 1136 ProductMetadata mergeMetadata; 1137 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 1138 1139 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 1140 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); 1141 REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->HasData()); 1142 1143 // Different RelativeFilePath clears the file entry 1144 Manifest::InstallationMetadataInfo newInstalledFiles2 = installedFiles; 1145 newInstalledFiles2.Files[0].RelativeFilePath = "test2.exe"; 1146 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles2; 1147 mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 1148 REQUIRE(!mergeResult.empty()); 1149 1150 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 1151 1152 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 1153 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); 1154 REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->DefaultInstallLocation.empty()); 1155 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files.empty()); 1156 1157 // Different other fields clears the fields themselves 1158 Manifest::InstallationMetadataInfo newInstalledFiles3 = installedFiles; 1159 newInstalledFiles3.Files[0].DisplayName = "name2"; 1160 newInstalledFiles3.Files[0].FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b756"); 1161 newInstalledFiles3.Files[0].InvocationParameter = "invocation2"; 1162 newInstalledFiles3.Files[0].FileType = Manifest::InstalledFileTypeEnum::Uninstall; 1163 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles3; 1164 mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 1165 REQUIRE(!mergeResult.empty()); 1166 1167 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 1168 1169 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 1170 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); 1171 REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->DefaultInstallLocation.empty()); 1172 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files.size() == 1); 1173 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].RelativeFilePath == "test.exe"); 1174 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].DisplayName == ""); 1175 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].InvocationParameter == ""); 1176 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].FileType == Manifest::InstalledFileTypeEnum::Unknown); 1177 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].FileSha256.empty()); 1178 } 1179 1180 TEST_CASE("MetadataCollection_Merge_SameInstaller_StartupLinkFiles", "[metadata_collection]") 1181 { 1182 TestMerge mergeData{ MinimalDefaults }; 1183 mergeData.Metadatas->emplace_back(MakeProductMetadata()); 1184 1185 std::vector<InstalledStartupLinkFile> startupLinkFiles; 1186 Correlation::InstalledStartupLinkFile startupLink; 1187 startupLink.RelativeFilePath = "TestApp.lnk"; 1188 startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; 1189 startupLinkFiles.emplace_back(std::move(startupLink)); 1190 1191 mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; 1192 mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.StartupLinkFiles = startupLinkFiles; 1193 1194 // Different relative file path gets added 1195 std::vector<InstalledStartupLinkFile> newStartupLinkFiles = startupLinkFiles; 1196 newStartupLinkFiles[0].RelativeFilePath = "TestApp2.lnk"; 1197 1198 mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; 1199 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.StartupLinkFiles = newStartupLinkFiles; 1200 std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 1201 REQUIRE(!mergeResult.empty()); 1202 1203 ProductMetadata mergeMetadata; 1204 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 1205 1206 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 1207 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles.has_value()); 1208 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->size() == 2); 1209 1210 // Different other fields clears the fields themselves 1211 std::vector<InstalledStartupLinkFile> newStartupLinkFiles2 = startupLinkFiles; 1212 newStartupLinkFiles2[0].FileType = Manifest::InstalledFileTypeEnum::Uninstall; 1213 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.StartupLinkFiles = newStartupLinkFiles2; 1214 mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 1215 REQUIRE(!mergeResult.empty()); 1216 1217 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 1218 1219 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 1220 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles.has_value()); 1221 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->size() == 1); 1222 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->at(0).RelativeFilePath == "TestApp.lnk"); 1223 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->at(0).FileType == Manifest::InstalledFileTypeEnum::Unknown); 1224 } 1225 1226 TEST_CASE("MetadataCollection_Merge_SameInstaller_Icons", "[metadata_collection]") 1227 { 1228 TestMerge mergeData{ MinimalDefaults }; 1229 mergeData.Metadatas->emplace_back(MakeProductMetadata()); 1230 1231 std::vector<AppInstaller::Repository::ExtractedIconInfo> testIcons; 1232 AppInstaller::Repository::ExtractedIconInfo iconInfo; 1233 iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); 1234 iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); 1235 iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; 1236 iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; 1237 iconInfo.IconTheme = Manifest::IconThemeEnum::Default; 1238 testIcons.emplace_back(std::move(iconInfo)); 1239 1240 mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; 1241 mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.Icons = testIcons; 1242 1243 // Different test icons 1244 std::vector<AppInstaller::Repository::ExtractedIconInfo> newTestIcons; 1245 AppInstaller::Repository::ExtractedIconInfo newIconInfo; 1246 newIconInfo.IconContent = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"); 1247 newIconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499"); 1248 newIconInfo.IconFileType = Manifest::IconFileTypeEnum::Jpeg; 1249 newIconInfo.IconResolution = Manifest::IconResolutionEnum::Square16; 1250 newIconInfo.IconTheme = Manifest::IconThemeEnum::Light; 1251 newTestIcons.emplace_back(std::move(newIconInfo)); 1252 1253 mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; 1254 mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.Icons = newTestIcons; 1255 std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); 1256 REQUIRE(!mergeResult.empty()); 1257 1258 ProductMetadata mergeMetadata; 1259 mergeMetadata.FromJson(web::json::value::parse(mergeResult)); 1260 1261 // New data always take over 1262 REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); 1263 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons.size() == 1); 1264 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); 1265 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499")); 1266 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Jpeg); 1267 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconResolution == Manifest::IconResolutionEnum::Square16); 1268 REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconTheme == Manifest::IconThemeEnum::Light); 1269 }