Skip to content

Commit deb3998

Browse files
evanbijoy251claude
andcommitted
fix: guard rego import with TYPE_CHECKING, add Rego bootstrap tests
The top-level `from uipath.runtime.governance.rego import RegoEvaluator` in _governance_bootstrap.py caused an ImportError during test collection when the installed uipath-runtime predates the rego submodule. Guard it with TYPE_CHECKING (annotations are strings via `from __future__ import annotations`) so it's only evaluated by type checkers, not at runtime. Add three tests covering the previously-uncovered Rego bootstrap path in resolve_governance (is_rego_enabled → build_rego_evaluator_async: success, returns None, raises). Also update the wrap_runtime test to patch UiPathGovernedRuntime with a forward-compatible subclass so the test is not gated on the dependency version. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fa6ccec commit deb3998

2 files changed

Lines changed: 148 additions & 1 deletion

File tree

packages/uipath/src/uipath/_cli/_governance_bootstrap.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import logging
1212
from collections.abc import Callable
1313
from dataclasses import dataclass, field
14+
from typing import TYPE_CHECKING
1415

1516
from uipath.core.governance import EnforcementMode, PolicyContext
1617
from uipath.core.governance.config import is_governance_enabled, is_rego_enabled
@@ -27,9 +28,11 @@
2728
GuardrailCompensator,
2829
)
2930
from uipath.runtime.governance.native.models import PolicyIndex
30-
from uipath.runtime.governance.rego import RegoEvaluator
3131
from uipath.runtime.governance.runtime import UiPathGovernedRuntime
3232

33+
if TYPE_CHECKING:
34+
from uipath.runtime.governance.rego import RegoEvaluator
35+
3336
from ._governance import build_policy_index_from_yaml
3437
from ._utils._console import ConsoleLogger
3538

packages/uipath/tests/cli/test_governance_bootstrap.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)