Skip to content

Commit da7675a

Browse files
Unit tests for validate methods (#43)
* draft regex base function * fix file import * draft regex implementation * fix logic into regex result * remove assertion * draft test for range check type * add match/no-match logic * commit save * add assertion for check option * add assertions for range type * add assertion for strings * commit save * draft operator check type * complete validate method for check_type operator * work on oeprator_evaluator method * drat operator all-same * add all-same operator * add contains operator * add not-contains. Update contains logic * add is-gt and is-lt operators * add range operators * add operator lib for DRY code * fix some lint errors * Update netcompare/operator.py Not sure about this...if you look at the if statements, your suggestion would brake the logic...or am I missing something? Co-authored-by: Patryk Szulczewski <[email protected]> * work on comments * working on operator args * fix tests * improve validator switch logic * fix pytests * tolerance validate tests * add validate parameter test * add mode to param match check type * add regex validate tests * working on operator validato tests * complete validate tests for operator * paramet tests * Update as per comments * fix as in comment PR Co-authored-by: Patryk Szulczewski <[email protected]>
1 parent ce2ecba commit da7675a

File tree

5 files changed

+190
-57
lines changed

5 files changed

+190
-57
lines changed

netcompare/check_types.py

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ def validate(**kwargs) -> None:
134134
# reference_data = getattr(kwargs, "reference_data")
135135
tolerance = kwargs.get("tolerance")
136136
if not tolerance:
137-
raise ValueError("Tolerance argument is mandatory for Tolerance Check Type.")
137+
raise ValueError("'tolerance' argument is mandatory for Tolerance Check Type.")
138138
if not isinstance(tolerance, int):
139-
raise ValueError(f"Tolerance argument must be an integer, and it's {type(tolerance)}.")
139+
raise ValueError(f"Tolerance argument's value must be an integer. You have: {type(tolerance)}.")
140140

141141
def evaluate(self, value_to_compare: Any, reference_data: Any, tolerance: int) -> Tuple[Dict, bool]:
142142
"""Returns the difference between values and the boolean. Overwrites method in base class."""
@@ -181,23 +181,23 @@ def validate(**kwargs) -> None:
181181
mode_options = ["match", "no-match"]
182182
params = kwargs.get("params")
183183
if not params:
184-
raise ValueError("Params argument is mandatory for ParameterMatch Check Type.")
184+
raise ValueError("'params' argument is mandatory for ParameterMatch Check Type.")
185185
if not isinstance(params, dict):
186-
raise ValueError(f"Params argument must be a dict, and it's {type(params)}.")
186+
raise ValueError(f"'params' argument must be a dict. You have: {type(params)}.")
187187

188188
mode = kwargs.get("mode")
189189
if not mode:
190-
raise ValueError("Mode argument is mandatory for ParameterMatch Check Type.")
191-
if not isinstance(mode, str):
192-
raise ValueError(f"Mode argument must be a string, and it's {type(mode)}.")
190+
raise ValueError("'mode' argument is mandatory for ParameterMatch Check Type.")
193191
if mode not in mode_options:
194-
raise ValueError(f"Mode argument should be {mode_options}, and it's {mode}")
192+
raise ValueError(
193+
f"'mode' argument should be one of the following: {', '.join(mode_options)}. You have: {mode}"
194+
)
195195

196196
def evaluate(self, value_to_compare: Mapping, params: Dict, mode: str) -> Tuple[Dict, bool]:
197197
"""Parameter Match evaluator implementation."""
198198
self.validate(params=params, mode=mode)
199199
# TODO: we don't use the mode?
200-
evaluation_result = parameter_evaluator(value_to_compare, params)
200+
evaluation_result = parameter_evaluator(value_to_compare, params, mode)
201201
return evaluation_result, not evaluation_result
202202

203203

@@ -210,17 +210,15 @@ def validate(**kwargs) -> None:
210210
mode_options = ["match", "no-match"]
211211
regex = kwargs.get("regex")
212212
if not regex:
213-
raise ValueError("Params argument is mandatory for Regex Match Check Type.")
213+
raise ValueError("'regex' argument is mandatory for Regex Check Type.")
214214
if not isinstance(regex, str):
215-
raise ValueError(f"Params argument must be a string, and it's {type(regex)}.")
215+
raise ValueError(f"'regex' argument must be a string. You have: {type(regex)}.")
216216

217217
mode = kwargs.get("mode")
218218
if not mode:
219-
raise ValueError("Mode argument is mandatory for Regex Match Check Type.")
220-
if not isinstance(mode, str):
221-
raise ValueError(f"Mode argument must be a string, and it's {type(mode)}.")
219+
raise ValueError("'mode' argument is mandatory for Regex Check Type.")
222220
if mode not in mode_options:
223-
raise ValueError(f"Mode argument should be {mode_options}, and it's {mode}")
221+
raise ValueError(f"'mode' argument should be {mode_options}. You have: {mode}")
224222

225223
def evaluate(self, value_to_compare: Mapping, regex: str, mode: str) -> Tuple[Mapping, bool]:
226224
"""Regex Match evaluator implementation."""
@@ -250,13 +248,19 @@ def validate(**kwargs) -> None:
250248
)
251249

