diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 599b65f33..9d03dcc1e 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.14.1" +version = "2.14.2" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/eval/mocks/_simulate_component_mocker.py b/packages/uipath/src/uipath/eval/mocks/_simulate_component_mocker.py index f35359ec4..325c436e4 100644 --- a/packages/uipath/src/uipath/eval/mocks/_simulate_component_mocker.py +++ b/packages/uipath/src/uipath/eval/mocks/_simulate_component_mocker.py @@ -8,6 +8,7 @@ from pydantic import TypeAdapter from uipath.platform.chat._llm_gateway_service import _cleanup_schema +from uipath.platform.common import UiPathConfig from .._execution_context import execution_id_context, span_collector_context from ._llm_mocker import LLMMocker @@ -98,9 +99,17 @@ async def response( "workloadInfo": workload_info, "traceId": trace_id, "parentSpanId": parent_span_id, + "folderKey": UiPathConfig.folder_key, } - logger.info("simulate-component: calling API for '%s'", tool_name) + logger.info( + "simulate-component: calling API for '%s' " + "(traceId=%s, parentSpanId=%s, folderKey=%s)", + tool_name, + trace_id, + parent_span_id, + UiPathConfig.folder_key, + ) try: service = _create_simulate_component_service() result = await service.simulate(payload) diff --git a/packages/uipath/tests/cli/eval/mocks/test_simulate_component.py b/packages/uipath/tests/cli/eval/mocks/test_simulate_component.py index f4131cf48..ddf3bf7f4 100644 --- a/packages/uipath/tests/cli/eval/mocks/test_simulate_component.py +++ b/packages/uipath/tests/cli/eval/mocks/test_simulate_component.py @@ -284,6 +284,54 @@ def sync_tool() -> int: assert result == 42 clear_execution_context() + @pytest.mark.asyncio + async def test_payload_includes_folder_key_from_config(self, monkeypatch): + monkeypatch.setenv("UIPATH_FOLDER_KEY", "folder-abc") + ctx = _make_context("my_tool", workload_id="wl-fk") + captured: list[dict[str, Any]] = [] + + async def _capture(payload, **kwargs): + captured.append(payload) + return {"status": 1, "simulatedOutput": "ok"} + + svc_mock = MagicMock() + svc_mock.simulate = _capture + + @mockable() + async def my_tool() -> str: + raise NotImplementedError() + + set_execution_context(ctx, _mock_span_collector, "exec-fk") + with patch(_SIMULATE_PATH, return_value=svc_mock): + await my_tool() + + assert captured[0]["folderKey"] == "folder-abc" + clear_execution_context() + + @pytest.mark.asyncio + async def test_payload_folder_key_is_none_when_not_set(self, monkeypatch): + monkeypatch.delenv("UIPATH_FOLDER_KEY", raising=False) + ctx = _make_context("my_tool", workload_id="wl-fk2") + captured: list[dict[str, Any]] = [] + + async def _capture(payload, **kwargs): + captured.append(payload) + return {"status": 1, "simulatedOutput": "ok"} + + svc_mock = MagicMock() + svc_mock.simulate = _capture + + @mockable() + async def my_tool() -> str: + raise NotImplementedError() + + set_execution_context(ctx, _mock_span_collector, "exec-fk2") + with patch(_SIMULATE_PATH, return_value=svc_mock): + await my_tool() + + assert captured[0]["folderKey"] is None + clear_execution_context() + @pytest.mark.asyncio async def test_payload_uses_configured_component_id_not_invoked_name(self): """componentId in payload must be the configured ID, not the normalised call name.""" @@ -346,6 +394,53 @@ def test_returns_none_when_spans_empty(self): clear_execution_context() + def test_returns_valid_trace_and_span_ids(self): + from opentelemetry import context as context_api + from opentelemetry import trace + from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags + + span_ctx = SpanContext( + trace_id=0x1234567890ABCDEF1234567890ABCDEF, + span_id=0xFEDCBA0987654321, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + ) + span = NonRecordingSpan(context=span_ctx) + token = context_api.attach(trace.set_span_in_context(span)) + try: + trace_id, span_id = SimulateComponentMocker._get_span_context() + assert trace_id == "1234567890abcdef1234567890abcdef" + assert span_id == "fedcba0987654321" + finally: + context_api.detach(token) + + +class TestPayloadLogging: + @pytest.mark.asyncio + async def test_log_includes_trace_and_folder_key(self, monkeypatch, caplog): + monkeypatch.setenv("UIPATH_FOLDER_KEY", "folder-log") + ctx = _make_context("my_tool", workload_id="wl-log") + svc_mock = _make_service_mock({"status": 1, "simulatedOutput": "ok"}) + + @mockable() + async def my_tool() -> str: + raise NotImplementedError() + + set_execution_context(ctx, _mock_span_collector, "exec-log") + import logging + + with caplog.at_level( + logging.INFO, logger="uipath.eval.mocks._simulate_component_mocker" + ): + with patch(_SIMULATE_PATH, return_value=svc_mock): + await my_tool() + + log_line = next(r.message for r in caplog.records if "calling API" in r.message) + assert "folderKey=folder-log" in log_line + assert "traceId=" in log_line + assert "parentSpanId=" in log_line + clear_execution_context() + # --------------------------------------------------------------------------- # MockerFactory — unknown strategy raises ValueError diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index c905ec7c3..13c6bacff 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2599,7 +2599,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.14.1" +version = "2.14.2" source = { editable = "." } dependencies = [ { name = "applicationinsights" },