package handler import ( "context" "log/slog" "strings" "testing" "time" ) func TestLogRingChronological(t *testing.T) { r := NewLogRing(3) // Push 5 entries; the oldest 2 should be evicted. for i := 0; i < 5; i++ { r.push(LogEntry{ Time: time.Unix(int64(i), 0).UTC(), Level: "INFO", Message: "msg", Attrs: map[string]any{"i": i}, }) } snap := r.Snapshot() if len(snap) != 3 { t.Fatalf("len(snap) = %d, want 3", len(snap)) } for i, e := range snap { wantI := i + 2 // 2,3,4 if got := e.Attrs["i"]; got != wantI { t.Errorf("snap[%d].Attrs.i = %v, want %d", i, got, wantI) } } } func TestLogRingEmpty(t *testing.T) { r := NewLogRing(5) if got := r.Snapshot(); len(got) != 0 { t.Errorf("empty ring snapshot len = %d, want 0", len(got)) } } func TestLogRingPartialFill(t *testing.T) { r := NewLogRing(10) for i := 0; i < 3; i++ { r.push(LogEntry{Message: "x", Attrs: map[string]any{"i": i}}) } if got := r.Snapshot(); len(got) != 3 { t.Errorf("partial-fill snapshot len = %d, want 3", len(got)) } } func TestRingHandlerCapturesRecord(t *testing.T) { ring := NewLogRing(10) h := NewRingHandler(ring, slog.LevelInfo) logger := slog.New(h) logger.Info("hello", "user", "alice", "n", 7) snap := ring.Snapshot() if len(snap) != 1 { t.Fatalf("len(snap) = %d, want 1", len(snap)) } e := snap[0] if e.Message != "hello" { t.Errorf("message = %q, want %q", e.Message, "hello") } if e.Level != slog.LevelInfo.String() { t.Errorf("level = %q, want %q", e.Level, slog.LevelInfo.String()) } if e.Attrs["user"] != "alice" { t.Errorf("attrs.user = %v, want alice", e.Attrs["user"]) } if e.Attrs["n"] != int64(7) { t.Errorf("attrs.n = %v (%T), want int64(7)", e.Attrs["n"], e.Attrs["n"]) } } func TestRingHandlerLevelFilter(t *testing.T) { ring := NewLogRing(10) h := NewRingHandler(ring, slog.LevelWarn) logger := slog.New(h) logger.Debug("d") logger.Info("i") logger.Warn("w") logger.Error("e") snap := ring.Snapshot() if len(snap) != 2 { t.Fatalf("len(snap) = %d, want 2 (warn+error)", len(snap)) } if snap[0].Message != "w" || snap[1].Message != "e" { t.Errorf("messages = [%q %q], want [w e]", snap[0].Message, snap[1].Message) } } func TestMultiHandlerFansOut(t *testing.T) { ring := NewLogRing(10) rh := NewRingHandler(ring, slog.LevelDebug) var buf strings.Builder th := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) multi := NewMultiHandler(th, rh) logger := slog.New(multi) logger.Info("teed", "a", 1) if !strings.Contains(buf.String(), "teed") { t.Errorf("text handler did not receive: %q", buf.String()) } snap := ring.Snapshot() if len(snap) != 1 || snap[0].Message != "teed" { t.Errorf("ring did not receive: %+v", snap) } } func TestMultiHandlerEnabled(t *testing.T) { ring := NewLogRing(10) rhInfo := NewRingHandler(ring, slog.LevelInfo) rhError := NewRingHandler(ring, slog.LevelError) multi := NewMultiHandler(rhInfo, rhError) // Multi.Enabled should be true if ANY child is enabled. if !multi.Enabled(context.Background(), slog.LevelInfo) { t.Error("Enabled(Info) = false; expected true (rhInfo accepts it)") } if multi.Enabled(context.Background(), slog.LevelDebug) { t.Error("Enabled(Debug) = true; expected false (no child accepts it)") } }