252250
# Validate "params" argument is not None.
253-
if not kwargs:
254-
raise KeyError(f"'params' argument must be provided. You have {kwargs}. Read the docs for more info.")
251+
if not kwargs or list(kwargs.keys())[0] != "params":
252+
raise ValueError(f"'params' argument must be provided. You have: {list(kwargs.keys())[0]}.")
253+
254+
params_key = kwargs["params"].get("mode")
255+
params_value = kwargs["params"].get("operator_data")
256+
257+
if not params_key or not params_value:
258+
raise ValueError(
259+
f"'mode' and 'operator_data' arguments must be provided. You have: {list(kwargs['params'].keys())}."
260+
)
255261

256-
params_key = kwargs["mode"]
257-
params_value = kwargs["operator_data"]
258262
# Validate "params" value is legal.
259-
if all(params_key in operator for operator in valid_options):
263+
if all(params_key not in sub_element for element in valid_options for sub_element in element):
260264
raise ValueError(
261265
f"'params' value must be one of the following: {[sub_element for element in valid_options for sub_element in element]}. You have: {params_key}"
262266
)
@@ -265,42 +269,46 @@ def validate(**kwargs) -> None:
265269
# "is-in", "not-in", "in-range", "not-range" requires an iterable
266270
if not isinstance(params_value, (list, tuple)):
267271
raise ValueError(
268-
f"Range check-option {in_operators} must have value of type list or tuple. i.e: dict(not-in=('Idle', 'Down'). You have: {params_value} of type {type(params_value)}You have: {params_value} of type {type(params_value)}"
272+
f"check options {in_operators} must have value of type list or tuple. i.e: dict(not-in=('Idle', 'Down'). You have: {params_value} of type {type(params_value)}."
269273
)
270274

271275
# "in-range", "not-range" requires int or float where value at index 0 is lower than value at index 1
272276
if params_key in ("in-range", "not-range"):
273-
if not isinstance(params_value[0], (int, float)) and not isinstance(params_value[1], float, int):
277+
if (
278+
len(params_value) != 2
279+
or not isinstance(params_value[0], (int, float))
280+
or not isinstance(params_value[1], (float, int))
281+
):
274282
raise ValueError(
275-
f"Range check-option {params_key} must have value of type list or tuple with items of type float or int. i.e: dict(not-range=(70000000, 80000000). You have: {params_value} of type {type(params_value)}"
283+
f"'range' check-option {params_key} must have value of type list or tuple with items of type float or int. i.e: dict(not-range=(70000000, 80000000). You have: {params_value}."
276284
)
277285
if not params_value[0] < params_value[1]:
278286
raise ValueError(
279-
f"'range' and 'not-range' must have value at index 0 lower than value at index 1. i.e: dict(not-range=(70000000, 80000000). You have: {params_value} of type {type(params_value)}"
287+
f"'range' and 'not-range' must have value at index 0 lower than value at index 1. i.e: dict(not-range=(70000000, 80000000). You have: {params_value}."
280288
)
281289

