rdev/internal/logging/logger_test.go
jordan d69da6d627 feat: add structured logging infrastructure and SDLC extensions
Major changes:
- Add internal/logging package with field constants, context propagation,
  sensitive data auto-redaction, and per-component log levels
- Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.)
- Extend SDLC with callback handlers, generate endpoints, and executor
- Add new cookbook trees for aeries and slackpath progression
- Add skeleton templates for queue, realtime, and microservices
- Add worker component template with async job processing
- Refactor services and handlers to use new logging infrastructure
- Split component.go into component_infra.go and component_listing.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 22:56:04 -07:00

216 lines
5.3 KiB
Go

package logging
import (
"bytes"
"context"
"encoding/json"
"strings"
"testing"
"time"
)
func TestNewLogger(t *testing.T) {
cfg := DefaultConfig()
cfg.RedactEnabled = false // Simplify for this test
logger := New(cfg)
if logger == nil {
t.Fatal("expected non-nil logger")
}
if logger.Slog() == nil {
t.Fatal("expected non-nil slog.Logger")
}
}
func TestLoggerWith(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
// Test With method
logger2 := logger.With("key", "value")
logger2.Info("test message")
output := buf.String()
if !strings.Contains(output, `"key"`) || !strings.Contains(output, `"value"`) {
t.Errorf("expected key/value in output, got: %s", output)
}
}
func TestLoggerWithComponent(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
logger.WithComponent("test_component").Info("test message")
output := buf.String()
if !strings.Contains(output, FieldComponent) || !strings.Contains(output, "test_component") {
t.Errorf("expected component field in output, got: %s", output)
}
}
func TestLoggerWithHandler(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
logger.WithHandler("CreateProject").Info("test message")
output := buf.String()
if !strings.Contains(output, FieldHandler) || !strings.Contains(output, "CreateProject") {
t.Errorf("expected handler field in output, got: %s", output)
}
}
func TestLoggerWithError(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
err := &testError{msg: "something went wrong"}
logger.WithError(err).Error("operation failed")
output := buf.String()
// Verify we use "error" field, not "err" or "e"
if !strings.Contains(output, `"error"`) || !strings.Contains(output, "something went wrong") {
t.Errorf("expected error field in output, got: %s", output)
}
}
type testError struct {
msg string
}
func (e *testError) Error() string {
return e.msg
}
func TestLoggerWithErrorNil(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
// WithError(nil) should not add error field
logger2 := logger.WithError(nil)
if logger2 != logger {
t.Error("WithError(nil) should return same logger")
}
}
func TestLoggerTimed(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
// Test Timed method
done := logger.Timed("test_operation")
time.Sleep(10 * time.Millisecond)
done()
output := buf.String()
if !strings.Contains(output, "test_operation") {
t.Errorf("expected operation in output, got: %s", output)
}
if !strings.Contains(output, FieldDuration) {
t.Errorf("expected duration field in output, got: %s", output)
}
if !strings.Contains(output, "operation completed") {
t.Errorf("expected completion message in output, got: %s", output)
}
}
func TestLoggerJSONFormat(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatJSON
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
logger.Info("test message", "key", "value")
var entry map[string]any
if err := json.Unmarshal(buf.Bytes(), &entry); err != nil {
t.Fatalf("expected valid JSON, got error: %v, output: %s", err, buf.String())
}
if entry["msg"] != "test message" {
t.Errorf("expected msg='test message', got: %v", entry["msg"])
}
if entry["key"] != "value" {
t.Errorf("expected key='value', got: %v", entry["key"])
}
}
func TestLoggerTextFormat(t *testing.T) {
var buf bytes.Buffer
cfg := DefaultConfig()
cfg.Format = FormatText
cfg.RedactEnabled = false
logger := NewWithWriter(cfg, &buf)
logger.Info("test message", "key", "value")
output := buf.String()
// Text format should contain the message and key=value
if !strings.Contains(output, "test message") {
t.Errorf("expected message in output, got: %s", output)
}
if !strings.Contains(output, "key=value") {
t.Errorf("expected key=value in output, got: %s", output)
}
}
func TestFromContext(t *testing.T) {
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := New(cfg)
SetDefault(logger)
// No logger in context should return default
ctx := context.Background()
l := FromContext(ctx)
if l == nil {
t.Fatal("expected non-nil logger from empty context")
}
// Logger in context should be returned
enriched := logger.With("test", "value")
ctx = WithContext(ctx, enriched)
l = FromContext(ctx)
if l != enriched {
t.Error("expected enriched logger from context")
}
}
func TestFromContextNil(t *testing.T) {
cfg := DefaultConfig()
cfg.RedactEnabled = false
logger := New(cfg)
SetDefault(logger)
// Test that FromContext handles nil context gracefully
// This is intentionally testing nil context handling
var nilCtx context.Context //nolint:staticcheck // testing nil context handling
l := FromContext(nilCtx)
if l == nil {
t.Fatal("expected non-nil logger from nil context")
}
}
func TestNop(t *testing.T) {
logger := Nop()
if logger == nil {
t.Fatal("expected non-nil nop logger")
}
// Should not panic when called
logger.Info("this should be discarded")
logger.WithComponent("test").Error("this too")
}