From 5b12d021c33739d571ff9cc1484a0078e87843f3 Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Wed, 13 Sep 2023 12:56:22 +0200 Subject: [PATCH 1/2] fix: implement a specific comparison function for temporal properties --- libraries/assertionUtils.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/libraries/assertionUtils.py b/libraries/assertionUtils.py index d05583d3..b419bfe5 100644 --- a/libraries/assertionUtils.py +++ b/libraries/assertionUtils.py @@ -1,3 +1,4 @@ +import datetime import re from dataclasses import dataclass from deepdiff import DeepDiff @@ -5,6 +6,8 @@ from deepdiff.helper import CannotCompare from prettydiff import get_annotated_lines_from_diff, diff_json, Flag from robot.api import logger +from dateTimeUtils import is_date + @dataclass class Theme: @@ -45,6 +48,23 @@ class StringOrSingleListContextOperator: return expected_context == actual_context +date_format_with_millis = "%Y-%m-%dT%H:%M:%S.%fZ" + + +class TemporalPropertyOperator: + def match(self, level) -> bool: + return (level.path().endswith("['createdAt']") + or level.path().endswith("['modifiedAt']") + or level.path().endswith("['observedAt']") + or level.path().endswith("['deletedAt']")) + + def give_up_diffing(self, level, diff_instance) -> bool: + expected_datetime = level.t1 + actual_datetime = level.t2 + return (is_date(actual_datetime, date_format_with_millis) + and datetime.datetime.strptime(expected_datetime, date_format_with_millis) == datetime.datetime.strptime(actual_datetime, date_format_with_millis)) + + def compare_func(x, y, level=None): try: return x['id'] == y['id'] @@ -64,18 +84,22 @@ def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ig if group_by is not None and ignore_core_context_version: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, - iterable_compare_func=compare_func, custom_operators=[AnyCoreContextVersionOperator()], + iterable_compare_func=compare_func, + custom_operators=[AnyCoreContextVersionOperator(), TemporalPropertyOperator()], group_by=group_by) elif group_by is not None: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, - iterable_compare_func=compare_func, custom_operators=[StringOrSingleListContextOperator()], + iterable_compare_func=compare_func, + custom_operators=[StringOrSingleListContextOperator(), TemporalPropertyOperator()], group_by=group_by) elif ignore_core_context_version: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, - iterable_compare_func=compare_func, custom_operators=[AnyCoreContextVersionOperator()]) + iterable_compare_func=compare_func, + custom_operators=[AnyCoreContextVersionOperator(), TemporalPropertyOperator()]) else: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, - iterable_compare_func=compare_func, custom_operators=[StringOrSingleListContextOperator()]) + iterable_compare_func=compare_func, + custom_operators=[StringOrSingleListContextOperator(), TemporalPropertyOperator()]) if len(res) > 0: output_pretty_diff(expected, actual, Theme(added="", removed="", reset="")) -- GitLab From 27b7dc6349ec218cf98d1cc68b5721a50a5fc370 Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Mon, 25 Sep 2023 07:59:07 +0200 Subject: [PATCH 2/2] feat: format for NGSI-LD DateTime as expressed in 4.6.3 --- libraries/assertionUtils.py | 12 ++++-------- libraries/dateTimeUtils.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libraries/assertionUtils.py b/libraries/assertionUtils.py index b419bfe5..6b82b539 100644 --- a/libraries/assertionUtils.py +++ b/libraries/assertionUtils.py @@ -6,7 +6,7 @@ from deepdiff.helper import CannotCompare from prettydiff import get_annotated_lines_from_diff, diff_json, Flag from robot.api import logger -from dateTimeUtils import is_date +from dateTimeUtils import parse_ngsild_date @dataclass @@ -48,9 +48,6 @@ class StringOrSingleListContextOperator: return expected_context == actual_context -date_format_with_millis = "%Y-%m-%dT%H:%M:%S.%fZ" - - class TemporalPropertyOperator: def match(self, level) -> bool: return (level.path().endswith("['createdAt']") @@ -59,10 +56,9 @@ class TemporalPropertyOperator: or level.path().endswith("['deletedAt']")) def give_up_diffing(self, level, diff_instance) -> bool: - expected_datetime = level.t1 - actual_datetime = level.t2 - return (is_date(actual_datetime, date_format_with_millis) - and datetime.datetime.strptime(expected_datetime, date_format_with_millis) == datetime.datetime.strptime(actual_datetime, date_format_with_millis)) + expected_datetime = parse_ngsild_date(level.t1) + actual_datetime = parse_ngsild_date(level.t2) + return actual_datetime is not None and expected_datetime == actual_datetime def compare_func(x, y, level=None): diff --git a/libraries/dateTimeUtils.py b/libraries/dateTimeUtils.py index d22264c8..973a5de9 100644 --- a/libraries/dateTimeUtils.py +++ b/libraries/dateTimeUtils.py @@ -1,4 +1,5 @@ -import datetime +from datetime import datetime +import re def is_date(date, format): @@ -7,7 +8,33 @@ def is_date(date, format): :param format: date format """ try: - datetime.datetime.strptime(date, format) + datetime.strptime(date, format) return True except ValueError: return False + + +def parse_ngsild_date(date_string): + """Function used in checks to assert if a received date is compliant with the NGSI-LD format + :param date_string: string to check for date + """ + try: + # timestamp with milliseconds separated by a comma (v1.3+) + match = re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2},\d{1,6}Z", date_string) + if match: + return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S,%fZ") + + # timestamp with milliseconds separated by a dot (v1.4+) + match = re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6}Z", date_string) + if match: + return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ") + + # timestamp without milliseconds + match = re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", date_string) + if match: + return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%SZ") + + # unknown timestamp format + return None + except ValueError: + return None -- GitLab