@@ -64,15 +64,37 @@ class OpenTelemetryTracingTracerTest {
6464 @ Mock private Tracer tracer ;
6565 @ Mock private SpanBuilder spanBuilder ;
6666 @ Mock private Span span ;
67+ @ Mock private SpanBuilder operationSpanBuilder ;
68+ @ Mock private Span operationSpan ;
6769 private OpenTelemetryTracingTracer openTelemetryTracingTracer ;
6870 private static final String ATTEMPT_SPAN_NAME = "Service/Method/attempt" ;
6971
7072 @ BeforeEach
7173 void setUp () {
72- when (tracer .spanBuilder (anyString ())).thenReturn (spanBuilder );
73- when (spanBuilder .setSpanKind (any (SpanKind .class ))).thenReturn (spanBuilder );
74- when (spanBuilder .setAllAttributes (any (Attributes .class ))).thenReturn (spanBuilder );
75- when (spanBuilder .startSpan ()).thenReturn (span );
74+ org .mockito .Mockito .lenient ().when (tracer .spanBuilder (anyString ())).thenReturn (spanBuilder );
75+ org .mockito .Mockito .lenient ()
76+ .when (spanBuilder .setSpanKind (any (SpanKind .class )))
77+ .thenReturn (spanBuilder );
78+ org .mockito .Mockito .lenient ().when (spanBuilder .setParent (any ())).thenReturn (spanBuilder );
79+ org .mockito .Mockito .lenient ()
80+ .when (spanBuilder .setAllAttributes (any (Attributes .class )))
81+ .thenReturn (spanBuilder );
82+ org .mockito .Mockito .lenient ().when (spanBuilder .startSpan ()).thenReturn (span );
83+
84+ org .mockito .Mockito .lenient ()
85+ .when (operationSpanBuilder .setSpanKind (any (SpanKind .class )))
86+ .thenReturn (operationSpanBuilder );
87+ org .mockito .Mockito .lenient ()
88+ .when (operationSpanBuilder .setAllAttributes (any (Attributes .class )))
89+ .thenReturn (operationSpanBuilder );
90+ org .mockito .Mockito .lenient ().when (operationSpanBuilder .startSpan ()).thenReturn (operationSpan );
91+ org .mockito .Mockito .lenient ()
92+ .when (operationSpan .storeInContext (any (io .opentelemetry .context .Context .class )))
93+ .thenAnswer (invocation -> invocation .getArgument (0 ));
94+ org .mockito .Mockito .lenient ()
95+ .when (tracer .spanBuilder ("Service/Method" ))
96+ .thenReturn (operationSpanBuilder );
97+
7698 openTelemetryTracingTracer =
7799 new OpenTelemetryTracingTracer (tracer , ApiTracerContext .empty (), ATTEMPT_SPAN_NAME );
78100 }
@@ -680,4 +702,128 @@ void testInjectTraceContext_addsHeaders() {
680702 assertThat (carrier .get ("traceparent" )).contains ("00000000000000000000000000000001" );
681703 assertThat (carrier .get ("traceparent" )).contains ("0000000000000002" );
682704 }
705+
706+ @ Test
707+ void testOperationLifecycle_startsOperationSpanWithInternalKind () {
708+ verify (tracer ).spanBuilder ("Service/Method" );
709+ verify (operationSpanBuilder ).setSpanKind (SpanKind .INTERNAL );
710+ }
711+
712+ @ Test
713+ void testAttemptStarted_setsParentToOperationSpan () {
714+ openTelemetryTracingTracer .attemptStarted (new Object (), 1 );
715+ verify (spanBuilder ).setParent (any (io .opentelemetry .context .Context .class ));
716+ }
717+
718+ @ Test
719+ void testOperationSucceeded_endsOperationSpanWithStatusOk () {
720+ openTelemetryTracingTracer .operationSucceeded ();
721+
722+ ArgumentCaptor <Attributes > attrsCaptor = ArgumentCaptor .forClass (Attributes .class );
723+ verify (operationSpan ).setAllAttributes (attrsCaptor .capture ());
724+ verify (operationSpan ).end ();
725+
726+ assertThat (attrsCaptor .getValue ().asMap ())
727+ .containsEntry (
728+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE ), "OK" );
729+ }
730+
731+ @ Test
732+ void testOperationFailed_endsOperationSpanWithErrorAttributes () {
733+ ApiException exception =
734+ new ApiException (
735+ "custom failure" ,
736+ null ,
737+ new StatusCode () {
738+ @ Override
739+ public Code getCode () {
740+ return Code .UNAVAILABLE ;
741+ }
742+
743+ @ Override
744+ public Object getTransportCode () {
745+ return null ;
746+ }
747+ },
748+ true );
749+
750+ openTelemetryTracingTracer .operationFailed (exception );
751+
752+ ArgumentCaptor <Attributes > attrsCaptor = ArgumentCaptor .forClass (Attributes .class );
753+ verify (operationSpan ).setAllAttributes (attrsCaptor .capture ());
754+ verify (operationSpan )
755+ .setAttribute (ObservabilityAttributes .STATUS_MESSAGE_ATTRIBUTE , "custom failure" );
756+ verify (operationSpan ).end ();
757+
758+ Map <AttributeKey <?>, Object > captured = attrsCaptor .getValue ().asMap ();
759+ assertThat (captured )
760+ .containsEntry (
761+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE ),
762+ "UNAVAILABLE" );
763+ assertThat (captured )
764+ .containsEntry (
765+ AttributeKey .stringKey (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE ), "ApiException" );
766+ }
767+
768+ @ Test
769+ void testOperationCancelled_endsOperationSpanWithCancelledStatus () {
770+ openTelemetryTracingTracer .operationCancelled ();
771+
772+ ArgumentCaptor <Attributes > attrsCaptor = ArgumentCaptor .forClass (Attributes .class );
773+ verify (operationSpan ).setAllAttributes (attrsCaptor .capture ());
774+ verify (operationSpan ).end ();
775+
776+ assertThat (attrsCaptor .getValue ().asMap ())
777+ .containsEntry (
778+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE ),
779+ "CANCELLED" );
780+ }
781+
782+ @ Test
783+ void testInScope_withAttemptSpan () {
784+ io .opentelemetry .context .Scope mockScope =
785+ org .mockito .Mockito .mock (io .opentelemetry .context .Scope .class );
786+ org .mockito .Mockito .when (span .makeCurrent ()).thenReturn (mockScope );
787+
788+ openTelemetryTracingTracer .attemptStarted (new Object (), 1 );
789+ try (ApiTracer .Scope scope = openTelemetryTracingTracer .inScope ()) {
790+ verify (span ).makeCurrent ();
791+ }
792+ verify (mockScope ).close ();
793+ }
794+
795+ @ Test
796+ void testInScope_withOperationSpanFallback () {
797+ io .opentelemetry .context .Scope mockScope =
798+ org .mockito .Mockito .mock (io .opentelemetry .context .Scope .class );
799+ org .mockito .Mockito .when (operationSpan .makeCurrent ()).thenReturn (mockScope );
800+
801+ try (ApiTracer .Scope scope = openTelemetryTracingTracer .inScope ()) {
802+ verify (operationSpan ).makeCurrent ();
803+ }
804+ verify (mockScope ).close ();
805+ }
806+
807+ @ Test
808+ void testInjectTraceContext_withOperationSpanFallback () {
809+ io .opentelemetry .api .trace .SpanContext mockSpanContext =
810+ io .opentelemetry .api .trace .SpanContext .create (
811+ "00000000000000000000000000000003" ,
812+ "0000000000000004" ,
813+ io .opentelemetry .api .trace .TraceFlags .getSampled (),
814+ io .opentelemetry .api .trace .TraceState .getDefault ());
815+ io .opentelemetry .api .trace .Span realSpan =
816+ io .opentelemetry .api .trace .Span .wrap (mockSpanContext );
817+ org .mockito .Mockito .when (operationSpanBuilder .startSpan ()).thenReturn (realSpan );
818+
819+ openTelemetryTracingTracer =
820+ new OpenTelemetryTracingTracer (tracer , ApiTracerContext .empty (), ATTEMPT_SPAN_NAME );
821+
822+ Map <String , String > carrier = new java .util .HashMap <>();
823+ openTelemetryTracingTracer .injectTraceContext (carrier );
824+
825+ assertThat (carrier ).containsKey ("traceparent" );
826+ assertThat (carrier .get ("traceparent" )).contains ("00000000000000000000000000000003" );
827+ assertThat (carrier .get ("traceparent" )).contains ("0000000000000004" );
828+ }
683829}
0 commit comments