@@ -38,6 +38,8 @@ def mock_env_vars():
3838def mock_span ():
3939 """Create a mock ReadableSpan for testing."""
4040 span = MagicMock (spec = ReadableSpan )
41+ # Ensure span doesn't get filtered by _should_drop_span
42+ span .attributes = {}
4143 return span
4244
4345
@@ -555,5 +557,98 @@ def test_unknown_span_type_preserved(self):
555557 print (f"✓ Final attributes keys: { list (attributes .keys ())} " )
556558
557559
560+ class TestSpanFiltering :
561+ """Tests for filtering spans marked with telemetry.filter=drop."""
562+
563+ @pytest .fixture
564+ def exporter_with_mocks (self , mock_env_vars ):
565+ """Create exporter with mocked HTTP client."""
566+ with patch ("uipath.tracing._otel_exporters.httpx.Client" ):
567+ exporter = LlmOpsHttpExporter ()
568+ yield exporter
569+
570+ def _create_mock_span (
571+ self ,
572+ should_drop : bool = False ,
573+ ) -> MagicMock :
574+ """Helper to create mock span with span attributes.
575+
576+ Args:
577+ should_drop: If True, sets telemetry.filter="drop".
578+ """
579+ span = MagicMock (spec = ReadableSpan )
580+
581+ if should_drop :
582+ span .attributes = {"telemetry.filter" : "drop" }
583+ else :
584+ span .attributes = {}
585+
586+ return span
587+
588+ def test_filters_spans_marked_for_drop (self , exporter_with_mocks ):
589+ """Span with telemetry.filter=drop → filtered out."""
590+ span = self ._create_mock_span (should_drop = True )
591+ assert exporter_with_mocks ._should_drop_span (span ) is True
592+
593+ def test_passes_unmarked_spans (self , exporter_with_mocks ):
594+ """Span without marker attribute → passes through."""
595+ span = self ._create_mock_span (should_drop = False )
596+ assert exporter_with_mocks ._should_drop_span (span ) is False
597+
598+ def test_passes_spans_with_no_attributes (self , exporter_with_mocks ):
599+ """Span with None attributes → passes through."""
600+ span = MagicMock (spec = ReadableSpan )
601+ span .attributes = None
602+ assert exporter_with_mocks ._should_drop_span (span ) is False
603+
604+ def test_passes_spans_with_empty_attributes (self , exporter_with_mocks ):
605+ """Span with empty attributes dict → passes through."""
606+ span = MagicMock (spec = ReadableSpan )
607+ span .attributes = {}
608+ assert exporter_with_mocks ._should_drop_span (span ) is False
609+
610+ def test_passes_spans_with_other_filter_values (self , exporter_with_mocks ):
611+ """Span with telemetry.filter=keep → passes through."""
612+ span = MagicMock (spec = ReadableSpan )
613+ span .attributes = {"telemetry.filter" : "keep" }
614+ assert exporter_with_mocks ._should_drop_span (span ) is False
615+
616+ def test_export_filters_marked_spans (self , exporter_with_mocks ):
617+ """export() should filter out spans marked for drop."""
618+ # Create mixed batch: 1 marked for drop, 1 unmarked
619+ drop_span = self ._create_mock_span (should_drop = True )
620+ keep_span = self ._create_mock_span (should_drop = False )
621+
622+ mock_uipath_span = MagicMock ()
623+ mock_uipath_span .to_dict .return_value = {
624+ "TraceId" : "test-trace-id" ,
625+ "Id" : "span-id" ,
626+ }
627+
628+ with patch (
629+ "uipath.tracing._otel_exporters._SpanUtils.otel_span_to_uipath_span" ,
630+ return_value = mock_uipath_span ,
631+ ) as mock_convert :
632+ mock_response = MagicMock ()
633+ mock_response .status_code = 200
634+ exporter_with_mocks .http_client .post .return_value = mock_response
635+
636+ result = exporter_with_mocks .export ([drop_span , keep_span ])
637+
638+ assert result == SpanExportResult .SUCCESS
639+ # Only keep_span should be converted (drop_span filtered)
640+ assert mock_convert .call_count == 1
641+
642+ def test_export_all_filtered_returns_success (self , exporter_with_mocks ):
643+ """When all spans filtered, export returns SUCCESS without HTTP call."""
644+ span = self ._create_mock_span (should_drop = True )
645+
646+ result = exporter_with_mocks .export ([span ])
647+
648+ assert result == SpanExportResult .SUCCESS
649+ # No HTTP call should be made
650+ exporter_with_mocks .http_client .post .assert_not_called ()
651+
652+
558653if __name__ == "__main__" :
559654 unittest .main ()
0 commit comments