winget-cli

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

PackageCollection.cpp (21762B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "TestCommon.h"
      5 
      6 #include <winget/Regex.h>
      7 #include <PackageCollection.h>
      8 
      9 // Duplicating here because a change to these values in the product *REALLY* needs to be thought through.
     10 using namespace std::string_literals;
     11 using namespace std::string_view_literals;
     12 
     13 using namespace AppInstaller::CLI;
     14 using namespace AppInstaller::Repository;
     15 using namespace AppInstaller::Utility;
     16 
     17 const std::string s_PackagesJson_Schema = "$schema";
     18 const std::string s_PackagesJson_SchemaUri_v2_0 = "https://aka.ms/winget-packages.schema.2.0.json";
     19 const std::string s_PackagesJson_WinGetVersion = "WinGetVersion";
     20 const std::string s_PackagesJson_CreationDate = "CreationDate";
     21 
     22 const std::string s_PackagesJson_Sources = "Sources";
     23 const std::string s_PackagesJson_Source_Details = "SourceDetails";
     24 const std::string s_PackagesJson_Source_Name = "Name";
     25 const std::string s_PackagesJson_Source_Identifier = "Identifier";
     26 const std::string s_PackagesJson_Source_Argument = "Argument";
     27 const std::string s_PackagesJson_Source_Type = "Type";
     28 
     29 const std::string s_PackagesJson_Packages = "Packages";
     30 const std::string s_PackagesJson_Package_PackageIdentifier = "PackageIdentifier";
     31 const std::string s_PackagesJson_Package_Version = "Version";
     32 const std::string s_PackagesJson_Package_Channel = "Channel";
     33 
     34 namespace
     35 {
     36 
     37     Json::Value ParseJsonString(const std::string& jsonString)
     38     {
     39         Json::Value root;
     40         std::stringstream{ jsonString } >> root;
     41         return root;
     42     }
     43 
     44     void ValidateJsonStringProperty(const Json::Value& node, const std::string& propertyName, std::string_view expectedValue, bool allowMissing = false)
     45     {
     46         if (allowMissing && expectedValue.empty() && !node.isMember(propertyName))
     47         {
     48             return;
     49         }
     50 
     51         REQUIRE(node.isMember(propertyName));
     52         REQUIRE(node[propertyName].isString());
     53         if (!expectedValue.empty())
     54         {
     55             REQUIRE(node[propertyName].asString() == expectedValue);
     56         }
     57     }
     58 
     59     const Json::Value& GetAndValidateJsonProperty(const Json::Value& node, const std::string& propertyName, Json::ValueType valueType)
     60     {
     61         REQUIRE(node.isMember(propertyName));
     62         REQUIRE(node[propertyName].type() == valueType);
     63         return node[propertyName];
     64     }
     65 
     66     void ValidateJsonWithCollection(const Json::Value& root, const PackageCollection& collection)
     67     {
     68         ValidateJsonStringProperty(root, s_PackagesJson_Schema, s_PackagesJson_SchemaUri_v2_0);
     69         ValidateJsonStringProperty(root, s_PackagesJson_WinGetVersion, collection.ClientVersion);
     70         // valijson now validates the date-time format, just check the string property exists.
     71         ValidateJsonStringProperty(root, s_PackagesJson_CreationDate, "");
     72 
     73         const auto& jsonSources = GetAndValidateJsonProperty(root, s_PackagesJson_Sources, Json::ValueType::arrayValue);
     74         REQUIRE(jsonSources.size() == collection.Sources.size());
     75 
     76         // Expect the order to be the same. Not really needed, but it makes things easier.
     77         auto jsonSourceItr = jsonSources.begin();
     78         auto sourceItr = collection.Sources.begin();
     79         for (; jsonSourceItr != jsonSources.end(); ++jsonSourceItr, ++sourceItr)
     80         {
     81             REQUIRE(jsonSourceItr->isObject());
     82             const auto& jsonSourceDetails = GetAndValidateJsonProperty(*jsonSourceItr, s_PackagesJson_Source_Details, Json::ValueType::objectValue);
     83             ValidateJsonStringProperty(jsonSourceDetails, s_PackagesJson_Source_Name, sourceItr->Details.Name);
     84             ValidateJsonStringProperty(jsonSourceDetails, s_PackagesJson_Source_Argument, sourceItr->Details.Arg);
     85             ValidateJsonStringProperty(jsonSourceDetails, s_PackagesJson_Source_Type, sourceItr->Details.Type);
     86             ValidateJsonStringProperty(jsonSourceDetails, s_PackagesJson_Source_Identifier, sourceItr->Details.Identifier);
     87 
     88             const auto& jsonPackages = GetAndValidateJsonProperty(*jsonSourceItr, s_PackagesJson_Packages, Json::ValueType::arrayValue);
     89             REQUIRE(jsonPackages.size() == sourceItr->Packages.size());
     90 
     91             auto jsonPackageItr = jsonPackages.begin();
     92             auto packageItr = sourceItr->Packages.begin();
     93             for (; jsonPackageItr != jsonPackages.end(); ++jsonPackageItr, ++packageItr)
     94             {
     95                 REQUIRE(jsonPackageItr->isObject());
     96                 ValidateJsonStringProperty(*jsonPackageItr, s_PackagesJson_Package_PackageIdentifier, packageItr->Id);
     97                 ValidateJsonStringProperty(*jsonPackageItr, s_PackagesJson_Package_Version, packageItr->VersionAndChannel.GetVersion().ToString(), true);
     98                 ValidateJsonStringProperty(*jsonPackageItr, s_PackagesJson_Package_Channel, packageItr->VersionAndChannel.GetChannel().ToString(), true);
     99             }
    100         }
    101     }
    102 
    103     void ValidateEqualCollections(const PackageCollection& first, const PackageCollection& second)
    104     {
    105         REQUIRE(first.ClientVersion == second.ClientVersion);
    106         REQUIRE(first.Sources.size() == second.Sources.size());
    107 
    108         auto firstSourceItr = first.Sources.begin();
    109         auto secondSourceItr = second.Sources.begin();
    110         for (; firstSourceItr != first.Sources.end(); ++firstSourceItr, ++secondSourceItr)
    111         {
    112             REQUIRE(firstSourceItr->Details.Name == secondSourceItr->Details.Name);
    113             REQUIRE(firstSourceItr->Details.Arg == secondSourceItr->Details.Arg);
    114             REQUIRE(firstSourceItr->Details.Type == secondSourceItr->Details.Type);
    115             REQUIRE(firstSourceItr->Details.Identifier == secondSourceItr->Details.Identifier);
    116 
    117             REQUIRE(firstSourceItr->Packages.size() == secondSourceItr->Packages.size());
    118             auto firstPackageItr = firstSourceItr->Packages.begin();
    119             auto secondPackageItr = secondSourceItr->Packages.begin();
    120             for (; firstPackageItr != firstSourceItr->Packages.end(); ++firstPackageItr, ++secondPackageItr)
    121             {
    122                 REQUIRE(firstPackageItr->Id == secondPackageItr->Id);
    123                 REQUIRE(firstPackageItr->VersionAndChannel.ToString() == secondPackageItr->VersionAndChannel.ToString());
    124             }
    125         }
    126     }
    127 }
    128 
    129 TEST_CASE("PackageCollection_Write_SingleSource", "[PackageCollection]")
    130 {
    131     PackageCollection::Source source;
    132     source.Details.Name = "TestSource";
    133     source.Details.Arg = "https://aka.ms/winget";
    134     source.Details.Type = "Microsoft.PreIndexed.Package";
    135     source.Details.Identifier = "TestSourceId";
    136 
    137     source.Packages.emplace_back(LocIndString{ "test.package1"sv }, Version{ "1.0.1" }, Channel{ "" });
    138     source.Packages.emplace_back(LocIndString{ "test.package2"sv }, Version{ "2" }, Channel{ "Public" });
    139 
    140     PackageCollection pc
    141     {
    142         "0.1.2.3",
    143         std::vector<PackageCollection::Source>{ source }
    144     };
    145 
    146     ValidateJsonWithCollection(PackagesJson::CreateJson(pc), pc);
    147 }
    148 
    149 TEST_CASE("PackageCollection_Write_MultipleSources", "[PackageCollection]")
    150 {
    151     PackageCollection::Source source1;
    152     source1.Details.Name = "TestSource";
    153     source1.Details.Arg = "https://aka.ms/winget";
    154     source1.Details.Type = "Microsoft.PreIndexed.Package";
    155     source1.Details.Identifier = "TestSourceId";
    156     source1.Packages.emplace_back(LocIndString{ "test.package1"sv }, Version{ "1.0.1" }, Channel{ "" });
    157 
    158     PackageCollection::Source source2;
    159     source2.Details.Name = "TestSource2";
    160     source2.Details.Arg = "https://aka.ms/winget";
    161     source2.Details.Type = "*Test";
    162     source2.Details.Identifier = "SecondId";
    163     source2.Packages.emplace_back(LocIndString{ "test.package2"sv }, Version{ "2.1.0" }, Channel{ "Beta" });
    164 
    165     PackageCollection pc
    166     {
    167         "1.0.0.0",
    168         std::vector<PackageCollection::Source>{ source1, source2 }
    169     };
    170 
    171     ValidateJsonWithCollection(PackagesJson::CreateJson(pc), pc);
    172 }
    173 
    174 TEST_CASE("PackageCollection_Read_SingleSource_1_0", "[PackageCollection]")
    175 {
    176     auto json = ParseJsonString(R"(
    177     {
    178       "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
    179       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    180       "Sources": [
    181         {
    182           "Packages": [
    183             {
    184               "Id": "test.WithVersion",
    185               "Version": "0.1",
    186               "Channel": "Preview"
    187             },
    188             {
    189               "Id": "test.NoVersion"
    190             }
    191           ],
    192           "SourceDetails": {
    193             "Argument": "https://aka.ms/winget",
    194             "Identifier": "TestSourceId",
    195             "Name": "TestSource",
    196             "Type": "Microsoft.PreIndexed.Package"
    197           }
    198         }
    199       ],
    200       "WinGetVersion": "1.0.0"
    201     })");
    202 
    203     auto parseResult = PackagesJson::TryParseJson(json);
    204     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
    205     REQUIRE(parseResult.Errors.empty());
    206 
    207     PackageCollection::Source source;
    208     source.Details.Name = "TestSource";
    209     source.Details.Arg = "https://aka.ms/winget";
    210     source.Details.Type = "Microsoft.PreIndexed.Package";
    211     source.Details.Identifier = "TestSourceId";
    212 
    213     source.Packages.emplace_back(LocIndString{ "test.WithVersion"sv }, Version{ "0.1" }, Channel{ "Preview" });
    214     source.Packages.emplace_back(LocIndString{ "test.NoVersion"sv }, Version{ "" }, Channel{ "" });
    215 
    216     PackageCollection expected
    217     {
    218         "1.0.0",
    219         std::vector<PackageCollection::Source>{ source }
    220     };
    221 
    222     ValidateEqualCollections(parseResult.Packages, expected);
    223 }
    224 
    225 TEST_CASE("PackageCollection_Read_SingleSource_2_0", "[PackageCollection]")
    226 {
    227     auto json = ParseJsonString(R"(
    228     {
    229       "$schema": "https://aka.ms/winget-packages.schema.2.0.json",
    230       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    231       "Sources": [
    232         {
    233           "Packages": [
    234             {
    235               "PackageIdentifier": "test.WithVersion",
    236               "Version": "0.1",
    237               "Channel": "Preview"
    238             },
    239             {
    240               "PackageIdentifier": "test.NoVersion"
    241             }
    242           ],
    243           "SourceDetails": {
    244             "Argument": "https://aka.ms/winget",
    245             "Identifier": "TestSourceId",
    246             "Name": "TestSource",
    247             "Type": "Microsoft.PreIndexed.Package"
    248           }
    249         }
    250       ]
    251     })");
    252 
    253     auto parseResult = PackagesJson::TryParseJson(json);
    254     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
    255     REQUIRE(parseResult.Errors.empty());
    256 
    257     PackageCollection::Source source;
    258     source.Details.Name = "TestSource";
    259     source.Details.Arg = "https://aka.ms/winget";
    260     source.Details.Type = "Microsoft.PreIndexed.Package";
    261     source.Details.Identifier = "TestSourceId";
    262 
    263     source.Packages.emplace_back(LocIndString{ "test.WithVersion"sv }, Version{ "0.1" }, Channel{ "Preview" });
    264     source.Packages.emplace_back(LocIndString{ "test.NoVersion"sv }, Version{ "" }, Channel{ "" });
    265 
    266     PackageCollection expected
    267     {
    268         "",
    269         std::vector<PackageCollection::Source>{ source }
    270     };
    271 
    272     ValidateEqualCollections(parseResult.Packages, expected);
    273 }
    274 
    275 TEST_CASE("PackageCollection_Read_MultipleSources_1_0", "[PackageCollection]")
    276 {
    277     auto json = ParseJsonString(R"(
    278     {
    279       "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
    280       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    281       "WinGetVersion": "1.0.0",
    282       "Sources": [
    283         {
    284           "SourceDetails": {
    285             "Argument": "//firstSource",
    286             "Identifier": "Id1",
    287             "Name": "First",
    288             "Type": "Microsoft.PreIndexed.Package"
    289           },
    290           "Packages": [
    291             {
    292               "Id": "test"
    293             }
    294           ]
    295         },
    296         {
    297           "SourceDetails": {
    298             "Argument": "//secondSource",
    299             "Identifier": "Id2",
    300             "Name": "Second",
    301             "Type": "*TestSource"
    302           },
    303           "Packages": [
    304             {
    305               "Id": "test2",
    306               "Version": "1.0"
    307             }
    308           ]
    309         }
    310       ]
    311     })");
    312 
    313 
    314     auto parseResult = PackagesJson::TryParseJson(json);
    315     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
    316     REQUIRE(parseResult.Errors.empty());
    317 
    318     PackageCollection::Source source1;
    319     source1.Details.Name = "First";
    320     source1.Details.Arg = "//firstSource";
    321     source1.Details.Type = "Microsoft.PreIndexed.Package";
    322     source1.Details.Identifier = "Id1";
    323     source1.Packages.emplace_back(LocIndString{ "test"sv }, Version{ "" }, Channel{ "" });
    324 
    325     PackageCollection::Source source2;
    326     source2.Details.Name = "Second";
    327     source2.Details.Arg = "//secondSource";
    328     source2.Details.Type = "*TestSource";
    329     source2.Details.Identifier = "Id2";
    330     source2.Packages.emplace_back(LocIndString{ "test2"sv }, Version{ "1.0" }, Channel{ "" });
    331 
    332     PackageCollection expected
    333     {
    334         "1.0.0",
    335         std::vector<PackageCollection::Source>{ source1, source2 }
    336     };
    337 
    338     ValidateEqualCollections(parseResult.Packages, expected);
    339 }
    340 
    341 TEST_CASE("PackageCollection_Read_MultipleSources_2_0", "[PackageCollection]")
    342 {
    343     auto json = ParseJsonString(R"(
    344     {
    345       "$schema": "https://aka.ms/winget-packages.schema.2.0.json",
    346       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    347       "WinGetVersion": "1.0.0",
    348       "Sources": [
    349         {
    350           "SourceDetails": {
    351             "Argument": "//firstSource",
    352             "Identifier": "Id1",
    353             "Name": "First",
    354             "Type": "Microsoft.PreIndexed.Package"
    355           },
    356           "Packages": [
    357             {
    358               "PackageIdentifier": "test"
    359             }
    360           ]
    361         },
    362         {
    363           "SourceDetails": {
    364             "Argument": "//secondSource",
    365             "Identifier": "Id2",
    366             "Name": "Second",
    367             "Type": "*TestSource"
    368           },
    369           "Packages": [
    370             {
    371               "PackageIdentifier": "test2",
    372               "Version": "1.0"
    373             }
    374           ]
    375         }
    376       ]
    377     })");
    378 
    379 
    380     auto parseResult = PackagesJson::TryParseJson(json);
    381     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
    382     REQUIRE(parseResult.Errors.empty());
    383 
    384     PackageCollection::Source source1;
    385     source1.Details.Name = "First";
    386     source1.Details.Arg = "//firstSource";
    387     source1.Details.Type = "Microsoft.PreIndexed.Package";
    388     source1.Details.Identifier = "Id1";
    389     source1.Packages.emplace_back(LocIndString{ "test"sv }, Version{ "" }, Channel{ "" });
    390 
    391     PackageCollection::Source source2;
    392     source2.Details.Name = "Second";
    393     source2.Details.Arg = "//secondSource";
    394     source2.Details.Type = "*TestSource";
    395     source2.Details.Identifier = "Id2";
    396     source2.Packages.emplace_back(LocIndString{ "test2"sv }, Version{ "1.0" }, Channel{ "" });
    397 
    398     PackageCollection expected
    399     {
    400         "1.0.0",
    401         std::vector<PackageCollection::Source>{ source1, source2 }
    402     };
    403 
    404     ValidateEqualCollections(parseResult.Packages, expected);
    405 }
    406 
    407 TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]")
    408 {
    409     auto json = ParseJsonString(R"(
    410     {
    411       "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
    412       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    413       "WinGetVersion": "1.0.0",
    414       "Sources": [
    415         {
    416           "SourceDetails": {
    417             "Argument": "//firstSource",
    418             "Identifier": "Id1",
    419             "Name": "First",
    420             "Type": "Microsoft.PreIndexed.Package"
    421           },
    422           "Packages": [
    423             {
    424               "Id": "test"
    425             }
    426           ]
    427         },
    428         {
    429           "SourceDetails": {
    430             "Argument": "//secondSource",
    431             "Identifier": "Id2",
    432             "Name": "Second",
    433             "Type": "*TestSource"
    434           },
    435           "Packages": [
    436             {
    437               "Id": "test2",
    438               "Version": "1.0"
    439             }
    440           ]
    441         },
    442         {
    443           "SourceDetails": {
    444             "Argument": "//secondSource",
    445             "Identifier": "Id2",
    446             "Name": "Second",
    447             "Type": "*TestSource"
    448           },
    449           "Packages": [
    450             {
    451               "Id": "test3",
    452               "Version": "1.2"
    453             }
    454           ]
    455         }
    456       ]
    457     })");
    458 
    459 
    460     auto parseResult = PackagesJson::TryParseJson(json);
    461     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
    462     REQUIRE(parseResult.Errors.empty());
    463 
    464     PackageCollection::Source source1;
    465     source1.Details.Name = "First";
    466     source1.Details.Arg = "//firstSource";
    467     source1.Details.Type = "Microsoft.PreIndexed.Package";
    468     source1.Details.Identifier = "Id1";
    469     source1.Packages.emplace_back(LocIndString{ "test"sv }, Version{ "" }, Channel{ "" });
    470 
    471     PackageCollection::Source source2;
    472     source2.Details.Name = "Second";
    473     source2.Details.Arg = "//secondSource";
    474     source2.Details.Type = "*TestSource";
    475     source2.Details.Identifier = "Id2";
    476     source2.Packages.emplace_back(LocIndString{ "test2"sv }, Version{ "1.0" }, Channel{ "" });
    477     source2.Packages.emplace_back(LocIndString{ "test3"sv }, Version{ "1.2" }, Channel{ "" });
    478 
    479     PackageCollection expected
    480     {
    481         "1.0.0",
    482         std::vector<PackageCollection::Source>{ source1, source2 }
    483     };
    484 
    485     ValidateEqualCollections(parseResult.Packages, expected);
    486 }
    487 
    488 TEST_CASE("PackageCollection_Read_MissingSchema", "[PackageCollection]")
    489 {
    490     auto json = ParseJsonString(R"(
    491     {
    492       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    493       "Sources": [
    494         {
    495           "Packages": [
    496             {
    497               "Id": "test.test"
    498             }
    499           ],
    500           "SourceDetails": {
    501             "Argument": "https://aka.ms/winget",
    502             "Identifier": "TestSourceId",
    503             "Name": "TestSource",
    504             "Type": "Microsoft.PreIndexed.Package"
    505           }
    506         }
    507       ],
    508       "WinGetVersion": "1.0.0"
    509     })");
    510 
    511     auto parseResult = PackagesJson::TryParseJson(json);
    512     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema);
    513 
    514     json = ParseJsonString("\"Not even a JSON object\"");
    515 
    516     parseResult = PackagesJson::TryParseJson(json);
    517     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema);
    518 }
    519 
    520 TEST_CASE("PackageCollection_Read_WrongSchema", "[PackageCollection]")
    521 {
    522     auto json = ParseJsonString(R"(
    523     {
    524       "$schema": "https://aka.ms/winget-settings.schema.json",
    525       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    526       "Sources": [
    527         {
    528           "Packages": [
    529             {
    530               "Id": "test.test"
    531             }
    532           ],
    533           "SourceDetails": {
    534             "Argument": "https://aka.ms/winget",
    535             "Identifier": "TestSourceId",
    536             "Name": "TestSource",
    537             "Type": "Microsoft.PreIndexed.Package"
    538           }
    539         }
    540       ],
    541       "WinGetVersion": "1.0.0"
    542     })");
    543 
    544     auto parseResult = PackagesJson::TryParseJson(json);
    545     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema);
    546 }
    547 
    548 TEST_CASE("PackageCollection_Read_SchemaValidationFail", "[PackageCollection]")
    549 {
    550     auto json = ParseJsonString(R"(
    551     {
    552       "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
    553       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    554       "NotSources": [
    555         {
    556           "Packages": [
    557             {
    558               "Id": "test.test"
    559             }
    560           ],
    561           "SourceDetails": {
    562             "Argument": "https://aka.ms/winget",
    563             "Identifier": "TestSourceId",
    564             "Name": "TestSource",
    565             "Type": "Microsoft.PreIndexed.Package"
    566           }
    567         }
    568       ],
    569       "WinGetVersion": "1.0.0"
    570     })");
    571 
    572     auto parseResult = PackagesJson::TryParseJson(json);
    573     INFO(parseResult.Errors);
    574 
    575     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed);
    576     REQUIRE(parseResult.Errors.find("Missing required property 'Sources'.") != std::string::npos);
    577 }
    578 
    579 TEST_CASE("PackageCollection_Read_SchemaValidationFail_Id", "[PackageCollection]")
    580 {
    581     auto json = ParseJsonString(R"(
    582     {
    583       "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
    584       "CreationDate": "2021-01-01T12:00:00.000-00:00",
    585       "Sources": [
    586         {
    587           "Packages": [
    588             {
    589               "NotId": "test.test"
    590             }
    591           ],
    592           "SourceDetails": {
    593             "Argument": "https://aka.ms/winget",
    594             "Identifier": "TestSourceId",
    595             "Name": "TestSource",
    596             "Type": "Microsoft.PreIndexed.Package"
    597           }
    598         }
    599       ],
    600       "WinGetVersion": "1.0.0"
    601     })");
    602 
    603     auto parseResult = PackagesJson::TryParseJson(json);
    604     INFO(parseResult.Errors);
    605 
    606     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed);
    607     REQUIRE(parseResult.Errors.find("Missing required property 'Id'.") != std::string::npos);
    608 }
    609 
    610 TEST_CASE("PackageCollection_Read_BadTimeStamp", "[PackageCollection]")
    611 {
    612     auto json = ParseJsonString(R"(
    613     {
    614       "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
    615       "CreationDate": "2021- 1- 1 12:00:00.000",
    616       "Sources": [
    617         {
    618           "Packages": [
    619             {
    620               "Id": "test"
    621             }
    622           ],
    623           "SourceDetails": {
    624             "Argument": "https://aka.ms/winget",
    625             "Identifier": "TestSourceId",
    626             "Name": "TestSource",
    627             "Type": "Microsoft.PreIndexed.Package"
    628           }
    629         }
    630       ],
    631       "WinGetVersion": "1.0.0"
    632     })");
    633 
    634     auto parseResult = PackagesJson::TryParseJson(json);
    635     INFO(parseResult.Errors);
    636 
    637     REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed);
    638     REQUIRE_FALSE(parseResult.Errors.empty());
    639 }