winget-cli

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

commit a9a63a35e4607571648f33d1d8800ad00117df64
parent 2fb627db6710e29f45d09cb327f0aa79176f049b
Author: Luis Chacón <lechacon@users.noreply.github.com>
Date:   Fri,  2 Apr 2021 12:41:31 -0700

Fix format of CreationDate on export (#843)


Diffstat:
Msrc/AppInstallerCLICore/PackageCollection.cpp | 3+--
Msrc/AppInstallerCLITests/PackageCollection.cpp | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/AppInstallerCommonCore/DateTime.cpp | 12++++++++++--
3 files changed, 66 insertions(+), 6 deletions(-)

diff --git a/src/AppInstallerCLICore/PackageCollection.cpp b/src/AppInstallerCLICore/PackageCollection.cpp @@ -108,9 +108,8 @@ namespace AppInstaller::CLI root[ss.PackagesJson_WinGetVersion] = wingetVersion; root[ss.PackagesJson_Schema] = ss.PackagesJson_SchemaUri_v1_0; - // TODO: This uses localtime. Do we want to use UTC or add time zone? std::stringstream currentTimeStream; - Utility::OutputTimePoint(currentTimeStream, std::chrono::system_clock::now()); + Utility::OutputTimePoint(currentTimeStream, std::chrono::system_clock::now(), true); root[ss.PackagesJson_CreationDate] = currentTimeStream.str(); return root; diff --git a/src/AppInstallerCLITests/PackageCollection.cpp b/src/AppInstallerCLITests/PackageCollection.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "TestCommon.h" +#include <winget/Regex.h> #include <PackageCollection.h> // Duplicating here because a change to these values in the product *REALLY* needs to be thought through. @@ -52,6 +53,19 @@ namespace REQUIRE(node[propertyName].asString() == expectedValue); } + void ValidateJsonStringPropertyRegex(const Json::Value& node, const std::string& propertyName, std::string_view regex, bool allowMissing = false) + { + if (allowMissing && !node.isMember(propertyName)) + { + return; + } + + REQUIRE(node.isMember(propertyName)); + REQUIRE(node[propertyName].isString()); + const auto& value = AppInstaller::Utility::ConvertToUTF16(node[propertyName].asString()); + REQUIRE(AppInstaller::Regex::Expression(regex).IsMatch(value)); + } + const Json::Value& GetAndValidateJsonProperty(const Json::Value& node, const std::string& propertyName, Json::ValueType valueType) { REQUIRE(node.isMember(propertyName)); @@ -63,7 +77,13 @@ namespace { ValidateJsonStringProperty(root, s_PackagesJson_Schema, s_PackagesJson_SchemaUri_v1_0); ValidateJsonStringProperty(root, s_PackagesJson_WinGetVersion, collection.ClientVersion); - REQUIRE(root.isMember(s_PackagesJson_CreationDate)); + + // valijson does not validate the date-time format, which should follow RFC3339 according to the JSON schema. + // Ensure we write something at least reasonable. + // The expected format is <Date>T<Time><TimeZone> + // with Date="YYYY-MM-DD"; Time="HH:mm:ss.xxx"; TimeZone="Z" or "+HH:mm" or "-HH:mm" (offset from UTC) + std::string_view dateTimeRegex = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]+(Z|[+-][0-9]{2}:[0-9]{2})"sv; + ValidateJsonStringPropertyRegex(root, s_PackagesJson_CreationDate, dateTimeRegex); const auto& jsonSources = GetAndValidateJsonProperty(root, s_PackagesJson_Sources, Json::ValueType::arrayValue); REQUIRE(jsonSources.size() == collection.Sources.size()); @@ -449,8 +469,41 @@ TEST_CASE("PackageCollection_Read_SchemaValidationFail", "[PackageCollection]") })"); auto parseResult = PackagesJson::TryParseJson(json); - INFO(parseResult.Errors); + INFO(parseResult.Errors); REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed); REQUIRE(parseResult.Errors.find("Missing required property 'Sources'.") != std::string::npos); +} + +TEST_CASE("PackageCollection_Read_BadTimeStamp", "[PackageCollection]") +{ + // We used to export without padding the creation date with 0s nor adding time zone. + // Ensure we don't break with that format. + auto json = ParseJsonString(R"( + { + "$schema": "https://aka.ms/winget-packages.schema.1.0.json", + "CreationDate": "2021- 1- 1 12:00:00.000", + "Sources": [ + { + "Packages": [ + { + "Id": "test" + } + ], + "SourceDetails": { + "Argument": "https://aka.ms/winget", + "Identifier": "TestSourceId", + "Name": "TestSource", + "Type": "Microsoft.PreIndexed.Package" + } + } + ], + "WinGetVersion": "1.0.0" + })"); + + auto parseResult = PackagesJson::TryParseJson(json); + INFO(parseResult.Errors); + + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success); + REQUIRE(parseResult.Errors.empty()); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/DateTime.cpp b/src/AppInstallerCommonCore/DateTime.cpp @@ -18,8 +18,8 @@ namespace AppInstaller::Utility << std::setw(4) << (1900 + localTime.tm_year) << '-' << std::setw(2) << std::setfill('0') << (1 + localTime.tm_mon) << '-' << std::setw(2) << std::setfill('0') << localTime.tm_mday << (useRFC3339 ? 'T' : ' ') - << std::setw(2) << std::setfill('0') << localTime.tm_hour << ':' - << std::setw(2) << std::setfill('0') << localTime.tm_min << ':' + << std::setw(2) << std::setfill('0') << localTime.tm_hour << ':' + << std::setw(2) << std::setfill('0') << localTime.tm_min << ':' << std::setw(2) << std::setfill('0') << localTime.tm_sec << '.'; // Get partial seconds @@ -27,6 +27,14 @@ namespace AppInstaller::Utility auto leftoverMillis = duration_cast<milliseconds>(sinceEpoch) - duration_cast<seconds>(sinceEpoch); stream << std::setw(3) << std::setfill('0') << leftoverMillis.count(); + + if (useRFC3339) + { + // RFC 3339 requires adding time zone info. + // No need to bother getting the actual time zone as we don't need it. + // -00:00 represents an unspecified time zone, not UTC. + stream << "-00:00"; + } } std::string GetCurrentTimeForFilename()