diff --git a/libraries/assertionUtils.py b/libraries/assertionUtils.py index d05583d31bda9e4d54bcbc636df5fdc48350216a..6b82b53953c5c8740f67e5882e112fe9d6423161 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 parse_ngsild_date + @dataclass class Theme: @@ -45,6 +48,19 @@ class StringOrSingleListContextOperator: return expected_context == actual_context +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 = 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): try: return x['id'] == y['id'] @@ -64,18 +80,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="")) diff --git a/libraries/dateTimeUtils.py b/libraries/dateTimeUtils.py index d22264c89a02166260c23779a3179e98e6b95f07..973a5de97e578eab550a67ba9c152e04ee214267 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