Skip to content

Commit 456ed77

Browse files
committed
lib/filtereventhandler: Handle eval exceptions
1 parent d90e316 commit 456ed77

2 files changed

Lines changed: 37 additions & 2 deletions

File tree

qtoggleserver/lib/filtereventhandler.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from qtoggleserver.core import expressions as core_expressions
77
from qtoggleserver.core import ports as core_ports
88
from qtoggleserver.core.expressions import exceptions as expression_exceptions
9+
from qtoggleserver.core.expressions.exceptions import ExpressionEvalException
910
from qtoggleserver.core.typing import Attribute, Attributes, NullablePortValue
1011
from qtoggleserver.slaves import devices as slaves_devices
1112
from qtoggleserver.slaves import events as slaves_events
@@ -275,7 +276,11 @@ async def accepts_port_value(
275276
elif isinstance(self._filter_port_value, core_expressions.Expression): # an expression
276277
port_values = {p.get_id(): p.get_last_read_value() for p in core_ports.get_all() if p.is_enabled()}
277278
eval_context = core_expressions.EvalContext(port_values=port_values, now_ms=int(time.time() * 1000))
278-
if new_value != await self._filter_port_value.eval(context=eval_context):
279+
try:
280+
if new_value != await self._filter_port_value.eval(context=eval_context):
281+
return False
282+
except ExpressionEvalException as e:
283+
self.warning('Expression evaluation failed for "%s": %s', self._filter_port_value, e)
279284
return False
280285
elif new_value != self._filter_port_value:
281286
return False
@@ -339,7 +344,11 @@ async def accepts(
339344
if self._filter_expression:
340345
port_values = {p.get_id(): p.get_last_read_value() for p in core_ports.get_all() if p.is_enabled()}
341346
eval_context = core_expressions.EvalContext(port_values=port_values, now_ms=int(time.time() * 1000))
342-
if not await self._filter_expression.eval(context=eval_context):
347+
try:
348+
if not await self._filter_expression.eval(context=eval_context):
349+
return False
350+
except ExpressionEvalException as e:
351+
self.warning('Expression evaluation failed for "%s": %s', self._filter_expression, e)
343352
return False
344353

345354
return True

tests/unit/qtoggleserver/lib/test_filtereventhandler.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,18 @@ async def test_expression_mismatch_blocks(self, mocker):
151151
handler = self.make_dummy_handler({"port_value": "50"})
152152
assert await handler.accepts_port_value(self.make_dummy_event(), (None, 40)) is False
153153

154+
async def test_expression_eval_error_blocks(self, mocker):
155+
"""Should reject when port_value expression evaluation raises ExpressionEvalException."""
156+
157+
mocker.patch("qtoggleserver.core.ports.get_all", return_value=[])
158+
handler = self.make_dummy_handler({"port_value": "50"})
159+
mocker.patch.object(
160+
handler._filter_port_value,
161+
"eval",
162+
new=mocker.AsyncMock(side_effect=expression_exceptions.ExpressionEvalException("eval error")),
163+
)
164+
assert await handler.accepts_port_value(self.make_dummy_event(), (None, 50)) is False
165+
154166

155167
class TestFilterEventHandlerAcceptsPort(FilterEventHandlerTestBase):
156168
async def test_no_filter_passes(self, mock_num_port1):
@@ -256,6 +268,20 @@ async def test_expression_blocks(self, mock_num_port1, mocker):
256268
event = core_events.ValueChange(None, 40, mock_num_port1)
257269
assert await handler.accepts(event, (None, 40), {}, {}, {}, {}, {}) is False
258270

271+
async def test_expression_eval_error_blocks(self, mock_num_port1, mocker):
272+
"""Should reject when filter expression evaluation raises ExpressionEvalException."""
273+
274+
mocker.patch("qtoggleserver.core.ports.get_all", return_value=[mock_num_port1])
275+
handler = ConcreteFilterEventHandler(filter={"expression": "GT($nid1, 50)"})
276+
handler._prepare_filter()
277+
mocker.patch.object(
278+
handler._filter_expression,
279+
"eval",
280+
new=mocker.AsyncMock(side_effect=expression_exceptions.ExpressionEvalException("eval error")),
281+
)
282+
event = core_events.ValueChange(None, 60, mock_num_port1)
283+
assert await handler.accepts(event, (None, 60), {}, {}, {}, {}, {}) is False
284+
259285
async def test_invalid_expression(self, mock_num_port1):
260286
"""Should raise ExpressionParseError when the filter expression is syntactically invalid."""
261287

0 commit comments

Comments
 (0)