package internalhttp import ( "bytes" "context" "log/slog" "net/http" "net/http/httptest" "testing" "galaxy/user/internal/service/authdirectory" usertelemetry "galaxy/user/internal/telemetry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func TestInternalHandlerEmitsTraceFieldsAndMetrics(t *testing.T) { t.Parallel() logger, buffer := newObservedLogger() telemetryRuntime, reader, recorder := newObservedInternalTelemetryRuntime(t) handler := mustNewHandler(t, Dependencies{ Logger: logger, Telemetry: telemetryRuntime, ExistsByUserID: existsByUserIDFunc(func(context.Context, authdirectory.ExistsByUserIDInput) (authdirectory.ExistsByUserIDResult, error) { return authdirectory.ExistsByUserIDResult{Exists: true}, nil }), }) recorderHTTP := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/api/v1/internal/users/user-123/exists", nil) request.Header.Set("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01") handler.ServeHTTP(recorderHTTP, request) require.Equal(t, http.StatusOK, recorderHTTP.Code) require.NotEmpty(t, recorder.Ended()) assert.Contains(t, buffer.String(), "otel_trace_id") assert.Contains(t, buffer.String(), "otel_span_id") assertMetricCount(t, reader, "user.internal_http.requests", map[string]string{ "route": "/api/v1/internal/users/:user_id/exists", "method": http.MethodGet, "edge_outcome": "success", }, 1) } func newObservedInternalTelemetryRuntime(t *testing.T) (*usertelemetry.Runtime, *sdkmetric.ManualReader, *tracetest.SpanRecorder) { t.Helper() reader := sdkmetric.NewManualReader() meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) recorder := tracetest.NewSpanRecorder() tracerProvider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)) runtime, err := usertelemetry.NewWithProviders(meterProvider, tracerProvider) require.NoError(t, err) return runtime, reader, recorder } func newObservedLogger() (*slog.Logger, *bytes.Buffer) { buffer := &bytes.Buffer{} return slog.New(slog.NewJSONHandler(buffer, &slog.HandlerOptions{Level: slog.LevelDebug})), buffer } func assertMetricCount(t *testing.T, reader *sdkmetric.ManualReader, metricName string, wantAttrs map[string]string, wantValue int64) { t.Helper() var resourceMetrics metricdata.ResourceMetrics require.NoError(t, reader.Collect(context.Background(), &resourceMetrics)) for _, scopeMetrics := range resourceMetrics.ScopeMetrics { for _, metric := range scopeMetrics.Metrics { if metric.Name != metricName { continue } sum, ok := metric.Data.(metricdata.Sum[int64]) require.True(t, ok) for _, point := range sum.DataPoints { if hasMetricAttributes(point.Attributes.ToSlice(), wantAttrs) { assert.Equal(t, wantValue, point.Value) return } } } } require.Failf(t, "test failed", "metric %q with attrs %v not found", metricName, wantAttrs) } func hasMetricAttributes(values []attribute.KeyValue, want map[string]string) bool { if len(values) != len(want) { return false } for _, value := range values { if want[string(value.Key)] != value.Value.AsString() { return false } } return true }