1+ """
2+ Unit tests for the InternalSpanProcessor.
3+ """
4+
5+ import unittest
6+ from unittest .mock import patch , MagicMock , call
7+
8+ from opentelemetry .sdk .trace import Span , ReadableSpan
9+
10+ from agentops .sdk .processors import InternalSpanProcessor
11+
12+
13+ class TestInternalSpanProcessor (unittest .TestCase ):
14+ """Tests for InternalSpanProcessor."""
15+
16+ def setUp (self ):
17+ self .processor = InternalSpanProcessor ()
18+
19+ # Reset the root span ID before each test
20+ self .processor ._root_span_id = None
21+
22+ @patch ('agentops.sdk.processors.log_trace_url' )
23+ def test_logs_url_for_first_span (self , mock_log_trace_url ):
24+ """Test that the first span triggers a log_trace_url call."""
25+ # Create a mock span
26+ mock_span = MagicMock (spec = Span )
27+ mock_context = MagicMock ()
28+ mock_context .trace_flags .sampled = True
29+ mock_context .span_id = 12345
30+ mock_span .context = mock_context
31+
32+ # Call on_start
33+ self .processor .on_start (mock_span )
34+
35+ # Assert that log_trace_url was called once
36+ mock_log_trace_url .assert_called_once_with (mock_span )
37+
38+ @patch ('agentops.sdk.processors.log_trace_url' )
39+ def test_logs_url_only_for_root_span (self , mock_log_trace_url ):
40+ """Test that log_trace_url is only called for the root span."""
41+ # First, create and start the root span
42+ mock_root_span = MagicMock (spec = Span )
43+ mock_root_context = MagicMock ()
44+ mock_root_context .trace_flags .sampled = True
45+ mock_root_context .span_id = 12345
46+ mock_root_span .context = mock_root_context
47+
48+ self .processor .on_start (mock_root_span )
49+
50+ # Reset the mock after root span creation
51+ mock_log_trace_url .reset_mock ()
52+
53+ # Now create and start a non-root span
54+ mock_non_root_span = MagicMock (spec = Span )
55+ mock_non_root_context = MagicMock ()
56+ mock_non_root_context .trace_flags .sampled = True
57+ mock_non_root_context .span_id = 67890 # Different from root span ID
58+ mock_non_root_span .context = mock_non_root_context
59+
60+ self .processor .on_start (mock_non_root_span )
61+
62+ # Assert that log_trace_url was not called for the non-root span
63+ mock_log_trace_url .assert_not_called ()
64+
65+ # End the non-root span
66+ mock_non_root_readable = MagicMock (spec = ReadableSpan )
67+ mock_non_root_readable .context = mock_non_root_context
68+
69+ self .processor .on_end (mock_non_root_readable )
70+
71+ # Assert that log_trace_url was still not called
72+ mock_log_trace_url .assert_not_called ()
73+
74+ # Now end the root span
75+ mock_root_readable = MagicMock (spec = ReadableSpan )
76+ mock_root_readable .context = mock_root_context
77+
78+ self .processor .on_end (mock_root_readable )
79+
80+ # Assert that log_trace_url was called for the root span end
81+ mock_log_trace_url .assert_called_once_with (mock_root_readable )
82+
83+ @patch ('agentops.sdk.processors.log_trace_url' )
84+ def test_logs_url_exactly_twice_for_root_span (self , mock_log_trace_url ):
85+ """Test that log_trace_url is called exactly twice for the root span (start and end)."""
86+ # Create a mock root span
87+ mock_root_span = MagicMock (spec = Span )
88+ mock_root_context = MagicMock ()
89+ mock_root_context .trace_flags .sampled = True
90+ mock_root_context .span_id = 12345
91+ mock_root_span .context = mock_root_context
92+
93+ # Start the root span
94+ self .processor .on_start (mock_root_span )
95+
96+ # Create a mock readable span for the end event
97+ mock_root_readable = MagicMock (spec = ReadableSpan )
98+ mock_root_readable .context = mock_root_context
99+
100+ # End the root span
101+ self .processor .on_end (mock_root_readable )
102+
103+ # Assert that log_trace_url was called exactly twice
104+ self .assertEqual (mock_log_trace_url .call_count , 2 )
105+ mock_log_trace_url .assert_has_calls ([
106+ call (mock_root_span ),
107+ call (mock_root_readable )
108+ ])
109+
110+ @patch ('agentops.sdk.processors.log_trace_url' )
111+ def test_ignores_unsampled_spans (self , mock_log_trace_url ):
112+ """Test that unsampled spans are ignored."""
113+ # Create a mock unsampled span
114+ mock_span = MagicMock (spec = Span )
115+ mock_context = MagicMock ()
116+ mock_context .trace_flags .sampled = False
117+ mock_span .context = mock_context
118+
119+ # Start and end the span
120+ self .processor .on_start (mock_span )
121+ self .processor .on_end (mock_span )
122+
123+ # Assert that log_trace_url was not called
124+ mock_log_trace_url .assert_not_called ()
125+
126+ # Assert that root_span_id was not set
127+ self .assertIsNone (self .processor ._root_span_id )
128+
129+ @patch ('agentops.sdk.processors.log_trace_url' )
130+ def test_shutdown_resets_root_span_id (self , mock_log_trace_url ):
131+ """Test that shutdown resets the root span ID."""
132+ # First set a root span
133+ mock_root_span = MagicMock (spec = Span )
134+ mock_root_context = MagicMock ()
135+ mock_root_context .trace_flags .sampled = True
136+ mock_root_context .span_id = 12345
137+ mock_root_span .context = mock_root_context
138+
139+ self .processor .on_start (mock_root_span )
140+
141+ # Verify root span ID was set
142+ self .assertEqual (self .processor ._root_span_id , 12345 )
143+
144+ # Call shutdown
145+ self .processor .shutdown ()
146+
147+ # Verify root span ID was reset
148+ self .assertIsNone (self .processor ._root_span_id )
149+
150+ # Create another span after shutdown
151+ mock_span = MagicMock (spec = Span )
152+ mock_context = MagicMock ()
153+ mock_context .trace_flags .sampled = True
154+ mock_context .span_id = 67890
155+ mock_span .context = mock_context
156+
157+ # Reset mocks
158+ mock_log_trace_url .reset_mock ()
159+
160+ # Start the span, it should be treated as a new root span
161+ self .processor .on_start (mock_span )
162+
163+ # Verify new root span was identified
164+ self .assertEqual (self .processor ._root_span_id , 67890 )
165+ mock_log_trace_url .assert_called_once_with (mock_span )
0 commit comments