package provider import ( "context" "errors" "slices" "testing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/tracing" ) func TestWithTracing_spanPerCall(t *testing.T) { t.Parallel() tests := []struct { name string inner *staticProvider call func(p Provider) error wantSpan string wantStatus codes.Code wantResult string wantAttr attribute.KeyValue }{ { name: "successful create", inner: &staticProvider{}, call: func(p Provider) error { _, err := p.Create(context.Background(), CreateRequest{}); return err }, wantSpan: "provider.create", wantStatus: codes.Ok, wantResult: resultOK, wantAttr: attribute.String("provider.id", "id-1"), }, { name: "get NotFound is not a span error", inner: &staticProvider{getErr: Wrap(ErrNotFound, "get", "x", "id-1", nil)}, call: func(p Provider) error { _, err := p.Get(context.Background(), "id-1"); return err }, wantSpan: "provider.get", wantStatus: codes.Ok, wantResult: resultNotFound, wantAttr: attribute.String("provider.id", "id-1"), }, { name: "delete transient error", inner: &staticProvider{deleteErr: Wrap(ErrTransient, "delete", "x", "id-1", errors.New("503"))}, call: func(p Provider) error { return p.Delete(context.Background(), "id-1") }, wantSpan: "provider.delete", wantStatus: codes.Error, wantResult: resultTransient, wantAttr: attribute.String("provider.id", "id-1"), }, { name: "quota exceeded create", inner: &staticProvider{createErr: Wrap(ErrQuotaExceeded, "create", "x", "", nil)}, call: func(p Provider) error { _, err := p.Create(context.Background(), CreateRequest{}); return err }, wantSpan: "provider.create", wantStatus: codes.Error, wantResult: resultQuotaExceeded, wantAttr: attribute.String("provider.name", "x"), }, { name: "list records instance count", inner: &staticProvider{}, call: func(p Provider) error { _, err := p.ListByTag(context.Background()); return err }, wantSpan: "provider.list", wantStatus: codes.Ok, wantResult: resultOK, wantAttr: attribute.Int("provider.instances", 0), }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() sr := tracetest.NewSpanRecorder() tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr)) t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) p := WithTracing("x", tc.inner, tracing.WithTracerProvider(tp)) err := tc.call(p) wantErr := errors.Join(tc.inner.createErr, tc.inner.getErr, tc.inner.deleteErr, tc.inner.listErr) if (wantErr == nil) != (err == nil) { t.Fatalf("decorator changed the error: got %v", err) } ended := sr.Ended() if len(ended) != 1 { t.Fatalf("got %d spans, want 1", len(ended)) } span := ended[0] if span.Name() != tc.wantSpan { t.Errorf("span name = %q, want %q", span.Name(), tc.wantSpan) } if span.SpanKind() != trace.SpanKindClient { t.Errorf("span kind = %v, want client", span.SpanKind()) } if span.Status().Code != tc.wantStatus { t.Errorf("status = %v, want %v", span.Status().Code, tc.wantStatus) } attrs := span.Attributes() hasAttr := func(want attribute.KeyValue) bool { return slices.Contains(attrs, want) } if !hasAttr(attribute.String("provider.result", tc.wantResult)) { t.Errorf("provider.result %q missing in %v", tc.wantResult, attrs) } if !hasAttr(tc.wantAttr) { t.Errorf("attribute %v missing in %v", tc.wantAttr, attrs) } }) } }