|
9 | 9 | Agent365ExporterOptions, |
10 | 10 | ) |
11 | 11 | from microsoft_agents_a365.observability.core.trace_processor import SpanProcessor |
| 12 | +from opentelemetry.sdk.resources import Resource |
| 13 | +from opentelemetry.sdk.trace import TracerProvider |
12 | 14 |
|
13 | 15 |
|
14 | 16 | class TestAgent365Configure(unittest.TestCase): |
15 | 17 | """Test suite for Agent365 configuration functionality.""" |
16 | 18 |
|
17 | 19 | def setUp(self): |
18 | 20 | """Set up test fixtures.""" |
| 21 | + # Reset TelemetryManager state before each test |
| 22 | + from microsoft_agents_a365.observability.core.config import _telemetry_manager |
| 23 | + from microsoft_agents_a365.observability.core.opentelemetry_scope import OpenTelemetryScope |
| 24 | + |
| 25 | + _telemetry_manager._tracer_provider = None |
| 26 | + _telemetry_manager._span_processors = {} |
| 27 | + OpenTelemetryScope._tracer = None |
| 28 | + |
19 | 29 | self.mock_token_resolver = Mock() |
20 | 30 | self.mock_token_resolver.return_value = "test_token_123" |
21 | 31 |
|
| 32 | + def tearDown(self): |
| 33 | + """Clean up after each test.""" |
| 34 | + # Reset the telemetry manager singleton state |
| 35 | + from microsoft_agents_a365.observability.core.config import _telemetry_manager |
| 36 | + from microsoft_agents_a365.observability.core.opentelemetry_scope import OpenTelemetryScope |
| 37 | + |
| 38 | + _telemetry_manager._tracer_provider = None |
| 39 | + _telemetry_manager._span_processors = {} |
| 40 | + OpenTelemetryScope._tracer = None |
| 41 | + |
| 42 | + # Do NOT reset otel_trace._TRACER_PROVIDER to None to avoid NonRecordingSpan issues in other tests |
| 43 | + |
22 | 44 | def test_configure_basic_functionality(self): |
23 | 45 | """Test configure function with basic parameters and legacy parameters.""" |
24 | 46 | # Test basic configuration without exporter_options |
@@ -61,13 +83,13 @@ def test_configure_with_exporter_options_and_parameter_precedence(self, mock_is_ |
61 | 83 | ) |
62 | 84 | self.assertTrue(result, "configure() should return True with exporter_options") |
63 | 85 |
|
64 | | - @patch("microsoft_agents_a365.observability.core.config.Agent365Exporter") |
| 86 | + @patch("microsoft_agents_a365.observability.core.config._Agent365Exporter") |
65 | 87 | @patch("microsoft_agents_a365.observability.core.config.BatchSpanProcessor") |
66 | 88 | @patch("microsoft_agents_a365.observability.core.config.is_agent365_exporter_enabled") |
67 | 89 | def test_batch_span_processor_and_exporter_called_with_correct_values( |
68 | 90 | self, mock_is_enabled, mock_batch_processor, mock_exporter |
69 | 91 | ): |
70 | | - """Test that BatchSpanProcessor and Agent365Exporter are called with correct values from exporter_options.""" |
| 92 | + """Test that BatchSpanProcessor and _Agent365Exporter are called with correct values from exporter_options.""" |
71 | 93 | # Enable Agent365 exporter for this test |
72 | 94 | mock_is_enabled.return_value = True |
73 | 95 |
|
@@ -112,6 +134,72 @@ def test_span_processor_creation(self): |
112 | 134 | processor = SpanProcessor() |
113 | 135 | self.assertIsNotNone(processor, "SpanProcessor should be created successfully") |
114 | 136 |
|
| 137 | + def test_configure_prevents_duplicate_initialization(self): |
| 138 | + """Test that calling configure() multiple times doesn't reinitialize.""" |
| 139 | + result1 = configure( |
| 140 | + service_name="test-service-1", |
| 141 | + service_namespace="test-namespace-1", |
| 142 | + ) |
| 143 | + self.assertTrue(result1) |
| 144 | + |
| 145 | + with patch( |
| 146 | + "microsoft_agents_a365.observability.core.config._telemetry_manager._logger" |
| 147 | + ) as mock_logger: |
| 148 | + result2 = configure( |
| 149 | + service_name="test-service-2", |
| 150 | + service_namespace="test-namespace-2", |
| 151 | + ) |
| 152 | + self.assertTrue(result2) |
| 153 | + mock_logger.warning.assert_called_once() |
| 154 | + self.assertIn("already configured", mock_logger.warning.call_args[0][0].lower()) |
| 155 | + |
| 156 | + @patch("microsoft_agents_a365.observability.core.config.is_agent365_exporter_enabled") |
| 157 | + @patch("microsoft_agents_a365.observability.core.config.trace.get_tracer_provider") |
| 158 | + def test_configure_uses_existing_tracer_provider(self, mock_get_provider, mock_is_enabled): |
| 159 | + """Test configure() uses existing TracerProvider and adds processors without calling set_tracer_provider.""" |
| 160 | + mock_is_enabled.return_value = False |
| 161 | + |
| 162 | + existing_provider = TracerProvider( |
| 163 | + resource=Resource.create({"service.name": "existing-service"}) |
| 164 | + ) |
| 165 | + mock_get_provider.return_value = existing_provider |
| 166 | + |
| 167 | + with patch( |
| 168 | + "microsoft_agents_a365.observability.core.config._telemetry_manager._logger" |
| 169 | + ) as mock_logger: |
| 170 | + with patch( |
| 171 | + "microsoft_agents_a365.observability.core.config.trace.set_tracer_provider" |
| 172 | + ) as mock_set: |
| 173 | + result = configure(service_name="new-service", service_namespace="new-namespace") |
| 174 | + self.assertTrue(result) |
| 175 | + |
| 176 | + # Verify existing provider was detected |
| 177 | + info_calls = [call[0][0] for call in mock_logger.info.call_args_list] |
| 178 | + self.assertTrue( |
| 179 | + any("Detected existing TracerProvider" in msg for msg in info_calls) |
| 180 | + ) |
| 181 | + |
| 182 | + # Verify didn't call set_tracer_provider |
| 183 | + mock_set.assert_not_called() |
| 184 | + |
| 185 | + # Verify both processors were added by inspecting the MultiSpanProcessor |
| 186 | + |
| 187 | + active_processor = existing_provider._active_span_processor |
| 188 | + self.assertIsNotNone(active_processor) |
| 189 | + |
| 190 | + # MultiSpanProcessor has a _span_processors list |
| 191 | + processors = active_processor._span_processors |
| 192 | + self.assertEqual( |
| 193 | + len(processors), |
| 194 | + 2, |
| 195 | + "Should have 2 processors: BatchSpanProcessor and SpanProcessor", |
| 196 | + ) |
| 197 | + |
| 198 | + # Verify types of processors |
| 199 | + processor_types = [type(p).__name__ for p in processors] |
| 200 | + self.assertIn("BatchSpanProcessor", processor_types) |
| 201 | + self.assertIn("SpanProcessor", processor_types) |
| 202 | + |
115 | 203 |
|
116 | 204 | if __name__ == "__main__": |
117 | 205 | unittest.main() |
0 commit comments