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)
}
}
}