// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini package main import ( "context" "log/slog" "math" "regexp" "strings" "testing" "time" ) func TestLevelOrdering(t *testing.T) { t.Parallel() ordered := []slog.Level{LevelTrace, slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError} for i := 1; i < len(ordered); i++ { if !(ordered[i-1] < ordered[i]) { t.Fatalf("level %v must be below %v", ordered[i-1], ordered[i]) } } if LevelTrace != slog.Level(-8) { t.Fatalf("LevelTrace = %d", LevelTrace) } } func TestParseLogLevel(t *testing.T) { t.Parallel() for name, want := range map[string]slog.Level{ "ERROR": slog.LevelError, "WARN": slog.LevelWarn, "INFO": slog.LevelInfo, "DEBUG": slog.LevelDebug, "TRACE": LevelTrace, "trace": LevelTrace, "Debug": slog.LevelDebug, } { got, err := ParseLogLevel(name) if err != nil || got != want { t.Errorf("ParseLogLevel(%q) = %v, %v", name, got, err) } } for _, name := range []string{"", "WARNING", "TRACEE", "TRAC", "ınfo"} { if _, err := ParseLogLevel(name); err == nil { t.Errorf("ParseLogLevel(%q) should fail", name) } } } func TestLevelName(t *testing.T) { t.Parallel() for level, want := range map[slog.Level]string{ slog.LevelError: "ERROR", slog.LevelWarn: "WARN", slog.LevelInfo: "INFO", slog.LevelDebug: "DEBUG", LevelTrace: "TRACE", slog.Level(2): "INFO+2", slog.Level(-12): "DEBUG-8", } { if got := LevelName(level); got != want { t.Errorf("LevelName(%d) = %q, want %q", level, got, want) } } } // emitAll writes one record per supported level func emitAll(log *slog.Logger) { ctx := context.Background() trace(ctx, log, "trace-record") log.Debug("debug-record") log.Info("info-record") log.Warn("warn-record") log.Error("error-record") } func TestLoggerFiltering(t *testing.T) { t.Parallel() all := []string{"trace", "debug", "info", "warn", "error"} levels := map[slog.Level]int{ // index of the lowest emitted record LevelTrace: 0, slog.LevelDebug: 1, slog.LevelInfo: 2, slog.LevelWarn: 3, slog.LevelError: 4, } for level, lowest := range levels { log, buf := newTestLogger(level) emitAll(log) out := buf.String() for i, name := range all { present := strings.Contains(out, "msg="+name+"-record") if present != (i >= lowest) { t.Errorf("level %s: record %s present=%v", LevelName(level), name, present) } if present && !strings.Contains(out, "level="+strings.ToUpper(name)+" msg="+name+"-record") { t.Errorf("level %s: record %s has wrong level label:\n%s", LevelName(level), name, out) } } } } func TestLoggerFormat(t *testing.T) { t.Parallel() log, buf := newTestLogger(LevelTrace) trace(context.Background(), log, "hello", "k", "v") line := strings.TrimSuffix(buf.String(), "\n") re := regexp.MustCompile(`^time=\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z level=TRACE msg=hello k=v$`) if !re.MatchString(line) { t.Fatalf("unexpected line %q", line) } } func TestLoggerEmitsRequestMessageAtInfo(t *testing.T) { t.Parallel() log, buf := newTestLogger(slog.LevelInfo) s := NewWebServer(log, &fakeLookup{}, &fakeAudit{}, &fakeDocker{}) rec := doRequest(s, "GET", "/nope", nil) if rec.Code != 404 { t.Fatalf("status %d", rec.Code) } if !strings.Contains(buf.String(), `level=INFO msg="this request has been made"`) { t.Fatalf("missing request message:\n%s", buf.String()) } if strings.Contains(buf.String(), "level=DEBUG") || strings.Contains(buf.String(), "level=TRACE") { t.Fatalf("lower-priority records leaked:\n%s", buf.String()) } } func TestTraceFuncEmitsEntryAndExit(t *testing.T) { t.Parallel() log, buf := newTestLogger(LevelTrace) func() { defer traceFunc(context.Background(), log, "Example")() log.Debug("inside") }() lines := strings.Split(strings.TrimSpace(buf.String()), "\n") if len(lines) != 3 || !strings.Contains(lines[0], `level=TRACE msg="function entry" func=Example`) || !strings.Contains(lines[1], "msg=inside") || !strings.Contains(lines[2], `level=TRACE msg="function exit" func=Example`) { t.Fatalf("unexpected output:\n%s", buf.String()) } // Nothing is emitted above TRACE quiet, qbuf := newTestLogger(slog.LevelDebug) traceFunc(context.Background(), quiet, "Example")() if qbuf.String() != "" { t.Fatalf("trace emitted at DEBUG: %q", qbuf.String()) } } func TestLoggerUnusualFieldsDoNotPanic(t *testing.T) { t.Parallel() log, buf := newTestLogger(LevelTrace) var nilPtr *int log.Info("") // Malformed argument lists are passed as slices on purpose (vet rejects them as literals) log.Info("x", []any{""}...) log.Info("x", "", "") log.Info("x", []any{"odd"}...) log.Info("x", []any{42, "non-string key"}...) log.Info("x", "nil", nil, "nilptr", nilPtr) log.Info("x", "level", "spoofed", "time", "not-a-time", "msg", "dup") log.Info("x", "level", slog.LevelWarn) log.Info("x", "nan", math.NaN(), "inf", math.Inf(1)) log.Info("x", "ctl", "a\nb\x00c\x1b[31m", "bad", string([]byte{0xff, 0xfe})) log.Info("x", "unicode", "日本語 🚀", "long", strings.Repeat("y", 100000)) log.Info("x", slog.Group("g", "level", "inner"), slog.Any("map", map[string]any{"a": []int{1}})) log.WithGroup("grp").Info("x", "level", "grouped", "time", time.Unix(0, 0)) log.Log(context.Background(), slog.Level(-100), "very low") log.Log(context.Background(), slog.Level(100), "very high") out := buf.String() // 15 calls, of which the level -100 record is below TRACE and filtered if c := strings.Count(out, "\n"); c != 14 { t.Fatalf("expected 14 records, got %d:\n%s", c, out) } // Control characters are escaped, so one record is one line if strings.Contains(out, "a\nb") { t.Fatalf("raw newline leaked into log output") } if strings.Contains(out, "very low") || !strings.Contains(out, "level=ERROR+92") { t.Fatalf("out-of-range levels not rendered:\n%s", out) } if !strings.Contains(out, "level=WARN") { t.Fatalf("user level attr of type slog.Level not rendered by name") } } func TestTokenID(t *testing.T) { t.Parallel() // sha256("abc") = ba7816bf... if got := tokenID("abc"); got != "ba7816bf" { t.Fatalf("tokenID(abc) = %q", got) } if tokenID("abc") != tokenID("abc") || tokenID("abc") == tokenID("abd") { t.Fatal("tokenID must be stable and distinguish inputs") } if got := tokenID(""); len(got) != 8 { t.Fatalf("tokenID('') = %q", got) } } func TestASCIIEqualFold(t *testing.T) { t.Parallel() cases := []struct { a, b string want bool }{ {"Bearer", "bearer", true}, {"BEARER", "bearer", true}, {"bearer", "bearer", true}, {"bearer", "bearer ", false}, {"bearerx", "bearer", false}, {"bearer", "bearex", false}, {"", "", true}, {"ı", "i", false}, {"[", "{", false}, {"@", "`", false}, } for _, c := range cases { if got := asciiEqualFold(c.a, c.b); got != c.want { t.Errorf("asciiEqualFold(%q, %q) = %v", c.a, c.b, got) } } }