Skip to content

Commit 09ae7e0

Browse files
authored
fix(evaluators/contains): Correct message when the evaluator is failing when input: str and data: str (#264)
* fix(evaluators/contains): Correct message when the evaluator is failing when input: str and data: str This fixes an issue when the input is str and data is str it returns Unevaluated instead of `Failed to find` message * refactor(contains): Enhance _format_message with type hints and detailed docstring refactor(tests): Remove unsupported mark from test_evaluate_unsupported
1 parent 5f2ef9f commit 09ae7e0

File tree

2 files changed

+162
-61
lines changed

2 files changed

+162
-61
lines changed

src/tirith/core/evaluators/contains.py

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import Any
23

34
from .base_evaluator import BaseEvaluator
45
from tirith.utils import sort_collections, json_format_value
@@ -30,76 +31,68 @@
3031

3132

3233
class Contains(BaseEvaluator):
34+
def _format_message(self, found: bool, evaluator_data: Any, evaluator_input: Any):
35+
"""
36+
Helper method to format evaluation messages consistently.
37+
38+
:param found: Whether the search was successful
39+
:type found: bool
40+
:param evaluator_data: The value that was searched for
41+
:type evaluator_data: Any
42+
:param evaluator_input: The container that was searched within
43+
:type evaluator_input: Any
44+
45+
:returns: Formatted message string
46+
:rtype: str
47+
"""
48+
action = "Found" if found else "Failed to find"
49+
return "{} {} inside {}".format(action, json_format_value(evaluator_data), json_format_value(evaluator_input))
50+
3351
def evaluate(self, evaluator_input, evaluator_data):
3452
evaluation_result = {"passed": False, "message": "Not evaluated"}
3553
try:
3654
# if evaluator_input and evaluator_data are both strings
3755
if isinstance(evaluator_input, str) and isinstance(evaluator_data, str):
3856
result = evaluator_data in evaluator_input
3957
evaluation_result["passed"] = result
40-
if result:
41-
evaluation_result["message"] = "Found {} inside {}".format(
42-
json_format_value(evaluator_data), json_format_value(evaluator_input)
43-
)
58+
evaluation_result["message"] = self._format_message(result, evaluator_data, evaluator_input)
59+
4460
# if evaluator_input is a list
4561
elif isinstance(evaluator_input, list):
4662
evaluator_input = sort_collections(evaluator_input)
4763
if isinstance(evaluator_data, list):
4864
evaluator_data = sort_collections(evaluator_data)
4965
result = evaluator_data in evaluator_input
50-
evaluation_result["passed"] = result
51-
if result:
52-
evaluation_result["message"] = "Found {} inside {}".format(
53-
json_format_value(evaluator_data), json_format_value(evaluator_input)
54-
)
55-
else:
56-
evaluation_result["message"] = "Failed to find {} inside {}".format(
57-
json_format_value(evaluator_data), json_format_value(evaluator_input)
58-
)
5966
else:
6067
result = evaluator_data in evaluator_input
61-
evaluation_result["passed"] = result
62-
if result:
63-
evaluation_result["message"] = "Found {} inside {}".format(
64-
json_format_value(evaluator_data), json_format_value(evaluator_input)
65-
)
66-
else:
67-
evaluation_result["message"] = "Failed to find {} inside {}".format(
68-
json_format_value(evaluator_data), json_format_value(evaluator_input)
69-
)
68+
69+
evaluation_result["passed"] = result
70+
evaluation_result["message"] = self._format_message(result, evaluator_data, evaluator_input)
71+
72+
# if evaluator_input is a dict
7073
elif isinstance(evaluator_input, dict):
7174
if isinstance(evaluator_data, dict):
72-
evaluation_result["passed"] = True
73-
evaluation_result["message"] = "Found {} inside {}".format(
74-
json_format_value(evaluator_data), json_format_value(evaluator_input)
75-
)
75+
# Check if all key-value pairs in evaluator_data exist in evaluator_input
76+
result = True
7677
for key in evaluator_data:
77-
if key in evaluator_input:
78-
if evaluator_input[key] != evaluator_data[key]:
79-
evaluation_result["passed"] = False
80-
evaluation_result["message"] = "Failed to find {} inside {}".format(
81-
json_format_value(evaluator_data), json_format_value(evaluator_input)
82-
)
83-
break
84-
else:
85-
evaluation_result["passed"] = False
86-
evaluation_result["message"] = "Failed to find {} inside {}".format(
87-
json_format_value(evaluator_data), json_format_value(evaluator_input)
88-
)
78+
if key not in evaluator_input or evaluator_input[key] != evaluator_data[key]:
79+
result = False
8980
break
81+
evaluation_result["passed"] = result
82+
evaluation_result["message"] = self._format_message(result, evaluator_data, evaluator_input)
9083
else:
84+
# Check if evaluator_data is a key in evaluator_input dict
9185
result = evaluator_data in evaluator_input
9286
evaluation_result["passed"] = result
93-
if result:
94-
evaluation_result["message"] = "Found {} inside {}".format(
95-
json_format_value(evaluator_data), json_format_value(evaluator_input)
96-
)
87+
evaluation_result["message"] = self._format_message(result, evaluator_data, evaluator_input)
88+
9789
else:
9890
evaluation_result["message"] = (
9991
"{} is an unsupported data type for evaluating against value in 'condition.value'".format(
100-
json_format_value(evaluator_data)
92+
json_format_value(evaluator_input)
10193
)
10294
)
95+
10396
return evaluation_result
10497
except Exception as e:
10598
logger.exception(e)

tests/core/evaluators/test_contains.py

Lines changed: 126 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
(["a", "b", "c", "d"], "a"),
77
("a", "a"),
88
("minura", "a"),
9+
("hello world", "world"),
910
({"a": "val1", "b": "val2"}, "a"),
1011
({"b": 6, "a": 2, "c": 16}, {"a": 2, "b": 6}),
1112
({"b": 6, "a": 2, "c": 16}, {"a": 2}),
@@ -15,22 +16,28 @@
1516
(["a", "b"], "b"),
1617
(["a", ["b"]], ["b"]),
1718
(["a"], "a"),
19+
([1, 2, 3], 2),
20+
([1, [2, 3]], [2, 3]),
1821
]
1922