282290
# "is-gt","is-lt" require either int() or float()
283291
elif params_key in number_operators and not isinstance(params_value, (float, int)):
284292
raise ValueError(
285-
f"Check-option {number_operators} must have value of type float or int. i.e: dict(is-lt=50). You have: {params_value} of type {type(params_value)}"
293+
f"check options {number_operators} must have value of type float or int. You have: {params_value} of type {type(params_value)}"
286294
)
287295

288296
# "contains", "not-contains" require string.
289297
elif params_key in string_operators and not isinstance(params_value, str):
290298
raise ValueError(
291-
f"Range check-option {string_operators} must have value of type string. i.e: dict(contains='EVPN'). You have: {params_value} of type {type(params_value)}"
299+
f"check options {string_operators} must have value of type string. You have: {params_value} of type {type(params_value)}"
292300
)
293301

294302
# "all-same" requires boolean True or False
295303
elif params_key in bool_operators and not isinstance(params_value, bool):
296304
raise ValueError(
297-
f"Range check-option {bool_operators} must have value of type bool. i.e: dict(all-same=True). You have: {params_value} of type {type(params_value)}"
305+
f"check option all-same must have value of type bool. You have: {params_value} of type {type(params_value)}"
298306
)
299307

300308
def evaluate(self, value_to_compare: Any, params: Any) -> Tuple[Mapping, bool]:
301309
"""Operator evaluator implementation."""
302310
self.validate(**params)
303311
# For naming consistency
304312
reference_data = params
305-
evaluation_result = operator_evaluator(reference_data, value_to_compare)
313+
evaluation_result = operator_evaluator(reference_data["params"], value_to_compare)
306314
return evaluation_result, not evaluation_result

netcompare/evaluators.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def diff_generator(pre_result: Any, post_result: Any) -> Dict:
3636
return fix_deepdiff_key_names(result)
3737

3838

