From 1d67da529327030d76275516f411914bb73553fc Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Sat, 9 Sep 2023 11:35:57 +0200 Subject: [PATCH 1/3] feat: improve diff rules for contexts and lists of elements - when comparing list of elements, ensure elements are compared based on their ids - when expecting a single context, allow a string or a list of one context - when checking for default core context, allow any version --- .../Entity/RetrieveEntity/018_01_01.robot | 6 ++- .../Entity/RetrieveEntity/018_01_02.robot | 6 ++- .../Entity/RetrieveEntity/018_01_03.robot | 6 ++- libraries/assertionUtils.py | 54 +++++++++++++++++-- resources/AssertionUtils.resource | 3 +- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_01.robot b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_01.robot index 78fe5637..8b9f85d6 100644 --- a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_01.robot +++ b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_01.robot @@ -27,7 +27,11 @@ ${expectation_filename}= building-simple-attributes-expectation.jsonld Check Response Status Code 201 ${response.status_code} ${response}= Query Entity ${entity_id} ${CONTENT_TYPE_LD_JSON} Check Response Status Code 200 ${response.status_code} - Check Response Body Containing Entity element ${expectation_filename} ${entity_id} ${response.json()} + Check Response Body Containing Entity element + ... ${expectation_filename} + ... ${entity_id} + ... ${response.json()} + ... ${True} *** Keywords *** diff --git a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_02.robot b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_02.robot index 9140d75b..586b2a4a 100644 --- a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_02.robot +++ b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_02.robot @@ -36,7 +36,11 @@ ${attribute_subcategory}= https://ngsi-ld-test-suite/context#subCatego ... ${CONTENT_TYPE_LD_JSON} ... attrs=${attributes_to_be_retrieved} Check Response Status Code 200 ${response.status_code} - Check Response Body Containing Entity element ${expectation_filename} ${entity_id} ${response.json()} + Check Response Body Containing Entity element + ... ${expectation_filename} + ... ${entity_id} + ... ${response.json()} + ... ${True} *** Keywords *** diff --git a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_03.robot b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_03.robot index f255e10c..394c20e0 100644 --- a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_03.robot +++ b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_01_03.robot @@ -31,7 +31,11 @@ ${geometry_property}= location ... ${CONTENT_TYPE_LD_JSON} ... geoproperty=${geometry_property} Check Response Status Code 200 ${response.status_code} - Check Response Body Containing Entity element ${expectation_filename} ${entity_id} ${response.json()} + Check Response Body Containing Entity element + ... ${expectation_filename} + ... ${entity_id} + ... ${response.json()} + ... ${True} *** Keywords *** diff --git a/libraries/assertionUtils.py b/libraries/assertionUtils.py index e0ac15dc..5be0e26d 100644 --- a/libraries/assertionUtils.py +++ b/libraries/assertionUtils.py @@ -1,5 +1,7 @@ +import re from dataclasses import dataclass from deepdiff import DeepDiff +from deepdiff.helper import CannotCompare from prettydiff import get_annotated_lines_from_diff, diff_json, Flag from robot.api import logger @@ -11,26 +13,72 @@ class Theme: reset: str -def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, group_by=None): +def wrap_context_to_list(context): + if type(context) is str: + return [context] + else: + return context + + +core_context_pattern = re.compile('https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v\d\.\d.jsonld') + + +class AnyCoreContextVersionOperator: + def match(self, level) -> bool: + return level.path().endswith("['@context']") + + def give_up_diffing(self, level, diff_instance) -> bool: + actual_context = wrap_context_to_list(level.t2) + return len(actual_context) == 1 and core_context_pattern.match(actual_context[0]) is not None + + +class StringOrSingleListContextOperator: + + def match(self, level) -> bool: + # The context can be at the root of the element to check... or deeper when we have list of elements + # So match on the end of the path + return level.path().endswith("['@context']") + + def give_up_diffing(self, level, diff_instance) -> bool: + expected_context = wrap_context_to_list(level.t1) + actual_context = wrap_context_to_list(level.t2) + return expected_context == actual_context + + +def compare_func(x, y, level=None): + try: + return x['id'] == y['id'] + except Exception: + raise CannotCompare() from None + + +def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ignore_core_context_version=False, + group_by=None): """Function exposed as a keyword to compare two dictionaries :param expected: expected dictionary :param actual: actual dictionary :param exclude_regex_paths: list of regex paths of keys to be ignored + :param ignore_core_context_version: whether any core context version is allowed in the results :param group_by: a key to group the results, useful for lists of results """ if 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()], 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()]) else: - res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1) + res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, + iterable_compare_func=compare_func, custom_operators=[StringOrSingleListContextOperator()]) if len(res) > 0: output_pretty_diff(expected, actual, Theme(added="", removed="", reset="")) return res -def output_pretty_diff(a, b, theme, indent_size: int = 2, console=False): +def output_pretty_diff(a, b, theme, indent_size: int = 2): logger.info("Dictionary comparison failed with -> ", also_console=True) lines = get_annotated_lines_from_diff(diff_json(a, b)) diff --git a/resources/AssertionUtils.resource b/resources/AssertionUtils.resource index 52faa52c..150b10e1 100755 --- a/resources/AssertionUtils.resource +++ b/resources/AssertionUtils.resource @@ -96,13 +96,14 @@ Check Response Body Containing Batch Operation Result END Check Response Body Containing Entity element - [Arguments] ${expectation_filename} ${entity_id} ${response_body} + [Arguments] ${expectation_filename} ${entity_id} ${response_body} ${ignore_core_context_version}=False ${entity_payload}= Load JSON From File ${EXECDIR}/data/entities/expectations/${expectation_filename} ${entity}= Update Value To JSON ${entity_payload} $..id ${entity_id} ${comparison_result}= Compare Dictionaries Ignoring Keys ... ${entity} ... ${response_body} ... ${instance_id_regex_expr} + ... ${ignore_core_context_version} Should Be Empty ${comparison_result} msg=${comparison_result.pretty()} Check Response Body Containing List Containing Entity Elements -- GitLab From 03c3f00e14c936eaf398d32a76bde7046c7d294e Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Sun, 10 Sep 2023 19:40:05 +0200 Subject: [PATCH 2/3] fix: add missing tests where core context version should be ignored --- .../Entity/QueryEntities/019_01_02.robot | 1 + .../Entity/QueryEntities/019_01_05.robot | 1 + .../Entity/RetrieveEntity/018_04.robot | 6 +++++- resources/AssertionUtils.resource | 19 ++++++++++++++++--- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_02.robot b/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_02.robot index c2f7962c..77753201 100644 --- a/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_02.robot +++ b/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_02.robot @@ -54,6 +54,7 @@ Query several entities based on the entities types ... ${expectation_filename} ... ${entities_ids_to_be_compared} ... ${response.json()} + ... ${True} *** Keywords *** diff --git a/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_05.robot b/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_05.robot index 99cc51c3..bb97da43 100644 --- a/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_05.robot +++ b/TP/NGSI-LD/ContextInformation/Consumption/Entity/QueryEntities/019_01_05.robot @@ -46,6 +46,7 @@ Query several entities based on a list of properties ... ${expectation_filename} ... ${entities_ids_to_be_compared} ... ${response.json()} + ... ${True} *** Keywords *** diff --git a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_04.robot b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_04.robot index 6d092052..ba179ed4 100644 --- a/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_04.robot +++ b/TP/NGSI-LD/ContextInformation/Consumption/Entity/RetrieveEntity/018_04.robot @@ -31,7 +31,11 @@ Get an entity in a simplified representation ... ${CONTENT_TYPE_LD_JSON} ... options=${options_parameter} Check Response Status Code 200 ${response.status_code} - Check Response Body Containing Entity element ${expectation_filename} ${entity_id} ${response.json()} + Check Response Body Containing Entity element + ... ${expectation_filename} + ... ${entity_id} + ... ${response.json()} + ... ${True} [Teardown] Delete Entity by Id Returning Response ${entity_id} diff --git a/resources/AssertionUtils.resource b/resources/AssertionUtils.resource index 150b10e1..07eeb098 100755 --- a/resources/AssertionUtils.resource +++ b/resources/AssertionUtils.resource @@ -107,14 +107,26 @@ Check Response Body Containing Entity element Should Be Empty ${comparison_result} msg=${comparison_result.pretty()} Check Response Body Containing List Containing Entity Elements - [Arguments] ${expectation_filename} ${entities_ids} ${response_body} + [Arguments] + ... ${expectation_filename} + ... ${entities_ids} + ... ${response_body} + ... ${ignore_core_context_version}=False FOR ${entity_id} IN @{entities_ids} ${entity}= Get Value From JSON ${response_body} $[?(@.id=='${entity_id}')] - Check Response Body Containing Entity element ${expectation_filename} ${entity_id} ${entity}[0] + Check Response Body Containing Entity element + ... ${expectation_filename} + ... ${entity_id} + ... ${entity}[0] + ... ${ignore_core_context_version} END Check Response Body Containing List Containing Entity Elements With Different Types - [Arguments] ${filename} ${entities_representation_ids} ${response_body} + [Arguments] + ... ${filename} + ... ${entities_representation_ids} + ... ${response_body} + ... ${ignore_core_context_version}=False ${entities_representation_payload}= Load JSON From File ${EXECDIR}/data/entities/expectations/${filename} ${index}= Set Variable 0 FOR ${entity_representation_id} IN @{entities_representation_ids} @@ -128,6 +140,7 @@ Check Response Body Containing List Containing Entity Elements With Different Ty ... ${entities_representation_payload} ... ${response_body} ... ${instance_id_regex_expr} + ... ${ignore_core_context_version} ... group_by=id Should Be Empty ${comparison_result} msg=${comparison_result.pretty()} -- GitLab From af239e85f89aa53e94b08cf2643bc47b156cf617 Mon Sep 17 00:00:00 2001 From: Benoit Orihuela Date: Sun, 10 Sep 2023 19:56:51 +0200 Subject: [PATCH 3/3] fix: allow ignoring core context also when group_by is asked --- libraries/assertionUtils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/assertionUtils.py b/libraries/assertionUtils.py index 5be0e26d..d05583d3 100644 --- a/libraries/assertionUtils.py +++ b/libraries/assertionUtils.py @@ -62,7 +62,11 @@ def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ig :param group_by: a key to group the results, useful for lists of results """ - if group_by is not None: + 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()], + 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()], group_by=group_by) -- GitLab