From 5be163298a1b2a3baed44699d0b9fbcee35d60ce Mon Sep 17 00:00:00 2001 From: echarrod Date: Mon, 8 Jun 2020 14:10:13 +0100 Subject: [PATCH] SDK-1589: Update Attibute Issuance Expiry Date format to be milliseconds, not microseconds; update version --- sonar-project.properties | 2 +- .../third_party_attribute_extension.py | 31 ++++++- .../test_third_party_attribute_extension.py | 85 ++++++++++++++++++- yoti_python_sdk/version.py | 2 +- 4 files changed, 111 insertions(+), 9 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index 4869bb67..491aea13 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.host.url = https://sonarcloud.io sonar.organization = getyoti sonar.projectKey = getyoti:python sonar.projectName = Python SDK -sonar.projectVersion = 2.12.0 +sonar.projectVersion = 2.12.1 sonar.exclusions = yoti_python_sdk/tests/**,examples/**,yoti_python_sdk/protobuf/**/* sonar.python.pylint.reportPath = coverage.out diff --git a/yoti_python_sdk/dynamic_sharing_service/extension/third_party_attribute_extension.py b/yoti_python_sdk/dynamic_sharing_service/extension/third_party_attribute_extension.py index 6299f05f..64ec5551 100644 --- a/yoti_python_sdk/dynamic_sharing_service/extension/third_party_attribute_extension.py +++ b/yoti_python_sdk/dynamic_sharing_service/extension/third_party_attribute_extension.py @@ -1,23 +1,46 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals + import copy +import pytz + class ThirdPartyAttributeExtension(object): THIRDPARTY_ATTRIBUTE = "THIRD_PARTY_ATTRIBUTE" def __init__(self): - self.__extension = {} - self.__extension["type"] = self.THIRDPARTY_ATTRIBUTE - self.__extension["content"] = {"expiry_date": None, "definitions": []} + self.__extension = { + "type": self.THIRDPARTY_ATTRIBUTE, + "content": {"expiry_date": None, "definitions": []}, + } def with_expiry_date(self, expiry_date): - self.__extension["content"]["expiry_date"] = expiry_date.isoformat() + """ + :param expiry_date: Expiry date for the attribute. If no timezone info is provided, UTC will be used. + :type expiry_date: datetime + """ + if expiry_date.tzinfo is None: + expiry_date = expiry_date.replace(tzinfo=pytz.UTC) + + utc_time = expiry_date.astimezone(pytz.utc) + rfc_3339_milliseconds = utc_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + self.__extension["content"]["expiry_date"] = rfc_3339_milliseconds + "Z" return self def with_definitions(self, *names): + """ + :param names: attribute definitions + :type names: str or list[str] + """ self.__extension["content"]["definitions"].extend([{"name": s} for s in names]) return self def build(self): + """ + Builds the object + + :return: the third party attribute + :rtype: ThirdPartyAttributeExtension + """ return copy.deepcopy(self.__extension) diff --git a/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py b/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py index 088a47ae..7c7a57ae 100644 --- a/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py +++ b/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py @@ -2,6 +2,10 @@ from __future__ import unicode_literals from datetime import datetime + +import pytest +import pytz + from yoti_python_sdk.dynamic_sharing_service.extension.third_party_attribute_extension import ( ThirdPartyAttributeExtension, ) @@ -19,7 +23,7 @@ def test_should_create_extension(): ) assert extension["type"] == ThirdPartyAttributeExtension.THIRDPARTY_ATTRIBUTE - assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458000" + assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458Z" assert extension["content"]["definitions"][0]["name"] == DEFINITION @@ -38,7 +42,7 @@ def test_with_definition_should_add_to_list(): ) assert extension["type"] == ThirdPartyAttributeExtension.THIRDPARTY_ATTRIBUTE - assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458000" + assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458Z" assert extension["content"]["definitions"][0]["name"] == DEFINITION1 assert extension["content"]["definitions"][1]["name"] == DEFINITION2 @@ -58,7 +62,82 @@ def test_with_definition_should_add_multiple(): ) assert extension["type"] == ThirdPartyAttributeExtension.THIRDPARTY_ATTRIBUTE - assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458000" + assert extension["content"]["expiry_date"] == "2019-10-30T12:10:09.458Z" assert extension["content"]["definitions"][0]["name"] == DEFINITION1 assert extension["content"]["definitions"][1]["name"] == DEFINITION2 + + +@pytest.mark.parametrize( + "expiry_date, expected_value", + [ + ( + datetime(2051, 1, 13, 19, 50, 53, 1, tzinfo=pytz.utc), + "2051-01-13T19:50:53.000Z", + ), + ( + datetime(2026, 2, 2, 22, 4, 5, 123, tzinfo=pytz.utc), + "2026-02-02T22:04:05.000Z", + ), + ( + datetime(2051, 4, 13, 19, 50, 53, 999, tzinfo=pytz.utc), + "2051-04-13T19:50:53.000Z", + ), + ( + datetime(2026, 1, 31, 22, 4, 5, 1232, tzinfo=pytz.utc), + "2026-01-31T22:04:05.001Z", + ), + ( + datetime(2026, 1, 31, 22, 4, 5, 17777, tzinfo=pytz.utc), + "2026-01-31T22:04:05.017Z", + ), + ( + datetime(2019, 10, 30, 12, 10, 9, int(458e3), tzinfo=pytz.utc), + "2019-10-30T12:10:09.458Z", + ), + ( + datetime(2026, 1, 2, 22, 4, 5, 123456, tzinfo=pytz.utc), + "2026-01-02T22:04:05.123Z", + ), + ], +) +def test_should_format_utc_expiry_dates_correctly(expiry_date, expected_value): + DEFINITION = "some_value" + + extension = ( + ThirdPartyAttributeExtension() + .with_expiry_date(expiry_date) + .with_definitions(DEFINITION) + .build() + ) + + assert extension["content"]["expiry_date"] == expected_value + + +@pytest.mark.parametrize( + "expiry_date, tz_name", + [ + (datetime(2030, 6, 6, 8, 0, 0, 0), "US/Eastern",), + (datetime(2030, 6, 6, 15, 0, 0, 0), "Europe/Moscow",), + (datetime(2030, 6, 6, 7, 0, 0, 0), "America/Jamaica",), + (datetime(2030, 6, 6, 23, 0, 0, 0), "Etc/GMT-11"), + (datetime(2030, 6, 6, 7, 0, 0, 0), "Etc/GMT+5"), + # In order to conform with the POSIX style, those zones beginning + # with "Etc/GMT" have their sign reversed from what most people expect. In this style, zones west of GMT have + # a positive sign and those east have a negative sign. + ], +) +def test_should_format_localized_expiry_dates(expiry_date, tz_name): + DEFINITION = "some_value" + + tz = pytz.timezone(tz_name) + localized_expiry_date = tz.localize(expiry_date) + + extension = ( + ThirdPartyAttributeExtension() + .with_expiry_date(localized_expiry_date) + .with_definitions(DEFINITION) + .build() + ) + + assert extension["content"]["expiry_date"] == "2030-06-06T12:00:00.000Z" diff --git a/yoti_python_sdk/version.py b/yoti_python_sdk/version.py index 3c62c652..ad24c607 100644 --- a/yoti_python_sdk/version.py +++ b/yoti_python_sdk/version.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = "2.12.0" +__version__ = "2.12.1"