package observability import ( "context" "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) func TestTraceContextRoundTrip(t *testing.T) { want := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{1}, SpanID: trace.SpanID{2}, TraceFlags: trace.FlagsSampled, Remote: true, }) ctx := trace.ContextWithRemoteSpanContext(context.Background(), want) traceparent, tracestate := InjectToTraceparentAndTracestate(ctx) if traceparent == "" { t.Fatal("traceparent is empty") } got := trace.SpanContextFromContext(ExtractFromTraceparentAndTracestate(context.Background(), traceparent, tracestate)) if got.TraceID() != want.TraceID() || got.SpanID() != want.SpanID() || !got.IsSampled() || !got.IsRemote() { t.Fatalf("span context = %v, want %v", got, want) } } func TestOTelRouteMiddlewareUsesRouteTemplate(t *testing.T) { oldProvider := otel.GetTracerProvider() recorder := tracetest.NewSpanRecorder() provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) otel.SetTracerProvider(provider) t.Cleanup(func() { _ = provider.Shutdown(context.Background()) otel.SetTracerProvider(oldProvider) }) router := chi.NewRouter() router.Use(OTelRouteMiddleware) router.Get("/things/{id}", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) }) handler := otelhttp.NewHandler(router, "HTTP") request := httptest.NewRequest(http.MethodGet, "/things/raw-user-id", nil) response := httptest.NewRecorder() handler.ServeHTTP(response, request) spans := recorder.Ended() if len(spans) != 1 { t.Fatalf("ended spans = %d, want 1", len(spans)) } if got := spans[0].Name(); got != "HTTP /things/{id}" { t.Fatalf("span name = %q, want route template", got) } for _, attr := range spans[0].Attributes() { if attr.Key == attribute.Key("http.route") && attr.Value.AsString() == "/things/{id}" { return } } t.Fatal("http.route template attribute is missing") } func TestTracingConstants(t *testing.T) { tests := []struct { got string want string }{ {UserDIDKey, "tangled.user.did"}, {OwnerDIDKey, "tangled.owner.did"}, {RepoDIDKey, "tangled.repo.did"}, {TargetRepoDIDKey, "tangled.repo.target.did"}, {PipelineIDKey, "tangled.pipeline.id"}, {WorkflowIDKey, "tangled.workflow.id"}, {WorkflowEngineKey, "tangled.workflow.engine"}, {LeaseIDKey, "tangled.lease.id"}, {ExecutorNodeIDKey, "tangled.executor.node_id"}, {JobIDKey, "tangled.job.id"}, {RequestIDKey, "tangled.request.id"}, {CollectionKey, "atproto.collection"}, {RKeyKey, "atproto.rkey"}, {StepNameKey, "tangled.step.name"}, {StepIndexKey, "tangled.step.index"}, } for _, tc := range tests { if tc.got != tc.want { t.Errorf("got %q, want %q", tc.got, tc.want) } } }