2023
checks_failing = [
2124
(["a", "b", "c", "d"], "e"),
22-
("e", ["a", "b", "c", "d"]),
23-
(2, "a"),
24-
("3", 3),
25-
("c", ["a", "b"]),
25+
("hello", "world"),
26+
("abc", "def"),
27+
({"a": "val1", "b": "val2"}, "c"),
28+
({"b": 6, "a": 2, "c": 16}, {"a": 3}),
29+
({"b": 6, "a": 2, "c": 16}, {"a": 2, "d": 4}),
30+
({"b": 6, "a": 2, "c": 16}, {"a": 2, "b": 7}),
31+
(["a", "b"], "c"),
32+
([1, 2, 3], 4),
33+
([1, [2, 3]], [3, 4]),
2634
]
2735

2836
checks_unsupported = [
2937
(2, "a"),
30-
("3", 3),
31-
(1, 1),
32-
(1, 2),
33-
(["a"], ["a", "b", "c", "d"]),
38+
(1.5, "test"),
39+
(True, "boolean"),
40+
(None, "none"),
3441
]
3542

3643
evaluator = Contains()
@@ -41,24 +48,125 @@
4148
@mark.parametrize("evaluator_input,evaluator_data", checks_passing)
4249
def test_evaluate_passing(evaluator_input, evaluator_data):
4350
result = evaluator.evaluate(evaluator_input, evaluator_data)
44-
assert result["passed"] == True # {"passed": True, "message": f"Found {evaluator_input} inside {evaluator_data}"}
51+
assert result["passed"]
52+
assert "Found" in result["message"]
53+
assert "inside" in result["message"]
4554

4655

47-
# # pytest -v -m failing
56+
# pytest -v -m failing
4857
@mark.failing
4958
@mark.parametrize("evaluator_input,evaluator_data", checks_failing)
5059
def test_evaluate_failing(evaluator_input, evaluator_data):
5160
result = evaluator.evaluate(evaluator_input, evaluator_data)
52-
assert (
53-
result["passed"] == False
54-
) # {"passed": True, "message": f"Failed to find {evaluator_input} inside {evaluator_data}"}
61+
assert not result["passed"]
62+
assert "Failed to find" in result["message"]
63+
assert "inside" in result["message"]
5564

