33import os
44from unittest .mock import patch
55
6- from opentelemetry import trace
76from opentelemetry .sdk .trace import TracerProvider
87from opentelemetry .trace import NonRecordingSpan , SpanContext , TraceFlags
98from uipath .core .feature_flags import FeatureFlags
1312FEATURE_FLAG = "EnableTraceContextHeaders"
1413
1514
15+ def _make_span ():
16+ """Create a real OTEL span for testing."""
17+ provider = TracerProvider ()
18+ tracer = provider .get_tracer ("test" )
19+ return tracer .start_span ("test-span" )
20+
21+
1622class TestFeatureFlagDisabled :
1723 """When the feature flag is off, no headers are returned."""
1824
@@ -28,48 +34,74 @@ def test_returns_empty_dict_when_explicitly_disabled(self) -> None:
2834
2935
3036class TestTraceparentHeader :
31- """When enabled, x-uipath-traceparent-id is populated from the active span."""
37+ """When enabled, x-uipath-traceparent-id is populated from config + span."""
3238
3339 def setup_method (self ) -> None :
3440 FeatureFlags .reset_flags ()
3541 FeatureFlags .configure_flags ({FEATURE_FLAG : True })
3642
37- def test_traceparent_from_active_span (self ) -> None :
38- provider = TracerProvider ()
39- tracer = provider .get_tracer ("test" )
40- with tracer .start_as_current_span ("test-span" ) as span :
41- ctx = span .get_span_context ()
42- expected_trace_id = format (ctx .trace_id , "032x" )
43- expected_span_id = format (ctx .span_id , "016x" )
44-
43+ def test_traceparent_from_config_and_span (self ) -> None :
44+ span = _make_span ()
45+ ctx = span .get_span_context ()
46+ expected_span_id = format (ctx .span_id , "032x" )
47+ config_trace = "abcdef1234567890abcdef1234567890"
48+ env = {"UIPATH_TRACE_ID" : config_trace }
49+ with (
50+ patch .dict (os .environ , env ),
51+ patch (
52+ "uipath.platform.chat.llm_trace_context.trace.get_current_span" ,
53+ return_value = span ,
54+ ),
55+ ):
4556 headers = build_trace_context_headers ()
4657
4758 assert "x-uipath-traceparent-id" in headers
4859 value = headers ["x-uipath-traceparent-id" ]
49- assert value == f"00-{ expected_trace_id } -{ expected_span_id } "
50- # Verify format: version (2) + dash + trace_id (32) + dash + span_id (16)
60+ assert value == f"00-{ config_trace } -{ expected_span_id } "
5161 parts = value .split ("-" )
5262 assert len (parts ) == 3
5363 assert parts [0 ] == "00"
5464 assert len (parts [1 ]) == 32
55- assert len (parts [2 ]) == 16
65+ assert len (parts [2 ]) == 32
66+
67+ def test_no_traceparent_without_config_trace_id (self ) -> None :
68+ headers = build_trace_context_headers ()
69+ assert "x-uipath-traceparent-id" not in headers
70+
71+ def test_traceparent_strips_dashes_from_config_trace_id (self ) -> None :
72+ span = _make_span ()
73+ uuid_trace = "abcdef12-3456-7890-abcd-ef1234567890"
74+ env = {"UIPATH_TRACE_ID" : uuid_trace }
75+ with (
76+ patch .dict (os .environ , env ),
77+ patch (
78+ "uipath.platform.chat.llm_trace_context.trace.get_current_span" ,
79+ return_value = span ,
80+ ),
81+ ):
82+ headers = build_trace_context_headers ()
5683
57- def test_no_traceparent_without_active_span ( self ) -> None :
58- # INVALID_SPAN has trace_id=0 and span_id=0
59- from opentelemetry . context import attach , detach
84+ value = headers [ "x-uipath-traceparent-id" ]
85+ parts = value . split ( "-" )
86+ assert parts [ 1 ] == "abcdef1234567890abcdef1234567890"
6087
88+ def test_no_traceparent_with_invalid_span (self ) -> None :
6189 ctx = SpanContext (
6290 trace_id = 0 ,
6391 span_id = 0 ,
6492 is_remote = False ,
6593 trace_flags = TraceFlags (0 ),
6694 )
67- non_recording = NonRecordingSpan (ctx )
68- token = attach (trace .set_span_in_context (non_recording ))
69- try :
95+ span = NonRecordingSpan (ctx )
96+ env = {"UIPATH_TRACE_ID" : "abcdef1234567890abcdef1234567890" }
97+ with (
98+ patch .dict (os .environ , env ),
99+ patch (
100+ "uipath.platform.chat.llm_trace_context.trace.get_current_span" ,
101+ return_value = span ,
102+ ),
103+ ):
70104 headers = build_trace_context_headers ()
71- finally :
72- detach (token )
73105
74106 assert "x-uipath-traceparent-id" not in headers
75107
@@ -84,7 +116,7 @@ def setup_method(self) -> None:
84116 def test_all_env_vars_present (self ) -> None :
85117 env = {
86118 "UIPATH_FOLDER_KEY" : "folder-abc" ,
87- "UIPATH_PROCESS_UUID " : "agent-123" ,
119+ "UIPATH_AGENT_ID " : "agent-123" ,
88120 "UIPATH_PROCESS_KEY" : "process-789" ,
89121 }
90122 with patch .dict (os .environ , env , clear = True ):
@@ -103,6 +135,31 @@ def test_partial_env_vars(self) -> None:
103135 baggage = headers ["x-uipath-tracebaggage" ]
104136 assert "folderKey=folder-only" in baggage
105137
138+ def test_agent_id_from_agent_id_env (self ) -> None :
139+ env = {"UIPATH_AGENT_ID" : "real-agent-id" }
140+ with patch .dict (os .environ , env , clear = True ):
141+ headers = build_trace_context_headers ()
142+
143+ baggage = headers ["x-uipath-tracebaggage" ]
144+ assert "agentId=real-agent-id" in baggage
145+
146+ def test_agent_id_falls_back_to_project_id (self ) -> None :
147+ env = {"UIPATH_PROJECT_ID" : "project-123" }
148+ with patch .dict (os .environ , env , clear = True ):
149+ headers = build_trace_context_headers ()
150+
151+ baggage = headers ["x-uipath-tracebaggage" ]
152+ assert "agentId=project-123" in baggage
153+
154+ def test_no_agent_id_without_env_vars (self ) -> None :
155+ env = {"UIPATH_FOLDER_KEY" : "f1" }
156+ with patch .dict (os .environ , env , clear = True ):
157+ headers = build_trace_context_headers ()
158+
159+ baggage = headers ["x-uipath-tracebaggage" ]
160+ assert "agentId" not in baggage
161+ assert "folderKey=f1" in baggage
162+
106163 def test_no_baggage_without_env_vars (self ) -> None :
107164 with patch .dict (os .environ , {}, clear = True ):
108165 headers = build_trace_context_headers ()
@@ -112,7 +169,7 @@ def test_no_baggage_without_env_vars(self) -> None:
112169 def test_baggage_comma_separated (self ) -> None :
113170 env = {
114171 "UIPATH_FOLDER_KEY" : "f1" ,
115- "UIPATH_PROCESS_UUID " : "a1" ,
172+ "UIPATH_AGENT_ID " : "a1" ,
116173 }
117174 with patch .dict (os .environ , env , clear = True ):
118175 headers = build_trace_context_headers ()
@@ -148,14 +205,22 @@ def setup_method(self) -> None:
148205 FeatureFlags .configure_flags ({FEATURE_FLAG : True })
149206
150207 def test_both_headers_present (self ) -> None :
151- provider = TracerProvider ()
152- tracer = provider .get_tracer ("test" )
153- env = {"UIPATH_FOLDER_KEY" : "folder-abc" }
208+ span = _make_span ()
209+ env = {
210+ "UIPATH_FOLDER_KEY" : "folder-abc" ,
211+ "UIPATH_TRACE_ID" : "abcdef1234567890abcdef1234567890" ,
212+ }
154213 with (
155- tracer .start_as_current_span ("test-span" ),
156214 patch .dict (os .environ , env , clear = True ),
215+ patch (
216+ "uipath.platform.chat.llm_trace_context.trace.get_current_span" ,
217+ return_value = span ,
218+ ),
157219 ):
158220 headers = build_trace_context_headers ()
159221
160222 assert "x-uipath-traceparent-id" in headers
223+ assert headers ["x-uipath-traceparent-id" ].startswith (
224+ "00-abcdef1234567890abcdef1234567890-"
225+ )
161226 assert "x-uipath-tracebaggage" in headers
0 commit comments