package telemetry_test import ( "bytes" "context" "encoding/json" "testing" "time" "github.com/rs/zerolog" "keycape/internal/server/telemetry" ) func sampleEvent() telemetry.Event { return telemetry.Event{ Timestamp: time.Now().UTC(), ClientID: "test-client", Endpoint: "/oauth2/token", Feature: "", Result: "success", ErrorType: "", Scopes: []string{"openid", "profile"}, GrantType: "authorization_code", Environment: "test", TraceID: "trace-abc-123", EventType: telemetry.EventTokenIssued, } } // ---- NoopEmitter ---- func TestNoopEmitter_DoesNotPanic(t *testing.T) { e := telemetry.NoopEmitter{} e.Emit(context.Background(), sampleEvent()) } func TestNoopEmitter_ImplementsInterface(t *testing.T) { var _ telemetry.Emitter = telemetry.NoopEmitter{} } // ---- LogEmitter ---- func TestLogEmitter_WritesJSON(t *testing.T) { var buf bytes.Buffer logger := zerolog.New(&buf) e := telemetry.NewLogEmitter(logger) ev := sampleEvent() e.Emit(context.Background(), ev) if buf.Len() == 0 { t.Fatal("expected output from LogEmitter, got nothing") } var out map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &out); err != nil { t.Fatalf("LogEmitter output is not valid JSON: %v\noutput: %s", err, buf.String()) } } func TestLogEmitter_ContainsEventFields(t *testing.T) { var buf bytes.Buffer logger := zerolog.New(&buf) e := telemetry.NewLogEmitter(logger) ev := sampleEvent() e.Emit(context.Background(), ev) var out map[string]interface{} _ = json.Unmarshal(buf.Bytes(), &out) requiredFields := []string{"client_id", "endpoint", "result", "environment", "trace_id", "event_type"} for _, f := range requiredFields { if _, ok := out[f]; !ok { t.Errorf("LogEmitter output missing field %q", f) } } } func TestLogEmitter_EventTypeValue(t *testing.T) { var buf bytes.Buffer logger := zerolog.New(&buf) e := telemetry.NewLogEmitter(logger) ev := sampleEvent() ev.EventType = telemetry.EventAuthFailure e.Emit(context.Background(), ev) var out map[string]interface{} _ = json.Unmarshal(buf.Bytes(), &out) if out["event_type"] != string(telemetry.EventAuthFailure) { t.Errorf("event_type: expected %q, got %v", telemetry.EventAuthFailure, out["event_type"]) } } func TestLogEmitter_ImplementsInterface(t *testing.T) { var buf bytes.Buffer logger := zerolog.New(&buf) var _ telemetry.Emitter = telemetry.NewLogEmitter(logger) } // ---- MultiEmitter ---- type capturingEmitter struct { events []telemetry.Event } func (c *capturingEmitter) Emit(_ context.Context, ev telemetry.Event) { c.events = append(c.events, ev) } func TestMultiEmitter_FansOut(t *testing.T) { a := &capturingEmitter{} b := &capturingEmitter{} m := telemetry.NewMultiEmitter(a, b) ev := sampleEvent() m.Emit(context.Background(), ev) if len(a.events) != 1 { t.Errorf("emitter a: expected 1 event, got %d", len(a.events)) } if len(b.events) != 1 { t.Errorf("emitter b: expected 1 event, got %d", len(b.events)) } } func TestMultiEmitter_EmptyIsNoop(t *testing.T) { m := telemetry.NewMultiEmitter() m.Emit(context.Background(), sampleEvent()) // must not panic } func TestMultiEmitter_ImplementsInterface(t *testing.T) { var _ telemetry.Emitter = telemetry.NewMultiEmitter() } // ---- Context helpers ---- func TestWithEmitter_RoundTrip(t *testing.T) { orig := telemetry.NoopEmitter{} ctx := telemetry.WithEmitter(context.Background(), orig) got := telemetry.EmitterFromContext(ctx) if got == nil { t.Fatal("EmitterFromContext returned nil after WithEmitter") } } func TestEmitterFromContext_FallsBackToNoop(t *testing.T) { got := telemetry.EmitterFromContext(context.Background()) if got == nil { t.Fatal("EmitterFromContext must never return nil — fallback to NoopEmitter expected") } // Verify the fallback doesn't panic got.Emit(context.Background(), sampleEvent()) } // ---- EventType constants ---- func TestEventTypeConstants(t *testing.T) { cases := []struct { et telemetry.EventType want string }{ {telemetry.EventAuthStart, "auth_start"}, {telemetry.EventAuthSuccess, "auth_success"}, {telemetry.EventAuthFailure, "auth_failure"}, {telemetry.EventTokenIssued, "token_issued"}, {telemetry.EventUnsupportedFeature, "unsupported_feature"}, {telemetry.EventInvalidRequest, "invalid_request"}, {telemetry.EventMigration, "migration_event"}, } for _, c := range cases { if string(c.et) != c.want { t.Errorf("EventType %q: expected %q", c.et, c.want) } } }