Files
anchorage/internal/pkg/logging/logging_test.go
William Gill 12bf35caf8 anchorage v1.0 initial tree
Greenfield Go multi-tenant IPFS Pinning Service wire-compatible with the
IPFS Pinning Services API spec. Paired 1:1 with Kubo over localhost RPC,
clustered via embedded NATS JetStream, Postgres source-of-truth with
RLS-enforced tenancy, Fiber + huma v2 for the HTTP surface, Authentik
OIDC for session login with kid-rotated HS256 JWT API tokens.

Feature-complete against the 22-milestone build plan, including the
ship-it v1.0 gap items:

  * admin CLIs: drain/uncordon, maintenance, mint-token, rotate-key,
    prune-denylist, rebalance --dry-run, cache-stats, cluster-presences
  * TTL leader election via NATS KV, fence tokens, JetStream dedup
  * rebalancer (plan/apply split), reconciler, requeue sweeper
  * ristretto caches with NATS-backed cross-node invalidation
    (placements live-nodes + token denylist)
  * maintenance watchdog for stuck cluster-pause flag
  * Prometheus /metrics with CIDR ACL, HTTP/pin/scheduler/cache gauges
  * rate limiting: session (10/min) + anonymous global (120/min)
  * integration tests: rebalance, refcount multi-org, RLS belt
  * goreleaser (tar + deb/rpm/apk + Alpine Docker) targeting Gitea

Stack: Cobra/Viper, Fiber v2 + huma v2, embedded NATS JetStream,
pgx/sqlc/golang-migrate, ristretto, TypeID, prometheus/client_golang,
testcontainers-go.
2026-04-16 18:13:36 -05:00

175 lines
5.1 KiB
Go

package logging
import (
"context"
"log/slog"
"testing"
)
func TestParseLevel(t *testing.T) {
tests := []struct {
input string
want slog.Level
err bool
}{
{"debug", slog.LevelDebug, false},
{"info", slog.LevelInfo, false},
{"warn", slog.LevelWarn, false},
{"warning", slog.LevelWarn, false},
{"error", slog.LevelError, false},
{"", slog.LevelInfo, false},
{"DEBUG", slog.LevelDebug, false},
{"INFO", slog.LevelInfo, false},
{"WARN", slog.LevelWarn, false},
{"ERROR", slog.LevelError, false},
{"invalid", slog.LevelInfo, true},
{"trace", slog.LevelInfo, true},
{"fatal", slog.LevelInfo, true},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got, err := parseLevel(tt.input)
if tt.err && err == nil {
t.Errorf("parseLevel(%q) expected error, got nil", tt.input)
}
if !tt.err && err != nil {
t.Errorf("parseLevel(%q) unexpected error: %v", tt.input, err)
}
if !tt.err && got != tt.want {
t.Errorf("parseLevel(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}
// recordingHandler captures log records for testing.
type recordingHandler struct {
records []slog.Record
level slog.Level
}
func (h *recordingHandler) Enabled(_ context.Context, level slog.Level) bool {
return level >= h.level
}
func (h *recordingHandler) Handle(_ context.Context, r slog.Record) error {
h.records = append(h.records, r)
return nil
}
func (h *recordingHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h }
func (h *recordingHandler) WithGroup(_ string) slog.Handler { return h }
func TestFanoutHandlerDispatchesBothHandlers(t *testing.T) {
all := &recordingHandler{level: slog.LevelDebug}
errOnly := &recordingHandler{level: slog.LevelWarn}
handler := &fanoutHandler{handlers: []slog.Handler{all, errOnly}}
logger := slog.New(handler)
logger.Debug("debug msg")
logger.Info("info msg")
logger.Warn("warn msg")
logger.Error("error msg")
// The all-levels handler should see every record.
if len(all.records) != 4 {
t.Errorf("all handler got %d records, want 4", len(all.records))
}
// The warn+ handler should see only warn and error.
if len(errOnly.records) != 2 {
t.Errorf("errOnly handler got %d records, want 2", len(errOnly.records))
}
if len(errOnly.records) >= 2 {
if errOnly.records[0].Level != slog.LevelWarn {
t.Errorf("first errOnly record level = %v, want WARN", errOnly.records[0].Level)
}
if errOnly.records[1].Level != slog.LevelError {
t.Errorf("second errOnly record level = %v, want ERROR", errOnly.records[1].Level)
}
}
}
func TestFanoutHandlerEnabled(t *testing.T) {
debugHandler := &recordingHandler{level: slog.LevelDebug}
errorHandler := &recordingHandler{level: slog.LevelError}
handler := &fanoutHandler{handlers: []slog.Handler{debugHandler, errorHandler}}
// At least one child accepts debug → fanout accepts debug.
if !handler.Enabled(context.Background(), slog.LevelDebug) {
t.Error("expected Enabled(Debug) = true")
}
if !handler.Enabled(context.Background(), slog.LevelInfo) {
t.Error("expected Enabled(Info) = true")
}
if !handler.Enabled(context.Background(), slog.LevelError) {
t.Error("expected Enabled(Error) = true")
}
}
func TestFanoutHandlerEnabledNoneMatch(t *testing.T) {
errorOnly := &recordingHandler{level: slog.LevelError}
handler := &fanoutHandler{handlers: []slog.Handler{errorOnly}}
if handler.Enabled(context.Background(), slog.LevelDebug) {
t.Error("expected Enabled(Debug) = false with error-only handler")
}
}
func TestFanoutWithAttrs(t *testing.T) {
h1 := &recordingHandler{level: slog.LevelInfo}
h2 := &recordingHandler{level: slog.LevelInfo}
handler := &fanoutHandler{handlers: []slog.Handler{h1, h2}}
newHandler := handler.WithAttrs([]slog.Attr{slog.String("key", "val")})
if newHandler == nil {
t.Fatal("WithAttrs returned nil")
}
}
func TestFanoutWithGroup(t *testing.T) {
h1 := &recordingHandler{level: slog.LevelInfo}
h2 := &recordingHandler{level: slog.LevelInfo}
handler := &fanoutHandler{handlers: []slog.Handler{h1, h2}}
newHandler := handler.WithGroup("test")
if newHandler == nil {
t.Fatal("WithGroup returned nil")
}
}
// TestInitStderrOnly verifies that an empty File falls back to stderr-only
// logging (no lumberjack file handler) — used by `anchorage version` and
// other short-lived commands that don't want to touch the filesystem.
func TestInitStderrOnly(t *testing.T) {
if err := Init(Config{Level: "debug", Format: "text"}); err != nil {
t.Fatalf("Init with empty File returned error: %v", err)
}
// Should be a fanoutHandler with exactly one child (stderr).
h, ok := slog.Default().Handler().(*fanoutHandler)
if !ok {
t.Fatalf("default handler is not fanoutHandler: %T", slog.Default().Handler())
}
if len(h.handlers) != 1 {
t.Errorf("expected 1 child handler when File is empty, got %d", len(h.handlers))
}
}
func TestInitRejectsBadFormat(t *testing.T) {
err := Init(Config{Level: "info", Format: "yaml"})
if err == nil {
t.Fatal("expected error for unknown format, got nil")
}
}
func TestInitRejectsBadLevel(t *testing.T) {
err := Init(Config{Level: "trace"})
if err == nil {
t.Fatal("expected error for unknown level, got nil")
}
}