Files
at-container-registry/pkg/logging/logger_test.go
2025-10-28 22:06:03 -05:00

398 lines
9.6 KiB
Go

package logging
import (
"bytes"
"log/slog"
"strings"
"testing"
)
// captureLogOutput runs a function and captures slog output
func captureLogOutput(level string, logFunc func()) string {
var buf bytes.Buffer
// Save original logger
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
// Parse level
var logLevel slog.Level
switch strings.ToLower(strings.TrimSpace(level)) {
case "debug":
logLevel = slog.LevelDebug
case "info", "":
logLevel = slog.LevelInfo
case "warn", "warning":
logLevel = slog.LevelWarn
case "error":
logLevel = slog.LevelError
default:
logLevel = slog.LevelInfo
}
// Create logger that writes to buffer
opts := &slog.HandlerOptions{
Level: logLevel,
}
handler := slog.NewTextHandler(&buf, opts)
slog.SetDefault(slog.New(handler))
// Run the function that generates logs
logFunc()
return buf.String()
}
func TestInitLogger(t *testing.T) {
// Save original logger to restore after all tests
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
tests := []struct {
name string
level string
shouldLogDebug bool
shouldLogInfo bool
shouldLogWarn bool
shouldLogError bool
}{
{
name: "debug level logs all",
level: "debug",
shouldLogDebug: true,
shouldLogInfo: true,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "info level logs info and above",
level: "info",
shouldLogDebug: false,
shouldLogInfo: true,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "warn level logs warn and above",
level: "warn",
shouldLogDebug: false,
shouldLogInfo: false,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "error level logs only errors",
level: "error",
shouldLogDebug: false,
shouldLogInfo: false,
shouldLogWarn: false,
shouldLogError: true,
},
{
name: "empty level defaults to info",
level: "",
shouldLogDebug: false,
shouldLogInfo: true,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "invalid level defaults to info",
level: "invalid",
shouldLogDebug: false,
shouldLogInfo: true,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "case insensitive - DEBUG",
level: "DEBUG",
shouldLogDebug: true,
shouldLogInfo: true,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "case insensitive - WaRn",
level: "WaRn",
shouldLogDebug: false,
shouldLogInfo: false,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "whitespace handling - ' info '",
level: " info ",
shouldLogDebug: false,
shouldLogInfo: true,
shouldLogWarn: true,
shouldLogError: true,
},
{
name: "warning alias for warn",
level: "warning",
shouldLogDebug: false,
shouldLogInfo: false,
shouldLogWarn: true,
shouldLogError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := captureLogOutput(tt.level, func() {
slog.Debug("debug message")
slog.Info("info message")
slog.Warn("warn message")
slog.Error("error message")
})
// Check debug
if tt.shouldLogDebug {
if !strings.Contains(output, "debug message") {
t.Errorf("Expected debug message to be logged")
}
} else {
if strings.Contains(output, "debug message") {
t.Errorf("Did not expect debug message to be logged")
}
}
// Check info
if tt.shouldLogInfo {
if !strings.Contains(output, "info message") {
t.Errorf("Expected info message to be logged")
}
} else {
if strings.Contains(output, "info message") {
t.Errorf("Did not expect info message to be logged")
}
}
// Check warn
if tt.shouldLogWarn {
if !strings.Contains(output, "warn message") {
t.Errorf("Expected warn message to be logged")
}
} else {
if strings.Contains(output, "warn message") {
t.Errorf("Did not expect warn message to be logged")
}
}
// Check error
if tt.shouldLogError {
if !strings.Contains(output, "error message") {
t.Errorf("Expected error message to be logged")
}
} else {
if strings.Contains(output, "error message") {
t.Errorf("Did not expect error message to be logged")
}
}
})
}
}
func TestInitLogger_LogLevels(t *testing.T) {
// Save original logger
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
// Test that InitLogger actually calls slog.SetDefault
InitLogger("debug")
// Create a buffer to capture output
var buf bytes.Buffer
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
slog.SetDefault(slog.New(handler))
// Log at debug level
slog.Debug("test debug message")
// Verify output contains the message
if !strings.Contains(buf.String(), "test debug message") {
t.Error("Debug message not logged after InitLogger")
}
}
func TestSetupTestLogger(t *testing.T) {
// Save original logger
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
// Test 1: SetupTestLogger suppresses INFO and DEBUG
cleanup := SetupTestLogger()
// Create a buffer to capture what SHOULD be discarded
// (but we can't really test io.Discard directly, so we'll test behavior)
// Log at different levels - since it's set to WARN, debug/info should be suppressed
// We can't capture io.Discard output, but we can verify the logger is configured correctly
logger := slog.Default()
// Verify handler is configured to discard
if logger == nil {
t.Error("Expected logger to be set")
}
// Test 2: Cleanup restores original logger
cleanup()
if slog.Default() != originalLogger {
t.Error("Expected cleanup to restore original logger")
}
}
func TestSetupTestLogger_LevelFiltering(t *testing.T) {
// Save original logger
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
// Setup test logger (WARN level, io.Discard)
cleanup := SetupTestLogger()
defer cleanup()
// Replace the handler output with a buffer so we can test
// (This is a bit of a workaround since the real SetupTestLogger uses io.Discard)
var buf bytes.Buffer
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{
Level: slog.LevelWarn,
})
slog.SetDefault(slog.New(handler))
// Log at different levels
slog.Debug("debug message")
slog.Info("info message")
slog.Warn("warn message")
slog.Error("error message")
output := buf.String()
// Debug and Info should NOT be in output (filtered by WARN level)
if strings.Contains(output, "debug message") {
t.Error("Debug message should be filtered out at WARN level")
}
if strings.Contains(output, "info message") {
t.Error("Info message should be filtered out at WARN level")
}
// Warn and Error SHOULD be in output
if !strings.Contains(output, "warn message") {
t.Error("Warn message should be logged at WARN level")
}
if !strings.Contains(output, "error message") {
t.Error("Error message should be logged at WARN level")
}
}
func TestSetupTestLogger_UsageWithTCleanup(t *testing.T) {
// This test demonstrates the intended usage pattern
originalLogger := slog.Default()
// Simulate using SetupTestLogger in a test
cleanup := SetupTestLogger()
t.Cleanup(cleanup)
// Logger should be different now
if slog.Default() == originalLogger {
t.Error("Expected logger to be changed after SetupTestLogger")
}
// When test ends, t.Cleanup will run and restore the logger
// We can't directly test this since it happens after the test function returns,
// but we're verifying the pattern works
}
func TestSetupTestLogger_MultipleCallsIndependent(t *testing.T) {
// Save original logger
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
// First call
cleanup1 := SetupTestLogger()
logger1 := slog.Default()
// Second call
cleanup2 := SetupTestLogger()
logger2 := slog.Default()
// Loggers might be different instances
if logger1 == nil || logger2 == nil {
t.Error("Expected loggers to be set")
}
// Cleanup in reverse order (like defer)
cleanup2()
cleanup1()
}
func TestInitLogger_OutputFormat(t *testing.T) {
// Save original logger
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
var buf bytes.Buffer
// Configure logger with buffer
opts := &slog.HandlerOptions{
Level: slog.LevelInfo,
}
handler := slog.NewTextHandler(&buf, opts)
slog.SetDefault(slog.New(handler))
// Log a message
slog.Info("test message", "key", "value")
output := buf.String()
// Verify text format (not JSON)
if !strings.Contains(output, "test message") {
t.Error("Expected message in output")
}
if !strings.Contains(output, "key=value") {
t.Error("Expected key=value in text format")
}
// Should NOT be JSON
if strings.HasPrefix(output, "{") {
t.Error("Expected text format, not JSON")
}
}
func BenchmarkInitLogger(b *testing.B) {
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
b.ResetTimer()
for i := 0; i < b.N; i++ {
InitLogger("info")
}
}
func BenchmarkSetupTestLogger(b *testing.B) {
originalLogger := slog.Default()
defer slog.SetDefault(originalLogger)
b.ResetTimer()
for i := 0; i < b.N; i++ {
cleanup := SetupTestLogger()
cleanup()
}
}
// Example test showing how to use SetupTestLogger
func ExampleSetupTestLogger() {
// In a test function:
cleanup := SetupTestLogger()
defer cleanup()
// Now logs at DEBUG and INFO are suppressed
slog.Debug("This won't show")
slog.Info("This won't show either")
slog.Warn("This WILL show")
// cleanup() will restore the original logger when defer runs
}