@@ -411,3 +411,75 @@ def evaluate_before_model(self, **_: Any) -> None:
411411 _noop_next ,
412412 )
413413 assert any ("governance check failed" in r .message for r in caplog .records )
414+
415+
416+ # --------------------------------------------------------------------------
417+ # coverage: swallow on every callback + extraction / _coerce_args edges
418+ # --------------------------------------------------------------------------
419+
420+
421+ class _Boom :
422+ """Evaluator whose every evaluate_* raises a non-block error."""
423+
424+ def __getattr__ (self , _name : str ) -> Any :
425+ def _raise (* _a : Any , ** _k : Any ) -> None :
426+ raise RuntimeError ("evaluator bug" )
427+
428+ return _raise
429+
430+
431+ @pytest .mark .parametrize (
432+ "invoke" ,
433+ [
434+ lambda cb : cb .before_model ([_msg ("x" )]),
435+ lambda cb : cb .after_model (SimpleNamespace (text = "y" )),
436+ lambda cb : cb .before_tool (FakeTool ("t" ), {}),
437+ lambda cb : cb .after_tool (FakeTool ("t" ), {"r" : 1 }),
438+ ],
439+ )
440+ def test_callbacks_swallow_non_block_errors (invoke , caplog ):
441+ cb = GovernanceCallbacks (evaluator = _Boom (), agent_name = "a" , session_id = "s" )
442+ with caplog .at_level (logging .WARNING ):
443+ invoke (cb ) # must NOT raise — a governance bug can't break the run
444+ assert any ("governance check failed" in r .message for r in caplog .records )
445+
446+
447+ def test_text_extraction_and_coerce_edges ():
448+ from uipath_agent_framework .governance .middleware import _coerce_args , _stringify
449+
450+ M = GovernanceCallbacks
451+ # _latest_message_text: empty + single (non-list)
452+ assert M ._latest_message_text ([]) == ""
453+ assert M ._latest_message_text (SimpleNamespace (text = "solo" )) == "solo"
454+ # _message_text: None / str / object-without-.text -> _stringify fallback
455+ assert M ._message_text (None ) == ""
456+ assert M ._message_text ("plain" ) == "plain"
457+ assert isinstance (M ._message_text (SimpleNamespace ()), str )
458+ # _response_text: None / .text / .messages[-1] / _stringify fallback
459+ assert M ._response_text (None ) == ""
460+ assert M ._response_text (SimpleNamespace (text = "via" )) == "via"
461+ assert "m" in M ._response_text (
462+ SimpleNamespace (text = None , messages = [SimpleNamespace (text = "m" )])
463+ )
464+ assert isinstance (M ._response_text (SimpleNamespace (text = None , messages = None )), str )
465+ # _coerce_args: None / Mapping / model_dump / non-coercible
466+ assert _coerce_args (None ) == {}
467+ assert _coerce_args ({"a" : 1 }) == {"a" : 1 }
468+ assert _coerce_args (SimpleNamespace (model_dump = lambda : {"b" : 2 })) == {"b" : 2 }
469+ assert _coerce_args (object ()) == {}
470+ # _stringify: str passthrough + circular-ref fallback (no crash)
471+ assert _stringify ("hi" ) == "hi"
472+ circular : dict [str , Any ] = {}
473+ circular ["self" ] = circular
474+ assert isinstance (_stringify (circular ), str )
475+
476+
477+ def test_coerce_args_warns_on_model_dump_failure (caplog ):
478+ from uipath_agent_framework .governance .middleware import _coerce_args
479+
480+ def _bad () -> dict [str , Any ]:
481+ raise ValueError ("boom" )
482+
483+ with caplog .at_level (logging .WARNING ):
484+ assert _coerce_args (SimpleNamespace (model_dump = _bad )) == {}
485+ assert any ("could not coerce" in r .message for r in caplog .records )
0 commit comments