5665

57-
# pytest -v -m failing
58-
@mark.failing
66+
# pytest -v -m unsupported
5967
@mark.parametrize("evaluator_input,evaluator_data", checks_unsupported)
6068
def test_evaluate_unsupported(evaluator_input, evaluator_data):
6169
result = evaluator.evaluate(evaluator_input, evaluator_data)
62-
assert (
63-
result["passed"] == False
64-
) # {"passed": True, "message": f"Failed to find {evaluator_input} inside {evaluator_data}"}
70+
assert not result["passed"]
71+
assert "unsupported data type" in result["message"]
72+
73+
74+
# Test specific string scenarios
75+
def test_string_contains():
76+
# Test case-sensitive string containment
77+
result = evaluator.evaluate("Hello World", "World")
78+
assert result["passed"]
79+
assert "Found" in result["message"]
80+
81+
result = evaluator.evaluate("Hello World", "world") # case-sensitive
82+
assert not result["passed"]
83+
assert "Failed to find" in result["message"]
84+
85+
86+
def test_empty_string():
87+
# Empty string should be contained in any string
88+
result = evaluator.evaluate("hello", "")
89+
assert result["passed"]
90+
91+
# But non-empty string should not be in empty string
92+
result = evaluator.evaluate("", "hello")
93+
assert not result["passed"]
94+
95+
96+
def test_list_contains():
97+
# Test nested list containment
98+
result = evaluator.evaluate([[1, 2], [3, 4]], [1, 2])
99+
assert result["passed"]
100+
101+
# Test mixed types in list
102+
result = evaluator.evaluate([1, "hello", 3.14], "hello")
103+
assert result["passed"]
104+
105+
result = evaluator.evaluate([1, "hello", 3.14], "world")
106+
assert not result["passed"]
107+
108+
109+
def test_dict_contains():
110+
# Test partial dict containment
111+
input_dict = {"name": "John", "age": 30, "city": "NYC"}
112+
113+
# Should pass - subset of key-value pairs
114+
result = evaluator.evaluate(input_dict, {"name": "John", "age": 30})
115+
assert result["passed"]
116+
117+
# Should fail - wrong value
118+
result = evaluator.evaluate(input_dict, {"name": "John", "age": 25})
119+
assert not result["passed"]
120+
121+
# Should fail - non-existent key
122+
result = evaluator.evaluate(input_dict, {"name": "John", "country": "USA"})
123+
assert not result["passed"]
124+
125+
126+
def test_dict_key_contains():
127+
# Test single key containment
128+
input_dict = {"name": "John", "age": 30}
129+
130+
result = evaluator.evaluate(input_dict, "name")
131+
assert result["passed"]
132+
133+
result = evaluator.evaluate(input_dict, "address")
134+
assert not result["passed"]
135+
136+
137+
def test_empty_containers():
138+
# Empty list
139+
result = evaluator.evaluate([], "anything")
140+
assert not result["passed"]
141+
142+
# Empty dict
143+
result = evaluator.evaluate({}, "anything")
144+
assert not result["passed"]
145+
146+
# Empty dict with empty dict
147+
result = evaluator.evaluate({}, {})
148+
assert result["passed"]
149+
150+
151+
def test_error_handling():
152+
# Test that exceptions are handled gracefully
153+
# This shouldn't happen in normal use, but let's test defensive coding
154+
try:
155+
result = evaluator.evaluate(None, None)
156+
assert not result["passed"]
157+
assert result["message"] == "Not evaluated"
158+
except Exception:
159+
pass # If it throws an exception, that's also acceptable
160+
161+
162+
def test_message_formatting():
163+
# Test that messages are properly formatted
164+
result = evaluator.evaluate("test string", "test")
165+
assert result["passed"]
166+
assert '"test"' in result["message"]
167+
assert '"test string"' in result["message"]
168+
169+
result = evaluator.evaluate([1, 2, 3], 4)
170+
assert not result["passed"]
171+
assert "4" in result["message"]
172+
assert "[1, 2, 3]" in result["message"]

0 commit comments

Comments
 (0)