66from unittest .mock import MagicMock , patch
77
88import pytest
9+ from scale_gp_beta .lib .tracing import (
10+ PlatformError as SGPPlatformError ,
11+ ApplicationError as SGPApplicationError ,
12+ CategorizedError as SGPCategorizedError ,
13+ )
914
1015from agentex .types .span import Span
1116from agentex .lib .core .tracing .trace import Trace , AsyncTrace
1217from agentex .lib .core .tracing .span_error import (
1318 SPAN_ERROR_KEY ,
19+ PlatformError ,
20+ ApplicationError ,
21+ CategorizedError ,
1422 get_span_error ,
1523 set_span_error ,
1624)
@@ -34,12 +42,52 @@ def _make_span(data=None) -> Span:
3442
3543
3644class TestSpanErrorHelpers :
45+ def test_uses_canonical_sgp_error_types (self ):
46+ assert CategorizedError is SGPCategorizedError
47+ assert ApplicationError is SGPApplicationError
48+ assert PlatformError is SGPPlatformError
49+
3750 def test_set_then_get_on_none_data (self ):
3851 span = _make_span (data = None )
3952 set_span_error (span , ValueError ("boom" ))
40- assert get_span_error (span ) == {"type" : "ValueError" , "message" : "boom" }
53+ assert get_span_error (span ) == {
54+ "type" : "ValueError" ,
55+ "message" : "boom" ,
56+ "category" : "unknown" ,
57+ }
4158 assert isinstance (span .data , dict )
42- assert span .data [SPAN_ERROR_KEY ] == {"type" : "ValueError" , "message" : "boom" }
59+ assert span .data [SPAN_ERROR_KEY ] == {
60+ "type" : "ValueError" ,
61+ "message" : "boom" ,
62+ "category" : "unknown" ,
63+ }
64+
65+ def test_set_uses_explicit_exception_category (self ):
66+ span = _make_span (data = None )
67+ set_span_error (span , PlatformError ("unavailable" ))
68+ assert get_span_error (span ) == {
69+ "type" : "PlatformError" ,
70+ "message" : "unavailable" ,
71+ "category" : "platform" ,
72+ }
73+
74+ def test_explicit_category_takes_precedence (self ):
75+ span = _make_span (data = None )
76+ set_span_error (span , PlatformError ("bad input" ), error_category = "application" )
77+ assert get_span_error (span )["category" ] == "application" # type: ignore[index]
78+
79+ def test_set_uses_application_error_category (self ):
80+ span = _make_span (data = None )
81+ set_span_error (span , ApplicationError ("bad input" ))
82+ assert get_span_error (span )["category" ] == "application" # type: ignore[index]
83+
84+ def test_bare_exception_attribute_does_not_opt_in (self ):
85+ class ImplicitlyCategorizedError (RuntimeError ):
86+ error_category = "platform"
87+
88+ span = _make_span (data = None )
89+ set_span_error (span , ImplicitlyCategorizedError ("boom" ))
90+ assert get_span_error (span )["category" ] == "unknown" # type: ignore[index]
4391
4492 def test_set_preserves_existing_dict_keys (self ):
4593 span = _make_span (data = {"__span_type__" : "LLM" })
@@ -76,7 +124,11 @@ def test_sync_span_records_error_and_reraises(self):
76124 captured ["span" ] = span
77125 raise ValueError ("boom" )
78126 err = get_span_error (captured ["span" ])
79- assert err == {"type" : "ValueError" , "message" : "boom" }
127+ assert err == {
128+ "type" : "ValueError" ,
129+ "message" : "boom" ,
130+ "category" : "unknown" ,
131+ }
80132
81133 def test_sync_span_success_has_no_error (self ):
82134 trace = Trace (processors = [], client = MagicMock (), trace_id = "t1" )
@@ -93,7 +145,11 @@ async def test_async_span_records_error_and_reraises(self):
93145 captured ["span" ] = span
94146 raise RuntimeError ("kaboom" )
95147 err = get_span_error (captured ["span" ])
96- assert err == {"type" : "RuntimeError" , "message" : "kaboom" }
148+ assert err == {
149+ "type" : "RuntimeError" ,
150+ "message" : "kaboom" ,
151+ "category" : "unknown" ,
152+ }
97153
98154
99155# ---------------------------------------------------------------------------
@@ -111,7 +167,7 @@ def set_error(
111167 self ,
112168 error_type : str | None = None ,
113169 error_message : str | None = None ,
114- exception : BaseException | None = None ,
170+ exception : BaseException | None = None , # noqa: ARG002
115171 ) -> None :
116172 self .status = "ERROR"
117173 self .metadata ["error" ] = True
@@ -131,14 +187,23 @@ def _env():
131187 def test_error_maps_to_status_error (self ):
132188 from agentex .lib .core .tracing .processors .sgp_tracing_processor import _build_sgp_span
133189
134- span = _make_span (data = {SPAN_ERROR_KEY : {"type" : "ValueError" , "message" : "boom" }})
190+ span = _make_span (
191+ data = {
192+ SPAN_ERROR_KEY : {
193+ "type" : "ValueError" ,
194+ "message" : "boom" ,
195+ "category" : "application" ,
196+ }
197+ }
198+ )
135199 with patch (f"{ PROCESSOR_MODULE } .create_span" , side_effect = _fake_create_span ):
136200 sgp_span = _build_sgp_span (span , self ._env ())
137201
138202 assert sgp_span .status == "ERROR"
139203 assert sgp_span .metadata ["error" ] is True
140204 assert sgp_span .metadata ["error_type" ] == "ValueError"
141205 assert sgp_span .metadata ["error_message" ] == "boom"
206+ assert sgp_span .metadata ["error_category" ] == "application"
142207
143208 def test_no_error_leaves_status_success (self ):
144209 from agentex .lib .core .tracing .processors .sgp_tracing_processor import _build_sgp_span
0 commit comments