ZDDC/zddc/internal/handler/logring_test.go
ZDDC 9ef90800b1 feat(zddc-server): admin debug page + X-Auth-Request-Email default + hidden-segment guard
Three improvements bundled because they all ship as zddc-server v0.0.2:

* /.admin/ debug dashboard with /whoami, /config, /logs sub-routes.
  Authorization via a top-level `admins:` glob list in <ZDDC_ROOT>/.zddc
  (root-only — subdir entries deliberately ignored to prevent privilege
  escalation via subtree write access). Non-admin requests get 404 so the
  page is invisible. Recent logs surface via a 500-entry slog ring buffer
  teed off the existing TextHandler. Lets operators debug without
  kubectl exec.

* Default ZDDC_EMAIL_HEADER changes from `X-Email` to
  `X-Auth-Request-Email` — the oauth2-proxy / nginx auth-request
  convention that the TND helm chart already sets explicitly.
  Operators who set the env var explicitly are unaffected; deployments
  relying on the previous default need to set ZDDC_EMAIL_HEADER=X-Email
  or update their proxy.

* dispatch() rejects any URL whose segments contain a dot prefix other
  than the recognized virtual prefixes (.admin, cfg.IndexPath /
  .archive). Matches the existing listing-pipeline filter so hidden
  subtrees on the served PVC (e.g. /srv/.devshell — used by the
  in-cluster dev-shell for persistent home-dir state) become
  unreachable via direct HTTP fetch, not just hidden in listings.

Refreshes the X-Email reference in website/index.html accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:02:06 -05:00

133 lines
3.2 KiB
Go

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)")
}
}