39-
def parameter_evaluator(values: Mapping, parameters: Mapping) -> Dict:
39+
def parameter_evaluator(values: Mapping, parameters: Mapping, mode: str) -> Dict:
4040
"""Parameter Match evaluator engine.
4141
4242
Args:
@@ -71,7 +71,9 @@ def parameter_evaluator(values: Mapping, parameters: Mapping) -> Dict:
7171
inner_value = list(value.values())[0]
7272

7373
for parameter_key, parameter_value in parameters.items():
74-
if inner_value[parameter_key] != parameter_value:
74+
if mode == "match" and inner_value[parameter_key] != parameter_value:
75+
result_item[parameter_key] = inner_value[parameter_key]
76+
elif mode == "no-match" and inner_value[parameter_key] == parameter_value:
7577
result_item[parameter_key] = inner_value[parameter_key]
7678

7779
if result_item:

tests/test_operators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def test_operator(filename, check_type_str, evaluate_args, path, expected_result
198198
# There is not concept of "pre" and "post" in operator.
199199
data = load_json_file("api", filename)
200200
value = check.get_value(data, path)
201-
actual_results = check.evaluate(value, **evaluate_args)
201+
actual_results = check.evaluate(value, evaluate_args)
202202
assert actual_results == expected_result, ASSERT_FAIL_MESSAGE.format(
203203
output=actual_results, expected_output=expected_result
204204
)

tests/test_type_checks.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,31 +64,6 @@ def tests_exceptions_init(check_type_str, exception_type, expected_in_output):
6464
assert expected_in_output in error.value.__str__()
6565

6666

67-
exception_tests_eval = [
68-
(
69-
"parameter_match",
70-
{"value_to_compare": {}, "mode": "some mode", "params": {"some": "thing"}},
71-
ValueError,
72-
"Mode argument should be",
73-
),
74-
(
75-
"regex",
76-
{"value_to_compare": {}, "mode": "some mode", "regex": "some regex"},
77-
ValueError,
78-
"Mode argument should be",
79-
),
80-
]
81-
82-
83-
@pytest.mark.parametrize("check_type_str, evaluate_args, exception_type, expected_in_output", exception_tests_eval)
84-
def tests_exceptions_eval(check_type_str, evaluate_args, exception_type, expected_in_output):
85-
"""Tests exceptions when calling .evaluate() method."""
86-
with pytest.raises(exception_type) as error:
87-
check = CheckType.init(check_type_str)
88-
check.evaluate(**evaluate_args)
89-
assert expected_in_output in error.value.__str__()
90-
91-
9267
exact_match_test_values_no_change = (
9368
"exact_match",
9469
{},
@@ -287,9 +262,25 @@ def test_checks(folder_name, check_type_str, evaluate_args, path, expected_resul
287262
False,
288263
),
289264
)
265+
parameter_no_match_api = (
266+
"pre.json",
267+
"parameter_match",
268+
{"mode": "no-match", "params": {"localAsn": "65130.1100", "linkType": "external"}},
269+
"result[0].vrfs.default.peerList[*].[$peerAddress$,localAsn,linkType]",
270+
(
271+
{
272+
"10.1.0.0": {"linkType": "external"},
273+
"10.2.0.0": {"localAsn": "65130.1100", "linkType": "external"},
274+
"10.64.207.255": {"localAsn": "65130.1100", "linkType": "external"},
275+
},
276+
False,
277+
),
278+
)
290279

291280

292-
@pytest.mark.parametrize("filename, check_type_str, evaluate_args, path, expected_result", [parameter_match_api])
281+
@pytest.mark.parametrize(
282+
"filename, check_type_str, evaluate_args, path, expected_result", [parameter_match_api, parameter_no_match_api]
283+
)
293284
def test_param_match(filename, check_type_str, evaluate_args, path, expected_result):
294285
"""Validate parameter_match check type."""
295286
check = CheckType.init(check_type_str)

tests/test_validates.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""Unit tests for validator CheckType method."""
2+
import pytest
3+
from netcompare.check_types import CheckType
4+
5+
tolerance_wrong_argumet = (
6+
"tolerance",
7+
{"gt": 10},
8+
"'tolerance' argument is mandatory for Tolerance Check Type.",
9+
)
10+
tolerance_wrong_value = (
11+
"tolerance",
12+
{"tolerance": "10"},
13+
"Tolerance argument's value must be an integer. You have: <class 'str'>.",
14+
)
15+
parameter_no_params = (
16+
"parameter_match",
17+
{"mode": "match", "wrong_key": {"localAsn": "65130.1100", "linkType": "external"}},
18+
"'params' argument is mandatory for ParameterMatch Check Type.",
19+
)
20+
parameter_wrong_type = (
21+
"parameter_match",
22+
{"mode": "match", "params": [{"localAsn": "65130.1100", "linkType": "external"}]},
23+
"'params' argument must be a dict. You have: <class 'list'>.",
24+
)
25+
parameter_no_mode = (
26+
"parameter_match",
27+
{"mode-no-mode": "match", "params": {"localAsn": "65130.1100", "linkType": "external"}},
28+
"'mode' argument is mandatory for ParameterMatch Check Type.",
29+
)
30+
parameter_mode_value = (
31+
"parameter_match",
32+
{"mode": ["match"], "params": {"localAsn": "65130.1100", "linkType": "external"}},
33+
"'mode' argument should be one of the following: match, no-match. You have: ['match']",
34+
)
35+
regex_no_params = (
36+
"regex",
37+
{"regexregex": ".*UNDERLAY.*", "mode": "match"},
38+
"'regex' argument is mandatory for Regex Check Type.",
39+
)
40+
regex_wrong_type = (
41+
"regex",
42+
{"regex": [".*UNDERLAY.*"], "mode-no-mode": "match"},
43+
"'regex' argument must be a string. You have: <class 'list'>.",
44+
)
45+
regex_no_mode = (
46+
"regex",
47+
{"regex": ".*UNDERLAY.*", "mode-no-mode": "match"},
48+
"'mode' argument is mandatory for Regex Check Type.",
49+
)
50+
regex_mode_value = (
51+
"regex",
52+
{"regex": ".*UNDERLAY.*", "mode": "match-no-match"},
53+
"'mode' argument should be ['match', 'no-match']. You have: match-no-match",
54+
)
55+
operator_params = (
56+
"operator",
57+
{"my_params": {"mode": "not-in", "operator_data": [20, 40, 60]}},
58+
"'params' argument must be provided. You have: my_params.",
59+
)
60+
operator_params_mode = (
61+
"operator",
62+
{"params": {"no-mode": "not-in", "not-operator_data": [20, 40, 60]}},
63+
"'mode' and 'operator_data' arguments must be provided. You have: ['no-mode', 'not-operator_data'].",
64+
)
65+
operator_params_wrong_operator = (
66+
"operator",
67+
{"params": {"mode": "random", "operator_data": [20, 40, 60]}},
68+
"'params' value must be one of the following: ['is-in', 'not-in', 'in-range', 'not-range', 'all-same', 'is-gt', 'is-lt', 'contains', 'not-contains']. You have: random",
69+
)
70+
operator_params_in = (
71+
"operator",
72+
{"params": {"mode": "in-range", "operator_data": "string"}},
73+
"check options ('is-in', 'not-in', 'in-range', 'not-range') must have value of type list or tuple. i.e: dict(not-in=('Idle', 'Down'). You have: string of type <class 'str'>.",
74+
)
75+
operator_params_in_range = (
76+
"operator",
77+
{"params": {"mode": "in-range", "operator_data": (0, "1")}},
78+
"'range' check-option in-range must have value of type list or tuple with items of type float or int. i.e: dict(not-range=(70000000, 80000000). You have: (0, '1').",
79+
)
80+
operator_params_in_range_lower_than = (
81+
"operator",
82+
{"params": {"mode": "in-range", "operator_data": (1, 0)}},
83+
"'range' and 'not-range' must have value at index 0 lower than value at index 1. i.e: dict(not-range=(70000000, 80000000). You have: (1, 0).",
84+
)
85+
operator_params_number = (
86+
"operator",
87+
{"params": {"mode": "is-gt", "operator_data": "1"}},
88+
"check options ('is-gt', 'is-lt') must have value of type float or int. You have: 1 of type <class 'str'>",
89+
)
90+
operator_params_contains = (
91+
"operator",
92+
{"params": {"mode": "contains", "operator_data": 1}},
93+
"check options ('contains', 'not-contains') must have value of type string. You have: 1 of type <class 'int'>",
94+
)
95+
operator_params_bool = (
96+
"operator",
97+
{"params": {"mode": "all-same", "operator_data": 1}},
98+
"check option all-same must have value of type bool. You have: 1 of type <class 'int'>",
99+
)
100+
101+
all_tests = [
102+
tolerance_wrong_argumet,
103+
tolerance_wrong_value,
104+
parameter_no_params,
105+
parameter_wrong_type,
106+
parameter_no_mode,
107+
parameter_mode_value,
108+
regex_no_params,
109+
regex_wrong_type,
110+
regex_no_mode,
111+
regex_mode_value,
112+
operator_params,
113+
operator_params_mode,
114+
operator_params_wrong_operator,
115+
operator_params_in,
116+
operator_params_in_range,
117+
operator_params_in_range_lower_than,
118+
operator_params_number,
119+
operator_params_contains,
120+
operator_params_bool,
121+
]
122+
123+
124+
@pytest.mark.parametrize("check_type_str, evaluate_args, expected_results", all_tests)
125+
def test_tolerance_key_name(check_type_str, evaluate_args, expected_results):
126+
"""Test CheckType validate method for each check-type."""
127+
check = CheckType.init(check_type_str)
128+
129+
with pytest.raises(ValueError) as exc_info:
130+
check.validate(**evaluate_args)
131+
132+
assert exc_info.type is ValueError and exc_info.value.args[0] == expected_results

0 commit comments

Comments
 (0)