|
10 | 10 | from uipath.platform.common import UiPathSpan, _SpanUtils |
11 | 11 |
|
12 | 12 |
|
| 13 | +@pytest.fixture(autouse=True) |
| 14 | +def _clear_agent_id_cache(): |
| 15 | + """Isolate the process-global agentId cache between tests.""" |
| 16 | + from uipath.platform.common._span_utils import _read_config_agent_id |
| 17 | + |
| 18 | + _read_config_agent_id.cache_clear() |
| 19 | + yield |
| 20 | + _read_config_agent_id.cache_clear() |
| 21 | + |
| 22 | + |
13 | 23 | class TestOTelToUiPathSpan: |
14 | 24 | """OTEL attribute -> top-level UiPathSpan field mapping. |
15 | 25 |
|
@@ -166,6 +176,97 @@ def test_reference_id_chain( |
166 | 176 | assert uipath_span.reference_id == expected |
167 | 177 |
|
168 | 178 |
|
| 179 | +class TestAgentIdResolution: |
| 180 | + """`agentId` span attribute resolution. |
| 181 | +
|
| 182 | + Priority: `uipath.json#agentId` (cached, read once per process) > the legacy |
| 183 | + `PROJECT_KEY` env var injected by the executor. When neither is present the |
| 184 | + `agentId` attribute is omitted entirely. |
| 185 | + """ |
| 186 | + |
| 187 | + @staticmethod |
| 188 | + def _make_span() -> Mock: |
| 189 | + mock_span = Mock(spec=OTelSpan) |
| 190 | + mock_context = SpanContext( |
| 191 | + trace_id=0x123456789ABCDEF0123456789ABCDEF0, |
| 192 | + span_id=0x0123456789ABCDEF, |
| 193 | + is_remote=False, |
| 194 | + ) |
| 195 | + mock_span.get_span_context.return_value = mock_context |
| 196 | + mock_span.name = "test-span" |
| 197 | + mock_span.parent = None |
| 198 | + mock_span.status.status_code = StatusCode.OK |
| 199 | + mock_span.attributes = {} |
| 200 | + mock_span.events = [] |
| 201 | + mock_span.links = [] |
| 202 | + now_ns = int(datetime.now().timestamp() * 1e9) |
| 203 | + mock_span.start_time = now_ns |
| 204 | + mock_span.end_time = now_ns + 1_000_000 |
| 205 | + return mock_span |
| 206 | + |
| 207 | + @staticmethod |
| 208 | + def _resolve(monkeypatch: pytest.MonkeyPatch, tmp_path) -> object: |
| 209 | + from uipath.platform.common._span_utils import _read_config_agent_id |
| 210 | + |
| 211 | + _read_config_agent_id.cache_clear() |
| 212 | + monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) |
| 213 | + monkeypatch.delenv("UIPATH_AGENT_ID", raising=False) |
| 214 | + monkeypatch.chdir(tmp_path) |
| 215 | + uipath_span = _SpanUtils.otel_span_to_uipath_span( |
| 216 | + TestAgentIdResolution._make_span(), serialize_attributes=False |
| 217 | + ) |
| 218 | + attributes = uipath_span.attributes |
| 219 | + assert isinstance(attributes, dict) |
| 220 | + return attributes.get("agentId") |
| 221 | + |
| 222 | + def test_agent_id_from_uipath_json_wins_over_env( |
| 223 | + self, monkeypatch: pytest.MonkeyPatch, tmp_path |
| 224 | + ) -> None: |
| 225 | + (tmp_path / "uipath.json").write_text(json.dumps({"agentId": "from-config"})) |
| 226 | + monkeypatch.setenv("PROJECT_KEY", "from-env") |
| 227 | + assert self._resolve(monkeypatch, tmp_path) == "from-config" |
| 228 | + |
| 229 | + def test_agent_id_falls_back_to_project_key_env( |
| 230 | + self, monkeypatch: pytest.MonkeyPatch, tmp_path |
| 231 | + ) -> None: |
| 232 | + # No uipath.json on disk. |
| 233 | + monkeypatch.setenv("PROJECT_KEY", "from-env") |
| 234 | + assert self._resolve(monkeypatch, tmp_path) == "from-env" |
| 235 | + |
| 236 | + def test_agent_id_falls_back_when_config_has_no_agent_id( |
| 237 | + self, monkeypatch: pytest.MonkeyPatch, tmp_path |
| 238 | + ) -> None: |
| 239 | + (tmp_path / "uipath.json").write_text(json.dumps({"functions": {}})) |
| 240 | + monkeypatch.setenv("PROJECT_KEY", "from-env") |
| 241 | + assert self._resolve(monkeypatch, tmp_path) == "from-env" |
| 242 | + |
| 243 | + def test_agent_id_absent_when_no_source( |
| 244 | + self, monkeypatch: pytest.MonkeyPatch, tmp_path |
| 245 | + ) -> None: |
| 246 | + monkeypatch.delenv("PROJECT_KEY", raising=False) |
| 247 | + assert self._resolve(monkeypatch, tmp_path) is None |
| 248 | + |
| 249 | + def test_config_agent_id_is_cached( |
| 250 | + self, monkeypatch: pytest.MonkeyPatch, tmp_path |
| 251 | + ) -> None: |
| 252 | + from uipath.platform.common._span_utils import _read_config_agent_id |
| 253 | + |
| 254 | + _read_config_agent_id.cache_clear() |
| 255 | + monkeypatch.delenv("UIPATH_CONFIG_PATH", raising=False) |
| 256 | + monkeypatch.chdir(tmp_path) |
| 257 | + config = tmp_path / "uipath.json" |
| 258 | + |
| 259 | + config.write_text(json.dumps({"agentId": "first"})) |
| 260 | + assert _read_config_agent_id() == "first" |
| 261 | + |
| 262 | + # A later edit is not observed: the value is read once and cached. |
| 263 | + config.write_text(json.dumps({"agentId": "second"})) |
| 264 | + assert _read_config_agent_id() == "first" |
| 265 | + |
| 266 | + _read_config_agent_id.cache_clear() |
| 267 | + assert _read_config_agent_id() == "second" |
| 268 | + |
| 269 | + |
169 | 270 | class TestNormalizeIds: |
170 | 271 | """Tests for OTEL ID normalization functions.""" |
171 | 272 |
|
|
0 commit comments