diff --git a/netcompare/check_types.py b/netcompare/check_types.py index 3b11c8d..119b310 100644 --- a/netcompare/check_types.py +++ b/netcompare/check_types.py @@ -107,9 +107,9 @@ def evaluate(self, reference_value: Any, value_to_compare: Any) -> Tuple[Dict, b class ExactMatchType(CheckType): """Exact Match class docstring.""" - def evaluate(self, reference_value: Any, value_to_compare: Any) -> Tuple[Dict, bool]: + def evaluate(self, reference_value: Any, value_to_compare: Any, normalize=True) -> Tuple[Dict, bool]: """Returns the difference between values and the boolean.""" - evaluation_result = diff_generator(reference_value, value_to_compare) + evaluation_result = diff_generator(reference_value, value_to_compare, normalize) return evaluation_result, not evaluation_result @@ -126,9 +126,9 @@ def __init__(self, *args): raise ValueError(f"Tolerance parameter must be defined as float at index 1. You have: {args}") from error self.tolerance_factor = float(tolerance) / 100 - def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple[Dict, bool]: + def evaluate(self, reference_value: Mapping, value_to_compare: Mapping, normalize=True) -> Tuple[Dict, bool]: """Returns the difference between values and the boolean. Overwrites method in base class.""" - diff = diff_generator(reference_value, value_to_compare) + diff = diff_generator(reference_value, value_to_compare, normalize) self._remove_within_tolerance(diff) return diff, not diff diff --git a/netcompare/evaluators.py b/netcompare/evaluators.py index 36f9cdd..98153dc 100644 --- a/netcompare/evaluators.py +++ b/netcompare/evaluators.py @@ -5,12 +5,13 @@ from .utils.diff_helpers import get_diff_iterables_items, fix_deepdiff_key_names -def diff_generator(pre_result: Any, post_result: Any) -> Dict: +def diff_generator(pre_result: Any, post_result: Any, normalize=True) -> Dict: """Generates diff between pre and post data based on check definition. Args: pre_result: dataset to compare post_result: dataset to compare + normalize: indicates if reference key was used in JMESPath to normalize data Returns: dict: differences between two datasets with the following keys: @@ -32,7 +33,7 @@ def diff_generator(pre_result: Any, post_result: Any) -> Dict: if iterables_items: result.update(iterables_items) - return fix_deepdiff_key_names(result) + return fix_deepdiff_key_names(result) if normalize else result def parameter_evaluator(values: Mapping, parameters: Mapping) -> Dict: diff --git a/tests/mock/textfsm_ospf_int_br/post.json b/tests/mock/textfsm_ospf_int_br/post.json new file mode 100644 index 0000000..c370491 --- /dev/null +++ b/tests/mock/textfsm_ospf_int_br/post.json @@ -0,0 +1,14 @@ +[ + {"interface": "Lo0", "area": "0", "ip_address_mask": "172.16.0.11/32", "cost": "1", "state": "LOOP", "neighbors_fc": "0/0"}, + {"interface": "Se0/0/0.100", "area": "0", "ip_address_mask": "172.16.1.1/30", "cost": "50", "state": "DOWN", "neighbors_fc": "1/1"}, + {"interface": "Fa0/0", "area": "0", "ip_address_mask": "10.0.0.5/24", "cost": "1", "state": "DR", "neighbors_fc": "1/1"}, + {"interface": "Fa0/1", "area": "11", "ip_address_mask": "10.1.2.1/24", "cost": "1", "state": "DR", "neighbors_fc": "0/0"}, + {"interface": "Tu1610", "area": "0", "ip_address_mask": "0.0.0.0/0", "cost": "50", "state": "P2P", "neighbors_fc": "0/0"}, + {"interface": "Lo5", "area": "0", "ip_address_mask": "10.48.8.5/32", "cost": "1", "state": "LOOP", "neighbors_fc": "0/0"}, + {"interface": "Lo4", "area": "0", "ip_address_mask": "10.48.8.4/32", "cost": "1", "state": "LOOP", "neighbors_fc": "0/0"}, + {"interface": "Tu1603", "area": "0", "ip_address_mask": "0.0.0.0/0", "cost": "50", "state": "DOWN", "neighbors_fc": "0/0"}, + {"interface": "Tu1602", "area": "0", "ip_address_mask": "0.0.0.0/0", "cost": "50", "state": "P2P", "neighbors_fc": "0/0"}, + {"interface": "PO4/0", "area": "0", "ip_address_mask": "10.1.232.6/30", "cost": "6", "state": "P2P", "neighbors_fc": "1/1"}, + {"interface": "Se3/2:0", "area": "0", "ip_address_mask": "10.1.224.218/30", "cost": "6", "state": "P2P", "neighbors_fc": "1/1"}, + {"interface": "Se3/1:0", "area": "0", "ip_address_mask": "10.1.225.150/30", "cost": "6", "state": "P2P", "neighbors_fc": "1/1"} +] \ No newline at end of file diff --git a/tests/mock/textfsm_ospf_int_br/pre.json b/tests/mock/textfsm_ospf_int_br/pre.json new file mode 100644 index 0000000..3ed15de --- /dev/null +++ b/tests/mock/textfsm_ospf_int_br/pre.json @@ -0,0 +1,14 @@ +[ + {"interface": "Lo0", "area": "0", "ip_address_mask": "172.16.0.11/32", "cost": "1", "state": "LOOP", "neighbors_fc": "0/0"}, + {"interface": "Se0/0/0.100", "area": "0", "ip_address_mask": "172.16.1.1/30", "cost": "50", "state": "P2P", "neighbors_fc": "1/1"}, + {"interface": "Fa0/0", "area": "0", "ip_address_mask": "10.0.0.5/24", "cost": "1", "state": "BDR", "neighbors_fc": "1/1"}, + {"interface": "Fa0/1", "area": "11", "ip_address_mask": "10.1.2.1/24", "cost": "1", "state": "DR", "neighbors_fc": "0/0"}, + {"interface": "Tu1610", "area": "0", "ip_address_mask": "0.0.0.0/0", "cost": "50", "state": "P2P", "neighbors_fc": "0/0"}, + {"interface": "Lo5", "area": "0", "ip_address_mask": "10.48.8.5/32", "cost": "1", "state": "LOOP", "neighbors_fc": "0/0"}, + {"interface": "Lo4", "area": "0", "ip_address_mask": "10.48.8.4/32", "cost": "1", "state": "LOOP", "neighbors_fc": "0/0"}, + {"interface": "Tu1603", "area": "0", "ip_address_mask": "0.0.0.0/0", "cost": "50", "state": "DOWN", "neighbors_fc": "0/0"}, + {"interface": "Tu1602", "area": "0", "ip_address_mask": "0.0.0.0/0", "cost": "50", "state": "P2P", "neighbors_fc": "0/0"}, + {"interface": "PO4/0", "area": "0", "ip_address_mask": "10.1.232.6/30", "cost": "6", "state": "P2P", "neighbors_fc": "1/1"}, + {"interface": "Se3/2:0", "area": "0", "ip_address_mask": "10.1.224.218/30", "cost": "6", "state": "P2P", "neighbors_fc": "1/1"}, + {"interface": "Se3/1:0", "area": "0", "ip_address_mask": "10.1.225.150/30", "cost": "6", "state": "P2P", "neighbors_fc": "1/1"} +] \ No newline at end of file diff --git a/tests/test_diff_generator.py b/tests/test_diff_generator.py index 511a9af..a4ce73c 100644 --- a/tests/test_diff_generator.py +++ b/tests/test_diff_generator.py @@ -8,6 +8,7 @@ exact_match_of_global_peers_via_napalm_getter = ( "napalm_getter_peer_state_change", "global.$peers$.*.[is_enabled,is_up]", + True, [], { "10.1.0.0": { @@ -24,6 +25,7 @@ exact_match_of_bgp_peer_caps_via_api = ( "api", "result[0].vrfs.default.peerList[*].[$peerAddress$,state,bgpPeerCaps]", + True, [], {"7.7.7.7": {"state": {"new_value": "Connected", "old_value": "Idle"}}}, ) @@ -31,6 +33,7 @@ exact_match_of_bgp_neigh_via_textfsm = ( "textfsm", "result[*].[$bgp_neigh$,state]", + True, [], {"10.17.254.2": {"state": {"new_value": "Up", "old_value": "Idle"}}}, ) @@ -38,6 +41,7 @@ raw_diff_of_interface_ma1_via_api_value_exclude = ( "raw_value_exclude", "result[*]", + True, ["interfaceStatistics", "interfaceCounters"], { "interfaces": { @@ -52,6 +56,7 @@ raw_diff_of_interface_ma1_via_api_novalue_exclude = ( "raw_novalue_exclude", None, + True, ["interfaceStatistics", "interfaceCounters"], { "result": { @@ -70,6 +75,7 @@ raw_diff_of_interface_ma1_via_api_novalue_noexclude = ( "raw_novalue_noexclude", None, + True, [], { "result": { @@ -103,6 +109,7 @@ exact_match_missing_item = ( "napalm_getter_missing_peer", None, + True, [], {"global": {"peers": {"7.7.7.7": "missing"}}}, ) @@ -110,6 +117,7 @@ exact_match_additional_item = ( "napalm_getter_additional_peer", None, + True, [], {"global": {"peers": {"8.8.8.8": "new"}}}, ) @@ -117,6 +125,7 @@ exact_match_changed_item = ( "napalm_getter_changed_peer", None, + True, [], {"global": {"peers": {"7.7.7.7": "missing", "8.8.8.8": "new"}}}, ) @@ -124,6 +133,7 @@ exact_match_multi_nested_list = ( "exact_match_nested", "global.$peers$.*.*.ipv4.[accepted_prefixes,received_prefixes]", + True, [], { "10.1.0.0": {"accepted_prefixes": {"new_value": -1, "old_value": -9}}, @@ -131,6 +141,28 @@ }, ) +textfsm_ospf_int_br_exact_match = ( + "textfsm_ospf_int_br", + "[*].[$interface$,area,ip_address_mask,cost,state,neighbors_fc]", + True, + [], + { + "Se0/0/0.100": {"state": {"new_value": "DOWN", "old_value": "P2P"}}, + "Fa0/0": {"state": {"new_value": "DR", "old_value": "BDR"}}, + }, +) + +textfsm_ospf_int_br_exact_match_no_ref_key = ( + "textfsm_ospf_int_br", + "", + False, + [], + { + "root[1]['state']": {'new_value': 'DOWN', 'old_value': 'P2P'}, + "root[2]['state']": {'new_value': 'DR', 'old_value': 'BDR'} + }, +) + eval_tests = [ exact_match_of_global_peers_via_napalm_getter, exact_match_of_bgp_peer_caps_via_api, @@ -142,15 +174,16 @@ exact_match_additional_item, exact_match_changed_item, exact_match_multi_nested_list, + textfsm_ospf_int_br_exact_match, + textfsm_ospf_int_br_exact_match_no_ref_key, ] -@pytest.mark.parametrize("folder_name, path, exclude, expected_output", eval_tests) -def test_eval(folder_name, path, exclude, expected_output): +@pytest.mark.parametrize("folder_name, path, normalize, exclude, expected_output", eval_tests) +def test_eval(folder_name, path, normalize, exclude, expected_output): """Run tests.""" pre_data, post_data = load_mocks(folder_name) pre_value = CheckType.get_value(pre_data, path, exclude) post_value = CheckType.get_value(post_data, path, exclude) - output = diff_generator(pre_value, post_value) - + output = diff_generator(pre_value, post_value, normalize) assert expected_output == output, ASSERT_FAIL_MESSAGE.format(output=output, expected_output=expected_output)