package logger import ( "os" "path/filepath" "strings" "testing" "gcy_hpc_server/internal/config" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest" ) func ptrBool(v bool) *bool { return &v } // TestNewLogger_JSONConfig creates a logger with JSON encoding and verifies // that log entries are emitted successfully. func TestNewLogger_JSONConfig(t *testing.T) { cfg := config.LogConfig{ Level: "debug", Encoding: "json", OutputStdout: ptrBool(true), } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } defer log.Sync() // Should not panic when logging log.Info("json logger test", zap.String("key", "value")) } // TestNewLogger_ConsoleConfig creates a logger with console encoding. func TestNewLogger_ConsoleConfig(t *testing.T) { cfg := config.LogConfig{ Level: "info", Encoding: "console", OutputStdout: ptrBool(true), } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } defer log.Sync() log.Info("console logger test", zap.Int("num", 42)) } // TestNewLogger_InvalidLevel verifies that an invalid log level returns an error. func TestNewLogger_InvalidLevel(t *testing.T) { cfg := config.LogConfig{ Level: "bogus", Encoding: "json", } _, err := NewLogger(cfg) if err == nil { t.Fatal("expected error for invalid log level, got nil") } } // TestNewLogger_EmptyConfig verifies defaults are applied when config is zero-value. func TestNewLogger_EmptyConfig(t *testing.T) { cfg := config.LogConfig{} // all zero values log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } defer log.Sync() log.Info("default config test") } // TestNewLogger_FileOutput verifies that file output with rotation config works. func TestNewLogger_FileOutput(t *testing.T) { tmpDir := t.TempDir() logFile := filepath.Join(tmpDir, "test.log") cfg := config.LogConfig{ Level: "info", Encoding: "json", FilePath: logFile, MaxSize: 10, MaxBackups: 3, MaxAge: 7, Compress: true, } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } log.Info("file output test", zap.String("msg", "hello")) log.Sync() data, err := os.ReadFile(logFile) if err != nil { t.Fatalf("failed to read log file: %v", err) } if len(data) == 0 { t.Fatal("log file is empty, expected output") } if !strings.Contains(string(data), "file output test") { t.Fatalf("log file content does not contain expected message;\ngot: %s", string(data)) } } // TestNewLogger_MultiWriter verifies that both stdout and file output work together. func TestNewLogger_MultiWriter(t *testing.T) { tmpDir := t.TempDir() logFile := filepath.Join(tmpDir, "multi.log") cfg := config.LogConfig{ Level: "info", Encoding: "json", OutputStdout: ptrBool(true), FilePath: logFile, } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } log.Info("multi writer test", zap.String("writer", "both")) log.Sync() data, err := os.ReadFile(logFile) if err != nil { t.Fatalf("failed to read log file: %v", err) } if !strings.Contains(string(data), "multi writer test") { t.Fatalf("log file content does not contain expected message;\ngot: %s", string(data)) } } // TestNewLogger_Observer verifies actual log output content using zaptest. func TestNewLogger_Observer(t *testing.T) { // Use zaptest.NewLogger to capture logs in test output log := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)), zaptest.Level(zapcore.DebugLevel), ) // These should all succeed without panicking log.Debug("debug msg", zap.String("k", "v")) log.Info("info msg", zap.Int("n", 1)) log.Warn("warn msg") log.Error("error msg") } // TestNewLogger_AllLevels verifies all valid log levels parse correctly. func TestNewLogger_AllLevels(t *testing.T) { levels := []string{"debug", "info", "warn", "error", "dpanic", "panic", "fatal"} for _, level := range levels { t.Run(level, func(t *testing.T) { cfg := config.LogConfig{ Level: level, Encoding: "json", OutputStdout: ptrBool(true), } log, err := NewLogger(cfg) if err != nil { t.Fatalf("level %q: NewLogger returned error: %v", level, err) } log.Sync() }) } } // TestNewLogger_InvalidEncoding falls back gracefully — the factory should // treat an unrecognized encoding as an error or default to JSON. func TestNewLogger_InvalidEncoding(t *testing.T) { cfg := config.LogConfig{ Level: "info", Encoding: "xml", OutputStdout: ptrBool(true), } // The implementation should default to JSON for unknown encoding. log, err := NewLogger(cfg) if err != nil { t.Fatalf("unexpected error for invalid encoding: %v", err) } defer log.Sync() log.Info("invalid encoding test") } // TestNewLogger_DefaultRotation verifies rotation defaults are applied. func TestNewLogger_DefaultRotation(t *testing.T) { tmpDir := t.TempDir() logFile := filepath.Join(tmpDir, "rotation.log") cfg := config.LogConfig{ Level: "info", Encoding: "json", FilePath: logFile, // MaxSize, MaxBackups, MaxAge, Compress all zero → defaults apply } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } log.Info("rotation defaults test") log.Sync() data, err := os.ReadFile(logFile) if err != nil { t.Fatalf("failed to read log file: %v", err) } if len(data) == 0 { t.Fatal("log file is empty") } } func TestNewLogger_OutputStdoutNil(t *testing.T) { cfg := config.LogConfig{ Level: "info", Encoding: "json", } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } defer log.Sync() log.Info("default stdout test") } func TestNewLogger_OutputStdoutFalseWithFile(t *testing.T) { tmpDir := t.TempDir() logFile := filepath.Join(tmpDir, "nostdout.log") cfg := config.LogConfig{ Level: "info", Encoding: "json", OutputStdout: ptrBool(false), FilePath: logFile, } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } log.Info("file only test") log.Sync() data, err := os.ReadFile(logFile) if err != nil { t.Fatalf("failed to read log file: %v", err) } if !strings.Contains(string(data), "file only test") { t.Fatalf("log file content does not contain expected message;\ngot: %s", string(data)) } } func TestNewLogger_OutputStdoutFalseFallback(t *testing.T) { cfg := config.LogConfig{ Level: "info", Encoding: "json", OutputStdout: ptrBool(false), } log, err := NewLogger(cfg) if err != nil { t.Fatalf("NewLogger returned error: %v", err) } defer log.Sync() log.Info("fallback stdout test") }