package publichttp import ( "bytes" "context" "net/http" "net/http/httptest" "testing" "galaxy/authsession/internal/service/confirmemailcode" "galaxy/authsession/internal/service/sendemailcode" authtelemetry "galaxy/authsession/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 TestPublicHandlerEmitsTraceFieldsAndMetrics(t *testing.T) { t.Parallel() logger, buffer := newObservedLogger() telemetryRuntime, reader, recorder := newObservedPublicTelemetryRuntime(t) handler := mustNewHandler(t, DefaultConfig(), Dependencies{ Logger: logger, Telemetry: telemetryRuntime, SendEmailCode: sendEmailCodeFunc(func(context.Context, sendemailcode.Input) (sendemailcode.Result, error) { return sendemailcode.Result{ChallengeID: "challenge-123"}, nil }), ConfirmEmailCode: confirmEmailCodeFunc(func(context.Context, confirmemailcode.Input) (confirmemailcode.Result, error) { return confirmemailcode.Result{}, nil }), }) recorderHTTP := httptest.NewRecorder() request := httptest.NewRequest( http.MethodPost, "/api/v1/public/auth/send-email-code", bytes.NewBufferString(`{"email":"pilot@example.com"}`), ) request.Header.Set("Content-Type", "application/json") 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, "authsession.public_http.requests", map[string]string{ "route": "/api/v1/public/auth/send-email-code", "method": http.MethodPost, "edge_outcome": "success", }, 1) } func newObservedPublicTelemetryRuntime(t *testing.T) (*authtelemetry.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 := authtelemetry.NewWithProviders(meterProvider, tracerProvider) require.NoError(t, err) return runtime, reader, recorder } 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 }