@@ -575,6 +575,19 @@ async def test_wrap_runtime_produces_governed_runtime_with_bootstrap_fields(
575575 ),
576576 )
577577
578+ # The installed uipath-runtime may not yet accept rego_evaluator —
579+ # patch in a forward-compatible subclass so this test isn't gated on
580+ # the dependency version.
581+ class _UiPathGovernedRuntimeWithRego (UiPathGovernedRuntime ):
582+ def __init__ (self , delegate : Any , * , rego_evaluator : Any = None , ** kwargs : Any ) -> None :
583+ super ().__init__ (delegate , ** kwargs )
584+ self ._rego_evaluator = rego_evaluator
585+
586+ monkeypatch .setattr (
587+ "uipath._cli._governance_bootstrap.UiPathGovernedRuntime" ,
588+ _UiPathGovernedRuntimeWithRego ,
589+ )
590+
578591 result = await resolve_governance (
579592 agent_framework = "langgraph" ,
580593 agent_type = "uipath_coded" ,
@@ -856,3 +869,134 @@ def _capture_unregister(func: Any) -> None:
856869 assert len (unregistered_arg ) == 1
857870 # Same bound method → same underlying dispatcher shutdown.
858871 assert registered_arg [0 ] == unregistered_arg [0 ]
872+
873+ # ------------------------------------------------------------------
874+ # Rego evaluator bootstrap path
875+ # ------------------------------------------------------------------
876+
877+ def _setup_successful_governance (
878+ self ,
879+ monkeypatch : pytest .MonkeyPatch ,
880+ ) -> None :
881+ """Common setup for a successful governance bootstrap (no Rego)."""
882+ monkeypatch .setattr (
883+ "uipath._cli._governance_bootstrap.is_governance_enabled" ,
884+ lambda : True ,
885+ )
886+ _install_fake_runtime_governance (
887+ monkeypatch ,
888+ audit_manager_cls = _FakeAuditManager ,
889+ metadata_cls = _FakeMetadata ,
890+ evaluator_cls = _FakeEvaluator ,
891+ compensator_cls = _FakeCompensator ,
892+ )
893+ _stub_provider (
894+ monkeypatch ,
895+ response_or_exc = _fake_policy_response (
896+ mode = EnforcementMode .ENFORCE , policies = "rules: []"
897+ ),
898+ )
899+
900+ def _stub_rego_module (
901+ self ,
902+ monkeypatch : pytest .MonkeyPatch ,
903+ * ,
904+ return_value : Any = None ,
905+ side_effect : Any = None ,
906+ ) -> None :
907+ """Inject a fake ``uipath.runtime.governance.rego`` into sys.modules
908+ so the lazy import inside ``resolve_governance`` succeeds.
909+ """
910+ import sys
911+
912+ fake_rego = MagicMock ()
913+ if side_effect is not None :
914+ fake_rego .build_rego_evaluator_async = AsyncMock (side_effect = side_effect )
915+ else :
916+ fake_rego .build_rego_evaluator_async = AsyncMock (return_value = return_value )
917+ monkeypatch .setitem (sys .modules , "uipath.runtime.governance.rego" , fake_rego )
918+
919+ async def test_rego_evaluator_populated_when_rego_enabled_and_build_succeeds (
920+ self ,
921+ monkeypatch : pytest .MonkeyPatch ,
922+ cwd : Path ,
923+ ) -> None :
924+ """When ``is_rego_enabled()`` is ``True`` and
925+ ``build_rego_evaluator_async`` returns a non-None evaluator, the
926+ bootstrap's ``rego_evaluator`` field must be set to that object.
927+ """
928+ self ._setup_successful_governance (monkeypatch )
929+ monkeypatch .setattr (
930+ "uipath._cli._governance_bootstrap.is_rego_enabled" ,
931+ lambda : True ,
932+ )
933+ mock_rego_evaluator = MagicMock ()
934+ mock_rego_evaluator .loaded_hooks = []
935+ self ._stub_rego_module (monkeypatch , return_value = mock_rego_evaluator )
936+
937+ result = await resolve_governance (
938+ agent_framework = "langgraph" ,
939+ agent_type = "uipath_coded" ,
940+ is_conversational = False ,
941+ )
942+ assert result is not None
943+ try :
944+ assert result .rego_evaluator is mock_rego_evaluator
945+ finally :
946+ result .dispose ()
947+
948+ async def test_rego_evaluator_none_when_rego_enabled_but_build_returns_none (
949+ self ,
950+ monkeypatch : pytest .MonkeyPatch ,
951+ cwd : Path ,
952+ ) -> None :
953+ """When ``build_rego_evaluator_async`` returns ``None`` (e.g. no
954+ bundles available), ``rego_evaluator`` must stay ``None`` and
955+ governance still succeeds.
956+ """
957+ self ._setup_successful_governance (monkeypatch )
958+ monkeypatch .setattr (
959+ "uipath._cli._governance_bootstrap.is_rego_enabled" ,
960+ lambda : True ,
961+ )
962+ self ._stub_rego_module (monkeypatch , return_value = None )
963+
964+ result = await resolve_governance (
965+ agent_framework = "langgraph" ,
966+ agent_type = "uipath_coded" ,
967+ is_conversational = False ,
968+ )
969+ assert result is not None
970+ try :
971+ assert result .rego_evaluator is None
972+ finally :
973+ result .dispose ()
974+
975+ async def test_rego_evaluator_none_when_build_raises (
976+ self ,
977+ monkeypatch : pytest .MonkeyPatch ,
978+ cwd : Path ,
979+ ) -> None :
980+ """Exceptions in ``build_rego_evaluator_async`` must be swallowed —
981+ a Rego failure must not crash the bootstrap. ``rego_evaluator``
982+ stays ``None`` and governance returns a valid (non-Rego) bootstrap.
983+ """
984+ self ._setup_successful_governance (monkeypatch )
985+ monkeypatch .setattr (
986+ "uipath._cli._governance_bootstrap.is_rego_enabled" ,
987+ lambda : True ,
988+ )
989+ self ._stub_rego_module (
990+ monkeypatch , side_effect = RuntimeError ("Rego bundle unavailable" )
991+ )
992+
993+ result = await resolve_governance (
994+ agent_framework = "langgraph" ,
995+ agent_type = "uipath_coded" ,
996+ is_conversational = False ,
997+ )
998+ assert result is not None
999+ try :
1000+ assert result .rego_evaluator is None
1001+ finally :
1002+ result .dispose ()
0 commit comments